diff options
Diffstat (limited to '')
40 files changed, 20278 insertions, 0 deletions
diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..42dfe63 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,3 @@ +# tests +failmalloc +main diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..e8a5adb --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,59 @@ +# XXX testdata/: EXTRA_DIST = cacert.pem index.html privkey.pem +if(HAVE_CUNIT) + string(REPLACE " " ";" c_flags "${WARNCFLAGS}") + add_compile_options(${c_flags}) + + include_directories( + "${CMAKE_SOURCE_DIR}/lib/includes" + "${CMAKE_SOURCE_DIR}/lib" + "${CMAKE_BINARY_DIR}/lib/includes" + ${CUNIT_INCLUDE_DIRS} + ) + + set(MAIN_SOURCES + main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c + nghttp2_test_helper.c + nghttp2_frame_test.c + nghttp2_stream_test.c + nghttp2_session_test.c + nghttp2_hd_test.c + nghttp2_npn_test.c + nghttp2_helper_test.c + nghttp2_buf_test.c + nghttp2_http_test.c + nghttp2_extpri_test.c + ) + + add_executable(main EXCLUDE_FROM_ALL + ${MAIN_SOURCES} + ) + target_include_directories(main PRIVATE ${CUNIT_INCLUDE_DIRS}) + target_link_libraries(main + nghttp2_static + ${CUNIT_LIBRARIES} + ) + add_test(main main) + add_dependencies(check main) + + if(ENABLE_FAILMALLOC) + set(FAILMALLOC_SOURCES + failmalloc.c failmalloc_test.c + malloc_wrapper.c + nghttp2_test_helper.c + ) + add_executable(failmalloc EXCLUDE_FROM_ALL + ${FAILMALLOC_SOURCES} + ) + target_link_libraries(failmalloc + nghttp2_static + ${CUNIT_LIBRARIES} + ) + add_test(failmalloc failmalloc) + add_dependencies(check failmalloc) + endif() + + if(ENABLE_APP) + # EXTRA_DIST = end_to_end.py + # TESTS += end_to_end.py + endif() +endif() diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..162e082 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,97 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# 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. +SUBDIRS = testdata + +EXTRA_DIST = CMakeLists.txt + +if HAVE_CUNIT + +check_PROGRAMS = main + +if ENABLE_FAILMALLOC +check_PROGRAMS += failmalloc +endif # ENABLE_FAILMALLOC + +OBJECTS = main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c \ + nghttp2_test_helper.c \ + nghttp2_frame_test.c \ + nghttp2_stream_test.c \ + nghttp2_session_test.c \ + nghttp2_hd_test.c \ + nghttp2_npn_test.c \ + nghttp2_helper_test.c \ + nghttp2_buf_test.c \ + nghttp2_http_test.c \ + nghttp2_extpri_test.c + +HFILES = nghttp2_pq_test.h nghttp2_map_test.h nghttp2_queue_test.h \ + nghttp2_session_test.h \ + nghttp2_frame_test.h nghttp2_stream_test.h nghttp2_hd_test.h \ + nghttp2_npn_test.h nghttp2_helper_test.h \ + nghttp2_test_helper.h \ + nghttp2_buf_test.h \ + nghttp2_http_test.h \ + nghttp2_extpri_test.h + +main_SOURCES = $(HFILES) $(OBJECTS) + +if ENABLE_STATIC +main_LDADD = ${top_builddir}/lib/libnghttp2.la +else +# 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 +endif + +main_LDADD += @CUNIT_LIBS@ @TESTLDADD@ +main_LDFLAGS = -static + +if ENABLE_FAILMALLOC +failmalloc_SOURCES = failmalloc.c failmalloc_test.c failmalloc_test.h \ + malloc_wrapper.c malloc_wrapper.h \ + nghttp2_test_helper.c nghttp2_test_helper.h +failmalloc_LDADD = $(main_LDADD) +failmalloc_LDFLAGS = $(main_LDFLAGS) +endif # ENABLE_FAILMALLOC + +AM_CFLAGS = $(WARNCFLAGS) \ + -I${top_srcdir}/lib \ + -I${top_srcdir}/lib/includes \ + -I${top_builddir}/lib/includes \ + -DBUILDING_NGHTTP2 \ + @CUNIT_CFLAGS@ @DEFS@ + +TESTS = main + +if ENABLE_FAILMALLOC +TESTS += failmalloc +endif # ENABLE_FAILMALLOC + +if ENABLE_APP + +# EXTRA_DIST = end_to_end.py +# TESTS += end_to_end.py + +endif # ENABLE_APP + +endif # HAVE_CUNIT diff --git a/tests/end_to_end.py b/tests/end_to_end.py new file mode 100755 index 0000000..cfb3cdd --- /dev/null +++ b/tests/end_to_end.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +"""End to end tests for the utility programs. + +This test assumes the utilities inside src directory have already been +built. + +At the moment top_buiddir is not in the environment, but top_builddir would be +more reliable than '..', so it's worth trying to pull it from the environment. +""" + +__author__ = 'Jim Morrison <jim@twist.com>' + + +import os +import subprocess +import time +import unittest + + +_PORT = 9893 + + +def _run_server(port, args): + srcdir = os.environ.get('srcdir', '.') + testdata = '%s/testdata' % srcdir + top_builddir = os.environ.get('top_builddir', '..') + base_args = ['%s/src/spdyd' % top_builddir, '-d', testdata] + if args: + base_args.extend(args) + base_args.extend([str(port), '%s/privkey.pem' % testdata, + '%s/cacert.pem' % testdata]) + return subprocess.Popen(base_args) + +def _check_server_up(port): + # Check this check for now. + time.sleep(1) + +def _kill_server(server): + while server.returncode is None: + server.terminate() + time.sleep(1) + server.poll() + + +class EndToEndSpdyTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.setUpServer([]) + + @classmethod + def setUpServer(cls, args): + cls.server = _run_server(_PORT, args) + _check_server_up(_PORT) + + @classmethod + def tearDownClass(cls): + _kill_server(cls.server) + + def setUp(self): + build_dir = os.environ.get('top_builddir', '..') + self.client = '%s/src/spdycat' % build_dir + self.stdout = 'No output' + + def call(self, path, args): + full_args = [self.client,'http://localhost:%d%s' % (_PORT, path)] + args + p = subprocess.Popen(full_args, stdout=subprocess.PIPE, + stdin=subprocess.PIPE) + self.stdout, self.stderr = p.communicate() + return p.returncode + + +class EndToEndSpdy2Tests(EndToEndSpdyTests): + def testSimpleRequest(self): + self.assertEquals(0, self.call('/', [])) + + def testSimpleRequestSpdy3(self): + self.assertEquals(0, self.call('/', ['-v', '-3'])) + self.assertIn('NPN selected the protocol: spdy/3', self.stdout) + + def testFailedRequests(self): + self.assertEquals( + 2, self.call('/', ['https://localhost:25/', 'http://localhost:79'])) + + def testOneFailedRequest(self): + self.assertEquals(1, subprocess.call([self.client, 'http://localhost:2/'])) + + def testOneTimedOutRequest(self): + self.assertEquals(1, self.call('/?spdyd_do_not_respond_to_req=yes', + ['--timeout=2'])) + self.assertEquals(0, self.call('/', ['--timeout=20'])) + + +class EndToEndSpdy3Tests(EndToEndSpdyTests): + @classmethod + def setUpClass(cls): + cls.setUpServer(['-3']) + + def testSimpleRequest(self): + self.assertEquals(0, self.call('/', ['-v'])) + self.assertIn('NPN selected the protocol: spdy/3', self.stdout) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/failmalloc.c b/tests/failmalloc.c new file mode 100644 index 0000000..6294cff --- /dev/null +++ b/tests/failmalloc.c @@ -0,0 +1,79 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "failmalloc_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("libnghttp2_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, "failmalloc_session_send", + test_nghttp2_session_send) || + !CU_add_test(pSuite, "failmalloc_session_send_server", + test_nghttp2_session_send_server) || + !CU_add_test(pSuite, "failmalloc_session_recv", + test_nghttp2_session_recv) || + !CU_add_test(pSuite, "failmalloc_frame", test_nghttp2_frame) || + !CU_add_test(pSuite, "failmalloc_hd", test_nghttp2_hd)) { + 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/failmalloc_test.c b/tests/failmalloc_test.c new file mode 100644 index 0000000..d8a229d --- /dev/null +++ b/tests/failmalloc_test.c @@ -0,0 +1,576 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012, 2014 Tatsuhiro Tsujikawa + * + * 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 "failmalloc_test.h" + +#include <CUnit/CUnit.h> + +#include <stdio.h> +#include <assert.h> + +#include "nghttp2_session.h" +#include "nghttp2_stream.h" +#include "nghttp2_frame.h" +#include "nghttp2_helper.h" +#include "malloc_wrapper.h" +#include "nghttp2_test_helper.h" + +typedef struct { + uint8_t data[8192]; + uint8_t *datamark, *datalimit; +} data_feed; + +typedef struct { + data_feed *df; + size_t data_source_length; +} my_user_data; + +static void data_feed_init(data_feed *df, nghttp2_bufs *bufs) { + nghttp2_buf *buf; + size_t data_length; + + buf = &bufs->head->buf; + data_length = nghttp2_buf_len(buf); + + assert(data_length <= sizeof(df->data)); + memcpy(df->data, buf->pos, data_length); + df->datamark = df->data; + df->datalimit = df->data + data_length; +} + +static ssize_t null_send_callback(nghttp2_session *session, const uint8_t *data, + size_t len, int flags, void *user_data) { + (void)session; + (void)data; + (void)flags; + (void)user_data; + + return (ssize_t)len; +} + +static ssize_t data_feed_recv_callback(nghttp2_session *session, uint8_t *data, + size_t len, int flags, void *user_data) { + data_feed *df = ((my_user_data *)user_data)->df; + size_t avail = (size_t)(df->datalimit - df->datamark); + size_t wlen = nghttp2_min(avail, len); + (void)session; + (void)flags; + + memcpy(data, df->datamark, wlen); + df->datamark += wlen; + return (ssize_t)wlen; +} + +static ssize_t fixed_length_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + size_t wlen; + (void)session; + (void)stream_id; + (void)buf; + (void)source; + + if (len < ud->data_source_length) { + wlen = len; + } else { + wlen = ud->data_source_length; + } + ud->data_source_length -= wlen; + if (ud->data_source_length == 0) { + *data_flags = NGHTTP2_DATA_FLAG_EOF; + } + return (ssize_t)wlen; +} + +#define TEST_FAILMALLOC_RUN(FUN) \ + do { \ + int nmalloc, i; \ + \ + nghttp2_failmalloc = 0; \ + nghttp2_nmalloc = 0; \ + FUN(); \ + nmalloc = nghttp2_nmalloc; \ + \ + nghttp2_failmalloc = 1; \ + for (i = 0; i < nmalloc; ++i) { \ + nghttp2_nmalloc = 0; \ + nghttp2_failstart = i; \ + /* printf("i=%zu\n", i); */ \ + FUN(); \ + /* printf("nmalloc=%d\n", nghttp2_nmalloc); */ \ + } \ + nghttp2_failmalloc = 0; \ + } while (0) + +static void run_nghttp2_session_send(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv nv[] = {MAKE_NV(":host", "example.org"), + MAKE_NV(":scheme", "https")}; + nghttp2_data_provider data_prd; + nghttp2_settings_entry iv[2]; + my_user_data ud; + int rv; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = 64 * 1024; + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 4096; + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[1].value = 100; + + rv = nghttp2_session_client_new3(&session, &callbacks, &ud, NULL, + nghttp2_mem_fm()); + if (rv != 0) { + goto client_new_fail; + } + rv = nghttp2_submit_request(session, NULL, nv, ARRLEN(nv), &data_prd, NULL); + if (rv < 0) { + goto fail; + } + rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL, nv, + ARRLEN(nv), NULL); + if (rv < 0) { + goto fail; + } + rv = nghttp2_session_send(session); + if (rv != 0) { + goto fail; + } + /* The HEADERS submitted by the previous nghttp2_submit_headers will + have stream ID 3. Send HEADERS to that stream. */ + rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 3, NULL, nv, + ARRLEN(nv), NULL); + if (rv != 0) { + goto fail; + } + rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 3, &data_prd); + if (rv != 0) { + goto fail; + } + rv = nghttp2_session_send(session); + if (rv != 0) { + goto fail; + } + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 3, NGHTTP2_CANCEL); + if (rv != 0) { + goto fail; + } + rv = nghttp2_session_send(session); + if (rv != 0) { + goto fail; + } + rv = nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); + if (rv != 0) { + goto fail; + } + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2); + if (rv != 0) { + goto fail; + } + rv = nghttp2_session_send(session); + if (rv != 0) { + goto fail; + } + rv = nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, 100, NGHTTP2_NO_ERROR, + NULL, 0); + if (rv != 0) { + goto fail; + } + rv = nghttp2_session_send(session); + if (rv != 0) { + goto fail; + } + +fail: + nghttp2_session_del(session); +client_new_fail:; +} + +void test_nghttp2_session_send(void) { + TEST_FAILMALLOC_RUN(run_nghttp2_session_send); +} + +static void run_nghttp2_session_send_server(void) { + nghttp2_session *session; + nghttp2_session_callbacks *callbacks; + int rv; + const uint8_t *txdata; + ssize_t txdatalen; + const uint8_t origin[] = "nghttp2.org"; + const uint8_t altsvc_field_value[] = "h2=\":443\""; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + static const nghttp2_origin_entry ov = { + (uint8_t *)nghttp2, + sizeof(nghttp2) - 1, + }; + + rv = nghttp2_session_callbacks_new(&callbacks); + if (rv != 0) { + return; + } + + rv = nghttp2_session_server_new3(&session, callbacks, NULL, NULL, + nghttp2_mem_fm()); + + nghttp2_session_callbacks_del(callbacks); + + if (rv != 0) { + return; + } + + rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin, + sizeof(origin) - 1, altsvc_field_value, + sizeof(altsvc_field_value) - 1); + if (rv != 0) { + goto fail; + } + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, &ov, 1); + if (rv != 0) { + goto fail; + } + + txdatalen = nghttp2_session_mem_send(session, &txdata); + + if (txdatalen < 0) { + goto fail; + } + +fail: + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_server(void) { + TEST_FAILMALLOC_RUN(run_nghttp2_session_send_server); +} + +static void run_nghttp2_session_recv(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_nv nv[] = { + MAKE_NV(":method", "GET"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "example.org"), + MAKE_NV(":path", "/"), + }; + nghttp2_settings_entry iv[2]; + my_user_data ud; + data_feed df; + int rv; + nghttp2_nv *nva; + size_t nvlen; + + rv = frame_pack_bufs_init(&bufs); + + if (rv != 0) { + return; + } + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.recv_callback = data_feed_recv_callback; + ud.df = &df; + + nghttp2_failmalloc_pause(); + nghttp2_hd_deflate_init(&deflater, nghttp2_mem_fm()); + nghttp2_session_server_new3(&session, &callbacks, &ud, NULL, + nghttp2_mem_fm()); + + /* Client preface */ + nghttp2_bufs_add(&bufs, NGHTTP2_CLIENT_MAGIC, NGHTTP2_CLIENT_MAGIC_LEN); + data_feed_init(&df, &bufs); + nghttp2_bufs_reset(&bufs); + nghttp2_failmalloc_unpause(); + + rv = nghttp2_session_recv(session); + if (rv != 0) { + goto fail; + } + + nghttp2_failmalloc_pause(); + /* SETTINGS */ + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 4096; + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[1].value = 100; + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, + nghttp2_frame_iv_copy(iv, 2, nghttp2_mem_fm()), + 2); + nghttp2_frame_pack_settings(&bufs, &frame.settings); + nghttp2_frame_settings_free(&frame.settings, nghttp2_mem_fm()); + data_feed_init(&df, &bufs); + nghttp2_bufs_reset(&bufs); + nghttp2_failmalloc_unpause(); + + rv = nghttp2_session_recv(session); + if (rv != 0) { + goto fail; + } + + nghttp2_failmalloc_pause(); + /* HEADERS */ + nvlen = ARRLEN(nv); + nghttp2_nv_array_copy(&nva, nv, nvlen, nghttp2_mem_fm()); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_STREAM, 1, + NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen); + nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + nghttp2_frame_headers_free(&frame.headers, nghttp2_mem_fm()); + data_feed_init(&df, &bufs); + nghttp2_bufs_reset(&bufs); + nghttp2_failmalloc_unpause(); + + rv = nghttp2_session_recv(session); + if (rv != 0) { + goto fail; + } + + /* PING */ + nghttp2_failmalloc_pause(); + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL); + nghttp2_frame_pack_ping(&bufs, &frame.ping); + nghttp2_frame_ping_free(&frame.ping); + data_feed_init(&df, &bufs); + nghttp2_bufs_reset(&bufs); + + nghttp2_failmalloc_unpause(); + + rv = nghttp2_session_recv(session); + if (rv != 0) { + goto fail; + } + + /* RST_STREAM */ + nghttp2_failmalloc_pause(); + nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_PROTOCOL_ERROR); + nghttp2_frame_pack_rst_stream(&bufs, &frame.rst_stream); + nghttp2_frame_rst_stream_free(&frame.rst_stream); + nghttp2_bufs_reset(&bufs); + + nghttp2_failmalloc_unpause(); + + rv = nghttp2_session_recv(session); + if (rv != 0) { + goto fail; + } + +fail: + nghttp2_bufs_free(&bufs); + nghttp2_session_del(session); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_session_recv(void) { + TEST_FAILMALLOC_RUN(run_nghttp2_session_recv); +} + +static void run_nghttp2_frame_pack_headers(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_frame frame, oframe; + nghttp2_bufs bufs; + nghttp2_nv nv[] = {MAKE_NV(":host", "example.org"), + MAKE_NV(":scheme", "https")}; + int rv; + nghttp2_nv *nva; + size_t nvlen; + + rv = frame_pack_bufs_init(&bufs); + + if (rv != 0) { + return; + } + + rv = nghttp2_hd_deflate_init(&deflater, nghttp2_mem_fm()); + if (rv != 0) { + goto deflate_init_fail; + } + rv = nghttp2_hd_inflate_init(&inflater, nghttp2_mem_fm()); + if (rv != 0) { + goto inflate_init_fail; + } + nvlen = ARRLEN(nv); + rv = nghttp2_nv_array_copy(&nva, nv, nvlen, nghttp2_mem_fm()); + if (rv < 0) { + goto nv_copy_fail; + } + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_STREAM, 1, + NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + if (rv != 0) { + goto fail; + } + rv = unpack_framebuf(&oframe, &bufs); + if (rv != 0) { + goto fail; + } + nghttp2_frame_headers_free(&oframe.headers, nghttp2_mem_fm()); + +fail: + nghttp2_frame_headers_free(&frame.headers, nghttp2_mem_fm()); +nv_copy_fail: + nghttp2_hd_inflate_free(&inflater); +inflate_init_fail: + nghttp2_hd_deflate_free(&deflater); +deflate_init_fail: + nghttp2_bufs_free(&bufs); +} + +static void run_nghttp2_frame_pack_settings(void) { + nghttp2_frame frame, oframe; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_settings_entry iv[2], *iv_copy; + int rv; + + rv = frame_pack_bufs_init(&bufs); + + if (rv != 0) { + return; + } + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 4096; + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[1].value = 100; + + iv_copy = nghttp2_frame_iv_copy(iv, 2, nghttp2_mem_fm()); + + if (iv_copy == NULL) { + goto iv_copy_fail; + } + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, iv_copy, 2); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + if (rv != 0) { + goto fail; + } + + buf = &bufs.head->buf; + + rv = nghttp2_frame_unpack_settings_payload2( + &oframe.settings.iv, &oframe.settings.niv, buf->pos + NGHTTP2_FRAME_HDLEN, + nghttp2_buf_len(buf) - NGHTTP2_FRAME_HDLEN, nghttp2_mem_fm()); + + if (rv != 0) { + goto fail; + } + nghttp2_frame_settings_free(&oframe.settings, nghttp2_mem_fm()); + +fail: + nghttp2_frame_settings_free(&frame.settings, nghttp2_mem_fm()); +iv_copy_fail: + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_frame(void) { + TEST_FAILMALLOC_RUN(run_nghttp2_frame_pack_headers); + TEST_FAILMALLOC_RUN(run_nghttp2_frame_pack_settings); +} + +static int deflate_inflate(nghttp2_hd_deflater *deflater, + nghttp2_hd_inflater *inflater, nghttp2_bufs *bufs, + nghttp2_nv *nva, size_t nvlen, nghttp2_mem *mem) { + int rv; + + rv = nghttp2_hd_deflate_hd_bufs(deflater, bufs, nva, nvlen); + + if (rv != 0) { + return rv; + } + + rv = (int)inflate_hd(inflater, NULL, bufs, 0, mem); + + if (rv < 0) { + return rv; + } + + nghttp2_bufs_reset(bufs); + + return 0; +} + +static void run_nghttp2_hd(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + int rv; + nghttp2_nv nva1[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "example.org"), + MAKE_NV(":path", "/slashdot"), + MAKE_NV("accept-encoding", "gzip, deflate"), MAKE_NV("foo", "bar")}; + nghttp2_nv nva2[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "example.org"), + MAKE_NV(":path", "/style.css"), MAKE_NV("cookie", "nghttp2=FTW"), + MAKE_NV("foo", "bar2")}; + + rv = frame_pack_bufs_init(&bufs); + + if (rv != 0) { + return; + } + + rv = nghttp2_hd_deflate_init(&deflater, nghttp2_mem_fm()); + + if (rv != 0) { + goto deflate_init_fail; + } + + rv = nghttp2_hd_inflate_init(&inflater, nghttp2_mem_fm()); + + if (rv != 0) { + goto inflate_init_fail; + } + + rv = deflate_inflate(&deflater, &inflater, &bufs, nva1, ARRLEN(nva1), + nghttp2_mem_fm()); + + if (rv != 0) { + goto deflate_hd_fail; + } + + rv = deflate_inflate(&deflater, &inflater, &bufs, nva2, ARRLEN(nva2), + nghttp2_mem_fm()); + + if (rv != 0) { + goto deflate_hd_fail; + } + +deflate_hd_fail: + nghttp2_hd_inflate_free(&inflater); +inflate_init_fail: + nghttp2_hd_deflate_free(&deflater); +deflate_init_fail: + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_hd(void) { TEST_FAILMALLOC_RUN(run_nghttp2_hd); } diff --git a/tests/failmalloc_test.h b/tests/failmalloc_test.h new file mode 100644 index 0000000..576932a --- /dev/null +++ b/tests/failmalloc_test.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 FAILMALLOC_TEST_H +#define FAILMALLOC_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_session_send(void); +void test_nghttp2_session_send_server(void); +void test_nghttp2_session_recv(void); +void test_nghttp2_frame(void); +void test_nghttp2_hd(void); + +#endif /* FAILMALLOC_TEST_H */ diff --git a/tests/main.c b/tests/main.c new file mode 100644 index 0000000..6ff97d0 --- /dev/null +++ b/tests/main.c @@ -0,0 +1,471 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_pq_test.h" +#include "nghttp2_map_test.h" +#include "nghttp2_queue_test.h" +#include "nghttp2_session_test.h" +#include "nghttp2_frame_test.h" +#include "nghttp2_stream_test.h" +#include "nghttp2_hd_test.h" +#include "nghttp2_npn_test.h" +#include "nghttp2_helper_test.h" +#include "nghttp2_buf_test.h" +#include "nghttp2_http_test.h" +#include "nghttp2_extpri_test.h" + +extern int nghttp2_enable_strict_preface; + +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; + + nghttp2_enable_strict_preface = 0; + + /* 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("libnghttp2_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, "pq", test_nghttp2_pq) || + !CU_add_test(pSuite, "pq_update", test_nghttp2_pq_update) || + !CU_add_test(pSuite, "pq_remove", test_nghttp2_pq_remove) || + !CU_add_test(pSuite, "map", test_nghttp2_map) || + !CU_add_test(pSuite, "map_functional", test_nghttp2_map_functional) || + !CU_add_test(pSuite, "map_each_free", test_nghttp2_map_each_free) || + !CU_add_test(pSuite, "queue", test_nghttp2_queue) || + !CU_add_test(pSuite, "npn", test_nghttp2_npn) || + !CU_add_test(pSuite, "session_recv", test_nghttp2_session_recv) || + !CU_add_test(pSuite, "session_recv_invalid_stream_id", + test_nghttp2_session_recv_invalid_stream_id) || + !CU_add_test(pSuite, "session_recv_invalid_frame", + test_nghttp2_session_recv_invalid_frame) || + !CU_add_test(pSuite, "session_recv_eof", test_nghttp2_session_recv_eof) || + !CU_add_test(pSuite, "session_recv_data", + test_nghttp2_session_recv_data) || + !CU_add_test(pSuite, "session_recv_data_no_auto_flow_control", + test_nghttp2_session_recv_data_no_auto_flow_control) || + !CU_add_test(pSuite, "session_recv_continuation", + test_nghttp2_session_recv_continuation) || + !CU_add_test(pSuite, "session_recv_headers_with_priority", + test_nghttp2_session_recv_headers_with_priority) || + !CU_add_test(pSuite, "session_recv_headers_with_padding", + test_nghttp2_session_recv_headers_with_padding) || + !CU_add_test(pSuite, "session_recv_headers_early_response", + test_nghttp2_session_recv_headers_early_response) || + !CU_add_test(pSuite, "session_recv_headers_for_closed_stream", + test_nghttp2_session_recv_headers_for_closed_stream) || + !CU_add_test(pSuite, "session_recv_headers_with_extpri", + test_nghttp2_session_recv_headers_with_extpri) || + !CU_add_test(pSuite, "session_server_recv_push_response", + test_nghttp2_session_server_recv_push_response) || + !CU_add_test(pSuite, "session_recv_premature_headers", + test_nghttp2_session_recv_premature_headers) || + !CU_add_test(pSuite, "session_recv_unknown_frame", + test_nghttp2_session_recv_unknown_frame) || + !CU_add_test(pSuite, "session_recv_unexpected_continuation", + test_nghttp2_session_recv_unexpected_continuation) || + !CU_add_test(pSuite, "session_recv_settings_header_table_size", + test_nghttp2_session_recv_settings_header_table_size) || + !CU_add_test(pSuite, "session_recv_too_large_frame_length", + test_nghttp2_session_recv_too_large_frame_length) || + !CU_add_test(pSuite, "session_recv_extension", + test_nghttp2_session_recv_extension) || + !CU_add_test(pSuite, "session_recv_altsvc", + test_nghttp2_session_recv_altsvc) || + !CU_add_test(pSuite, "session_recv_origin", + test_nghttp2_session_recv_origin) || + !CU_add_test(pSuite, "session_recv_priority_update", + test_nghttp2_session_recv_priority_update) || + !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || + !CU_add_test(pSuite, "session_add_frame", + test_nghttp2_session_add_frame) || + !CU_add_test(pSuite, "session_on_request_headers_received", + test_nghttp2_session_on_request_headers_received) || + !CU_add_test(pSuite, "session_on_response_headers_received", + test_nghttp2_session_on_response_headers_received) || + !CU_add_test(pSuite, "session_on_headers_received", + test_nghttp2_session_on_headers_received) || + !CU_add_test(pSuite, "session_on_push_response_headers_received", + test_nghttp2_session_on_push_response_headers_received) || + !CU_add_test(pSuite, "session_on_priority_received", + test_nghttp2_session_on_priority_received) || + !CU_add_test(pSuite, "session_on_rst_stream_received", + test_nghttp2_session_on_rst_stream_received) || + !CU_add_test(pSuite, "session_on_settings_received", + test_nghttp2_session_on_settings_received) || + !CU_add_test(pSuite, "session_on_push_promise_received", + test_nghttp2_session_on_push_promise_received) || + !CU_add_test(pSuite, "session_on_ping_received", + test_nghttp2_session_on_ping_received) || + !CU_add_test(pSuite, "session_on_goaway_received", + test_nghttp2_session_on_goaway_received) || + !CU_add_test(pSuite, "session_on_window_update_received", + test_nghttp2_session_on_window_update_received) || + !CU_add_test(pSuite, "session_on_data_received", + test_nghttp2_session_on_data_received) || + !CU_add_test(pSuite, "session_on_data_received_fail_fast", + test_nghttp2_session_on_data_received_fail_fast) || + !CU_add_test(pSuite, "session_on_altsvc_received", + test_nghttp2_session_on_altsvc_received) || + !CU_add_test(pSuite, "session_send_headers_start_stream", + test_nghttp2_session_send_headers_start_stream) || + !CU_add_test(pSuite, "session_send_headers_reply", + test_nghttp2_session_send_headers_reply) || + !CU_add_test(pSuite, "session_send_headers_frame_size_error", + test_nghttp2_session_send_headers_frame_size_error) || + !CU_add_test(pSuite, "session_send_headers_push_reply", + test_nghttp2_session_send_headers_push_reply) || + !CU_add_test(pSuite, "session_send_rst_stream", + test_nghttp2_session_send_rst_stream) || + !CU_add_test(pSuite, "session_send_push_promise", + test_nghttp2_session_send_push_promise) || + !CU_add_test(pSuite, "session_is_my_stream_id", + test_nghttp2_session_is_my_stream_id) || + !CU_add_test(pSuite, "session_upgrade2", test_nghttp2_session_upgrade2) || + !CU_add_test(pSuite, "session_reprioritize_stream", + test_nghttp2_session_reprioritize_stream) || + !CU_add_test( + pSuite, "session_reprioritize_stream_with_idle_stream_dep", + test_nghttp2_session_reprioritize_stream_with_idle_stream_dep) || + !CU_add_test(pSuite, "submit_data", test_nghttp2_submit_data) || + !CU_add_test(pSuite, "submit_data_read_length_too_large", + test_nghttp2_submit_data_read_length_too_large) || + !CU_add_test(pSuite, "submit_data_read_length_smallest", + test_nghttp2_submit_data_read_length_smallest) || + !CU_add_test(pSuite, "submit_data_twice", + test_nghttp2_submit_data_twice) || + !CU_add_test(pSuite, "submit_request_with_data", + test_nghttp2_submit_request_with_data) || + !CU_add_test(pSuite, "submit_request_without_data", + test_nghttp2_submit_request_without_data) || + !CU_add_test(pSuite, "submit_response_with_data", + test_nghttp2_submit_response_with_data) || + !CU_add_test(pSuite, "submit_response_without_data", + test_nghttp2_submit_response_without_data) || + !CU_add_test(pSuite, "Submit_response_push_response", + test_nghttp2_submit_response_push_response) || + !CU_add_test(pSuite, "submit_trailer", test_nghttp2_submit_trailer) || + !CU_add_test(pSuite, "submit_headers_start_stream", + test_nghttp2_submit_headers_start_stream) || + !CU_add_test(pSuite, "submit_headers_reply", + test_nghttp2_submit_headers_reply) || + !CU_add_test(pSuite, "submit_headers_push_reply", + test_nghttp2_submit_headers_push_reply) || + !CU_add_test(pSuite, "submit_headers", test_nghttp2_submit_headers) || + !CU_add_test(pSuite, "submit_headers_continuation", + test_nghttp2_submit_headers_continuation) || + !CU_add_test(pSuite, "submit_headers_continuation_extra_large", + test_nghttp2_submit_headers_continuation_extra_large) || + !CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) || + !CU_add_test(pSuite, "session_submit_settings", + test_nghttp2_submit_settings) || + !CU_add_test(pSuite, "session_submit_settings_update_local_window_size", + test_nghttp2_submit_settings_update_local_window_size) || + !CU_add_test(pSuite, "session_submit_settings_multiple_times", + test_nghttp2_submit_settings_multiple_times) || + !CU_add_test(pSuite, "session_submit_push_promise", + test_nghttp2_submit_push_promise) || + !CU_add_test(pSuite, "submit_window_update", + test_nghttp2_submit_window_update) || + !CU_add_test(pSuite, "submit_window_update_local_window_size", + test_nghttp2_submit_window_update_local_window_size) || + !CU_add_test(pSuite, "submit_shutdown_notice", + test_nghttp2_submit_shutdown_notice) || + !CU_add_test(pSuite, "submit_invalid_nv", + test_nghttp2_submit_invalid_nv) || + !CU_add_test(pSuite, "submit_extension", test_nghttp2_submit_extension) || + !CU_add_test(pSuite, "submit_altsvc", test_nghttp2_submit_altsvc) || + !CU_add_test(pSuite, "submit_origin", test_nghttp2_submit_origin) || + !CU_add_test(pSuite, "submit_priority_update", + test_nghttp2_submit_priority_update) || + !CU_add_test(pSuite, "submit_rst_stream", + test_nghttp2_submit_rst_stream) || + !CU_add_test(pSuite, "session_open_stream", + test_nghttp2_session_open_stream) || + !CU_add_test(pSuite, "session_open_stream_with_idle_stream_dep", + test_nghttp2_session_open_stream_with_idle_stream_dep) || + !CU_add_test(pSuite, "session_get_next_ob_item", + test_nghttp2_session_get_next_ob_item) || + !CU_add_test(pSuite, "session_pop_next_ob_item", + test_nghttp2_session_pop_next_ob_item) || + !CU_add_test(pSuite, "session_reply_fail", + test_nghttp2_session_reply_fail) || + !CU_add_test(pSuite, "session_max_concurrent_streams", + test_nghttp2_session_max_concurrent_streams) || + !CU_add_test(pSuite, "session_stop_data_with_rst_stream", + test_nghttp2_session_stop_data_with_rst_stream) || + !CU_add_test(pSuite, "session_defer_data", + test_nghttp2_session_defer_data) || + !CU_add_test(pSuite, "session_flow_control", + test_nghttp2_session_flow_control) || + !CU_add_test(pSuite, "session_flow_control_data_recv", + test_nghttp2_session_flow_control_data_recv) || + !CU_add_test(pSuite, "session_flow_control_data_with_padding_recv", + test_nghttp2_session_flow_control_data_with_padding_recv) || + !CU_add_test(pSuite, "session_data_read_temporal_failure", + test_nghttp2_session_data_read_temporal_failure) || + !CU_add_test(pSuite, "session_on_stream_close", + test_nghttp2_session_on_stream_close) || + !CU_add_test(pSuite, "session_on_ctrl_not_send", + test_nghttp2_session_on_ctrl_not_send) || + !CU_add_test(pSuite, "session_get_outbound_queue_size", + test_nghttp2_session_get_outbound_queue_size) || + !CU_add_test(pSuite, "session_get_effective_local_window_size", + test_nghttp2_session_get_effective_local_window_size) || + !CU_add_test(pSuite, "session_set_option", + test_nghttp2_session_set_option) || + !CU_add_test(pSuite, "session_data_backoff_by_high_pri_frame", + test_nghttp2_session_data_backoff_by_high_pri_frame) || + !CU_add_test(pSuite, "session_pack_data_with_padding", + test_nghttp2_session_pack_data_with_padding) || + !CU_add_test(pSuite, "session_pack_headers_with_padding", + test_nghttp2_session_pack_headers_with_padding) || + !CU_add_test(pSuite, "pack_settings_payload", + test_nghttp2_pack_settings_payload) || + !CU_add_test(pSuite, "session_stream_dep_add", + test_nghttp2_session_stream_dep_add) || + !CU_add_test(pSuite, "session_stream_dep_remove", + test_nghttp2_session_stream_dep_remove) || + !CU_add_test(pSuite, "session_stream_dep_add_subtree", + test_nghttp2_session_stream_dep_add_subtree) || + !CU_add_test(pSuite, "session_stream_dep_remove_subtree", + test_nghttp2_session_stream_dep_remove_subtree) || + !CU_add_test( + pSuite, "session_stream_dep_all_your_stream_are_belong_to_us", + test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us) || + !CU_add_test(pSuite, "session_stream_attach_item", + test_nghttp2_session_stream_attach_item) || + !CU_add_test(pSuite, "session_stream_attach_item_subtree", + test_nghttp2_session_stream_attach_item_subtree) || + !CU_add_test(pSuite, "session_stream_get_state", + test_nghttp2_session_stream_get_state) || + !CU_add_test(pSuite, "session_stream_get_something", + test_nghttp2_session_stream_get_something) || + !CU_add_test(pSuite, "session_find_stream", + test_nghttp2_session_find_stream) || + !CU_add_test(pSuite, "session_keep_closed_stream", + test_nghttp2_session_keep_closed_stream) || + !CU_add_test(pSuite, "session_keep_idle_stream", + test_nghttp2_session_keep_idle_stream) || + !CU_add_test(pSuite, "session_detach_idle_stream", + test_nghttp2_session_detach_idle_stream) || + !CU_add_test(pSuite, "session_large_dep_tree", + test_nghttp2_session_large_dep_tree) || + !CU_add_test(pSuite, "session_graceful_shutdown", + test_nghttp2_session_graceful_shutdown) || + !CU_add_test(pSuite, "session_on_header_temporal_failure", + test_nghttp2_session_on_header_temporal_failure) || + !CU_add_test(pSuite, "session_recv_client_magic", + test_nghttp2_session_recv_client_magic) || + !CU_add_test(pSuite, "session_delete_data_item", + test_nghttp2_session_delete_data_item) || + !CU_add_test(pSuite, "session_open_idle_stream", + test_nghttp2_session_open_idle_stream) || + !CU_add_test(pSuite, "session_cancel_reserved_remote", + test_nghttp2_session_cancel_reserved_remote) || + !CU_add_test(pSuite, "session_reset_pending_headers", + test_nghttp2_session_reset_pending_headers) || + !CU_add_test(pSuite, "session_send_data_callback", + test_nghttp2_session_send_data_callback) || + !CU_add_test(pSuite, "session_on_begin_headers_temporal_failure", + test_nghttp2_session_on_begin_headers_temporal_failure) || + !CU_add_test(pSuite, "session_defer_then_close", + test_nghttp2_session_defer_then_close) || + !CU_add_test(pSuite, "session_detach_item_from_closed_stream", + test_nghttp2_session_detach_item_from_closed_stream) || + !CU_add_test(pSuite, "session_flooding", test_nghttp2_session_flooding) || + !CU_add_test(pSuite, "session_change_stream_priority", + test_nghttp2_session_change_stream_priority) || + !CU_add_test(pSuite, "session_change_extpri_stream_priority", + test_nghttp2_session_change_extpri_stream_priority) || + !CU_add_test(pSuite, "session_create_idle_stream", + test_nghttp2_session_create_idle_stream) || + !CU_add_test(pSuite, "session_repeated_priority_change", + test_nghttp2_session_repeated_priority_change) || + !CU_add_test(pSuite, "session_repeated_priority_submission", + test_nghttp2_session_repeated_priority_submission) || + !CU_add_test(pSuite, "session_set_local_window_size", + test_nghttp2_session_set_local_window_size) || + !CU_add_test(pSuite, "session_cancel_from_before_frame_send", + test_nghttp2_session_cancel_from_before_frame_send) || + !CU_add_test(pSuite, "session_too_many_settings", + test_nghttp2_session_too_many_settings) || + !CU_add_test(pSuite, "session_removed_closed_stream", + test_nghttp2_session_removed_closed_stream) || + !CU_add_test(pSuite, "session_pause_data", + test_nghttp2_session_pause_data) || + !CU_add_test(pSuite, "session_no_closed_streams", + test_nghttp2_session_no_closed_streams) || + !CU_add_test(pSuite, "session_set_stream_user_data", + test_nghttp2_session_set_stream_user_data) || + !CU_add_test(pSuite, "session_no_rfc7540_priorities", + test_nghttp2_session_no_rfc7540_priorities) || + !CU_add_test(pSuite, "session_server_fallback_rfc7540_priorities", + test_nghttp2_session_server_fallback_rfc7540_priorities) || + !CU_add_test(pSuite, "http_mandatory_headers", + test_nghttp2_http_mandatory_headers) || + !CU_add_test(pSuite, "http_content_length", + test_nghttp2_http_content_length) || + !CU_add_test(pSuite, "http_content_length_mismatch", + test_nghttp2_http_content_length_mismatch) || + !CU_add_test(pSuite, "http_non_final_response", + test_nghttp2_http_non_final_response) || + !CU_add_test(pSuite, "http_trailer_headers", + test_nghttp2_http_trailer_headers) || + !CU_add_test(pSuite, "http_ignore_regular_header", + test_nghttp2_http_ignore_regular_header) || + !CU_add_test(pSuite, "http_ignore_content_length", + test_nghttp2_http_ignore_content_length) || + !CU_add_test(pSuite, "http_record_request_method", + test_nghttp2_http_record_request_method) || + !CU_add_test(pSuite, "http_push_promise", + test_nghttp2_http_push_promise) || + !CU_add_test(pSuite, "http_head_method_upgrade_workaround", + test_nghttp2_http_head_method_upgrade_workaround) || + !CU_add_test( + pSuite, "http_no_rfc9113_leading_and_trailing_ws_validation", + test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation) || + !CU_add_test(pSuite, "frame_pack_headers", + test_nghttp2_frame_pack_headers) || + !CU_add_test(pSuite, "frame_pack_headers_frame_too_large", + test_nghttp2_frame_pack_headers_frame_too_large) || + !CU_add_test(pSuite, "frame_pack_priority", + test_nghttp2_frame_pack_priority) || + !CU_add_test(pSuite, "frame_pack_rst_stream", + test_nghttp2_frame_pack_rst_stream) || + !CU_add_test(pSuite, "frame_pack_settings", + test_nghttp2_frame_pack_settings) || + !CU_add_test(pSuite, "frame_pack_push_promise", + test_nghttp2_frame_pack_push_promise) || + !CU_add_test(pSuite, "frame_pack_ping", test_nghttp2_frame_pack_ping) || + !CU_add_test(pSuite, "frame_pack_goaway", + test_nghttp2_frame_pack_goaway) || + !CU_add_test(pSuite, "frame_pack_window_update", + test_nghttp2_frame_pack_window_update) || + !CU_add_test(pSuite, "frame_pack_altsvc", + test_nghttp2_frame_pack_altsvc) || + !CU_add_test(pSuite, "frame_pack_origin", + test_nghttp2_frame_pack_origin) || + !CU_add_test(pSuite, "frame_pack_priority_update", + test_nghttp2_frame_pack_priority_update) || + !CU_add_test(pSuite, "nv_array_copy", test_nghttp2_nv_array_copy) || + !CU_add_test(pSuite, "iv_check", test_nghttp2_iv_check) || + !CU_add_test(pSuite, "hd_deflate", test_nghttp2_hd_deflate) || + !CU_add_test(pSuite, "hd_deflate_same_indexed_repr", + test_nghttp2_hd_deflate_same_indexed_repr) || + !CU_add_test(pSuite, "hd_inflate_indexed", + test_nghttp2_hd_inflate_indexed) || + !CU_add_test(pSuite, "hd_inflate_indname_noinc", + test_nghttp2_hd_inflate_indname_noinc) || + !CU_add_test(pSuite, "hd_inflate_indname_inc", + test_nghttp2_hd_inflate_indname_inc) || + !CU_add_test(pSuite, "hd_inflate_indname_inc_eviction", + test_nghttp2_hd_inflate_indname_inc_eviction) || + !CU_add_test(pSuite, "hd_inflate_newname_noinc", + test_nghttp2_hd_inflate_newname_noinc) || + !CU_add_test(pSuite, "hd_inflate_newname_inc", + test_nghttp2_hd_inflate_newname_inc) || + !CU_add_test(pSuite, "hd_inflate_clearall_inc", + test_nghttp2_hd_inflate_clearall_inc) || + !CU_add_test(pSuite, "hd_inflate_zero_length_huffman", + test_nghttp2_hd_inflate_zero_length_huffman) || + !CU_add_test(pSuite, "hd_inflate_expect_table_size_update", + test_nghttp2_hd_inflate_expect_table_size_update) || + !CU_add_test(pSuite, "hd_inflate_unexpected_table_size_update", + test_nghttp2_hd_inflate_unexpected_table_size_update) || + !CU_add_test(pSuite, "hd_ringbuf_reserve", + test_nghttp2_hd_ringbuf_reserve) || + !CU_add_test(pSuite, "hd_change_table_size", + test_nghttp2_hd_change_table_size) || + !CU_add_test(pSuite, "hd_deflate_inflate", + test_nghttp2_hd_deflate_inflate) || + !CU_add_test(pSuite, "hd_no_index", test_nghttp2_hd_no_index) || + !CU_add_test(pSuite, "hd_deflate_bound", test_nghttp2_hd_deflate_bound) || + !CU_add_test(pSuite, "hd_public_api", test_nghttp2_hd_public_api) || + !CU_add_test(pSuite, "hd_deflate_hd_vec", + test_nghttp2_hd_deflate_hd_vec) || + !CU_add_test(pSuite, "hd_decode_length", test_nghttp2_hd_decode_length) || + !CU_add_test(pSuite, "hd_huff_encode", test_nghttp2_hd_huff_encode) || + !CU_add_test(pSuite, "hd_huff_decode", test_nghttp2_hd_huff_decode) || + !CU_add_test(pSuite, "adjust_local_window_size", + test_nghttp2_adjust_local_window_size) || + !CU_add_test(pSuite, "check_header_name", + test_nghttp2_check_header_name) || + !CU_add_test(pSuite, "check_header_value", + test_nghttp2_check_header_value) || + !CU_add_test(pSuite, "check_header_value_rfc9113", + test_nghttp2_check_header_value_rfc9113) || + !CU_add_test(pSuite, "bufs_add", test_nghttp2_bufs_add) || + !CU_add_test(pSuite, "bufs_add_stack_buffer_overflow_bug", + test_nghttp2_bufs_add_stack_buffer_overflow_bug) || + !CU_add_test(pSuite, "bufs_addb", test_nghttp2_bufs_addb) || + !CU_add_test(pSuite, "bufs_orb", test_nghttp2_bufs_orb) || + !CU_add_test(pSuite, "bufs_remove", test_nghttp2_bufs_remove) || + !CU_add_test(pSuite, "bufs_reset", test_nghttp2_bufs_reset) || + !CU_add_test(pSuite, "bufs_advance", test_nghttp2_bufs_advance) || + !CU_add_test(pSuite, "bufs_next_present", + test_nghttp2_bufs_next_present) || + !CU_add_test(pSuite, "bufs_realloc", test_nghttp2_bufs_realloc) || + !CU_add_test(pSuite, "http_parse_priority", + test_nghttp2_http_parse_priority) || + !CU_add_test(pSuite, "sf_parse_item", test_nghttp2_sf_parse_item) || + !CU_add_test(pSuite, "sf_parse_inner_list", + test_nghttp2_sf_parse_inner_list) || + !CU_add_test(pSuite, "extpri_to_uint8", test_nghttp2_extpri_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/malloc_wrapper.c b/tests/malloc_wrapper.c new file mode 100644 index 0000000..f814c3d --- /dev/null +++ b/tests/malloc_wrapper.c @@ -0,0 +1,85 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "malloc_wrapper.h" + +int nghttp2_failmalloc = 0; +int nghttp2_failstart = 0; +int nghttp2_countmalloc = 1; +int nghttp2_nmalloc = 0; + +#define CHECK_PREREQ \ + do { \ + if (nghttp2_failmalloc && nghttp2_nmalloc >= nghttp2_failstart) { \ + return NULL; \ + } \ + if (nghttp2_countmalloc) { \ + ++nghttp2_nmalloc; \ + } \ + } while (0) + +static void *my_malloc(size_t size, void *mud) { + (void)mud; + + CHECK_PREREQ; + return malloc(size); +} + +static void my_free(void *ptr, void *mud) { + (void)mud; + + free(ptr); +} + +static void *my_calloc(size_t nmemb, size_t size, void *mud) { + (void)mud; + + CHECK_PREREQ; + return calloc(nmemb, size); +} + +static void *my_realloc(void *ptr, size_t size, void *mud) { + (void)mud; + + CHECK_PREREQ; + return realloc(ptr, size); +} + +static nghttp2_mem mem = {NULL, my_malloc, my_free, my_calloc, my_realloc}; + +nghttp2_mem *nghttp2_mem_fm(void) { return &mem; } + +static int failmalloc_bk, countmalloc_bk; + +void nghttp2_failmalloc_pause(void) { + failmalloc_bk = nghttp2_failmalloc; + countmalloc_bk = nghttp2_countmalloc; + nghttp2_failmalloc = 0; + nghttp2_countmalloc = 0; +} + +void nghttp2_failmalloc_unpause(void) { + nghttp2_failmalloc = failmalloc_bk; + nghttp2_countmalloc = countmalloc_bk; +} diff --git a/tests/malloc_wrapper.h b/tests/malloc_wrapper.h new file mode 100644 index 0000000..a3a3dd7 --- /dev/null +++ b/tests/malloc_wrapper.h @@ -0,0 +1,65 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 MALLOC_WRAPPER_H +#define MALLOC_WRAPPER_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stdlib.h> + +#include "nghttp2_mem.h" + +/* Global variables to control the behavior of malloc() */ + +/* If nonzero, malloc failure mode is on */ +extern int nghttp2_failmalloc; +/* If nghttp2_failstart <= nghttp2_nmalloc and nghttp2_failmalloc is + nonzero, malloc() fails. */ +extern int nghttp2_failstart; +/* If nonzero, nghttp2_nmalloc is incremented if malloc() succeeds. */ +extern int nghttp2_countmalloc; +/* The number of successful invocation of malloc(). This value is only + incremented if nghttp2_nmalloc is nonzero. */ +extern int nghttp2_nmalloc; + +/* Returns pointer to nghttp2_mem, which, when dereferenced, contains + specifically instrumented memory allocators for failmalloc + tests. */ +nghttp2_mem *nghttp2_mem_fm(void); + +/* Copies nghttp2_failmalloc and nghttp2_countmalloc to statically + allocated space and sets 0 to them. This will effectively make + malloc() work like normal malloc(). This is useful when you want to + disable malloc() failure mode temporarily. */ +void nghttp2_failmalloc_pause(void); + +/* Restores the values of nghttp2_failmalloc and nghttp2_countmalloc + with the values saved by the previous + nghttp2_failmalloc_pause(). */ +void nghttp2_failmalloc_unpause(void); + +#endif /* MALLOC_WRAPPER_H */ diff --git a/tests/nghttp2_buf_test.c b/tests/nghttp2_buf_test.c new file mode 100644 index 0000000..8d1f752 --- /dev/null +++ b/tests/nghttp2_buf_test.c @@ -0,0 +1,342 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_buf_test.h" + +#include <CUnit/CUnit.h> + +#include "nghttp2_buf.h" +#include "nghttp2_test_helper.h" + +void test_nghttp2_bufs_add(void) { + int rv; + nghttp2_bufs bufs; + uint8_t data[2048]; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 1000, 3, mem); + CU_ASSERT(0 == rv); + + CU_ASSERT(bufs.cur->buf.pos == bufs.cur->buf.last); + + rv = nghttp2_bufs_add(&bufs, data, 493); + CU_ASSERT(0 == rv); + CU_ASSERT(493 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(493 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(507 == nghttp2_bufs_cur_avail(&bufs)); + + rv = nghttp2_bufs_add(&bufs, data, 507); + CU_ASSERT(0 == rv); + CU_ASSERT(1000 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1000 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(bufs.cur == bufs.head); + + rv = nghttp2_bufs_add(&bufs, data, 1); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1001 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(bufs.cur == bufs.head->next); + + nghttp2_bufs_free(&bufs); +} + +/* Test for GH-232, stack-buffer-overflow */ +void test_nghttp2_bufs_add_stack_buffer_overflow_bug(void) { + int rv; + nghttp2_bufs bufs; + uint8_t data[1024]; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 100, 200, mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_add(&bufs, data, sizeof(data)); + + CU_ASSERT(0 == rv); + CU_ASSERT(sizeof(data) == nghttp2_bufs_len(&bufs)); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_addb(void) { + int rv; + nghttp2_bufs bufs; + ssize_t i; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 1000, 3, mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_addb(&bufs, 14); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(14 == *bufs.cur->buf.pos); + + for (i = 0; i < 999; ++i) { + rv = nghttp2_bufs_addb(&bufs, 254); + + CU_ASSERT(0 == rv); + CU_ASSERT((size_t)(i + 2) == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT((size_t)(i + 2) == nghttp2_bufs_len(&bufs)); + CU_ASSERT(254 == *(bufs.cur->buf.last - 1)); + CU_ASSERT(bufs.cur == bufs.head); + } + + rv = nghttp2_bufs_addb(&bufs, 253); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1001 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(253 == *(bufs.cur->buf.last - 1)); + CU_ASSERT(bufs.cur == bufs.head->next); + + rv = nghttp2_bufs_addb_hold(&bufs, 15); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1001 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(15 == *(bufs.cur->buf.last)); + + /* test fast version */ + + nghttp2_bufs_fast_addb(&bufs, 240); + + CU_ASSERT(2 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1002 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(240 == *(bufs.cur->buf.last - 1)); + + nghttp2_bufs_fast_addb_hold(&bufs, 113); + + CU_ASSERT(2 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1002 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(113 == *(bufs.cur->buf.last)); + + /* addb_hold when last == end */ + bufs.cur->buf.last = bufs.cur->buf.end; + + rv = nghttp2_bufs_addb_hold(&bufs, 19); + CU_ASSERT(0 == rv); + CU_ASSERT(0 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(2000 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(19 == *(bufs.cur->buf.last)); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_orb(void) { + int rv; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 1000, 3, mem); + CU_ASSERT(0 == rv); + + *(bufs.cur->buf.last) = 0; + + rv = nghttp2_bufs_orb_hold(&bufs, 15); + CU_ASSERT(0 == rv); + CU_ASSERT(0 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(0 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(15 == *(bufs.cur->buf.last)); + + rv = nghttp2_bufs_orb(&bufs, 240); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(255 == *(bufs.cur->buf.last - 1)); + + *(bufs.cur->buf.last) = 0; + nghttp2_bufs_fast_orb_hold(&bufs, 240); + CU_ASSERT(240 == *(bufs.cur->buf.last)); + + nghttp2_bufs_fast_orb(&bufs, 15); + CU_ASSERT(255 == *(bufs.cur->buf.last - 1)); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_remove(void) { + int rv; + nghttp2_bufs bufs; + nghttp2_buf_chain *chain; + int i; + uint8_t *out; + ssize_t outlen; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 1000, 3, mem); + CU_ASSERT(0 == rv); + + nghttp2_buf_shift_right(&bufs.cur->buf, 10); + + rv = nghttp2_bufs_add(&bufs, "hello ", 6); + CU_ASSERT(0 == rv); + + for (i = 0; i < 2; ++i) { + chain = bufs.cur; + + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(0 == rv); + + CU_ASSERT(chain->next == bufs.cur); + } + + rv = nghttp2_bufs_add(&bufs, "world", 5); + CU_ASSERT(0 == rv); + + outlen = nghttp2_bufs_remove(&bufs, &out); + CU_ASSERT(11 == outlen); + + CU_ASSERT(0 == memcmp("hello world", out, (size_t)outlen)); + CU_ASSERT(11 == nghttp2_bufs_len(&bufs)); + + mem->free(out, NULL); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_reset(void) { + int rv; + nghttp2_bufs bufs; + nghttp2_buf_chain *ci; + size_t offset = 9; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init3(&bufs, 250, 3, 1, offset, mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_add(&bufs, "foo", 3); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_add(&bufs, "bar", 3); + CU_ASSERT(0 == rv); + + CU_ASSERT(6 == nghttp2_bufs_len(&bufs)); + + nghttp2_bufs_reset(&bufs); + + CU_ASSERT(0 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(bufs.cur == bufs.head); + + for (ci = bufs.head; ci; ci = ci->next) { + CU_ASSERT((ssize_t)offset == ci->buf.pos - ci->buf.begin); + CU_ASSERT(ci->buf.pos == ci->buf.last); + } + + CU_ASSERT(bufs.head->next == NULL); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_advance(void) { + int rv; + nghttp2_bufs bufs; + int i; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 250, 3, mem); + CU_ASSERT(0 == rv); + + for (i = 0; i < 2; ++i) { + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(0 == rv); + } + + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(NGHTTP2_ERR_BUFFER_ERROR == rv); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_next_present(void) { + int rv; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 250, 3, mem); + CU_ASSERT(0 == rv); + + CU_ASSERT(0 == nghttp2_bufs_next_present(&bufs)); + + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(0 == rv); + + nghttp2_bufs_rewind(&bufs); + + CU_ASSERT(0 == nghttp2_bufs_next_present(&bufs)); + + bufs.cur = bufs.head->next; + + rv = nghttp2_bufs_addb(&bufs, 1); + CU_ASSERT(0 == rv); + + nghttp2_bufs_rewind(&bufs); + + CU_ASSERT(0 != nghttp2_bufs_next_present(&bufs)); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_realloc(void) { + int rv; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init3(&bufs, 266, 3, 1, 10, mem); + CU_ASSERT(0 == rv); + + /* Create new buffer to see that these buffers are deallocated on + realloc */ + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_realloc(&bufs, 522); + CU_ASSERT(0 == rv); + + CU_ASSERT(512 == nghttp2_bufs_cur_avail(&bufs)); + + rv = nghttp2_bufs_realloc(&bufs, 9); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + nghttp2_bufs_free(&bufs); +} diff --git a/tests/nghttp2_buf_test.h b/tests/nghttp2_buf_test.h new file mode 100644 index 0000000..714b89f --- /dev/null +++ b/tests/nghttp2_buf_test.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_BUF_TEST_H +#define NGHTTP2_BUF_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_bufs_add(void); +void test_nghttp2_bufs_add_stack_buffer_overflow_bug(void); +void test_nghttp2_bufs_addb(void); +void test_nghttp2_bufs_orb(void); +void test_nghttp2_bufs_remove(void); +void test_nghttp2_bufs_reset(void); +void test_nghttp2_bufs_advance(void); +void test_nghttp2_bufs_next_present(void); +void test_nghttp2_bufs_realloc(void); + +#endif /* NGHTTP2_BUF_TEST_H */ diff --git a/tests/nghttp2_extpri_test.c b/tests/nghttp2_extpri_test.c new file mode 100644 index 0000000..aa91230 --- /dev/null +++ b/tests/nghttp2_extpri_test.c @@ -0,0 +1,50 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 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. + */ +#include "nghttp2_extpri_test.h" + +#include <CUnit/CUnit.h> + +#include "nghttp2_extpri.h" +#include "nghttp2_test_helper.h" + +void test_nghttp2_extpri_to_uint8(void) { + { + nghttp2_extpri pri = {1, 0}; + CU_ASSERT(1 == nghttp2_extpri_to_uint8(&pri)); + } + { + nghttp2_extpri pri = {1, 1}; + CU_ASSERT((0x80 | 1) == nghttp2_extpri_to_uint8(&pri)); + } + { + nghttp2_extpri pri = {7, 1}; + CU_ASSERT((0x80 | 7) == nghttp2_extpri_to_uint8(&pri)); + } + { + nghttp2_extpri pri = {7, 0}; + CU_ASSERT(7 == nghttp2_extpri_to_uint8(&pri)); + } +} diff --git a/tests/nghttp2_extpri_test.h b/tests/nghttp2_extpri_test.h new file mode 100644 index 0000000..a8a93b9 --- /dev/null +++ b/tests/nghttp2_extpri_test.h @@ -0,0 +1,35 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 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. + */ +#ifndef NGHTTP2_EXTPRI_TEST_H +#define NGHTTP2_EXTPRI_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_extpri_to_uint8(void); + +#endif /* NGHTTP2_EXTPRI_TEST_H */ diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c new file mode 100644 index 0000000..495e35d --- /dev/null +++ b/tests/nghttp2_frame_test.c @@ -0,0 +1,746 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_frame_test.h" + +#include <assert.h> +#include <stdio.h> + +#include <CUnit/CUnit.h> + +#include "nghttp2_frame.h" +#include "nghttp2_helper.h" +#include "nghttp2_test_helper.h" +#include "nghttp2_priority_spec.h" + +static nghttp2_nv make_nv(const char *name, const char *value) { + nghttp2_nv nv; + nv.name = (uint8_t *)name; + nv.value = (uint8_t *)value; + nv.namelen = strlen(name); + nv.valuelen = strlen(value); + nv.flags = NGHTTP2_NV_FLAG_NONE; + + return nv; +} + +#define HEADERS_LENGTH 7 + +static nghttp2_nv *headers(nghttp2_mem *mem) { + nghttp2_nv *nva = mem->malloc(sizeof(nghttp2_nv) * HEADERS_LENGTH, NULL); + nva[0] = make_nv("method", "GET"); + nva[1] = make_nv("scheme", "https"); + nva[2] = make_nv("url", "/"); + nva[3] = make_nv("x-head", "foo"); + nva[4] = make_nv("x-head", "bar"); + nva[5] = make_nv("version", "HTTP/1.1"); + nva[6] = make_nv("x-empty", ""); + return nva; +} + +static void check_frame_header(size_t length, uint8_t type, uint8_t flags, + int32_t stream_id, nghttp2_frame_hd *hd) { + CU_ASSERT(length == hd->length); + CU_ASSERT(type == hd->type); + CU_ASSERT(flags == hd->flags); + CU_ASSERT(stream_id == hd->stream_id); + CU_ASSERT(0 == hd->reserved); +} + +void test_nghttp2_frame_pack_headers() { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_headers frame, oframe; + nghttp2_bufs bufs; + nghttp2_nv *nva; + nghttp2_priority_spec pri_spec; + size_t nvlen; + nva_out out; + size_t hdblocklen; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + nva = headers(mem); + nvlen = HEADERS_LENGTH; + + nghttp2_priority_spec_default_init(&pri_spec); + + nghttp2_frame_headers_init( + &frame, NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS, 1000000007, + NGHTTP2_HCAT_REQUEST, &pri_spec, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater); + + nghttp2_bufs_rewind(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + + check_frame_header(nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN, + NGHTTP2_HEADERS, + NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS, + 1000000007, &oframe.hd); + /* We did not include PRIORITY flag */ + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == oframe.pri_spec.weight); + + hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN; + CU_ASSERT((ssize_t)hdblocklen == + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem)); + + CU_ASSERT(7 == out.nvlen); + CU_ASSERT(nvnameeq("method", &out.nva[0])); + CU_ASSERT(nvvalueeq("GET", &out.nva[0])); + + nghttp2_frame_headers_free(&oframe, mem); + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + memset(&oframe, 0, sizeof(oframe)); + /* Next, include NGHTTP2_FLAG_PRIORITY */ + nghttp2_priority_spec_init(&frame.pri_spec, 1000000009, 12, 1); + frame.hd.flags |= NGHTTP2_FLAG_PRIORITY; + + rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + + check_frame_header(nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN, + NGHTTP2_HEADERS, + NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY, + 1000000007, &oframe.hd); + + CU_ASSERT(1000000009 == oframe.pri_spec.stream_id); + CU_ASSERT(12 == oframe.pri_spec.weight); + CU_ASSERT(1 == oframe.pri_spec.exclusive); + + hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN - + nghttp2_frame_priority_len(oframe.hd.flags); + CU_ASSERT((ssize_t)hdblocklen == + inflate_hd(&inflater, &out, &bufs, + NGHTTP2_FRAME_HDLEN + + nghttp2_frame_priority_len(oframe.hd.flags), + mem)); + + nghttp2_nv_array_sort(out.nva, out.nvlen); + CU_ASSERT(nvnameeq("method", &out.nva[0])); + + nghttp2_frame_headers_free(&oframe, mem); + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame, mem); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_frame_pack_headers_frame_too_large(void) { + nghttp2_hd_deflater deflater; + nghttp2_headers frame; + nghttp2_bufs bufs; + nghttp2_nv *nva; + size_t big_vallen = NGHTTP2_HD_MAX_NV; + nghttp2_nv big_hds[16]; + size_t big_hdslen = ARRLEN(big_hds); + size_t i; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + for (i = 0; i < big_hdslen; ++i) { + big_hds[i].name = (uint8_t *)"header"; + big_hds[i].value = mem->malloc(big_vallen + 1, NULL); + memset(big_hds[i].value, '0' + (int)i, big_vallen); + big_hds[i].value[big_vallen] = '\0'; + big_hds[i].namelen = strlen((char *)big_hds[i].name); + big_hds[i].valuelen = big_vallen; + big_hds[i].flags = NGHTTP2_NV_FLAG_NONE; + } + + nghttp2_nv_array_copy(&nva, big_hds, big_hdslen, mem); + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_frame_headers_init( + &frame, NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS, 1000000007, + NGHTTP2_HCAT_REQUEST, NULL, nva, big_hdslen); + rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater); + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == rv); + + nghttp2_frame_headers_free(&frame, mem); + nghttp2_bufs_free(&bufs); + for (i = 0; i < big_hdslen; ++i) { + mem->free(big_hds[i].value, NULL); + } + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_frame_pack_priority(void) { + nghttp2_priority frame, oframe; + nghttp2_bufs bufs; + nghttp2_priority_spec pri_spec; + int rv; + + frame_pack_bufs_init(&bufs); + + /* First, pack priority with priority group and weight */ + nghttp2_priority_spec_init(&pri_spec, 1000000009, 12, 1); + + nghttp2_frame_priority_init(&frame, 1000000007, &pri_spec); + rv = nghttp2_frame_pack_priority(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 5 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(5, NGHTTP2_PRIORITY, NGHTTP2_FLAG_NONE, 1000000007, + &oframe.hd); + + CU_ASSERT(1000000009 == oframe.pri_spec.stream_id); + CU_ASSERT(12 == oframe.pri_spec.weight); + CU_ASSERT(1 == oframe.pri_spec.exclusive); + + nghttp2_frame_priority_free(&oframe); + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_frame_priority_free(&frame); +} + +void test_nghttp2_frame_pack_rst_stream(void) { + nghttp2_rst_stream frame, oframe; + nghttp2_bufs bufs; + int rv; + + frame_pack_bufs_init(&bufs); + + nghttp2_frame_rst_stream_init(&frame, 1000000007, NGHTTP2_PROTOCOL_ERROR); + rv = nghttp2_frame_pack_rst_stream(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, 1000000007, + &oframe.hd); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == oframe.error_code); + + nghttp2_frame_rst_stream_free(&oframe); + nghttp2_bufs_reset(&bufs); + + /* Unknown error code is passed to callback as is */ + frame.error_code = 1000000009; + rv = nghttp2_frame_pack_rst_stream(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + + check_frame_header(4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, 1000000007, + &oframe.hd); + + CU_ASSERT(1000000009 == oframe.error_code); + + nghttp2_frame_rst_stream_free(&oframe); + + nghttp2_frame_rst_stream_free(&frame); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_frame_pack_settings() { + nghttp2_settings frame, oframe; + nghttp2_bufs bufs; + int i; + int rv; + nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 256}, + {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 16384}, + {NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, 4096}}; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nghttp2_frame_settings_init(&frame, NGHTTP2_FLAG_NONE, + nghttp2_frame_iv_copy(iv, 3, mem), 3); + rv = nghttp2_frame_pack_settings(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 3 * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH == + nghttp2_bufs_len(&bufs)); + + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(3 * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH, NGHTTP2_SETTINGS, + NGHTTP2_FLAG_NONE, 0, &oframe.hd); + CU_ASSERT(3 == oframe.niv); + for (i = 0; i < 3; ++i) { + CU_ASSERT(iv[i].settings_id == oframe.iv[i].settings_id); + CU_ASSERT(iv[i].value == oframe.iv[i].value); + } + + nghttp2_bufs_free(&bufs); + nghttp2_frame_settings_free(&frame, mem); + nghttp2_frame_settings_free(&oframe, mem); +} + +void test_nghttp2_frame_pack_push_promise() { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_push_promise frame, oframe; + nghttp2_bufs bufs; + nghttp2_nv *nva; + size_t nvlen; + nva_out out; + size_t hdblocklen; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + nva = headers(mem); + nvlen = HEADERS_LENGTH; + nghttp2_frame_push_promise_init(&frame, NGHTTP2_FLAG_END_HEADERS, 1000000007, + (1U << 31) - 1, nva, nvlen); + rv = nghttp2_frame_pack_push_promise(&bufs, &frame, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + + check_frame_header(nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN, + NGHTTP2_PUSH_PROMISE, NGHTTP2_FLAG_END_HEADERS, 1000000007, + &oframe.hd); + CU_ASSERT((1U << 31) - 1 == oframe.promised_stream_id); + + hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN - 4; + CU_ASSERT((ssize_t)hdblocklen == + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN + 4, mem)); + + CU_ASSERT(7 == out.nvlen); + CU_ASSERT(nvnameeq("method", &out.nva[0])); + CU_ASSERT(nvvalueeq("GET", &out.nva[0])); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_frame_push_promise_free(&oframe, mem); + nghttp2_frame_push_promise_free(&frame, mem); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_frame_pack_ping(void) { + nghttp2_ping frame, oframe; + nghttp2_bufs bufs; + const uint8_t opaque_data[] = "01234567"; + int rv; + + frame_pack_bufs_init(&bufs); + + nghttp2_frame_ping_init(&frame, NGHTTP2_FLAG_ACK, opaque_data); + rv = nghttp2_frame_pack_ping(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 8 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(8, NGHTTP2_PING, NGHTTP2_FLAG_ACK, 0, &oframe.hd); + CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, sizeof(opaque_data) - 1) == + 0); + + nghttp2_bufs_free(&bufs); + nghttp2_frame_ping_free(&oframe); + nghttp2_frame_ping_free(&frame); +} + +void test_nghttp2_frame_pack_goaway() { + nghttp2_goaway frame, oframe; + nghttp2_bufs bufs; + size_t opaque_data_len = 16; + uint8_t *opaque_data; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + opaque_data = mem->malloc(opaque_data_len, NULL); + memcpy(opaque_data, "0123456789abcdef", opaque_data_len); + nghttp2_frame_goaway_init(&frame, 1000000007, NGHTTP2_PROTOCOL_ERROR, + opaque_data, opaque_data_len); + rv = nghttp2_frame_pack_goaway(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 8 + opaque_data_len == + nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(24, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, &oframe.hd); + CU_ASSERT(1000000007 == oframe.last_stream_id); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == oframe.error_code); + + CU_ASSERT(opaque_data_len == oframe.opaque_data_len); + CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, opaque_data_len) == 0); + + nghttp2_frame_goaway_free(&oframe, mem); + nghttp2_bufs_reset(&bufs); + + /* Unknown error code is passed to callback as is */ + frame.error_code = 1000000009; + + rv = nghttp2_frame_pack_goaway(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(24, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, &oframe.hd); + CU_ASSERT(1000000009 == oframe.error_code); + + nghttp2_frame_goaway_free(&oframe, mem); + + nghttp2_frame_goaway_free(&frame, mem); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_frame_pack_window_update(void) { + nghttp2_window_update frame, oframe; + nghttp2_bufs bufs; + int rv; + + frame_pack_bufs_init(&bufs); + + nghttp2_frame_window_update_init(&frame, NGHTTP2_FLAG_NONE, 1000000007, 4096); + rv = nghttp2_frame_pack_window_update(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(4, NGHTTP2_WINDOW_UPDATE, NGHTTP2_FLAG_NONE, 1000000007, + &oframe.hd); + CU_ASSERT(4096 == oframe.window_size_increment); + + nghttp2_bufs_free(&bufs); + nghttp2_frame_window_update_free(&oframe); + nghttp2_frame_window_update_free(&frame); +} + +void test_nghttp2_frame_pack_altsvc(void) { + nghttp2_extension frame, oframe; + nghttp2_ext_altsvc altsvc, oaltsvc; + nghttp2_bufs bufs; + int rv; + size_t payloadlen; + static const uint8_t origin[] = "nghttp2.org"; + static const uint8_t field_value[] = "h2=\":443\""; + nghttp2_buf buf; + uint8_t *rawbuf; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + frame.payload = &altsvc; + oframe.payload = &oaltsvc; + + rawbuf = nghttp2_mem_malloc(mem, 32); + nghttp2_buf_wrap_init(&buf, rawbuf, 32); + + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1); + + nghttp2_frame_altsvc_init(&frame, 1000000007, buf.pos, sizeof(origin) - 1, + buf.pos + sizeof(origin) - 1, + sizeof(field_value) - 1); + + payloadlen = 2 + sizeof(origin) - 1 + sizeof(field_value) - 1; + + rv = nghttp2_frame_pack_altsvc(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + payloadlen == nghttp2_bufs_len(&bufs)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(0 == rv); + + check_frame_header(payloadlen, NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 1000000007, + &oframe.hd); + + CU_ASSERT(sizeof(origin) - 1 == oaltsvc.origin_len); + CU_ASSERT(0 == memcmp(origin, oaltsvc.origin, sizeof(origin) - 1)); + CU_ASSERT(sizeof(field_value) - 1 == oaltsvc.field_value_len); + CU_ASSERT(0 == + memcmp(field_value, oaltsvc.field_value, sizeof(field_value) - 1)); + + nghttp2_frame_altsvc_free(&oframe, mem); + nghttp2_frame_altsvc_free(&frame, mem); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_frame_pack_origin(void) { + nghttp2_extension frame, oframe; + nghttp2_ext_origin origin, oorigin; + nghttp2_bufs bufs; + nghttp2_buf *buf; + int rv; + size_t payloadlen; + static const uint8_t example[] = "https://example.com"; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + nghttp2_origin_entry ov[] = { + { + (uint8_t *)example, + sizeof(example) - 1, + }, + { + NULL, + 0, + }, + { + (uint8_t *)nghttp2, + sizeof(nghttp2) - 1, + }, + }; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + frame.payload = &origin; + oframe.payload = &oorigin; + + nghttp2_frame_origin_init(&frame, ov, 3); + + payloadlen = 2 + sizeof(example) - 1 + 2 + 2 + sizeof(nghttp2) - 1; + + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + payloadlen == nghttp2_bufs_len(&bufs)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(0 == rv); + + check_frame_header(payloadlen, NGHTTP2_ORIGIN, NGHTTP2_FLAG_NONE, 0, + &oframe.hd); + + CU_ASSERT(2 == oorigin.nov); + CU_ASSERT(sizeof(example) - 1 == oorigin.ov[0].origin_len); + CU_ASSERT(0 == memcmp(example, oorigin.ov[0].origin, sizeof(example) - 1)); + CU_ASSERT(sizeof(nghttp2) - 1 == oorigin.ov[1].origin_len); + CU_ASSERT(0 == memcmp(nghttp2, oorigin.ov[1].origin, sizeof(nghttp2) - 1)); + + nghttp2_frame_origin_free(&oframe, mem); + + /* Check the case where origin length is too large */ + buf = &bufs.head->buf; + nghttp2_put_uint16be(buf->pos + NGHTTP2_FRAME_HDLEN, + (uint16_t)(payloadlen - 1)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(NGHTTP2_ERR_FRAME_SIZE_ERROR == rv); + + nghttp2_bufs_reset(&bufs); + memset(&oframe, 0, sizeof(oframe)); + memset(&oorigin, 0, sizeof(oorigin)); + oframe.payload = &oorigin; + + /* Empty ORIGIN frame */ + nghttp2_frame_origin_init(&frame, NULL, 0); + + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN == nghttp2_bufs_len(&bufs)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(0 == rv); + + check_frame_header(0, NGHTTP2_ORIGIN, NGHTTP2_FLAG_NONE, 0, &oframe.hd); + + CU_ASSERT(0 == oorigin.nov); + CU_ASSERT(NULL == oorigin.ov); + + nghttp2_frame_origin_free(&oframe, mem); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_frame_pack_priority_update(void) { + nghttp2_extension frame, oframe; + nghttp2_ext_priority_update priority_update, opriority_update; + nghttp2_bufs bufs; + int rv; + size_t payloadlen; + static const uint8_t field_value[] = "i,u=0"; + + frame_pack_bufs_init(&bufs); + + frame.payload = &priority_update; + oframe.payload = &opriority_update; + + nghttp2_frame_priority_update_init(&frame, 1000000007, (uint8_t *)field_value, + sizeof(field_value) - 1); + + payloadlen = 4 + sizeof(field_value) - 1; + + rv = nghttp2_frame_pack_priority_update(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + payloadlen == nghttp2_bufs_len(&bufs)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(0 == rv); + + check_frame_header(payloadlen, NGHTTP2_PRIORITY_UPDATE, NGHTTP2_FLAG_NONE, 0, + &oframe.hd); + + CU_ASSERT(sizeof(field_value) - 1 == opriority_update.field_value_len); + CU_ASSERT(0 == memcmp(field_value, opriority_update.field_value, + sizeof(field_value) - 1)); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_nv_array_copy(void) { + nghttp2_nv *nva; + ssize_t rv; + nghttp2_nv emptynv[] = {MAKE_NV("", ""), MAKE_NV("", "")}; + nghttp2_nv nv[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")}; + nghttp2_nv bignv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + bignv.name = (uint8_t *)"echo"; + bignv.namelen = strlen("echo"); + bignv.valuelen = (1 << 14) - 1; + bignv.value = mem->malloc(bignv.valuelen, NULL); + bignv.flags = NGHTTP2_NV_FLAG_NONE; + memset(bignv.value, '0', bignv.valuelen); + + rv = nghttp2_nv_array_copy(&nva, NULL, 0, mem); + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == nva); + + rv = nghttp2_nv_array_copy(&nva, emptynv, ARRLEN(emptynv), mem); + CU_ASSERT(0 == rv); + CU_ASSERT(nva[0].namelen == 0); + CU_ASSERT(nva[0].valuelen == 0); + CU_ASSERT(nva[1].namelen == 0); + CU_ASSERT(nva[1].valuelen == 0); + + nghttp2_nv_array_del(nva, mem); + + rv = nghttp2_nv_array_copy(&nva, nv, ARRLEN(nv), mem); + CU_ASSERT(0 == rv); + CU_ASSERT(nva[0].namelen == 5); + CU_ASSERT(0 == memcmp("alpha", nva[0].name, 5)); + CU_ASSERT(nva[0].valuelen == 5); + CU_ASSERT(0 == memcmp("bravo", nva[0].value, 5)); + CU_ASSERT(nva[1].namelen == 7); + CU_ASSERT(0 == memcmp("charlie", nva[1].name, 7)); + CU_ASSERT(nva[1].valuelen == 5); + CU_ASSERT(0 == memcmp("delta", nva[1].value, 5)); + + nghttp2_nv_array_del(nva, mem); + + /* Large header field is acceptable */ + rv = nghttp2_nv_array_copy(&nva, &bignv, 1, mem); + CU_ASSERT(0 == rv); + + nghttp2_nv_array_del(nva, mem); + + mem->free(bignv.value, NULL); +} + +void test_nghttp2_iv_check(void) { + nghttp2_settings_entry iv[5]; + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 100; + iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[1].value = 1024; + + CU_ASSERT(nghttp2_iv_check(iv, 2)); + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = NGHTTP2_MAX_WINDOW_SIZE; + CU_ASSERT(nghttp2_iv_check(iv, 2)); + + /* Too large window size */ + iv[1].value = (uint32_t)NGHTTP2_MAX_WINDOW_SIZE + 1; + CU_ASSERT(0 == nghttp2_iv_check(iv, 2)); + + /* ENABLE_PUSH only allows 0 or 1 */ + iv[1].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[1].value = 0; + CU_ASSERT(nghttp2_iv_check(iv, 2)); + iv[1].value = 1; + CU_ASSERT(nghttp2_iv_check(iv, 2)); + iv[1].value = 3; + CU_ASSERT(!nghttp2_iv_check(iv, 2)); + + /* Undefined SETTINGS ID is allowed */ + iv[1].settings_id = 1000000009; + iv[1].value = 0; + CU_ASSERT(nghttp2_iv_check(iv, 2)); + + /* Full size SETTINGS_HEADER_TABLE_SIZE (UINT32_MAX) must be + accepted */ + iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[1].value = UINT32_MAX; + CU_ASSERT(nghttp2_iv_check(iv, 2)); + + /* Too small SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN - 1; + CU_ASSERT(!nghttp2_iv_check(iv, 1)); + + /* Too large SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1; + CU_ASSERT(!nghttp2_iv_check(iv, 1)); + + /* Max and min SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN; + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[1].value = NGHTTP2_MAX_FRAME_SIZE_MAX; + CU_ASSERT(nghttp2_iv_check(iv, 2)); +} diff --git a/tests/nghttp2_frame_test.h b/tests/nghttp2_frame_test.h new file mode 100644 index 0000000..dc07625 --- /dev/null +++ b/tests/nghttp2_frame_test.h @@ -0,0 +1,47 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_FRAME_TEST_H +#define NGHTTP2_FRAME_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_frame_pack_headers(void); +void test_nghttp2_frame_pack_headers_frame_too_large(void); +void test_nghttp2_frame_pack_priority(void); +void test_nghttp2_frame_pack_rst_stream(void); +void test_nghttp2_frame_pack_settings(void); +void test_nghttp2_frame_pack_push_promise(void); +void test_nghttp2_frame_pack_ping(void); +void test_nghttp2_frame_pack_goaway(void); +void test_nghttp2_frame_pack_window_update(void); +void test_nghttp2_frame_pack_altsvc(void); +void test_nghttp2_frame_pack_origin(void); +void test_nghttp2_frame_pack_priority_update(void); +void test_nghttp2_nv_array_copy(void); +void test_nghttp2_iv_check(void); + +#endif /* NGHTTP2_FRAME_TEST_H */ diff --git a/tests/nghttp2_hd_test.c b/tests/nghttp2_hd_test.c new file mode 100644 index 0000000..657d895 --- /dev/null +++ b/tests/nghttp2_hd_test.c @@ -0,0 +1,1577 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_hd_test.h" + +#include <stdio.h> +#include <assert.h> + +#include <CUnit/CUnit.h> + +#include "nghttp2_hd.h" +#include "nghttp2_frame.h" +#include "nghttp2_test_helper.h" + +void test_nghttp2_hd_deflate(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_nv nva1[] = {MAKE_NV(":path", "/my-example/index.html"), + MAKE_NV(":scheme", "https"), MAKE_NV("hello", "world")}; + nghttp2_nv nva2[] = {MAKE_NV(":path", "/script.js"), + MAKE_NV(":scheme", "https")}; + nghttp2_nv nva3[] = {MAKE_NV("cookie", "k1=v1"), MAKE_NV("cookie", "k2=v2"), + MAKE_NV("via", "proxy")}; + nghttp2_nv nva4[] = {MAKE_NV(":path", "/style.css"), + MAKE_NV("cookie", "k1=v1"), MAKE_NV("cookie", "k1=v1")}; + nghttp2_nv nva5[] = {MAKE_NV(":path", "/style.css"), + MAKE_NV("x-nghttp2", "")}; + nghttp2_bufs bufs; + ssize_t blocklen; + nva_out out; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + CU_ASSERT(0 == nghttp2_hd_deflate_init(&deflater, mem)); + CU_ASSERT(0 == nghttp2_hd_inflate_init(&inflater, mem)); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva1, ARRLEN(nva1)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(3 == out.nvlen); + assert_nv_equal(nva1, out.nva, 3, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Second headers */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, ARRLEN(nva2)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(2 == out.nvlen); + assert_nv_equal(nva2, out.nva, 2, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Third headers, including same header field name, but value is not + the same. */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva3, ARRLEN(nva3)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(3 == out.nvlen); + assert_nv_equal(nva3, out.nva, 3, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Fourth headers, including duplicate header fields. */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva4, ARRLEN(nva4)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(3 == out.nvlen); + assert_nv_equal(nva4, out.nva, 3, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Fifth headers includes empty value */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva5, ARRLEN(nva5)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(2 == out.nvlen); + assert_nv_equal(nva5, out.nva, 2, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Cleanup */ + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_hd_deflate_same_indexed_repr(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_nv nva1[] = {MAKE_NV("host", "alpha"), MAKE_NV("host", "alpha")}; + nghttp2_nv nva2[] = {MAKE_NV("host", "alpha"), MAKE_NV("host", "alpha"), + MAKE_NV("host", "alpha")}; + nghttp2_bufs bufs; + ssize_t blocklen; + nva_out out; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + CU_ASSERT(0 == nghttp2_hd_deflate_init(&deflater, mem)); + CU_ASSERT(0 == nghttp2_hd_inflate_init(&inflater, mem)); + + /* Encode 2 same headers. Emit 1 literal reprs and 1 index repr. */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva1, ARRLEN(nva1)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(2 == out.nvlen); + assert_nv_equal(nva1, out.nva, 2, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Encode 3 same headers. This time, emits 3 index reprs. */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, ARRLEN(nva2)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen == 3); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(3 == out.nvlen); + assert_nv_equal(nva2, out.nva, 3, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Cleanup */ + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_hd_inflate_indexed(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv = MAKE_NV(":path", "/"); + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + + nghttp2_bufs_addb(&bufs, (1 << 7) | 4); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(1 == blocklen); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + + assert_nv_equal(&nv, out.nva, 1, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* index = 0 is error */ + nghttp2_bufs_addb(&bufs, 1 << 7); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(1 == blocklen); + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_indname_noinc(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv[] = {/* Huffman */ + MAKE_NV("user-agent", "nghttp2"), + /* Expecting no huffman */ + MAKE_NV("user-agent", "x")}; + size_t i; + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + + for (i = 0; i < ARRLEN(nv); ++i) { + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 57, &nv[i], + NGHTTP2_HD_WITHOUT_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv[i], out.nva, 1, mem); + CU_ASSERT(0 == inflater.ctx.hd_table.len); + CU_ASSERT(61 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + } + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_indname_inc(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv = MAKE_NV("user-agent", "nghttp2"); + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 57, &nv, + NGHTTP2_HD_WITH_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + CU_ASSERT(1 == inflater.ctx.hd_table.len); + CU_ASSERT(62 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + assert_nv_equal( + &nv, + nghttp2_hd_inflate_get_table_entry( + &inflater, NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len), + 1, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_indname_inc_eviction(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + uint8_t value[1025]; + nva_out out; + nghttp2_nv nv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + + memset(value, '0', sizeof(value)); + value[sizeof(value) - 1] = '\0'; + nv.value = value; + nv.valuelen = sizeof(value) - 1; + + nv.flags = NGHTTP2_NV_FLAG_NONE; + + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 14, &nv, + NGHTTP2_HD_WITH_INDEXING)); + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 15, &nv, + NGHTTP2_HD_WITH_INDEXING)); + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 16, &nv, + NGHTTP2_HD_WITH_INDEXING)); + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 17, &nv, + NGHTTP2_HD_WITH_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(4 == out.nvlen); + CU_ASSERT(14 == out.nva[0].namelen); + CU_ASSERT(0 == memcmp("accept-charset", out.nva[0].name, out.nva[0].namelen)); + CU_ASSERT(sizeof(value) - 1 == out.nva[0].valuelen); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + CU_ASSERT(3 == inflater.ctx.hd_table.len); + CU_ASSERT(64 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_newname_noinc(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv[] = {/* Expecting huffman for both */ + MAKE_NV("my-long-content-length", "nghttp2"), + /* Expecting no huffman for both */ + MAKE_NV("x", "y"), + /* Huffman for key only */ + MAKE_NV("my-long-content-length", "y"), + /* Huffman for value only */ + MAKE_NV("x", "nghttp2")}; + size_t i; + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + for (i = 0; i < ARRLEN(nv); ++i) { + CU_ASSERT(0 == nghttp2_hd_emit_newname_block(&bufs, &nv[i], + NGHTTP2_HD_WITHOUT_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv[i], out.nva, 1, mem); + CU_ASSERT(0 == inflater.ctx.hd_table.len); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + } + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_newname_inc(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv = MAKE_NV("x-rel", "nghttp2"); + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT( + 0 == nghttp2_hd_emit_newname_block(&bufs, &nv, NGHTTP2_HD_WITH_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + CU_ASSERT(1 == inflater.ctx.hd_table.len); + assert_nv_equal( + &nv, + nghttp2_hd_inflate_get_table_entry( + &inflater, NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len), + 1, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_clearall_inc(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv; + uint8_t value[4061]; + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + bufs_large_init(&bufs, 8192); + + nva_out_init(&out); + /* Total 4097 bytes space required to hold this entry */ + nv.name = (uint8_t *)"alpha"; + nv.namelen = strlen((char *)nv.name); + memset(value, '0', sizeof(value)); + value[sizeof(value) - 1] = '\0'; + nv.value = value; + nv.valuelen = sizeof(value) - 1; + + nv.flags = NGHTTP2_NV_FLAG_NONE; + + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT( + 0 == nghttp2_hd_emit_newname_block(&bufs, &nv, NGHTTP2_HD_WITH_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + CU_ASSERT(0 == inflater.ctx.hd_table.len); + + nva_out_reset(&out, mem); + + /* Do it again */ + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + CU_ASSERT(0 == inflater.ctx.hd_table.len); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* This time, 4096 bytes space required, which is just fits in the + header table */ + nv.valuelen = sizeof(value) - 2; + + CU_ASSERT( + 0 == nghttp2_hd_emit_newname_block(&bufs, &nv, NGHTTP2_HD_WITH_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + CU_ASSERT(1 == inflater.ctx.hd_table.len); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_zero_length_huffman(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + /* Literal header without indexing - new name */ + uint8_t data[] = {0x40, 0x01, 0x78 /* 'x' */, 0x80}; + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + + nghttp2_bufs_add(&bufs, data, sizeof(data)); + + /* /\* Literal header without indexing - new name *\/ */ + /* ptr[0] = 0x40; */ + /* ptr[1] = 1; */ + /* ptr[2] = 'x'; */ + /* ptr[3] = 0x80; */ + + nghttp2_hd_inflate_init(&inflater, mem); + CU_ASSERT(4 == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + CU_ASSERT(1 == out.nva[0].namelen); + CU_ASSERT('x' == out.nva[0].name[0]); + CU_ASSERT(NULL == out.nva[0].value); + CU_ASSERT(0 == out.nva[0].valuelen); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_expect_table_size_update(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + nghttp2_mem *mem; + /* Indexed Header: :method: GET */ + uint8_t data[] = {0x82}; + nva_out out; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + nva_out_init(&out); + + nghttp2_bufs_add(&bufs, data, sizeof(data)); + nghttp2_hd_inflate_init(&inflater, mem); + /* This will make inflater require table size update in the next + inflation. */ + nghttp2_hd_inflate_change_table_size(&inflater, 4095); + nghttp2_hd_inflate_change_table_size(&inflater, 4096); + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + /* This does not require for encoder to emit table size update since + * size is not changed. */ + nghttp2_hd_inflate_init(&inflater, mem); + nghttp2_hd_inflate_change_table_size(&inflater, 4096); + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + /* This does not require for encodre to emit table size update since + new size is larger than current size. */ + nghttp2_hd_inflate_init(&inflater, mem); + nghttp2_hd_inflate_change_table_size(&inflater, 4097); + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + /* Received table size is strictly larger than minimum table size */ + nghttp2_hd_inflate_init(&inflater, mem); + nghttp2_hd_inflate_change_table_size(&inflater, 111); + nghttp2_hd_inflate_change_table_size(&inflater, 4096); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_emit_table_size(&bufs, 112); + + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + /* Receiving 2 table size updates, min and last value */ + nghttp2_hd_inflate_init(&inflater, mem); + nghttp2_hd_inflate_change_table_size(&inflater, 111); + nghttp2_hd_inflate_change_table_size(&inflater, 4096); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_emit_table_size(&bufs, 111); + nghttp2_hd_emit_table_size(&bufs, 4096); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + /* 2nd update is larger than last value */ + nghttp2_hd_inflate_init(&inflater, mem); + nghttp2_hd_inflate_change_table_size(&inflater, 111); + nghttp2_hd_inflate_change_table_size(&inflater, 4095); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_emit_table_size(&bufs, 111); + nghttp2_hd_emit_table_size(&bufs, 4096); + + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_hd_inflate_unexpected_table_size_update(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + nghttp2_mem *mem; + /* Indexed Header: :method: GET, followed by table size update. + This violates RFC 7541. */ + uint8_t data[] = {0x82, 0x20}; + nva_out out; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + nva_out_init(&out); + + nghttp2_bufs_add(&bufs, data, sizeof(data)); + nghttp2_hd_inflate_init(&inflater, mem); + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_ringbuf_reserve(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_nv nv; + nghttp2_bufs bufs; + nva_out out; + int i; + ssize_t rv; + ssize_t blocklen; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + nva_out_init(&out); + + nv.flags = NGHTTP2_NV_FLAG_NONE; + nv.name = (uint8_t *)"a"; + nv.namelen = strlen((const char *)nv.name); + nv.valuelen = 4; + nv.value = mem->malloc(nv.valuelen + 1, NULL); + memset(nv.value, 0, nv.valuelen); + + nghttp2_hd_deflate_init2(&deflater, 8000, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + nghttp2_hd_inflate_change_table_size(&inflater, 8000); + nghttp2_hd_deflate_change_table_size(&deflater, 8000); + + for (i = 0; i < 150; ++i) { + memcpy(nv.value, &i, sizeof(i)); + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, &nv, 1); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + } + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + mem->free(nv.value, NULL); +} + +void test_nghttp2_hd_change_table_size(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_nv nva[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")}; + nghttp2_nv nva2[] = {MAKE_NV(":path", "/")}; + nghttp2_bufs bufs; + ssize_t rv; + nva_out out; + ssize_t blocklen; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + /* inflater changes notifies 8000 max header table size */ + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 8000)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 8000)); + + CU_ASSERT(4096 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(4096 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max); + + /* This will emit encoding context update with header table size 4096 */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(2 == deflater.ctx.hd_table.len); + CU_ASSERT(63 == nghttp2_hd_deflate_get_num_table_entries(&deflater)); + CU_ASSERT(4096 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(2 == inflater.ctx.hd_table.len); + CU_ASSERT(63 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + CU_ASSERT(4096 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* inflater changes header table size to 1024 */ + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 1024)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 1024)); + + CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(1024 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(1024 == inflater.settings_hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(2 == deflater.ctx.hd_table.len); + CU_ASSERT(63 == nghttp2_hd_deflate_get_num_table_entries(&deflater)); + CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(2 == inflater.ctx.hd_table.len); + CU_ASSERT(63 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + CU_ASSERT(1024 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(1024 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* inflater changes header table size to 0 */ + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 0)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 0)); + + CU_ASSERT(0 == deflater.ctx.hd_table.len); + CU_ASSERT(61 == nghttp2_hd_deflate_get_num_table_entries(&deflater)); + CU_ASSERT(0 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(0 == inflater.ctx.hd_table.len); + CU_ASSERT(61 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + CU_ASSERT(0 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(0 == inflater.settings_hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(0 == deflater.ctx.hd_table.len); + CU_ASSERT(61 == nghttp2_hd_deflate_get_num_table_entries(&deflater)); + CU_ASSERT(0 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(0 == inflater.ctx.hd_table.len); + CU_ASSERT(61 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + CU_ASSERT(0 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(0 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + /* Check table buffer is expanded */ + frame_pack_bufs_init(&bufs); + + nghttp2_hd_deflate_init2(&deflater, 8192, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + /* First inflater changes header table size to 8000 */ + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 8000)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 8000)); + + CU_ASSERT(8000 == deflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8000 == nghttp2_hd_deflate_get_max_dynamic_table_size(&deflater)); + CU_ASSERT(4096 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(4096 == nghttp2_hd_inflate_get_max_dynamic_table_size(&inflater)); + CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(2 == deflater.ctx.hd_table.len); + CU_ASSERT(8000 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(2 == inflater.ctx.hd_table.len); + CU_ASSERT(8000 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 16383)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 16383)); + + CU_ASSERT(8192 == deflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8192 == nghttp2_hd_deflate_get_max_dynamic_table_size(&deflater)); + + CU_ASSERT(8000 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8000 == nghttp2_hd_inflate_get_max_dynamic_table_size(&inflater)); + CU_ASSERT(16383 == inflater.settings_hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(2 == deflater.ctx.hd_table.len); + CU_ASSERT(8192 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(2 == inflater.ctx.hd_table.len); + CU_ASSERT(8192 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(16383 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Lastly, check the error condition */ + + rv = nghttp2_hd_emit_table_size(&bufs, 25600); + CU_ASSERT(rv == 0); + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + /* Check that encoder can handle the case where its allowable buffer + size is less than default size, 4096 */ + nghttp2_hd_deflate_init2(&deflater, 1024, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max); + + /* This emits context update with buffer size 1024 */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(2 == deflater.ctx.hd_table.len); + CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(2 == inflater.ctx.hd_table.len); + CU_ASSERT(1024 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(4096 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + /* Check that table size UINT32_MAX can be received */ + nghttp2_hd_deflate_init2(&deflater, UINT32_MAX, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, UINT32_MAX)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, UINT32_MAX)); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(UINT32_MAX == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(UINT32_MAX == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(UINT32_MAX == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + /* Check that context update emitted twice */ + nghttp2_hd_deflate_init2(&deflater, 4096, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 0)); + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 3000)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 0)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 3000)); + + CU_ASSERT(0 == deflater.min_hd_table_bufsize_max); + CU_ASSERT(3000 == deflater.ctx.hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, 1); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(3 < blocklen); + CU_ASSERT(3000 == deflater.ctx.hd_table_bufsize_max); + CU_ASSERT(UINT32_MAX == deflater.min_hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(3000 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(3000 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + nghttp2_bufs_free(&bufs); +} + +static void check_deflate_inflate(nghttp2_hd_deflater *deflater, + nghttp2_hd_inflater *inflater, + nghttp2_nv *nva, size_t nvlen, + nghttp2_mem *mem) { + nghttp2_bufs bufs; + ssize_t blocklen; + nva_out out; + int rv; + + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, nva, nvlen); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen >= 0); + + CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(nvlen == out.nvlen); + assert_nv_equal(nva, out.nva, nvlen, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_hd_deflate_inflate(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_nv nv1[] = { + MAKE_NV(":status", "200 OK"), + MAKE_NV("access-control-allow-origin", "*"), + MAKE_NV("cache-control", "private, max-age=0, must-revalidate"), + MAKE_NV("content-length", "76073"), + MAKE_NV("content-type", "text/html"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("server", "Apache"), + MAKE_NV("vary", "foobar"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "MISS from alphabravo"), + MAKE_NV("x-cache-action", "MISS"), + MAKE_NV("x-cache-age", "0"), + MAKE_NV("x-cache-lookup", "MISS from alphabravo:3128"), + MAKE_NV("x-lb-nocache", "true"), + }; + nghttp2_nv nv2[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=56682045"), + MAKE_NV("content-type", "text/css"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Thu, 14 May 2015 07:22:57 GMT"), + MAKE_NV("last-modified", "Tue, 14 May 2013 07:22:15 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128")}; + nghttp2_nv nv3[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=56682072"), + MAKE_NV("content-type", "text/css"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Thu, 14 May 2015 07:23:24 GMT"), + MAKE_NV("last-modified", "Tue, 14 May 2013 07:22:13 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv4[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=56682022"), + MAKE_NV("content-type", "text/css"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Thu, 14 May 2015 07:22:34 GMT"), + MAKE_NV("last-modified", "Tue, 14 May 2013 07:22:14 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv5[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=4461139"), + MAKE_NV("content-type", "application/x-javascript"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Mon, 16 Sep 2013 21:34:31 GMT"), + MAKE_NV("last-modified", "Thu, 05 May 2011 09:15:59 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv6[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=18645951"), + MAKE_NV("content-type", "application/x-javascript"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Fri, 28 Feb 2014 01:48:03 GMT"), + MAKE_NV("last-modified", "Tue, 12 Jul 2011 16:02:59 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv7[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=31536000"), + MAKE_NV("content-type", "application/javascript"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("etag", "\"6807-4dc5b54e0dcc0\""), + MAKE_NV("expires", "Wed, 21 May 2014 08:32:17 GMT"), + MAKE_NV("last-modified", "Fri, 10 May 2013 11:18:51 GMT"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv8[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=31536000"), + MAKE_NV("content-type", "application/javascript"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("etag", "\"41c6-4de7d28585b00\""), + MAKE_NV("expires", "Thu, 12 Jun 2014 10:00:58 GMT"), + MAKE_NV("last-modified", "Thu, 06 Jun 2013 14:30:36 GMT"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv9[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=31536000"), + MAKE_NV("content-type", "application/javascript"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("etag", "\"19d6e-4dc5b35a541c0\""), + MAKE_NV("expires", "Wed, 21 May 2014 08:32:18 GMT"), + MAKE_NV("last-modified", "Fri, 10 May 2013 11:10:07 GMT"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv10[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=56682045"), + MAKE_NV("content-type", "text/css"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Thu, 14 May 2015 07:22:57 GMT"), + MAKE_NV("last-modified", "Tue, 14 May 2013 07:21:53 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + check_deflate_inflate(&deflater, &inflater, nv1, ARRLEN(nv1), mem); + check_deflate_inflate(&deflater, &inflater, nv2, ARRLEN(nv2), mem); + check_deflate_inflate(&deflater, &inflater, nv3, ARRLEN(nv3), mem); + check_deflate_inflate(&deflater, &inflater, nv4, ARRLEN(nv4), mem); + check_deflate_inflate(&deflater, &inflater, nv5, ARRLEN(nv5), mem); + check_deflate_inflate(&deflater, &inflater, nv6, ARRLEN(nv6), mem); + check_deflate_inflate(&deflater, &inflater, nv7, ARRLEN(nv7), mem); + check_deflate_inflate(&deflater, &inflater, nv8, ARRLEN(nv8), mem); + check_deflate_inflate(&deflater, &inflater, nv9, ARRLEN(nv9), mem); + check_deflate_inflate(&deflater, &inflater, nv10, ARRLEN(nv10), mem); + + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_hd_no_index(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nva[] = { + MAKE_NV(":method", "GET"), MAKE_NV(":method", "POST"), + MAKE_NV(":path", "/foo"), MAKE_NV("version", "HTTP/1.1"), + MAKE_NV(":method", "GET"), + }; + size_t i; + nva_out out; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + /* 1st :method: GET can be indexable, last one is not */ + for (i = 1; i < ARRLEN(nva); ++i) { + nva[i].flags = NGHTTP2_NV_FLAG_NO_INDEX; + } + + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, ARRLEN(nva)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(ARRLEN(nva) == out.nvlen); + assert_nv_equal(nva, out.nva, ARRLEN(nva), mem); + + CU_ASSERT(out.nva[0].flags == NGHTTP2_NV_FLAG_NONE); + for (i = 1; i < ARRLEN(nva); ++i) { + CU_ASSERT(out.nva[i].flags == NGHTTP2_NV_FLAG_NO_INDEX); + } + + nva_out_reset(&out, mem); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_hd_deflate_bound(void) { + nghttp2_hd_deflater deflater; + nghttp2_nv nva[] = {MAKE_NV(":method", "GET"), MAKE_NV("alpha", "bravo")}; + nghttp2_bufs bufs; + size_t bound, bound2; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nghttp2_hd_deflate_init(&deflater, mem); + + bound = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva)); + + CU_ASSERT(12 + 6 * 2 * 2 + nva[0].namelen + nva[0].valuelen + nva[1].namelen + + nva[1].valuelen == + bound); + + nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, ARRLEN(nva)); + + CU_ASSERT(bound > (size_t)nghttp2_bufs_len(&bufs)); + + bound2 = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva)); + + CU_ASSERT(bound == bound2); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_hd_public_api(void) { + nghttp2_hd_deflater *deflater; + nghttp2_hd_inflater *inflater; + nghttp2_nv nva[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")}; + uint8_t buf[4096]; + size_t buflen; + ssize_t blocklen; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + CU_ASSERT(0 == nghttp2_hd_deflate_new(&deflater, 4096)); + CU_ASSERT(0 == nghttp2_hd_inflate_new(&inflater)); + + buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva)); + + blocklen = nghttp2_hd_deflate_hd(deflater, buf, buflen, nva, ARRLEN(nva)); + + CU_ASSERT(blocklen > 0); + + nghttp2_bufs_wrap_init(&bufs, buf, (size_t)blocklen, mem); + bufs.head->buf.last += blocklen; + + CU_ASSERT(blocklen == inflate_hd(inflater, NULL, &bufs, 0, mem)); + + nghttp2_bufs_wrap_free(&bufs); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + + /* See NGHTTP2_ERR_INSUFF_BUFSIZE */ + CU_ASSERT(0 == nghttp2_hd_deflate_new(&deflater, 4096)); + + blocklen = nghttp2_hd_deflate_hd(deflater, buf, (size_t)(blocklen - 1), nva, + ARRLEN(nva)); + + CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == blocklen); + + nghttp2_hd_deflate_del(deflater); +} + +void test_nghttp2_hd_deflate_hd_vec(void) { + nghttp2_hd_deflater *deflater; + nghttp2_hd_inflater *inflater; + nghttp2_nv nva[] = { + MAKE_NV(":method", "PUT"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost:3000"), + MAKE_NV(":path", "/usr/foo/alpha/bravo"), + MAKE_NV("content-type", "image/png"), + MAKE_NV("content-length", "1000000007"), + }; + uint8_t buf[4096]; + ssize_t blocklen; + nghttp2_mem *mem; + nghttp2_vec vec[256]; + size_t buflen; + nghttp2_bufs bufs; + nva_out out; + size_t i; + + mem = nghttp2_mem_default(); + + nva_out_init(&out); + + nghttp2_hd_deflate_new(&deflater, 4096); + nghttp2_hd_inflate_new(&inflater); + + buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva)); + + vec[0].base = &buf[0]; + vec[0].len = buflen / 2; + vec[1].base = &buf[buflen / 2]; + vec[1].len = buflen / 2; + + blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, 2, nva, ARRLEN(nva)); + + CU_ASSERT(blocklen > 0); + + nghttp2_bufs_wrap_init(&bufs, buf, (size_t)blocklen, mem); + bufs.head->buf.last += blocklen; + + CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(ARRLEN(nva) == out.nvlen); + assert_nv_equal(nva, out.nva, ARRLEN(nva), mem); + + nghttp2_bufs_wrap_free(&bufs); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + nva_out_reset(&out, mem); + + /* check the case when veclen is 0 */ + nghttp2_hd_deflate_new(&deflater, 4096); + nghttp2_hd_inflate_new(&inflater); + + blocklen = nghttp2_hd_deflate_hd_vec(deflater, NULL, 0, nva, ARRLEN(nva)); + + CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == blocklen); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + + /* check the case when chunk length is 0 */ + vec[0].base = NULL; + vec[0].len = 0; + vec[1].base = NULL; + vec[1].len = 0; + + nghttp2_hd_deflate_new(&deflater, 4096); + nghttp2_hd_inflate_new(&inflater); + + blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, 2, nva, ARRLEN(nva)); + + CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == blocklen); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + + /* check the case where chunk size differs in each chunk */ + nghttp2_hd_deflate_new(&deflater, 4096); + nghttp2_hd_inflate_new(&inflater); + + buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva)); + + vec[0].base = &buf[0]; + vec[0].len = buflen / 2; + vec[1].base = &buf[buflen / 2]; + vec[1].len = (buflen / 2) + 1; + + blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, 2, nva, ARRLEN(nva)); + + CU_ASSERT(blocklen > 0); + + nghttp2_bufs_wrap_init(&bufs, buf, (size_t)blocklen, mem); + bufs.head->buf.last += blocklen; + + CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem)); + CU_ASSERT(ARRLEN(nva) == out.nvlen); + assert_nv_equal(nva, out.nva, ARRLEN(nva), mem); + + nghttp2_bufs_wrap_free(&bufs); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + nva_out_reset(&out, mem); + + /* check the case where chunk size is 1 */ + nghttp2_hd_deflate_new(&deflater, 4096); + nghttp2_hd_inflate_new(&inflater); + + buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva)); + + assert(buflen <= ARRLEN(vec)); + + for (i = 0; i < buflen; ++i) { + vec[i].base = &buf[i]; + vec[i].len = 1; + } + + blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, buflen, nva, ARRLEN(nva)); + + CU_ASSERT(blocklen > 0); + + nghttp2_bufs_wrap_init(&bufs, buf, (size_t)blocklen, mem); + bufs.head->buf.last += blocklen; + + CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem)); + CU_ASSERT(ARRLEN(nva) == out.nvlen); + assert_nv_equal(nva, out.nva, ARRLEN(nva), mem); + + nghttp2_bufs_wrap_free(&bufs); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + nva_out_reset(&out, mem); +} + +static size_t encode_length(uint8_t *buf, uint64_t n, size_t prefix) { + size_t k = (size_t)((1 << prefix) - 1); + size_t len = 0; + *buf = (uint8_t)(*buf & ~k); + if (n >= k) { + *buf = (uint8_t)(*buf | k); + ++buf; + n -= k; + ++len; + } else { + *buf = (uint8_t)(*buf | n); + ++buf; + return 1; + } + do { + ++len; + if (n >= 128) { + *buf = (uint8_t)((1 << 7) | (n & 0x7f)); + ++buf; + n >>= 7; + } else { + *buf++ = (uint8_t)n; + break; + } + } while (n); + return len; +} + +void test_nghttp2_hd_decode_length(void) { + uint32_t out; + size_t shift; + int fin; + uint8_t buf[16]; + uint8_t *bufp; + size_t len; + ssize_t rv; + size_t i; + + memset(buf, 0, sizeof(buf)); + len = encode_length(buf, UINT32_MAX, 7); + + rv = nghttp2_hd_decode_length(&out, &shift, &fin, 0, 0, buf, buf + len, 7); + + CU_ASSERT((ssize_t)len == rv); + CU_ASSERT(0 != fin); + CU_ASSERT(UINT32_MAX == out); + + /* Make sure that we can decode integer if we feed 1 byte at a + time */ + out = 0; + shift = 0; + fin = 0; + bufp = buf; + + for (i = 0; i < len; ++i, ++bufp) { + rv = nghttp2_hd_decode_length(&out, &shift, &fin, out, shift, bufp, + bufp + 1, 7); + + CU_ASSERT(rv == 1); + + if (fin) { + break; + } + } + + CU_ASSERT(i == len - 1); + CU_ASSERT(0 != fin); + CU_ASSERT(UINT32_MAX == out); + + /* Check overflow case */ + memset(buf, 0, sizeof(buf)); + len = encode_length(buf, 1ll << 32, 7); + + rv = nghttp2_hd_decode_length(&out, &shift, &fin, 0, 0, buf, buf + len, 7); + + CU_ASSERT(-1 == rv); + + /* Check the case that shift goes beyond 32 bits */ + buf[0] = 255; + buf[1] = 128; + buf[2] = 128; + buf[3] = 128; + buf[4] = 128; + buf[5] = 128; + buf[6] = 1; + + rv = nghttp2_hd_decode_length(&out, &shift, &fin, 0, 0, buf, buf + 7, 8); + + CU_ASSERT(-1 == rv); +} + +void test_nghttp2_hd_huff_encode(void) { + int rv; + ssize_t len; + nghttp2_buf outbuf; + nghttp2_bufs bufs; + nghttp2_hd_huff_decode_context ctx; + const uint8_t t1[] = {22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, + 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; + uint8_t b[256]; + + nghttp2_buf_wrap_init(&outbuf, b, sizeof(b)); + frame_pack_bufs_init(&bufs); + + rv = nghttp2_hd_huff_encode(&bufs, t1, sizeof(t1)); + + CU_ASSERT(rv == 0); + + nghttp2_hd_huff_decode_context_init(&ctx); + + len = nghttp2_hd_huff_decode(&ctx, &outbuf, bufs.cur->buf.pos, + nghttp2_bufs_len(&bufs), 1); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == len); + CU_ASSERT((ssize_t)sizeof(t1) == nghttp2_buf_len(&outbuf)); + + CU_ASSERT(0 == memcmp(t1, outbuf.pos, sizeof(t1))); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_hd_huff_decode(void) { + const uint8_t e[] = {0x1f, 0xff, 0xff, 0xff, 0xff, 0xff}; + nghttp2_hd_huff_decode_context ctx; + nghttp2_buf outbuf; + uint8_t b[256]; + ssize_t len; + + nghttp2_buf_wrap_init(&outbuf, b, sizeof(b)); + nghttp2_hd_huff_decode_context_init(&ctx); + len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 1, 1); + + CU_ASSERT(1 == len); + CU_ASSERT(0 == memcmp("a", outbuf.pos, 1)); + + /* Premature sequence must elicit decoding error */ + nghttp2_buf_wrap_init(&outbuf, b, sizeof(b)); + nghttp2_hd_huff_decode_context_init(&ctx); + len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 2, 1); + + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == len); + + /* Fully decoding EOS is error */ + nghttp2_buf_wrap_init(&outbuf, b, sizeof(b)); + nghttp2_hd_huff_decode_context_init(&ctx); + len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 2, 6); + + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == len); + + /* Check failure state */ + nghttp2_buf_wrap_init(&outbuf, b, sizeof(b)); + nghttp2_hd_huff_decode_context_init(&ctx); + len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 5, 0); + + CU_ASSERT(5 == len); + CU_ASSERT(nghttp2_hd_huff_decode_failure_state(&ctx)); +} diff --git a/tests/nghttp2_hd_test.h b/tests/nghttp2_hd_test.h new file mode 100644 index 0000000..ab0117c --- /dev/null +++ b/tests/nghttp2_hd_test.h @@ -0,0 +1,55 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_HD_TEST_H +#define NGHTTP2_HD_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_hd_deflate(void); +void test_nghttp2_hd_deflate_same_indexed_repr(void); +void test_nghttp2_hd_inflate_indexed(void); +void test_nghttp2_hd_inflate_indname_noinc(void); +void test_nghttp2_hd_inflate_indname_inc(void); +void test_nghttp2_hd_inflate_indname_inc_eviction(void); +void test_nghttp2_hd_inflate_newname_noinc(void); +void test_nghttp2_hd_inflate_newname_inc(void); +void test_nghttp2_hd_inflate_clearall_inc(void); +void test_nghttp2_hd_inflate_zero_length_huffman(void); +void test_nghttp2_hd_inflate_expect_table_size_update(void); +void test_nghttp2_hd_inflate_unexpected_table_size_update(void); +void test_nghttp2_hd_ringbuf_reserve(void); +void test_nghttp2_hd_change_table_size(void); +void test_nghttp2_hd_deflate_inflate(void); +void test_nghttp2_hd_no_index(void); +void test_nghttp2_hd_deflate_bound(void); +void test_nghttp2_hd_public_api(void); +void test_nghttp2_hd_deflate_hd_vec(void); +void test_nghttp2_hd_decode_length(void); +void test_nghttp2_hd_huff_encode(void); +void test_nghttp2_hd_huff_decode(void); + +#endif /* NGHTTP2_HD_TEST_H */ diff --git a/tests/nghttp2_helper_test.c b/tests/nghttp2_helper_test.c new file mode 100644 index 0000000..ca91bb6 --- /dev/null +++ b/tests/nghttp2_helper_test.c @@ -0,0 +1,193 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_helper_test.h" + +#include <CUnit/CUnit.h> + +#include "nghttp2_helper.h" + +void test_nghttp2_adjust_local_window_size(void) { + int32_t local_window_size = 100; + int32_t recv_window_size = 50; + int32_t recv_reduction = 0; + int32_t delta; + + delta = 0; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(50 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(0 == delta); + + delta = 49; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(1 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(49 == delta); + + delta = 1; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(0 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(1 == delta); + + delta = 1; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(101 == local_window_size); + CU_ASSERT(0 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(1 == delta); + + delta = -1; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(-1 == recv_window_size); + CU_ASSERT(1 == recv_reduction); + CU_ASSERT(0 == delta); + + delta = 1; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(101 == local_window_size); + CU_ASSERT(0 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(0 == delta); + + delta = 100; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(201 == local_window_size); + CU_ASSERT(0 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(100 == delta); + + delta = -3; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(198 == local_window_size); + CU_ASSERT(-3 == recv_window_size); + CU_ASSERT(3 == recv_reduction); + CU_ASSERT(0 == delta); + + recv_window_size += 3; + + delta = 3; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(201 == local_window_size); + CU_ASSERT(3 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(0 == delta); + + local_window_size = 100; + recv_window_size = 50; + recv_reduction = 0; + delta = INT32_MAX; + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, &recv_reduction, + &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(50 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(INT32_MAX == delta); + + delta = INT32_MIN; + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, &recv_reduction, + &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(50 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(INT32_MIN == delta); +} + +#define check_header_name(S) \ + nghttp2_check_header_name((const uint8_t *)S, sizeof(S) - 1) + +void test_nghttp2_check_header_name(void) { + CU_ASSERT(check_header_name(":path")); + CU_ASSERT(check_header_name("path")); + CU_ASSERT(check_header_name("!#$%&'*+-.^_`|~")); + CU_ASSERT(!check_header_name(":PATH")); + CU_ASSERT(!check_header_name("path:")); + CU_ASSERT(!check_header_name("")); + CU_ASSERT(!check_header_name(":")); +} + +#define check_header_value(S) \ + nghttp2_check_header_value((const uint8_t *)S, sizeof(S) - 1) + +void test_nghttp2_check_header_value(void) { + uint8_t goodval[] = {'a', 'b', 0x80u, 'c', 0xffu, 'd', '\t', ' '}; + uint8_t badval1[] = {'a', 0x1fu, 'b'}; + uint8_t badval2[] = {'a', 0x7fu, 'b'}; + + CU_ASSERT(check_header_value(" !|}~")); + 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")); +} + +#define check_header_value_rfc9113(S) \ + nghttp2_check_header_value_rfc9113((const uint8_t *)S, sizeof(S) - 1) + +void test_nghttp2_check_header_value_rfc9113(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_rfc9113("!|}~")); + CU_ASSERT(!check_header_value_rfc9113(" !|}~")); + CU_ASSERT(!check_header_value_rfc9113("!|}~ ")); + CU_ASSERT(!check_header_value_rfc9113("\t!|}~")); + CU_ASSERT(!check_header_value_rfc9113("!|}~\t")); + CU_ASSERT(check_header_value_rfc9113(goodval)); + CU_ASSERT(!check_header_value_rfc9113(badval1)); + CU_ASSERT(!check_header_value_rfc9113(badval2)); + CU_ASSERT(check_header_value_rfc9113("")); + CU_ASSERT(!check_header_value_rfc9113(" ")); + CU_ASSERT(!check_header_value_rfc9113("\t")); +} diff --git a/tests/nghttp2_helper_test.h b/tests/nghttp2_helper_test.h new file mode 100644 index 0000000..8790dcf --- /dev/null +++ b/tests/nghttp2_helper_test.h @@ -0,0 +1,37 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_HELPER_TEST_H +#define NGHTTP2_HELPER_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_adjust_local_window_size(void); +void test_nghttp2_check_header_name(void); +void test_nghttp2_check_header_value(void); +void test_nghttp2_check_header_value_rfc9113(void); + +#endif /* NGHTTP2_HELPER_TEST_H */ diff --git a/tests/nghttp2_http_test.c b/tests/nghttp2_http_test.c new file mode 100644 index 0000000..748157c --- /dev/null +++ b/tests/nghttp2_http_test.c @@ -0,0 +1,655 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 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. + */ +#include "nghttp2_http_test.h" + +#include <assert.h> + +#include <CUnit/CUnit.h> + +#include "nghttp2_http.h" +#include "nghttp2_test_helper.h" + +void test_nghttp2_http_parse_priority(void) { + int rv; + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = ""; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)-1 == pri.urgency); + CU_ASSERT(-1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=7,i"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)7 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0,i=?0"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)0 == pri.urgency); + CU_ASSERT(0 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=3, i"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)3 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0, i, i=?0, u=6"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)6 == pri.urgency); + CU_ASSERT(0 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0,"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0, "; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u="; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?1"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)-1 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?2"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i="; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=-1"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=8"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri 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 = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)2 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = {'u', '='}; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v)); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } +} + +void test_nghttp2_sf_parse_item(void) { + { + nghttp2_sf_value val; + const uint8_t s[] = "?1"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(1 == val.b); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "?1 "; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(1 == val.b); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "?1;foo=bar"; + val.type = 0xff; + + CU_ASSERT(10 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(1 == val.b); + } + + { + const uint8_t s[] = {'?', '1', ';', 'f', 'o', 'o', '='}; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s))); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "?0"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(0 == val.b); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "?0 "; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(0 == val.b); + } + + { + const uint8_t s[] = "?2"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "?"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "?1"; + + CU_ASSERT(2 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==:"; + val.type = 0xff; + + CU_ASSERT(46 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(44 == val.s.len); + CU_ASSERT(0 == memcmp("cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==", + val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==: "; + val.type = 0xff; + + CU_ASSERT(46 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(44 == val.s.len); + CU_ASSERT(0 == memcmp("cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==", + val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "::"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(0 == val.s.len); + } + + { + const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg=="; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = ":"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = ":@:"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = ":foo:"; + + CU_ASSERT(5 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = + ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=:"; + val.type = 0xff; + + CU_ASSERT(67 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(65 == val.s.len); + CU_ASSERT( + 0 == + memcmp( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=", + val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "foo123/456"; + val.type = 0xff; + + CU_ASSERT(10 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_TOKEN == val.type); + CU_ASSERT(10 == val.s.len); + CU_ASSERT(0 == memcmp(s, val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "foo123/456 "; + val.type = 0xff; + + CU_ASSERT(10 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_TOKEN == val.type); + CU_ASSERT(10 == val.s.len); + CU_ASSERT(0 == memcmp(s, val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "*"; + val.type = 0xff; + + CU_ASSERT(1 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_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 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "\"hello world\""; + val.type = 0xff; + + CU_ASSERT(13 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_STRING == val.type); + CU_ASSERT(11 == val.s.len); + CU_ASSERT(0 == memcmp("hello world", val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "\"hello world\" "; + val.type = 0xff; + + CU_ASSERT(13 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_STRING == val.type); + CU_ASSERT(11 == val.s.len); + CU_ASSERT(0 == memcmp("hello world", val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "\"foo\\\"\\\\\""; + val.type = 0xff; + + CU_ASSERT(9 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_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 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"foo"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"\x7f\""; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"\x1f\""; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"foo\""; + + CU_ASSERT(5 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "4.5"; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(3 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(4.5 - val.d) < 1e-9); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "4.5 "; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(3 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(4.5 - val.d) < 1e-9); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "-4.5"; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(4 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(-4.5 - val.d) < 1e-9); + } + + { + const uint8_t s[] = "4.5"; + + CU_ASSERT(3 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "123456789012.123"; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(16 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(123456789012.123 - val.d) < 1e-9); + } + + { + const uint8_t s[] = "1123456789012.123"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "123456789012.1234"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "1."; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "123456789012345"; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(15 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INTEGER == val.type); + CU_ASSERT(123456789012345 == val.i); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "1 "; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(1 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INTEGER == val.type); + CU_ASSERT(1 == val.i); + } + + { + const uint8_t s[] = "1"; + + CU_ASSERT(1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "1234567890123456"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_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 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_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 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "foo;"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } +} + +void test_nghttp2_sf_parse_inner_list(void) { + { + nghttp2_sf_value val; + const uint8_t s[] = "()"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "( )"; + val.type = 0xff; + + CU_ASSERT(7 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "(a)"; + val.type = 0xff; + + CU_ASSERT(3 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "(a b)"; + val.type = 0xff; + + CU_ASSERT(5 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "( a b )"; + val.type = 0xff; + + CU_ASSERT(10 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "( a;foo=bar)"; + val.type = 0xff; + + CU_ASSERT(12 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + const uint8_t s[] = "("; + + CU_ASSERT(-1 == nghttp2_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "(a"; + + CU_ASSERT(-1 == nghttp2_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "(a "; + + CU_ASSERT(-1 == nghttp2_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "(a;b"; + + CU_ASSERT(-1 == nghttp2_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } +} diff --git a/tests/nghttp2_http_test.h b/tests/nghttp2_http_test.h new file mode 100644 index 0000000..901d681 --- /dev/null +++ b/tests/nghttp2_http_test.h @@ -0,0 +1,37 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 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. + */ +#ifndef NGHTTP2_HTTP_TEST_H +#define NGHTTP2_HTTP_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_http_parse_priority(void); +void test_nghttp2_sf_parse_item(void); +void test_nghttp2_sf_parse_inner_list(void); + +#endif /* NGHTTP2_HTTP_TEST_H */ diff --git a/tests/nghttp2_map_test.c b/tests/nghttp2_map_test.c new file mode 100644 index 0000000..9a87eff --- /dev/null +++ b/tests/nghttp2_map_test.c @@ -0,0 +1,206 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2017 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. + */ +#include "nghttp2_map_test.h" + +#include <CUnit/CUnit.h> + +#include "nghttp2_map.h" + +typedef struct strentry { + nghttp2_map_key_type key; + const char *str; +} strentry; + +static void strentry_init(strentry *entry, nghttp2_map_key_type key, + const char *str) { + entry->key = key; + entry->str = str; +} + +void test_nghttp2_map(void) { + strentry foo, FOO, bar, baz, shrubbery; + nghttp2_map map; + nghttp2_map_init(&map, nghttp2_mem_default()); + + strentry_init(&foo, 1, "foo"); + strentry_init(&FOO, 1, "FOO"); + strentry_init(&bar, 2, "bar"); + strentry_init(&baz, 3, "baz"); + strentry_init(&shrubbery, 4, "shrubbery"); + + CU_ASSERT(0 == nghttp2_map_insert(&map, foo.key, &foo)); + CU_ASSERT(strcmp("foo", ((strentry *)nghttp2_map_find(&map, 1))->str) == 0); + CU_ASSERT(1 == nghttp2_map_size(&map)); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_map_insert(&map, FOO.key, &FOO)); + + CU_ASSERT(1 == nghttp2_map_size(&map)); + CU_ASSERT(strcmp("foo", ((strentry *)nghttp2_map_find(&map, 1))->str) == 0); + + CU_ASSERT(0 == nghttp2_map_insert(&map, bar.key, &bar)); + CU_ASSERT(2 == nghttp2_map_size(&map)); + + CU_ASSERT(0 == nghttp2_map_insert(&map, baz.key, &baz)); + CU_ASSERT(3 == nghttp2_map_size(&map)); + + CU_ASSERT(0 == nghttp2_map_insert(&map, shrubbery.key, &shrubbery)); + CU_ASSERT(4 == nghttp2_map_size(&map)); + + CU_ASSERT(strcmp("baz", ((strentry *)nghttp2_map_find(&map, 3))->str) == 0); + + nghttp2_map_remove(&map, 3); + CU_ASSERT(3 == nghttp2_map_size(&map)); + CU_ASSERT(NULL == nghttp2_map_find(&map, 3)); + + nghttp2_map_remove(&map, 1); + CU_ASSERT(2 == nghttp2_map_size(&map)); + CU_ASSERT(NULL == nghttp2_map_find(&map, 1)); + + /* Erasing non-existent entry */ + nghttp2_map_remove(&map, 1); + CU_ASSERT(2 == nghttp2_map_size(&map)); + CU_ASSERT(NULL == nghttp2_map_find(&map, 1)); + + CU_ASSERT(strcmp("bar", ((strentry *)nghttp2_map_find(&map, 2))->str) == 0); + CU_ASSERT(strcmp("shrubbery", ((strentry *)nghttp2_map_find(&map, 4))->str) == + 0); + + nghttp2_map_free(&map); +} + +static void shuffle(int *a, int n) { + int i; + for (i = n - 1; i >= 1; --i) { + size_t j = (size_t)((double)(i + 1) * rand() / (RAND_MAX + 1.0)); + int t = a[j]; + a[j] = a[i]; + a[i] = t; + } +} + +static int eachfun(void *data, void *ptr) { + (void)data; + (void)ptr; + + return 0; +} + +#define NUM_ENT 6000 +static strentry arr[NUM_ENT]; +static int order[NUM_ENT]; + +void test_nghttp2_map_functional(void) { + nghttp2_map map; + int i; + strentry *ent; + + nghttp2_map_init(&map, nghttp2_mem_default()); + for (i = 0; i < NUM_ENT; ++i) { + strentry_init(&arr[i], (nghttp2_map_key_type)(i + 1), "foo"); + order[i] = i + 1; + } + /* insertion */ + shuffle(order, NUM_ENT); + for (i = 0; i < NUM_ENT; ++i) { + ent = &arr[order[i] - 1]; + CU_ASSERT(0 == nghttp2_map_insert(&map, ent->key, ent)); + } + + CU_ASSERT(NUM_ENT == nghttp2_map_size(&map)); + + /* traverse */ + nghttp2_map_each(&map, eachfun, NULL); + /* find */ + shuffle(order, NUM_ENT); + for (i = 0; i < NUM_ENT; ++i) { + CU_ASSERT(NULL != nghttp2_map_find(&map, (nghttp2_map_key_type)order[i])); + } + /* remove */ + for (i = 0; i < NUM_ENT; ++i) { + CU_ASSERT(0 == nghttp2_map_remove(&map, (nghttp2_map_key_type)order[i])); + } + + /* each_free (but no op function for testing purpose) */ + for (i = 0; i < NUM_ENT; ++i) { + strentry_init(&arr[i], (nghttp2_map_key_type)(i + 1), "foo"); + } + /* insert once again */ + for (i = 0; i < NUM_ENT; ++i) { + ent = &arr[i]; + CU_ASSERT(0 == nghttp2_map_insert(&map, ent->key, ent)); + } + nghttp2_map_each_free(&map, eachfun, NULL); + nghttp2_map_free(&map); +} + +static int entry_free(void *data, void *ptr) { + const nghttp2_mem *mem = ptr; + + mem->free(data, NULL); + return 0; +} + +void test_nghttp2_map_each_free(void) { + const nghttp2_mem *mem = nghttp2_mem_default(); + strentry *foo = mem->malloc(sizeof(strentry), NULL), + *bar = mem->malloc(sizeof(strentry), NULL), + *baz = mem->malloc(sizeof(strentry), NULL), + *shrubbery = mem->malloc(sizeof(strentry), NULL); + nghttp2_map map; + nghttp2_map_init(&map, nghttp2_mem_default()); + + strentry_init(foo, 1, "foo"); + strentry_init(bar, 2, "bar"); + strentry_init(baz, 3, "baz"); + strentry_init(shrubbery, 4, "shrubbery"); + + nghttp2_map_insert(&map, foo->key, foo); + nghttp2_map_insert(&map, bar->key, bar); + nghttp2_map_insert(&map, baz->key, baz); + nghttp2_map_insert(&map, shrubbery->key, shrubbery); + + nghttp2_map_each_free(&map, entry_free, (void *)mem); + nghttp2_map_free(&map); +} + +void test_nghttp2_map_clear(void) { + nghttp2_mem *mem = nghttp2_mem_default(); + nghttp2_map map; + strentry foo; + + strentry_init(&foo, 1, "foo"); + + nghttp2_map_init(&map, mem); + + CU_ASSERT(0 == nghttp2_map_insert(&map, foo.key, &foo)); + + nghttp2_map_clear(&map); + + CU_ASSERT(0 == nghttp2_map_size(&map)); + + nghttp2_map_free(&map); +} diff --git a/tests/nghttp2_map_test.h b/tests/nghttp2_map_test.h new file mode 100644 index 0000000..235624d --- /dev/null +++ b/tests/nghttp2_map_test.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2017 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. + */ +#ifndef NGHTTP2_MAP_TEST_H +#define NGHTTP2_MAP_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_map(void); +void test_nghttp2_map_functional(void); +void test_nghttp2_map_each_free(void); +void test_nghttp2_map_clear(void); + +#endif /* NGHTTP2_MAP_TEST_H */ diff --git a/tests/nghttp2_npn_test.c b/tests/nghttp2_npn_test.c new file mode 100644 index 0000000..cbd65b7 --- /dev/null +++ b/tests/nghttp2_npn_test.c @@ -0,0 +1,72 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Twist Inc. + * + * 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 "nghttp2_npn_test.h" + +#include <string.h> + +#include <CUnit/CUnit.h> +#include <nghttp2/nghttp2.h> + +static void http2(void) { + const unsigned char p[] = {8, 'h', 't', 't', 'p', '/', '1', '.', '1', 2, + 'h', '2', 6, 's', 'p', 'd', 'y', '/', '3'}; + unsigned char outlen; + unsigned char *out; + CU_ASSERT(1 == nghttp2_select_next_protocol(&out, &outlen, p, sizeof(p))); + CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen); + CU_ASSERT(memcmp(NGHTTP2_PROTO_VERSION_ID, out, outlen) == 0); +} + +static void http11(void) { + const unsigned char spdy[] = { + 6, 's', 'p', 'd', 'y', '/', '4', 8, 's', 'p', 'd', 'y', '/', + '2', '.', '1', 8, 'h', 't', 't', 'p', '/', '1', '.', '1', + }; + unsigned char outlen; + unsigned char *out; + CU_ASSERT(0 == + nghttp2_select_next_protocol(&out, &outlen, spdy, sizeof(spdy))); + CU_ASSERT(8 == outlen); + CU_ASSERT(memcmp("http/1.1", out, outlen) == 0); +} + +static void no_overlap(void) { + const unsigned char spdy[] = { + 6, 's', 'p', 'd', 'y', '/', '4', 8, 's', 'p', 'd', 'y', '/', + '2', '.', '1', 8, 'h', 't', 't', 'p', '/', '1', '.', '0', + }; + unsigned char outlen = 0; + unsigned char *out = NULL; + CU_ASSERT(-1 == + nghttp2_select_next_protocol(&out, &outlen, spdy, sizeof(spdy))); + CU_ASSERT(0 == outlen); + CU_ASSERT(NULL == out); +} + +void test_nghttp2_npn(void) { + http2(); + http11(); + no_overlap(); +} diff --git a/tests/nghttp2_npn_test.h b/tests/nghttp2_npn_test.h new file mode 100644 index 0000000..f1c9763 --- /dev/null +++ b/tests/nghttp2_npn_test.h @@ -0,0 +1,34 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Twist Inc. + * + * 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 NGHTTP2_NPN_TEST_H +#define NGHTTP2_NPN_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_npn(void); + +#endif /* NGHTTP2_NPN_TEST_H */ diff --git a/tests/nghttp2_pq_test.c b/tests/nghttp2_pq_test.c new file mode 100644 index 0000000..5fcb0ee --- /dev/null +++ b/tests/nghttp2_pq_test.c @@ -0,0 +1,226 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_pq_test.h" + +#include <CUnit/CUnit.h> + +#include "nghttp2_pq.h" + +typedef struct { + nghttp2_pq_entry ent; + const char *s; +} string_entry; + +static string_entry *string_entry_new(const char *s) { + nghttp2_mem *mem; + string_entry *ent; + + mem = nghttp2_mem_default(); + + ent = nghttp2_mem_malloc(mem, sizeof(string_entry)); + ent->s = s; + + return ent; +} + +static void string_entry_del(string_entry *ent) { free(ent); } + +static int pq_less(const void *lhs, const void *rhs) { + return strcmp(((string_entry *)lhs)->s, ((string_entry *)rhs)->s) < 0; +} + +void test_nghttp2_pq(void) { + int i; + nghttp2_pq pq; + string_entry *top; + + nghttp2_pq_init(&pq, pq_less, nghttp2_mem_default()); + CU_ASSERT(nghttp2_pq_empty(&pq)); + CU_ASSERT(0 == nghttp2_pq_size(&pq)); + CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("foo")->ent)); + CU_ASSERT(0 == nghttp2_pq_empty(&pq)); + CU_ASSERT(1 == nghttp2_pq_size(&pq)); + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("foo", top->s) == 0); + CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("bar")->ent)); + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("bar", top->s) == 0); + CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("baz")->ent)); + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("bar", top->s) == 0); + CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("C")->ent)); + CU_ASSERT(4 == nghttp2_pq_size(&pq)); + + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("C", top->s) == 0); + string_entry_del(top); + nghttp2_pq_pop(&pq); + + CU_ASSERT(3 == nghttp2_pq_size(&pq)); + + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("bar", top->s) == 0); + nghttp2_pq_pop(&pq); + string_entry_del(top); + + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("baz", top->s) == 0); + nghttp2_pq_pop(&pq); + string_entry_del(top); + + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("foo", top->s) == 0); + nghttp2_pq_pop(&pq); + string_entry_del(top); + + CU_ASSERT(nghttp2_pq_empty(&pq)); + CU_ASSERT(0 == nghttp2_pq_size(&pq)); + CU_ASSERT(NULL == nghttp2_pq_top(&pq)); + + /* Add bunch of entry to see realloc works */ + for (i = 0; i < 10000; ++i) { + CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("foo")->ent)); + CU_ASSERT((size_t)(i + 1) == nghttp2_pq_size(&pq)); + } + for (i = 10000; i > 0; --i) { + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(NULL != top); + nghttp2_pq_pop(&pq); + string_entry_del(top); + CU_ASSERT((size_t)(i - 1) == nghttp2_pq_size(&pq)); + } + + nghttp2_pq_free(&pq); +} + +typedef struct { + nghttp2_pq_entry ent; + int key; + int val; +} node; + +static int node_less(const void *lhs, const void *rhs) { + node *ln = (node *)lhs; + node *rn = (node *)rhs; + return ln->key < rn->key; +} + +static int node_update(nghttp2_pq_entry *item, void *arg) { + node *nd = (node *)item; + (void)arg; + + if ((nd->key % 2) == 0) { + nd->key *= -1; + return 1; + } else { + return 0; + } +} + +void test_nghttp2_pq_update(void) { + nghttp2_pq pq; + node nodes[10]; + int i; + node *nd; + int ans[] = {-8, -6, -4, -2, 0, 1, 3, 5, 7, 9}; + + nghttp2_pq_init(&pq, node_less, nghttp2_mem_default()); + + for (i = 0; i < (int)(sizeof(nodes) / sizeof(nodes[0])); ++i) { + nodes[i].key = i; + nodes[i].val = i; + nghttp2_pq_push(&pq, &nodes[i].ent); + } + + nghttp2_pq_update(&pq, node_update, NULL); + + for (i = 0; i < (int)(sizeof(nodes) / sizeof(nodes[0])); ++i) { + nd = (node *)nghttp2_pq_top(&pq); + CU_ASSERT(ans[i] == nd->key); + nghttp2_pq_pop(&pq); + } + + nghttp2_pq_free(&pq); +} + +static void push_nodes(nghttp2_pq *pq, node *dest, size_t n) { + size_t i; + for (i = 0; i < n; ++i) { + dest[i].key = (int)i; + dest[i].val = (int)i; + nghttp2_pq_push(pq, &dest[i].ent); + } +} + +static void check_nodes(nghttp2_pq *pq, size_t n, int *ans_key, int *ans_val) { + size_t i; + for (i = 0; i < n; ++i) { + node *nd = (node *)nghttp2_pq_top(pq); + CU_ASSERT(ans_key[i] == nd->key); + CU_ASSERT(ans_val[i] == nd->val); + nghttp2_pq_pop(pq); + } +} + +void test_nghttp2_pq_remove(void) { + nghttp2_pq pq; + node nodes[10]; + int ans_key1[] = {1, 2, 3, 4, 5}; + int ans_val1[] = {1, 2, 3, 4, 5}; + int ans_key2[] = {0, 1, 2, 4, 5}; + int ans_val2[] = {0, 1, 2, 4, 5}; + int ans_key3[] = {0, 1, 2, 3, 4}; + int ans_val3[] = {0, 1, 2, 3, 4}; + + nghttp2_pq_init(&pq, node_less, nghttp2_mem_default()); + + push_nodes(&pq, nodes, 6); + + nghttp2_pq_remove(&pq, &nodes[0].ent); + + check_nodes(&pq, 5, ans_key1, ans_val1); + + nghttp2_pq_free(&pq); + + nghttp2_pq_init(&pq, node_less, nghttp2_mem_default()); + + push_nodes(&pq, nodes, 6); + + nghttp2_pq_remove(&pq, &nodes[3].ent); + + check_nodes(&pq, 5, ans_key2, ans_val2); + + nghttp2_pq_free(&pq); + + nghttp2_pq_init(&pq, node_less, nghttp2_mem_default()); + + push_nodes(&pq, nodes, 6); + + nghttp2_pq_remove(&pq, &nodes[5].ent); + + check_nodes(&pq, 5, ans_key3, ans_val3); + + nghttp2_pq_free(&pq); +} diff --git a/tests/nghttp2_pq_test.h b/tests/nghttp2_pq_test.h new file mode 100644 index 0000000..969662a --- /dev/null +++ b/tests/nghttp2_pq_test.h @@ -0,0 +1,36 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_PQ_TEST_H +#define NGHTTP2_PQ_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_pq(void); +void test_nghttp2_pq_update(void); +void test_nghttp2_pq_remove(void); + +#endif /* NGHTTP2_PQ_TEST_H */ diff --git a/tests/nghttp2_queue_test.c b/tests/nghttp2_queue_test.c new file mode 100644 index 0000000..76cc98a --- /dev/null +++ b/tests/nghttp2_queue_test.c @@ -0,0 +1,48 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_queue_test.h" + +#include <CUnit/CUnit.h> + +#include "nghttp2_queue.h" + +void test_nghttp2_queue(void) { + int ints[] = {1, 2, 3, 4, 5}; + int i; + nghttp2_queue queue; + nghttp2_queue_init(&queue); + CU_ASSERT(nghttp2_queue_empty(&queue)); + for (i = 0; i < 5; ++i) { + nghttp2_queue_push(&queue, &ints[i]); + CU_ASSERT_EQUAL(ints[0], *(int *)(nghttp2_queue_front(&queue))); + CU_ASSERT(!nghttp2_queue_empty(&queue)); + } + for (i = 0; i < 5; ++i) { + CU_ASSERT_EQUAL(ints[i], *(int *)(nghttp2_queue_front(&queue))); + nghttp2_queue_pop(&queue); + } + CU_ASSERT(nghttp2_queue_empty(&queue)); + nghttp2_queue_free(&queue); +} diff --git a/tests/nghttp2_queue_test.h b/tests/nghttp2_queue_test.h new file mode 100644 index 0000000..64f8ce8 --- /dev/null +++ b/tests/nghttp2_queue_test.h @@ -0,0 +1,34 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_QUEUE_TEST_H +#define NGHTTP2_QUEUE_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_queue(void); + +#endif /* NGHTTP2_QUEUE_TEST_H */ diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c new file mode 100644 index 0000000..08152d4 --- /dev/null +++ b/tests/nghttp2_session_test.c @@ -0,0 +1,13306 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_session_test.h" + +#include <stdio.h> +#include <assert.h> + +#include <CUnit/CUnit.h> + +#include "nghttp2_session.h" +#include "nghttp2_stream.h" +#include "nghttp2_net.h" +#include "nghttp2_helper.h" +#include "nghttp2_test_helper.h" +#include "nghttp2_priority_spec.h" +#include "nghttp2_extpri.h" + +typedef struct { + uint8_t buf[65535]; + size_t length; +} accumulator; + +typedef struct { + uint8_t data[8192]; + uint8_t *datamark; + uint8_t *datalimit; + size_t feedseq[8192]; + size_t seqidx; +} scripted_data_feed; + +typedef struct { + accumulator *acc; + scripted_data_feed *df; + int frame_recv_cb_called, invalid_frame_recv_cb_called; + uint8_t recv_frame_type; + nghttp2_frame_hd recv_frame_hd; + int frame_send_cb_called; + uint8_t sent_frame_type; + int before_frame_send_cb_called; + int frame_not_send_cb_called; + uint8_t not_sent_frame_type; + int not_sent_error; + int stream_close_cb_called; + uint32_t stream_close_error_code; + size_t data_source_length; + int32_t stream_id; + size_t block_count; + int data_chunk_recv_cb_called; + const nghttp2_frame *frame; + size_t fixed_sendlen; + int header_cb_called; + int invalid_header_cb_called; + int begin_headers_cb_called; + nghttp2_nv nv; + size_t data_chunk_len; + size_t padlen; + int begin_frame_cb_called; + nghttp2_buf scratchbuf; + size_t data_source_read_cb_paused; +} my_user_data; + +static const nghttp2_nv reqnv[] = { + MAKE_NV(":method", "GET"), + MAKE_NV(":path", "/"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), +}; + +static const nghttp2_nv resnv[] = { + MAKE_NV(":status", "200"), +}; + +static const nghttp2_nv trailernv[] = { + // from http://tools.ietf.org/html/rfc6249#section-7 + MAKE_NV("digest", "SHA-256=" + "MWVkMWQxYTRiMzk5MDQ0MzI3NGU5NDEyZTk5OWY1ZGFmNzgyZTJlODYz" + "YjRjYzFhOTlmNTQwYzI2M2QwM2U2MQ=="), +}; + +static void scripted_data_feed_init2(scripted_data_feed *df, + nghttp2_bufs *bufs) { + nghttp2_buf_chain *ci; + nghttp2_buf *buf; + uint8_t *ptr; + size_t len; + + memset(df, 0, sizeof(scripted_data_feed)); + ptr = df->data; + len = 0; + + for (ci = bufs->head; ci; ci = ci->next) { + buf = &ci->buf; + ptr = nghttp2_cpymem(ptr, buf->pos, nghttp2_buf_len(buf)); + len += nghttp2_buf_len(buf); + } + + df->datamark = df->data; + df->datalimit = df->data + len; + df->feedseq[0] = len; +} + +static ssize_t null_send_callback(nghttp2_session *session, const uint8_t *data, + size_t len, int flags, void *user_data) { + (void)session; + (void)data; + (void)flags; + (void)user_data; + + return (ssize_t)len; +} + +static ssize_t fail_send_callback(nghttp2_session *session, const uint8_t *data, + size_t len, int flags, void *user_data) { + (void)session; + (void)data; + (void)len; + (void)flags; + (void)user_data; + + return NGHTTP2_ERR_CALLBACK_FAILURE; +} + +static ssize_t fixed_bytes_send_callback(nghttp2_session *session, + const uint8_t *data, size_t len, + int flags, void *user_data) { + size_t fixed_sendlen = ((my_user_data *)user_data)->fixed_sendlen; + (void)session; + (void)data; + (void)flags; + + return (ssize_t)(fixed_sendlen < len ? fixed_sendlen : len); +} + +static ssize_t scripted_recv_callback(nghttp2_session *session, uint8_t *data, + size_t len, int flags, void *user_data) { + scripted_data_feed *df = ((my_user_data *)user_data)->df; + size_t wlen = df->feedseq[df->seqidx] > len ? len : df->feedseq[df->seqidx]; + (void)session; + (void)flags; + + memcpy(data, df->datamark, wlen); + df->datamark += wlen; + df->feedseq[df->seqidx] -= wlen; + if (df->feedseq[df->seqidx] == 0) { + ++df->seqidx; + } + return (ssize_t)wlen; +} + +static ssize_t eof_recv_callback(nghttp2_session *session, uint8_t *data, + size_t len, int flags, void *user_data) { + (void)session; + (void)data; + (void)len; + (void)flags; + (void)user_data; + + return NGHTTP2_ERR_EOF; +} + +static ssize_t accumulator_send_callback(nghttp2_session *session, + const uint8_t *buf, size_t len, + int flags, void *user_data) { + accumulator *acc = ((my_user_data *)user_data)->acc; + (void)session; + (void)flags; + + assert(acc->length + len < sizeof(acc->buf)); + memcpy(acc->buf + acc->length, buf, len); + acc->length += len; + return (ssize_t)len; +} + +static int on_begin_frame_callback(nghttp2_session *session, + const nghttp2_frame_hd *hd, + void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)hd; + + ++ud->begin_frame_cb_called; + return 0; +} + +static int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + + ++ud->frame_recv_cb_called; + ud->recv_frame_type = frame->hd.type; + ud->recv_frame_hd = frame->hd; + + return 0; +} + +static int on_invalid_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, + int lib_error_code, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)frame; + (void)lib_error_code; + + ++ud->invalid_frame_recv_cb_called; + return 0; +} + +static int on_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + + ++ud->frame_send_cb_called; + ud->sent_frame_type = frame->hd.type; + return 0; +} + +static int on_frame_not_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, int lib_error, + void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + + ++ud->frame_not_send_cb_called; + ud->not_sent_frame_type = frame->hd.type; + ud->not_sent_error = lib_error; + return 0; +} + +static int cancel_before_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)frame; + + ++ud->before_frame_send_cb_called; + return NGHTTP2_ERR_CANCEL; +} + +static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)flags; + (void)stream_id; + (void)data; + + ++ud->data_chunk_recv_cb_called; + ud->data_chunk_len = len; + return 0; +} + +static int pause_on_data_chunk_recv_callback(nghttp2_session *session, + uint8_t flags, int32_t stream_id, + const uint8_t *data, size_t len, + void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)flags; + (void)stream_id; + (void)data; + (void)len; + + ++ud->data_chunk_recv_cb_called; + return NGHTTP2_ERR_PAUSE; +} + +static ssize_t select_padding_callback(nghttp2_session *session, + const nghttp2_frame *frame, + size_t max_payloadlen, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + + return (ssize_t)nghttp2_min(max_payloadlen, frame->hd.length + ud->padlen); +} + +static ssize_t too_large_data_source_length_callback( + nghttp2_session *session, uint8_t frame_type, int32_t stream_id, + int32_t session_remote_window_size, int32_t stream_remote_window_size, + uint32_t remote_max_frame_size, void *user_data) { + (void)session; + (void)frame_type; + (void)stream_id; + (void)session_remote_window_size; + (void)stream_remote_window_size; + (void)remote_max_frame_size; + (void)user_data; + + return NGHTTP2_MAX_FRAME_SIZE_MAX + 1; +} + +static ssize_t smallest_length_data_source_length_callback( + nghttp2_session *session, uint8_t frame_type, int32_t stream_id, + int32_t session_remote_window_size, int32_t stream_remote_window_size, + uint32_t remote_max_frame_size, void *user_data) { + (void)session; + (void)frame_type; + (void)stream_id; + (void)session_remote_window_size; + (void)stream_remote_window_size; + (void)remote_max_frame_size; + (void)user_data; + + return 1; +} + +static ssize_t fixed_length_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + size_t wlen; + (void)session; + (void)stream_id; + (void)buf; + (void)source; + + if (len < ud->data_source_length) { + wlen = len; + } else { + wlen = ud->data_source_length; + } + ud->data_source_length -= wlen; + if (ud->data_source_length == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + return (ssize_t)wlen; +} + +static ssize_t temporal_failure_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + (void)session; + (void)stream_id; + (void)buf; + (void)len; + (void)data_flags; + (void)source; + (void)user_data; + + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +} + +static ssize_t fail_data_source_read_callback(nghttp2_session *session, + int32_t stream_id, uint8_t *buf, + size_t len, uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) { + (void)session; + (void)stream_id; + (void)buf; + (void)len; + (void)data_flags; + (void)source; + (void)user_data; + + return NGHTTP2_ERR_CALLBACK_FAILURE; +} + +static ssize_t no_end_stream_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + (void)session; + (void)stream_id; + (void)buf; + (void)len; + (void)source; + (void)user_data; + + *data_flags |= NGHTTP2_DATA_FLAG_EOF | NGHTTP2_DATA_FLAG_NO_END_STREAM; + return 0; +} + +static ssize_t no_copy_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + size_t wlen; + (void)session; + (void)stream_id; + (void)buf; + (void)source; + + if (len < ud->data_source_length) { + wlen = len; + } else { + wlen = ud->data_source_length; + } + + ud->data_source_length -= wlen; + + *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; + + if (ud->data_source_length == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + return (ssize_t)wlen; +} + +static int send_data_callback(nghttp2_session *session, nghttp2_frame *frame, + const uint8_t *framehd, size_t length, + nghttp2_data_source *source, void *user_data) { + accumulator *acc = ((my_user_data *)user_data)->acc; + (void)session; + (void)source; + + memcpy(acc->buf + acc->length, framehd, NGHTTP2_FRAME_HDLEN); + acc->length += NGHTTP2_FRAME_HDLEN; + + if (frame->data.padlen) { + *(acc->buf + acc->length++) = (uint8_t)(frame->data.padlen - 1); + } + + acc->length += length; + + if (frame->data.padlen) { + acc->length += frame->data.padlen - 1; + } + + return 0; +} + +static ssize_t block_count_send_callback(nghttp2_session *session, + const uint8_t *data, size_t len, + int flags, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)data; + (void)flags; + + if (ud->block_count == 0) { + return NGHTTP2_ERR_WOULDBLOCK; + } + + --ud->block_count; + return (ssize_t)len; +} + +static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)flags; + + ++ud->header_cb_called; + ud->nv.name = (uint8_t *)name; + ud->nv.namelen = namelen; + ud->nv.value = (uint8_t *)value; + ud->nv.valuelen = valuelen; + + ud->frame = frame; + return 0; +} + +static int pause_on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, void *user_data) { + on_header_callback(session, frame, name, namelen, value, valuelen, flags, + user_data); + return NGHTTP2_ERR_PAUSE; +} + +static int temporal_failure_on_header_callback( + nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, + void *user_data) { + on_header_callback(session, frame, name, namelen, value, valuelen, flags, + user_data); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +} + +static int on_invalid_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)flags; + + ++ud->invalid_header_cb_called; + ud->nv.name = (uint8_t *)name; + ud->nv.namelen = namelen; + ud->nv.value = (uint8_t *)value; + ud->nv.valuelen = valuelen; + + ud->frame = frame; + return 0; +} + +static int pause_on_invalid_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, + size_t valuelen, uint8_t flags, + void *user_data) { + on_invalid_header_callback(session, frame, name, namelen, value, valuelen, + flags, user_data); + return NGHTTP2_ERR_PAUSE; +} + +static int reset_on_invalid_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, + size_t valuelen, uint8_t flags, + void *user_data) { + on_invalid_header_callback(session, frame, name, namelen, value, valuelen, + flags, user_data); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +} + +static int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)frame; + + ++ud->begin_headers_cb_called; + return 0; +} + +static int temporal_failure_on_begin_headers_callback( + nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { + on_begin_headers_callback(session, frame, user_data); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +} + +static ssize_t defer_data_source_read_callback(nghttp2_session *session, + int32_t stream_id, uint8_t *buf, + size_t len, uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) { + (void)session; + (void)stream_id; + (void)buf; + (void)len; + (void)data_flags; + (void)source; + (void)user_data; + + return NGHTTP2_ERR_DEFERRED; +} + +static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + nghttp2_error_code error_code, + void *user_data) { + my_user_data *my_data = (my_user_data *)user_data; + (void)session; + (void)stream_id; + (void)error_code; + + ++my_data->stream_close_cb_called; + my_data->stream_close_error_code = error_code; + + return 0; +} + +static ssize_t pack_extension_callback(nghttp2_session *session, uint8_t *buf, + size_t len, const nghttp2_frame *frame, + void *user_data) { + nghttp2_buf *p = frame->ext.payload; + (void)session; + (void)len; + (void)user_data; + + memcpy(buf, p->pos, nghttp2_buf_len(p)); + + return (ssize_t)nghttp2_buf_len(p); +} + +static int on_extension_chunk_recv_callback(nghttp2_session *session, + const nghttp2_frame_hd *hd, + const uint8_t *data, size_t len, + void *user_data) { + my_user_data *my_data = (my_user_data *)user_data; + nghttp2_buf *buf = &my_data->scratchbuf; + (void)session; + (void)hd; + + buf->last = nghttp2_cpymem(buf->last, data, len); + + return 0; +} + +static int cancel_on_extension_chunk_recv_callback(nghttp2_session *session, + const nghttp2_frame_hd *hd, + const uint8_t *data, + size_t len, + void *user_data) { + (void)session; + (void)hd; + (void)data; + (void)len; + (void)user_data; + + return NGHTTP2_ERR_CANCEL; +} + +static int unpack_extension_callback(nghttp2_session *session, void **payload, + const nghttp2_frame_hd *hd, + void *user_data) { + my_user_data *my_data = (my_user_data *)user_data; + nghttp2_buf *buf = &my_data->scratchbuf; + (void)session; + (void)hd; + + *payload = buf; + + return 0; +} + +static int cancel_unpack_extension_callback(nghttp2_session *session, + void **payload, + const nghttp2_frame_hd *hd, + void *user_data) { + (void)session; + (void)payload; + (void)hd; + (void)user_data; + + return NGHTTP2_ERR_CANCEL; +} + +static nghttp2_settings_entry *dup_iv(const nghttp2_settings_entry *iv, + size_t niv) { + return nghttp2_frame_iv_copy(iv, niv, nghttp2_mem_default()); +} + +static nghttp2_priority_spec pri_spec_default = {0, NGHTTP2_DEFAULT_WEIGHT, 0}; + +void test_nghttp2_session_recv(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + scripted_data_feed df; + my_user_data user_data; + nghttp2_bufs bufs; + size_t framelen; + nghttp2_frame frame; + size_t i; + nghttp2_outbound_item *item; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_hd_deflater deflater; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.recv_callback = scripted_recv_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_begin_frame_callback = on_begin_frame_callback; + + user_data.df = &df; + + nghttp2_session_server_new(&session, &callbacks, &user_data); + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + scripted_data_feed_init2(&df, &bufs); + + framelen = nghttp2_bufs_len(&bufs); + + /* Send 1 byte per each read */ + for (i = 0; i < framelen; ++i) { + df.feedseq[i] = 1; + } + + nghttp2_frame_headers_free(&frame.headers, mem); + + user_data.frame_recv_cb_called = 0; + user_data.begin_frame_cb_called = 0; + + while (df.seqidx < framelen) { + CU_ASSERT(0 == nghttp2_session_recv(session)); + } + CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(1 == user_data.begin_frame_cb_called); + + nghttp2_bufs_reset(&bufs); + + /* Receive PRIORITY */ + nghttp2_frame_priority_init(&frame.priority, 5, &pri_spec_default); + + rv = nghttp2_frame_pack_priority(&bufs, &frame.priority); + + CU_ASSERT(0 == rv); + + nghttp2_frame_priority_free(&frame.priority); + + scripted_data_feed_init2(&df, &bufs); + + user_data.frame_recv_cb_called = 0; + user_data.begin_frame_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_recv(session)); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(1 == user_data.begin_frame_cb_called); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Some tests for frame too large */ + nghttp2_session_server_new(&session, &callbacks, &user_data); + + /* Receive PING with too large payload */ + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL); + + rv = nghttp2_frame_pack_ping(&bufs, &frame.ping); + + CU_ASSERT(0 == rv); + + /* Add extra 16 bytes */ + nghttp2_bufs_seek_last_present(&bufs); + assert(nghttp2_buf_len(&bufs.cur->buf) >= 16); + + bufs.cur->buf.last += 16; + nghttp2_put_uint32be( + bufs.cur->buf.pos, + (uint32_t)(((frame.hd.length + 16) << 8) + bufs.cur->buf.pos[3])); + + nghttp2_frame_ping_free(&frame.ping); + + scripted_data_feed_init2(&df, &bufs); + user_data.frame_recv_cb_called = 0; + user_data.begin_frame_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_recv(session)); + CU_ASSERT(0 == user_data.frame_recv_cb_called); + CU_ASSERT(0 == user_data.begin_frame_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_FRAME_SIZE_ERROR == item->frame.goaway.error_code); + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_free(&bufs); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_invalid_stream_id(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + scripted_data_feed df; + my_user_data user_data; + nghttp2_bufs bufs; + nghttp2_frame frame; + nghttp2_hd_deflater deflater; + int rv; + nghttp2_mem *mem; + nghttp2_nv *nva; + size_t nvlen; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.recv_callback = scripted_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + user_data.df = &df; + user_data.invalid_frame_recv_cb_called = 0; + nghttp2_session_server_new(&session, &callbacks, &user_data); + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + scripted_data_feed_init2(&df, &bufs); + nghttp2_frame_headers_free(&frame.headers, mem); + + CU_ASSERT(0 == nghttp2_session_recv(session)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_invalid_frame(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + scripted_data_feed df; + my_user_data user_data; + nghttp2_bufs bufs; + nghttp2_frame frame; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_hd_deflater deflater; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.recv_callback = scripted_recv_callback; + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + user_data.df = &df; + user_data.frame_send_cb_called = 0; + nghttp2_session_server_new(&session, &callbacks, &user_data); + nghttp2_hd_deflate_init(&deflater, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + scripted_data_feed_init2(&df, &bufs); + + CU_ASSERT(0 == nghttp2_session_recv(session)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == user_data.frame_send_cb_called); + + /* Receive exactly same bytes of HEADERS is treated as error, because it has + * pseudo headers and without END_STREAM flag set */ + scripted_data_feed_init2(&df, &bufs); + + CU_ASSERT(0 == nghttp2_session_recv(session)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == user_data.frame_send_cb_called); + CU_ASSERT(NGHTTP2_RST_STREAM == user_data.sent_frame_type); + + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_eof(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.recv_callback = eof_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + CU_ASSERT(NGHTTP2_ERR_EOF == nghttp2_session_recv(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + uint8_t data[8092]; + ssize_t rv; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + nghttp2_frame_hd hd; + int i; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + /* Create DATA frame with length 4KiB */ + memset(data, 0, sizeof(data)); + hd.length = 4096; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_NONE; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + + /* stream 1 is not opened, so it must be responded with connection + error. This is not mandated by the spec */ + ud.data_chunk_recv_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + + CU_ASSERT(0 == ud.data_chunk_recv_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &ud); + + /* Create stream 1 with CLOSING state. DATA is ignored. */ + stream = open_sent_stream2(session, 1, NGHTTP2_STREAM_CLOSING); + + /* Set initial window size 16383 to check stream flow control, + isolating it from the connection flow control */ + stream->local_window_size = 16383; + + ud.data_chunk_recv_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + + CU_ASSERT(0 == ud.data_chunk_recv_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NULL == item); + + /* This is normal case. DATA is acceptable. */ + stream->state = NGHTTP2_STREAM_OPENED; + + ud.data_chunk_recv_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + + CU_ASSERT(1 == ud.data_chunk_recv_cb_called); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + ud.data_chunk_recv_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + + /* Now we got data more than initial-window-size / 2, WINDOW_UPDATE + must be queued */ + CU_ASSERT(1 == ud.data_chunk_recv_cb_called); + CU_ASSERT(1 == ud.frame_recv_cb_called); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(1 == item->frame.window_update.hd.stream_id); + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Set initial window size to 1MiB, so that we can check connection + flow control individually */ + stream->local_window_size = 1 << 20; + /* Connection flow control takes into account DATA which is received + in the error condition. We have received 4096 * 4 bytes of + DATA. Additional 4 DATA frames, connection flow control will kick + in. */ + for (i = 0; i < 5; ++i) { + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + } + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(0 == item->frame.window_update.hd.stream_id); + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Reception of DATA with stream ID = 0 causes connection error */ + hd.length = 4096; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_NONE; + hd.stream_id = 0; + nghttp2_frame_pack_frame_hd(data, &hd); + + ud.data_chunk_recv_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + + CU_ASSERT(0 == ud.data_chunk_recv_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + + nghttp2_session_del(session); + + /* Check window_update_queued flag in both session and stream */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + hd.length = 4096; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_NONE; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + + stream = open_recv_stream(session, 1); + + /* Send 32767 bytes of DATA. In our current flow control algorithm, + it triggers first WINDOW_UPDATE of window_size_increment + 32767. */ + for (i = 0; i < 7; ++i) { + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + } + + hd.length = 4095; + nghttp2_frame_pack_frame_hd(data, &hd); + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4095); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4095 == rv); + + /* Now 2 WINDOW_UPDATEs for session and stream should be queued. */ + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(1 == stream->window_update_queued); + CU_ASSERT(1 == session->window_update_queued); + + /* Then send 32768 bytes of DATA. Since we have not sent queued + WINDOW_UDPATE frame, recv_window_size should not be decreased */ + hd.length = 4096; + nghttp2_frame_pack_frame_hd(data, &hd); + + for (i = 0; i < 8; ++i) { + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + } + + /* WINDOW_UPDATE is blocked for session and stream, so + recv_window_size must not be decreased. */ + CU_ASSERT(32768 == stream->recv_window_size); + CU_ASSERT(32768 == session->recv_window_size); + CU_ASSERT(1 == stream->window_update_queued); + CU_ASSERT(1 == session->window_update_queued); + + ud.frame_send_cb_called = 0; + + /* This sends queued WINDOW_UPDATES. And then check + recv_window_size, and queue WINDOW_UPDATEs for both session and + stream, and send them at once. */ + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(4 == ud.frame_send_cb_called); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(0 == stream->window_update_queued); + CU_ASSERT(0 == session->window_update_queued); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_data_no_auto_flow_control(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_option *option; + nghttp2_frame_hd hd; + size_t padlen; + uint8_t data[8192]; + ssize_t rv; + size_t sendlen; + nghttp2_stream *stream; + size_t i; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_window_update(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + /* Create DATA frame with length 4KiB + 11 bytes padding*/ + padlen = 11; + memset(data, 0, sizeof(data)); + hd.length = 4096 + 1 + padlen; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_PADDED; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + data[NGHTTP2_FRAME_HDLEN] = (uint8_t)padlen; + + /* First create stream 1, then close it. Check that data is + consumed for connection in this situation */ + open_recv_stream(session, 1); + + /* Receive first 100 bytes */ + sendlen = 100; + rv = nghttp2_session_mem_recv(session, data, sendlen); + CU_ASSERT((ssize_t)sendlen == rv); + + /* We consumed pad length field (1 byte) */ + CU_ASSERT(1 == session->consumed_size); + + /* close stream here */ + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, NGHTTP2_NO_ERROR); + nghttp2_session_send(session); + + /* stream 1 has been closed, and we disabled auto flow-control, so + data must be immediately consumed for connection. */ + rv = nghttp2_session_mem_recv(session, data + sendlen, + NGHTTP2_FRAME_HDLEN + hd.length - sendlen); + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length - sendlen) == rv); + + /* We already consumed pad length field (1 byte), so do +1 here */ + CU_ASSERT((int32_t)(NGHTTP2_FRAME_HDLEN + hd.length - sendlen + 1) == + session->consumed_size); + + nghttp2_session_del(session); + + /* Reuse DATA created previously. */ + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + /* Now we are expecting final response header, which means receiving + DATA for that stream is illegal. */ + stream = open_recv_stream(session, 1); + stream->http_flags |= NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE; + + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + hd.length); + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length) == rv); + + /* Whole payload must be consumed now because HTTP messaging rule + was not honored. */ + CU_ASSERT((int32_t)hd.length == session->consumed_size); + + nghttp2_session_del(session); + + /* Check window_update_queued flag in both session and stream */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + stream = open_recv_stream(session, 1); + + hd.length = 4096; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_NONE; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + + /* Receive up to 65535 bytes of DATA */ + for (i = 0; i < 15; ++i) { + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + } + + hd.length = 4095; + nghttp2_frame_pack_frame_hd(data, &hd); + + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4095); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4095 == rv); + + CU_ASSERT(65535 == session->recv_window_size); + CU_ASSERT(65535 == stream->recv_window_size); + + /* The first call of nghttp2_session_consume_connection() will queue + WINDOW_UPDATE. Next call does not. */ + nghttp2_session_consume_connection(session, 32767); + nghttp2_session_consume_connection(session, 32768); + + CU_ASSERT(32768 == session->recv_window_size); + CU_ASSERT(65535 == stream->recv_window_size); + CU_ASSERT(1 == session->window_update_queued); + CU_ASSERT(0 == stream->window_update_queued); + + ud.frame_send_cb_called = 0; + + /* This will send WINDOW_UPDATE, and check whether we should send + WINDOW_UPDATE, and queue and send it at once. */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(65535 == stream->recv_window_size); + CU_ASSERT(0 == session->window_update_queued); + CU_ASSERT(0 == stream->window_update_queued); + CU_ASSERT(2 == ud.frame_send_cb_called); + + /* Do the same for stream */ + nghttp2_session_consume_stream(session, 1, 32767); + nghttp2_session_consume_stream(session, 1, 32768); + + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(32768 == stream->recv_window_size); + CU_ASSERT(0 == session->window_update_queued); + CU_ASSERT(1 == stream->window_update_queued); + + ud.frame_send_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(0 == session->window_update_queued); + CU_ASSERT(0 == stream->window_update_queued); + CU_ASSERT(2 == ud.frame_send_cb_called); + + nghttp2_session_del(session); + nghttp2_option_del(option); +} + +void test_nghttp2_session_recv_continuation(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_hd_deflater deflater; + uint8_t data[1024]; + size_t datalen; + nghttp2_frame_hd cont_hd; + nghttp2_priority_spec pri_spec; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_header_callback = on_header_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_begin_frame_callback = on_begin_frame_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* Make 1 HEADERS and insert CONTINUATION header */ + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + /* make sure that all data is in the first buf */ + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* HEADERS's payload is 1 byte */ + memcpy(data, buf->pos, NGHTTP2_FRAME_HDLEN + 1); + datalen = NGHTTP2_FRAME_HDLEN + 1; + buf->pos += NGHTTP2_FRAME_HDLEN + 1; + + nghttp2_put_uint32be(data, (uint32_t)((1 << 8) + data[3])); + + /* First CONTINUATION, 2 bytes */ + nghttp2_frame_hd_init(&cont_hd, 2, NGHTTP2_CONTINUATION, NGHTTP2_FLAG_NONE, + 1); + + nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd); + datalen += NGHTTP2_FRAME_HDLEN; + + memcpy(data + datalen, buf->pos, cont_hd.length); + datalen += cont_hd.length; + buf->pos += cont_hd.length; + + /* Second CONTINUATION, rest of the bytes */ + nghttp2_frame_hd_init(&cont_hd, nghttp2_buf_len(buf), NGHTTP2_CONTINUATION, + NGHTTP2_FLAG_END_HEADERS, 1); + + nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd); + datalen += NGHTTP2_FRAME_HDLEN; + + memcpy(data + datalen, buf->pos, cont_hd.length); + datalen += cont_hd.length; + buf->pos += cont_hd.length; + + CU_ASSERT(0 == nghttp2_buf_len(buf)); + + ud.header_cb_called = 0; + ud.begin_frame_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, data, datalen); + CU_ASSERT((ssize_t)datalen == rv); + CU_ASSERT(4 == ud.header_cb_called); + CU_ASSERT(3 == ud.begin_frame_cb_called); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* HEADERS with padding followed by CONTINUATION */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* make sure that all data is in the first buf */ + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + /* HEADERS payload is 3 byte (1 for padding field, 1 for padding) */ + memcpy(data, buf->pos, NGHTTP2_FRAME_HDLEN); + nghttp2_put_uint32be(data, (uint32_t)((3 << 8) + data[3])); + data[4] |= NGHTTP2_FLAG_PADDED; + /* padding field */ + data[NGHTTP2_FRAME_HDLEN] = 1; + data[NGHTTP2_FRAME_HDLEN + 1] = buf->pos[NGHTTP2_FRAME_HDLEN]; + /* padding */ + data[NGHTTP2_FRAME_HDLEN + 2] = 0; + datalen = NGHTTP2_FRAME_HDLEN + 3; + buf->pos += NGHTTP2_FRAME_HDLEN + 1; + + /* CONTINUATION, rest of the bytes */ + nghttp2_frame_hd_init(&cont_hd, nghttp2_buf_len(buf), NGHTTP2_CONTINUATION, + NGHTTP2_FLAG_END_HEADERS, 1); + nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd); + datalen += NGHTTP2_FRAME_HDLEN; + + memcpy(data + datalen, buf->pos, cont_hd.length); + datalen += cont_hd.length; + buf->pos += cont_hd.length; + + CU_ASSERT(0 == nghttp2_buf_len(buf)); + + ud.header_cb_called = 0; + ud.begin_frame_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, data, datalen); + + CU_ASSERT((ssize_t)datalen == rv); + CU_ASSERT(4 == ud.header_cb_called); + CU_ASSERT(2 == ud.begin_frame_cb_called); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Expecting CONTINUATION, but get the other frame */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* HEADERS without END_HEADERS flag */ + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* make sure that all data is in the first buf */ + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + memcpy(data, buf->pos, nghttp2_buf_len(buf)); + datalen = nghttp2_buf_len(buf); + + /* Followed by PRIORITY */ + nghttp2_priority_spec_default_init(&pri_spec); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + nghttp2_bufs_reset(&bufs); + + rv = nghttp2_frame_pack_priority(&bufs, &frame.priority); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + memcpy(data + datalen, buf->pos, nghttp2_buf_len(buf)); + datalen += nghttp2_buf_len(buf); + + ud.begin_headers_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, datalen); + CU_ASSERT((ssize_t)datalen == rv); + + CU_ASSERT(1 == ud.begin_headers_cb_called); + CU_ASSERT(NGHTTP2_GOAWAY == + nghttp2_session_get_next_ob_item(session)->frame.hd.type); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_headers_with_priority(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_hd_deflater deflater; + nghttp2_outbound_item *item; + nghttp2_priority_spec pri_spec; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + open_recv_stream(session, 1); + + /* With NGHTTP2_FLAG_PRIORITY without exclusive flag set */ + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + + nghttp2_priority_spec_init(&pri_spec, 1, 99, 0); + + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 3, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 3); + + CU_ASSERT(99 == stream->weight); + CU_ASSERT(1 == stream->dep_prev->stream_id); + + nghttp2_bufs_reset(&bufs); + + /* With NGHTTP2_FLAG_PRIORITY, but cut last 1 byte to make it + invalid. */ + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + + nghttp2_priority_spec_init(&pri_spec, 0, 99, 0); + + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 5, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > NGHTTP2_FRAME_HDLEN + 5); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + /* Make payload shorter than required length to store priority + group */ + nghttp2_put_uint32be(buf->pos, (uint32_t)((4 << 8) + buf->pos[3])); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 5); + + CU_ASSERT(NULL == stream); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_FRAME_SIZE_ERROR == item->frame.goaway.error_code); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Check dep_stream_id == stream_id */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + + nghttp2_priority_spec_init(&pri_spec, 1, 0, 0); + + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NULL == stream); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_headers_with_padding(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_frame_hd hd; + nghttp2_outbound_item *item; + my_user_data ud; + ssize_t rv; + + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.send_callback = null_send_callback; + + /* HEADERS: Wrong padding length */ + nghttp2_session_server_new(&session, &callbacks, &ud); + nghttp2_session_send(session); + + nghttp2_frame_hd_init(&hd, 10, NGHTTP2_HEADERS, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY | + NGHTTP2_FLAG_PADDED, + 1); + buf = &bufs.head->buf; + nghttp2_frame_pack_frame_hd(buf->last, &hd); + buf->last += NGHTTP2_FRAME_HDLEN; + /* padding is 6 bytes */ + *buf->last++ = 5; + /* priority field */ + nghttp2_put_uint32be(buf->last, 3); + buf->last += sizeof(uint32_t); + *buf->last++ = 1; + /* rest is garbage */ + memset(buf->last, 0, 4); + buf->last += 4; + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_bufs_reset(&bufs); + nghttp2_session_del(session); + + /* PUSH_PROMISE: Wrong padding length */ + nghttp2_session_client_new(&session, &callbacks, &ud); + nghttp2_session_send(session); + + open_sent_stream(session, 1); + + nghttp2_frame_hd_init(&hd, 9, NGHTTP2_PUSH_PROMISE, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PADDED, 1); + buf = &bufs.head->buf; + nghttp2_frame_pack_frame_hd(buf->last, &hd); + buf->last += NGHTTP2_FRAME_HDLEN; + /* padding is 6 bytes */ + *buf->last++ = 5; + /* promised stream ID field */ + nghttp2_put_uint32be(buf->last, 2); + buf->last += sizeof(uint32_t); + /* rest is garbage */ + memset(buf->last, 0, 4); + buf->last += 4; + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_bufs_free(&bufs); + nghttp2_session_del(session); +} + +static int response_on_begin_frame_callback(nghttp2_session *session, + const nghttp2_frame_hd *hd, + void *user_data) { + int rv; + (void)user_data; + + if (hd->type != NGHTTP2_HEADERS) { + return 0; + } + + rv = nghttp2_submit_response(session, hd->stream_id, resnv, ARRLEN(resnv), + NULL); + + CU_ASSERT(0 == rv); + + return 0; +} + +void test_nghttp2_session_recv_headers_early_response(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + ssize_t rv; + nghttp2_stream *stream; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_begin_frame_callback = response_on_begin_frame_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + 1, NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + + /* Only receive 9 bytes headers, and invoke + on_begin_frame_callback */ + rv = nghttp2_session_mem_recv(session, buf->pos, 9); + + CU_ASSERT(9 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + rv = + nghttp2_session_mem_recv(session, buf->pos + 9, nghttp2_buf_len(buf) - 9); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) - 9 == rv); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(stream->flags & NGHTTP2_STREAM_FLAG_CLOSED); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_recv_headers_for_closed_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_hd_deflater deflater; + nghttp2_stream *stream; + nghttp2_mem *mem; + const uint8_t *data; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_header_callback = on_header_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* Make sure that on_header callback never be invoked for closed + stream */ + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.header_cb_called = 0; + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, NGHTTP2_FRAME_HDLEN); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN == rv); + CU_ASSERT(0 == ud.header_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NULL != stream); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(rv > 0); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NULL == stream); + + ud.header_cb_called = 0; + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos + NGHTTP2_FRAME_HDLEN, + nghttp2_buf_len(buf) - NGHTTP2_FRAME_HDLEN); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) - NGHTTP2_FRAME_HDLEN == rv); + CU_ASSERT(0 == ud.header_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_headers_with_extpri(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + nghttp2_hd_deflater deflater; + nghttp2_stream *stream; + nghttp2_mem *mem; + const nghttp2_nv extpri_reqnv[] = { + MAKE_NV(":method", "GET"), MAKE_NV(":path", "/"), + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"), + MAKE_NV("priority", "i,u=2"), + }; + nghttp2_settings_entry iv; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 1; + + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(extpri_reqnv); + nghttp2_nv_array_copy(&nva, extpri_reqnv, nvlen, mem); + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_bufs_reset(&bufs); + + /* Client should ignore priority header field included in + PUSH_PROMISE. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + open_sent_stream(session, 1); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(extpri_reqnv); + nghttp2_nv_array_copy(&nva, extpri_reqnv, nvlen, mem); + + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 2, nva, nvlen); + + rv = nghttp2_frame_pack_push_promise(&bufs, &frame.push_promise, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + stream = nghttp2_session_get_stream(session, 2); + + CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == + nghttp2_extpri_uint8_urgency(stream->http_extpri)); + CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == + nghttp2_extpri_uint8_urgency(stream->extpri)); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_server_recv_push_response(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_mem *mem; + nghttp2_frame frame; + nghttp2_hd_deflater deflater; + nghttp2_nv *nva; + size_t nvlen; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + nvlen = ARRLEN(resnv); + nghttp2_nv_array_copy(&nva, resnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_HEADERS, &pri_spec_default, nva, + nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + + ud.invalid_frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.invalid_frame_recv_cb_called); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_premature_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_hd_deflater deflater; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + uint32_t payloadlen; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + /* Intentionally feed payload cutting last 1 byte off */ + payloadlen = nghttp2_get_uint32(buf->pos) >> 8; + nghttp2_put_uint32be(buf->pos, ((payloadlen - 1) << 8) + buf->pos[3]); + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf) - 1); + + CU_ASSERT((ssize_t)(nghttp2_buf_len(buf) - 1) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_COMPRESSION_ERROR == item->frame.rst_stream.error_code); + CU_ASSERT(1 == item->frame.hd.stream_id); + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Test for PUSH_PROMISE */ + nghttp2_session_client_new(&session, &callbacks, &ud); + nghttp2_hd_deflate_init(&deflater, mem); + + open_sent_stream3(session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default, + NGHTTP2_STREAM_OPENING, NULL); + + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, + reqnv, ARRLEN(reqnv), mem); + + CU_ASSERT(0 == rv); + + buf = &bufs.head->buf; + payloadlen = nghttp2_get_uint32(buf->pos) >> 8; + /* Intentionally feed payload cutting last 1 byte off */ + nghttp2_put_uint32be(buf->pos, ((payloadlen - 1) << 8) + buf->pos[3]); + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf) - 1); + + CU_ASSERT((ssize_t)(nghttp2_buf_len(buf) - 1) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_COMPRESSION_ERROR == item->frame.rst_stream.error_code); + CU_ASSERT(2 == item->frame.hd.stream_id); + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_recv_unknown_frame(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + uint8_t data[16384]; + size_t datalen; + nghttp2_frame_hd hd; + ssize_t rv; + + nghttp2_frame_hd_init(&hd, 16000, 99, NGHTTP2_FLAG_NONE, 0); + + nghttp2_frame_pack_frame_hd(data, &hd); + datalen = NGHTTP2_FRAME_HDLEN + hd.length; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + ud.frame_recv_cb_called = 0; + + /* Unknown frame must be ignored */ + rv = nghttp2_session_mem_recv(session, data, datalen); + + CU_ASSERT(rv == (ssize_t)datalen); + CU_ASSERT(0 == ud.frame_recv_cb_called); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_unexpected_continuation(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + uint8_t data[16384]; + size_t datalen; + nghttp2_frame_hd hd; + ssize_t rv; + nghttp2_outbound_item *item; + + nghttp2_frame_hd_init(&hd, 16000, NGHTTP2_CONTINUATION, + NGHTTP2_FLAG_END_HEADERS, 1); + + nghttp2_frame_pack_frame_hd(data, &hd); + datalen = NGHTTP2_FRAME_HDLEN + hd.length; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 1); + + ud.frame_recv_cb_called = 0; + + /* unexpected CONTINUATION must be treated as connection error */ + rv = nghttp2_session_mem_recv(session, data, datalen); + + CU_ASSERT(rv == (ssize_t)datalen); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_settings_header_table_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_settings_entry iv[3]; + nghttp2_nv nv = MAKE_NV(":authority", "example.org"); + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 3000; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16384; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2), + 2); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + CU_ASSERT(3000 == session->remote_settings.header_table_size); + CU_ASSERT(16384 == session->remote_settings.initial_window_size); + + nghttp2_bufs_reset(&bufs); + + /* 2 SETTINGS_HEADER_TABLE_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 3001; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16383; + + iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[2].value = 3001; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3), + 3); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)(nghttp2_buf_len(buf)) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + CU_ASSERT(3001 == session->remote_settings.header_table_size); + CU_ASSERT(16383 == session->remote_settings.initial_window_size); + + nghttp2_bufs_reset(&bufs); + + /* 2 SETTINGS_HEADER_TABLE_SIZE; first entry clears dynamic header + table. */ + + nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL); + nghttp2_session_send(session); + + CU_ASSERT(0 < session->hd_deflater.ctx.hd_table.len); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 0; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16382; + + iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[2].value = 4096; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3), + 3); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + CU_ASSERT(4096 == session->remote_settings.header_table_size); + CU_ASSERT(16382 == session->remote_settings.initial_window_size); + CU_ASSERT(0 == session->hd_deflater.ctx.hd_table.len); + + nghttp2_bufs_reset(&bufs); + + /* 2 SETTINGS_HEADER_TABLE_SIZE; second entry clears dynamic header + table. */ + + nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL); + nghttp2_session_send(session); + + CU_ASSERT(0 < session->hd_deflater.ctx.hd_table.len); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 3000; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16381; + + iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[2].value = 0; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3), + 3); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + CU_ASSERT(0 == session->remote_settings.header_table_size); + CU_ASSERT(16381 == session->remote_settings.initial_window_size); + CU_ASSERT(0 == session->hd_deflater.ctx.hd_table.len); + + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_too_large_frame_length(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t buf[NGHTTP2_FRAME_HDLEN]; + nghttp2_outbound_item *item; + nghttp2_frame_hd hd; + + /* Initial max frame size is NGHTTP2_MAX_FRAME_SIZE_MIN */ + nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_FRAME_SIZE_MIN + 1, NGHTTP2_HEADERS, + NGHTTP2_FLAG_NONE, 1); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_frame_pack_frame_hd(buf, &hd); + + CU_ASSERT(sizeof(buf) == nghttp2_session_mem_recv(session, buf, sizeof(buf))); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_extension(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_buf buf; + nghttp2_frame_hd hd; + nghttp2_mem *mem; + const char data[] = "Hello World!"; + ssize_t rv; + nghttp2_option *option; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.on_extension_chunk_recv_callback = on_extension_chunk_recv_callback; + callbacks.unpack_extension_callback = unpack_extension_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_user_recv_extension_type(option, 111); + + nghttp2_buf_init2(&ud.scratchbuf, 4096, mem); + nghttp2_buf_init2(&buf, 4096, mem); + + nghttp2_frame_hd_init(&hd, sizeof(data), 111, 0xab, 1000000007); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + buf.last = nghttp2_cpymem(buf.last, data, sizeof(data)); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&ud.recv_frame_hd, 0, 0, 0, 0); + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv); + CU_ASSERT(111 == ud.recv_frame_hd.type); + CU_ASSERT(0xab == ud.recv_frame_hd.flags); + CU_ASSERT(1000000007 == ud.recv_frame_hd.stream_id); + CU_ASSERT(0 == memcmp(data, ud.scratchbuf.pos, sizeof(data))); + + nghttp2_session_del(session); + + /* cancel in on_extension_chunk_recv_callback */ + nghttp2_buf_reset(&ud.scratchbuf); + + callbacks.on_extension_chunk_recv_callback = + cancel_on_extension_chunk_recv_callback; + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* cancel in unpack_extension_callback */ + nghttp2_buf_reset(&ud.scratchbuf); + + callbacks.on_extension_chunk_recv_callback = on_extension_chunk_recv_callback; + callbacks.unpack_extension_callback = cancel_unpack_extension_callback; + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + nghttp2_buf_free(&buf, mem); + nghttp2_buf_free(&ud.scratchbuf, mem); + + nghttp2_option_del(option); +} + +void test_nghttp2_session_recv_altsvc(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_buf buf; + nghttp2_frame_hd hd; + nghttp2_mem *mem; + ssize_t rv; + nghttp2_option *option; + static const uint8_t origin[] = "nghttp2.org"; + static const uint8_t field_value[] = "h2=\":443\""; + + mem = nghttp2_mem_default(); + + nghttp2_buf_init2(&buf, NGHTTP2_FRAME_HDLEN + NGHTTP2_MAX_FRAME_SIZE_MIN, + mem); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 + sizeof(field_value) - 1, + NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ALTSVC == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags); + CU_ASSERT(0 == ud.recv_frame_hd.stream_id); + + nghttp2_session_del(session); + + /* size of origin is larger than frame length */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 - 1, NGHTTP2_ALTSVC, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1 - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* zero-length value */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1, NGHTTP2_ALTSVC, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + + ud.invalid_frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(1 == ud.invalid_frame_recv_cb_called); + + nghttp2_session_del(session); + + /* non-empty origin to a stream other than 0 */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + open_sent_stream(session, 1); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 + sizeof(field_value) - 1, + NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 1); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1); + + ud.invalid_frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(1 == ud.invalid_frame_recv_cb_called); + + nghttp2_session_del(session); + + /* empty origin to stream 0 */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(field_value) - 1, NGHTTP2_ALTSVC, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, 0); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1); + + ud.invalid_frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(1 == ud.invalid_frame_recv_cb_called); + + nghttp2_session_del(session); + + /* send large frame (16KiB) */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_FRAME_SIZE_MIN, NGHTTP2_ALTSVC, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + memset(buf.last, 0, nghttp2_buf_avail(&buf)); + buf.last += nghttp2_buf_avail(&buf); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ALTSVC == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_MAX_FRAME_SIZE_MIN == ud.recv_frame_hd.length); + + nghttp2_session_del(session); + + /* send too large frame */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + session->local_settings.max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MIN - 1; + + nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_FRAME_SIZE_MIN + 1, NGHTTP2_ALTSVC, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + memset(buf.last, 0, nghttp2_buf_avail(&buf)); + buf.last += nghttp2_buf_avail(&buf); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* received by server */ + nghttp2_buf_reset(&buf); + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 + sizeof(field_value) - 1, + NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + nghttp2_buf_free(&buf, mem); + nghttp2_option_del(option); +} + +void test_nghttp2_session_recv_origin(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_bufs bufs; + ssize_t rv; + nghttp2_option *option; + nghttp2_extension frame; + nghttp2_ext_origin origin; + nghttp2_origin_entry ov; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + + frame_pack_bufs_init(&bufs); + + frame.payload = &origin; + + ov.origin = (uint8_t *)nghttp2; + ov.origin_len = sizeof(nghttp2) - 1; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ORIGIN); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ORIGIN == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags); + CU_ASSERT(0 == ud.recv_frame_hd.stream_id); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* The length of origin is larger than payload length. */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + nghttp2_put_uint16be(bufs.head->buf.pos + NGHTTP2_FRAME_HDLEN, + (uint16_t)sizeof(nghttp2)); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* A frame should be ignored if it is sent to a stream other than + stream 0. */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + frame.hd.stream_id = 1; + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* A frame should be ignored if the reserved flag is set */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + frame.hd.flags = 0xf0; + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* A frame should be ignored if it is received by a server. */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* Receiving empty ORIGIN frame */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, NULL, 0); + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ORIGIN == ud.recv_frame_hd.type); + + nghttp2_session_del(session); + + nghttp2_option_del(option); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_recv_priority_update(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_bufs bufs; + ssize_t rv; + nghttp2_option *option; + nghttp2_extension frame; + nghttp2_ext_priority_update priority_update; + nghttp2_stream *stream; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + uint8_t large_field_value[sizeof(session->iframe.raw_sbuf) + 1]; + nghttp2_outbound_item *item; + size_t i; + int32_t stream_id; + static const uint8_t field_value[] = "u=2,i"; + + mem = nghttp2_mem_default(); + + memset(large_field_value, ' ', sizeof(large_field_value)); + memcpy(large_field_value, field_value, sizeof(field_value) - 1); + + frame_pack_bufs_init(&bufs); + + frame.payload = &priority_update; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, + NGHTTP2_PRIORITY_UPDATE); + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + session->pending_no_rfc7540_priorities = 1; + + nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value, + sizeof(field_value) - 1); + + nghttp2_frame_pack_priority_update(&bufs, &frame); + + open_recv_stream(session, 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags); + CU_ASSERT(0 == ud.recv_frame_hd.stream_id); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* Check that priority which is received in idle state is + retained. */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + session->pending_no_rfc7540_priorities = 1; + + nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value, + sizeof(field_value) - 1); + + nghttp2_frame_pack_priority_update(&bufs, &frame); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags); + CU_ASSERT(0 == ud.recv_frame_hd.stream_id); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_bufs_reset(&bufs); + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* PRIORITY_UPDATE with too large field_value is discarded */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + session->pending_no_rfc7540_priorities = 1; + + nghttp2_frame_priority_update_init(&frame, 1, large_field_value, + sizeof(large_field_value)); + + nghttp2_frame_pack_priority_update(&bufs, &frame); + + open_recv_stream(session, 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == stream->extpri); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* Connection error if client receives PRIORITY_UPDATE. */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + session->pending_no_rfc7540_priorities = 1; + + nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value, + sizeof(field_value) - 1); + + nghttp2_frame_pack_priority_update(&bufs, &frame); + + open_sent_stream(session, 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* The number of idle streams exceeds the maximum. */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + session->pending_no_rfc7540_priorities = 1; + session->local_settings.max_concurrent_streams = 100; + + for (i = 0; i < 101; ++i) { + stream_id = (int32_t)(i * 2 + 1); + nghttp2_frame_priority_update_init( + &frame, stream_id, (uint8_t *)field_value, sizeof(field_value) - 1); + + nghttp2_frame_pack_priority_update(&bufs, &frame); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + if (i < 100) { + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == ud.recv_frame_hd.type); + } else { + CU_ASSERT(0 == ud.frame_recv_cb_called); + } + + nghttp2_bufs_reset(&bufs); + } + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + + nghttp2_session_del(session); + nghttp2_option_del(option); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_continue(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + const nghttp2_nv nv1[] = {MAKE_NV(":method", "GET"), MAKE_NV(":path", "/")}; + const nghttp2_nv nv2[] = {MAKE_NV("user-agent", "nghttp2/1.0.0"), + MAKE_NV("alpha", "bravo")}; + nghttp2_bufs bufs; + nghttp2_buf *buf; + size_t framelen1, framelen2; + ssize_t rv; + uint8_t buffer[4096]; + nghttp2_buf databuf; + nghttp2_frame frame; + nghttp2_nv *nva; + size_t nvlen; + const nghttp2_frame *recv_frame; + nghttp2_frame_hd data_hd; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + nghttp2_buf_wrap_init(&databuf, buffer, sizeof(buffer)); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_data_chunk_recv_callback = pause_on_data_chunk_recv_callback; + callbacks.on_header_callback = pause_on_header_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; + + nghttp2_session_server_new(&session, &callbacks, &user_data); + /* disable strict HTTP layering checks */ + session->opt_flags |= NGHTTP2_OPTMASK_NO_HTTP_MESSAGING; + + nghttp2_hd_deflate_init(&deflater, mem); + + /* Make 2 HEADERS frames */ + nvlen = ARRLEN(nv1); + nghttp2_nv_array_copy(&nva, nv1, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + framelen1 = nghttp2_buf_len(buf); + databuf.last = nghttp2_cpymem(databuf.last, buf->pos, nghttp2_buf_len(buf)); + + nvlen = ARRLEN(nv2); + nghttp2_nv_array_copy(&nva, nv2, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + framelen2 = nghttp2_buf_len(buf); + databuf.last = nghttp2_cpymem(databuf.last, buf->pos, nghttp2_buf_len(buf)); + + /* Receive 1st HEADERS and pause */ + user_data.begin_headers_cb_called = 0; + user_data.header_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(rv >= 0); + databuf.pos += rv; + + recv_frame = user_data.frame; + CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type); + CU_ASSERT(framelen1 - NGHTTP2_FRAME_HDLEN == recv_frame->hd.length); + + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.header_cb_called); + + CU_ASSERT(nghttp2_nv_equal(&nv1[0], &user_data.nv)); + + /* get 2nd header field */ + user_data.begin_headers_cb_called = 0; + user_data.header_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(rv >= 0); + databuf.pos += rv; + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.header_cb_called); + + CU_ASSERT(nghttp2_nv_equal(&nv1[1], &user_data.nv)); + + /* will call end_headers_callback and receive 2nd HEADERS and pause */ + user_data.begin_headers_cb_called = 0; + user_data.header_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(rv >= 0); + databuf.pos += rv; + + recv_frame = user_data.frame; + CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type); + CU_ASSERT(framelen2 - NGHTTP2_FRAME_HDLEN == recv_frame->hd.length); + + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.header_cb_called); + + CU_ASSERT(nghttp2_nv_equal(&nv2[0], &user_data.nv)); + + /* get 2nd header field */ + user_data.begin_headers_cb_called = 0; + user_data.header_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(rv >= 0); + databuf.pos += rv; + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.header_cb_called); + + CU_ASSERT(nghttp2_nv_equal(&nv2[1], &user_data.nv)); + + /* No input data, frame_recv_callback is called */ + user_data.begin_headers_cb_called = 0; + user_data.header_cb_called = 0; + user_data.frame_recv_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(rv >= 0); + databuf.pos += rv; + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == user_data.header_cb_called); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + + /* Receive DATA */ + nghttp2_frame_hd_init(&data_hd, 16, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 1); + + nghttp2_buf_reset(&databuf); + nghttp2_frame_pack_frame_hd(databuf.pos, &data_hd); + + /* Intentionally specify larger buffer size to see pause is kicked + in. */ + databuf.last = databuf.end; + + user_data.frame_recv_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(16 + NGHTTP2_FRAME_HDLEN == rv); + CU_ASSERT(0 == user_data.frame_recv_cb_called); + + /* Next nghttp2_session_mem_recv invokes on_frame_recv_callback and + pause again in on_data_chunk_recv_callback since we pass same + DATA frame. */ + user_data.frame_recv_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + CU_ASSERT(16 + NGHTTP2_FRAME_HDLEN == rv); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + + /* And finally call on_frame_recv_callback with 0 size input */ + user_data.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, NULL, 0); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_add_frame(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + accumulator acc; + my_user_data user_data; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + + acc.length = 0; + user_data.acc = &acc; + + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &user_data)); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + + nghttp2_frame_headers_init( + &frame->headers, NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + (int32_t)session->next_stream_id, NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen); + + session->next_stream_id += 2; + + CU_ASSERT(0 == nghttp2_session_add_item(session, item)); + CU_ASSERT(NULL != nghttp2_outbound_queue_top(&session->ob_syn)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_HEADERS == acc.buf[3]); + CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY) == acc.buf[4]); + /* check stream id */ + CU_ASSERT(1 == nghttp2_get_uint32(&acc.buf[5])); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_request_headers_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream; + int32_t stream_id = 1; + nghttp2_nv malformed_nva[] = {MAKE_NV(":path", "\x01")}; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_priority_spec pri_spec; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &user_data); + + nghttp2_priority_spec_init(&pri_spec, 0, 255, 0); + + nghttp2_frame_headers_init( + &frame.headers, NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + stream_id, NGHTTP2_HCAT_REQUEST, &pri_spec, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.begin_headers_cb_called); + stream = nghttp2_session_get_stream(session, stream_id); + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + CU_ASSERT(255 == stream->weight); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* More than un-ACKed max concurrent streams leads REFUSED_STREAM */ + session->pending_local_max_concurrent_stream = 1; + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 3, NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + + nghttp2_frame_headers_free(&frame.headers, mem); + session->local_settings.max_concurrent_streams = + NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS; + + /* Stream ID less than or equal to the previously received request + HEADERS is just ignored due to race condition */ + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 3, NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* Stream ID is our side and it is idle stream ID, then treat it as + connection error */ + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 2, NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + /* Check malformed headers. The library accept it. */ + nghttp2_session_server_new(&session, &callbacks, &user_data); + + nvlen = ARRLEN(malformed_nva); + nghttp2_nv_array_copy(&nva, malformed_nva, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 1, NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + /* Check client side */ + nghttp2_session_client_new(&session, &callbacks, &user_data); + + /* Receiving peer's idle stream ID is subject to connection error */ + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + /* Receiving our's idle stream ID is subject to connection error */ + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + session->next_stream_id = 5; + session->last_sent_stream_id = 3; + + /* Stream ID which is not idle and not in stream map is just + ignored */ + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, &user_data); + + /* Stream ID which is equal to local_last_stream_id is ok. */ + session->local_last_stream_id = 3; + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session, &frame)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* If GOAWAY has been sent, new stream is ignored */ + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 5, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + session->goaway_flags |= NGHTTP2_GOAWAY_SENT; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, &user_data); + + /* HEADERS to closed stream */ + stream = open_recv_stream(session, 1); + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR); + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_response_headers_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + stream = open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_response_headers_received(session, &frame, + stream)); + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + + nghttp2_frame_headers_free(&frame.headers, mem); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_headers_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + stream = open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENED); + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_headers_received(session, &frame, stream)); + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + + /* stream closed */ + frame.hd.flags |= NGHTTP2_FLAG_END_STREAM; + + CU_ASSERT(0 == nghttp2_session_on_headers_received(session, &frame, stream)); + CU_ASSERT(2 == user_data.begin_headers_cb_called); + + /* Check to see when NGHTTP2_STREAM_CLOSING, incoming HEADERS is + discarded. */ + stream = open_sent_stream2(session, 3, NGHTTP2_STREAM_CLOSING); + frame.hd.stream_id = 3; + frame.hd.flags = NGHTTP2_FLAG_END_HEADERS; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_headers_received(session, &frame, stream)); + /* See no counters are updated */ + CU_ASSERT(2 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + + /* Server initiated stream */ + stream = open_recv_stream(session, 2); + + frame.hd.flags = NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM; + frame.hd.stream_id = 2; + + CU_ASSERT(0 == nghttp2_session_on_headers_received(session, &frame, stream)); + CU_ASSERT(3 == user_data.begin_headers_cb_called); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + + /* Further reception of HEADERS is subject to stream error */ + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_headers_received(session, &frame, stream)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_push_response_headers_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + stream = open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + /* nghttp2_session_on_push_response_headers_received assumes + stream's state is NGHTTP2_STREAM_RESERVED and session->server is + 0. */ + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + CU_ASSERT(1 == session->num_incoming_reserved_streams); + CU_ASSERT(0 == nghttp2_session_on_push_response_headers_received( + session, &frame, stream)); + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == session->num_incoming_reserved_streams); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(0 == (stream->flags & NGHTTP2_STREAM_FLAG_PUSH)); + + /* If un-ACKed max concurrent streams limit is exceeded, + RST_STREAMed */ + session->pending_local_max_concurrent_stream = 1; + stream = open_recv_stream2(session, 4, NGHTTP2_STREAM_RESERVED); + frame.hd.stream_id = 4; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_response_headers_received(session, &frame, + stream)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_REFUSED_STREAM == item->frame.rst_stream.error_code); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == session->num_incoming_streams); + + /* If ACKed max concurrent streams limit is exceeded, GOAWAY is + issued */ + session->local_settings.max_concurrent_streams = 1; + + stream = open_recv_stream2(session, 6, NGHTTP2_STREAM_RESERVED); + frame.hd.stream_id = 6; + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_response_headers_received(session, &frame, + stream)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + + nghttp2_frame_headers_free(&frame.headers, mem); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_priority_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream, *dep_stream; + nghttp2_priority_spec pri_spec; + nghttp2_outbound_item *item; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &user_data); + stream = open_recv_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 0, 2, 0); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + /* depend on stream 0 */ + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + CU_ASSERT(2 == stream->weight); + + stream = open_sent_stream(session, 2); + dep_stream = open_recv_stream(session, 3); + + frame.hd.stream_id = 2; + + /* using dependency stream */ + nghttp2_priority_spec_init(&frame.priority.pri_spec, 3, 1, 0); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + CU_ASSERT(dep_stream == stream->dep_prev); + + /* PRIORITY against idle stream */ + + frame.hd.stream_id = 100; + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + stream = nghttp2_session_get_stream_raw(session, frame.hd.stream_id); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(dep_stream == stream->dep_prev); + + nghttp2_frame_priority_free(&frame.priority); + nghttp2_session_del(session); + + /* Check dep_stream_id == stream_id case */ + nghttp2_session_server_new(&session, &callbacks, &user_data); + open_recv_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 1, 0, 0); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_frame_priority_free(&frame.priority); + nghttp2_session_del(session); + + /* Check again dep_stream_id == stream_id, and stream_id is idle */ + nghttp2_session_server_new(&session, &callbacks, &user_data); + + nghttp2_priority_spec_init(&pri_spec, 1, 16, 0); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_frame_priority_free(&frame.priority); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_rst_stream_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + nghttp2_session_server_new(&session, &callbacks, &user_data); + open_recv_stream(session, 1); + + nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_PROTOCOL_ERROR); + + CU_ASSERT(0 == nghttp2_session_on_rst_stream_received(session, &frame)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1)); + + nghttp2_frame_rst_stream_free(&frame.rst_stream); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_settings_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_stream *stream1, *stream2; + nghttp2_frame frame; + const size_t niv = 5; + nghttp2_settings_entry iv[255]; + nghttp2_outbound_item *item; + nghttp2_nv nv = MAKE_NV(":authority", "example.org"); + nghttp2_mem *mem; + nghttp2_option *option; + uint8_t data[2048]; + nghttp2_frame_hd hd; + int rv; + ssize_t nread; + nghttp2_stream *stream; + + mem = nghttp2_mem_default(); + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 50; + + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[1].value = 1000000009; + + iv[2].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[2].value = 64 * 1024; + + iv[3].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[3].value = 1024; + + iv[4].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[4].value = 0; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + session->remote_settings.initial_window_size = 16 * 1024; + + stream1 = open_sent_stream(session, 1); + stream2 = open_recv_stream(session, 2); + + /* Set window size for each streams and will see how settings + updates these values */ + stream1->remote_window_size = 16 * 1024; + stream2->remote_window_size = -48 * 1024; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, + dup_iv(iv, niv), niv); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + CU_ASSERT(1000000009 == session->remote_settings.max_concurrent_streams); + CU_ASSERT(64 * 1024 == session->remote_settings.initial_window_size); + CU_ASSERT(1024 == session->remote_settings.header_table_size); + CU_ASSERT(0 == session->remote_settings.enable_push); + + CU_ASSERT(64 * 1024 == stream1->remote_window_size); + CU_ASSERT(0 == stream2->remote_window_size); + + frame.settings.iv[2].value = 16 * 1024; + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + CU_ASSERT(16 * 1024 == stream1->remote_window_size); + CU_ASSERT(-48 * 1024 == stream2->remote_window_size); + + CU_ASSERT(16 * 1024 == nghttp2_session_get_stream_remote_window_size( + session, stream1->stream_id)); + CU_ASSERT(0 == nghttp2_session_get_stream_remote_window_size( + session, stream2->stream_id)); + + nghttp2_frame_settings_free(&frame.settings, mem); + + nghttp2_session_del(session); + + /* Check ACK with niv > 0 */ + nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, dup_iv(iv, 1), + 1); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_frame_settings_free(&frame.settings, mem); + nghttp2_session_del(session); + + /* Check ACK against no inflight SETTINGS */ + nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_frame_settings_free(&frame.settings, mem); + nghttp2_session_del(session); + + /* Check that 2 SETTINGS_HEADER_TABLE_SIZE 0 and 4096 are included + and header table size is once cleared to 0. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL); + + nghttp2_session_send(session); + + CU_ASSERT(session->hd_deflater.ctx.hd_table.len > 0); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 0; + + iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[1].value = 2048; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2), + 2); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + CU_ASSERT(0 == session->hd_deflater.ctx.hd_table.len); + CU_ASSERT(2048 == session->hd_deflater.ctx.hd_table_bufsize_max); + CU_ASSERT(2048 == session->remote_settings.header_table_size); + + nghttp2_frame_settings_free(&frame.settings, mem); + nghttp2_session_del(session); + + /* Check that remote SETTINGS_MAX_CONCURRENT_STREAMS is set to a value set by + nghttp2_option_set_peer_max_concurrent_streams() and reset to the default + value (unlimited) after receiving initial SETTINGS frame from the peer. */ + nghttp2_option_new(&option); + nghttp2_option_set_peer_max_concurrent_streams(option, 1000); + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + CU_ASSERT(1000 == session->remote_settings.max_concurrent_streams); + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + CU_ASSERT(NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS == + session->remote_settings.max_concurrent_streams); + + nghttp2_frame_settings_free(&frame.settings, mem); + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* Check too large SETTINGS_MAX_FRAME_SIZE */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 1), + 1); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_frame_settings_free(&frame.settings, mem); + nghttp2_session_del(session); + + /* Check the case where stream window size overflows */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream1 = open_recv_stream(session, 1); + + /* This will increment window size by 1 */ + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1, + 1); + + CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); + + nghttp2_frame_window_update_free(&frame.window_update); + + iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[0].value = NGHTTP2_MAX_WINDOW_SIZE; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 1), + 1); + + /* Now window size gets NGHTTP2_MAX_WINDOW_SIZE + 1, which is + unacceptable situation in protocol spec. */ + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + nghttp2_frame_settings_free(&frame.settings, mem); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_SETTINGS == item->frame.hd.type); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_STREAM_CLOSING == stream1->state); + + nghttp2_session_del(session); + + /* It is invalid that peer disables ENABLE_CONNECT_PROTOCOL once it + has been enabled. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + session->remote_settings.enable_connect_protocol = 1; + + iv[0].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL; + iv[0].value = 0; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 1), + 1); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + nghttp2_frame_settings_free(&frame.settings, mem); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); + + /* Should send WINDOW_UPDATE with no_auto_window_update option on if + the initial window size is decreased and becomes smaller than or + equal to the amount of data that has already received. */ + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_window_update(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[0].value = 1024; + + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = open_recv_stream(session, 1); + + memset(data, 0, sizeof(data)); + hd.length = 1024; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_NONE; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + + nread = + nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + hd.length); + + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length) == nread); + + rv = nghttp2_session_consume(session, 1, hd.length); + + CU_ASSERT(0 == rv); + CU_ASSERT((int32_t)hd.length == stream->recv_window_size); + CU_ASSERT((int32_t)hd.length == stream->consumed_size); + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + + rv = nghttp2_session_on_settings_received(session, &frame, 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(1024 == stream->local_window_size); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(0 == stream->consumed_size); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT((int32_t)hd.length == + item->frame.window_update.window_size_increment); + + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* It is invalid to change SETTINGS_NO_RFC7540_PRIORITIES in the + following SETTINGS. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + session->remote_settings.no_rfc7540_priorities = 1; + + iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv[0].value = 0; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 1), + 1); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + nghttp2_frame_settings_free(&frame.settings, mem); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_push_promise_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream, *promised_stream; + nghttp2_outbound_item *item; + nghttp2_nv malformed_nva[] = {MAKE_NV(":path", "\x01")}; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_mem *mem; + nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + stream = open_sent_stream(session, 1); + + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 2, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + promised_stream = nghttp2_session_get_stream(session, 2); + CU_ASSERT(NGHTTP2_STREAM_RESERVED == promised_stream->state); + CU_ASSERT(2 == session->last_recv_stream_id); + + /* Attempt to PUSH_PROMISE against half close (remote) */ + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + frame.push_promise.promised_stream_id = 4; + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 4)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_STREAM_CLOSED == item->frame.goaway.error_code); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(4 == session->last_recv_stream_id); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + stream = open_sent_stream(session, 1); + + /* Attempt to PUSH_PROMISE against stream in closing state */ + stream->state = NGHTTP2_STREAM_CLOSING; + frame.push_promise.promised_stream_id = 6; + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 6)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(6 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code); + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Attempt to PUSH_PROMISE against idle stream */ + frame.hd.stream_id = 3; + frame.push_promise.promised_stream_id = 8; + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(0 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + stream = open_sent_stream(session, 1); + + /* Same ID twice */ + frame.hd.stream_id = 1; + frame.push_promise.promised_stream_id = 2; + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 2)); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* After GOAWAY, PUSH_PROMISE will be discarded */ + frame.push_promise.promised_stream_id = 10; + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 10)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + /* Attempt to PUSH_PROMISE against reserved (remote) stream */ + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 2, 4, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); + + /* Disable PUSH */ + nghttp2_session_client_new(&session, &callbacks, &user_data); + + open_sent_stream(session, 1); + + session->local_settings.enable_push = 0; + + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 2, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(0 == session->num_incoming_reserved_streams); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); + + /* Check malformed headers. We accept malformed headers */ + nghttp2_session_client_new(&session, &callbacks, &user_data); + + open_sent_stream(session, 1); + + nvlen = ARRLEN(malformed_nva); + nghttp2_nv_array_copy(&nva, malformed_nva, nvlen, mem); + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 2, nva, nvlen); + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); + + /* If local_settings.enable_push = 0 is pending, but not acked from + peer, incoming PUSH_PROMISE is rejected */ + nghttp2_session_client_new(&session, &callbacks, &user_data); + + open_sent_stream(session, 1); + + /* Submit settings with ENABLE_PUSH = 0 (thus disabling push) */ + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 2, NULL, 0); + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == session->num_incoming_reserved_streams); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); + + /* Check max_incoming_reserved_streams */ + nghttp2_session_client_new(&session, &callbacks, &user_data); + session->max_incoming_reserved_streams = 1; + + open_sent_stream(session, 1); + open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + CU_ASSERT(1 == session->num_incoming_reserved_streams); + + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 4, NULL, 0); + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(1 == session->num_incoming_reserved_streams); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_ping_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_outbound_item *top; + const uint8_t opaque_data[] = "01234567"; + nghttp2_option *option; + + user_data.frame_recv_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_ACK, opaque_data); + + CU_ASSERT(0 == nghttp2_session_on_ping_received(session, &frame)); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + + /* Since this ping frame has ACK flag set, no further action is + performed. */ + CU_ASSERT(NULL == nghttp2_outbound_queue_top(&session->ob_urgent)); + + /* Clear the flag, and receive it again */ + frame.hd.flags = NGHTTP2_FLAG_NONE; + + CU_ASSERT(0 == nghttp2_session_on_ping_received(session, &frame)); + CU_ASSERT(2 == user_data.frame_recv_cb_called); + top = nghttp2_outbound_queue_top(&session->ob_urgent); + CU_ASSERT(NGHTTP2_PING == top->frame.hd.type); + CU_ASSERT(NGHTTP2_FLAG_ACK == top->frame.hd.flags); + CU_ASSERT(memcmp(opaque_data, top->frame.ping.opaque_data, 8) == 0); + + nghttp2_frame_ping_free(&frame.ping); + nghttp2_session_del(session); + + /* Use nghttp2_option_set_no_auto_ping_ack() */ + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_ping_ack(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, &user_data, option); + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL); + + user_data.frame_recv_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_ping_received(session, &frame)); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(NULL == nghttp2_outbound_queue_top(&session->ob_urgent)); + + nghttp2_frame_ping_free(&frame.ping); + nghttp2_session_del(session); + nghttp2_option_del(option); +} + +void test_nghttp2_session_on_goaway_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + int i; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + user_data.frame_recv_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + callbacks.on_stream_close_callback = on_stream_close_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + for (i = 1; i <= 7; ++i) { + if (nghttp2_session_is_my_stream_id(session, i)) { + open_sent_stream(session, i); + } else { + open_recv_stream(session, i); + } + } + + nghttp2_frame_goaway_init(&frame.goaway, 3, NGHTTP2_PROTOCOL_ERROR, NULL, 0); + + user_data.stream_close_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_goaway_received(session, &frame)); + + CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(3 == session->remote_last_stream_id); + /* on_stream_close should be callsed for 2 times (stream 5 and 7) */ + CU_ASSERT(2 == user_data.stream_close_cb_called); + + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 1)); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 2)); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 3)); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 4)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 5)); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 6)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 7)); + + nghttp2_frame_goaway_free(&frame.goaway, mem); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_window_update_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream; + nghttp2_outbound_item *data_item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + user_data.frame_recv_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + stream = open_sent_stream(session, 1); + + data_item = create_data_ob_item(mem); + + CU_ASSERT(0 == nghttp2_stream_attach_item(stream, data_item)); + + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1, + 16 * 1024); + + CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 16 * 1024 == + stream->remote_window_size); + + CU_ASSERT(0 == nghttp2_stream_defer_item( + stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL)); + + CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); + CU_ASSERT(2 == user_data.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 16 * 1024 * 2 == + stream->remote_window_size); + CU_ASSERT(0 == (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL)); + + nghttp2_frame_window_update_free(&frame.window_update); + + /* Receiving WINDOW_UPDATE on reserved (remote) stream is a + connection error */ + open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 2, + 4096); + + CU_ASSERT(!(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); + CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND); + + nghttp2_frame_window_update_free(&frame.window_update); + + nghttp2_session_del(session); + + /* Receiving WINDOW_UPDATE on reserved (local) stream is allowed */ + nghttp2_session_server_new(&session, &callbacks, &user_data); + + stream = open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 2, + 4096); + + CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); + CU_ASSERT(!(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 4096 == stream->remote_window_size); + + nghttp2_frame_window_update_free(&frame.window_update); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_data_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_outbound_item *top; + nghttp2_stream *stream; + nghttp2_frame frame; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + stream = open_recv_stream(session, 2); + + nghttp2_frame_hd_init(&frame.hd, 4096, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 2); + + CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame)); + CU_ASSERT(0 == stream->shut_flags); + + frame.hd.flags = NGHTTP2_FLAG_END_STREAM; + + CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame)); + CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags); + + /* If NGHTTP2_STREAM_CLOSING state, DATA frame is discarded. */ + open_sent_stream2(session, 1, NGHTTP2_STREAM_CLOSING); + + frame.hd.flags = NGHTTP2_FLAG_NONE; + frame.hd.stream_id = 1; + + CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame)); + CU_ASSERT(NULL == nghttp2_outbound_queue_top(&session->ob_reg)); + + /* Check INVALID_STREAM case: DATA frame with stream ID which does + not exist. */ + + frame.hd.stream_id = 3; + + CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame)); + top = nghttp2_outbound_queue_top(&session->ob_reg); + /* DATA against nonexistent stream is just ignored for now. */ + CU_ASSERT(top == NULL); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_data_received_fail_fast(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t buf[9]; + nghttp2_stream *stream; + nghttp2_frame_hd hd; + nghttp2_outbound_item *item; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 1); + nghttp2_frame_pack_frame_hd(buf, &hd); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* DATA to closed (remote) */ + stream = open_recv_stream(session, 1); + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + + CU_ASSERT((ssize_t)sizeof(buf) == + nghttp2_session_mem_recv(session, buf, sizeof(buf))); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* DATA to closed stream with explicit closed (remote) */ + stream = open_recv_stream(session, 1); + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR); + + CU_ASSERT((ssize_t)sizeof(buf) == + nghttp2_session_mem_recv(session, buf, sizeof(buf))); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_altsvc_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_frame frame; + nghttp2_option *option; + uint8_t origin[] = "nghttp2.org"; + uint8_t field_value[] = "h2=\":443\""; + int rv; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + /* We just pass the strings without making a copy. This is OK, + since we never call nghttp2_frame_altsvc_free(). */ + nghttp2_frame_altsvc_init(&frame.ext, 0, origin, sizeof(origin) - 1, + field_value, sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_altsvc_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* Receiving empty origin with stream ID == 0 */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + nghttp2_frame_altsvc_init(&frame.ext, 0, origin, 0, field_value, + sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_altsvc_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* Receiving non-empty origin with stream ID != 0 */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + open_sent_stream(session, 1); + + nghttp2_frame_altsvc_init(&frame.ext, 1, origin, sizeof(origin) - 1, + field_value, sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_altsvc_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* Receiving empty origin with stream ID != 0; this is OK */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + open_sent_stream(session, 1); + + nghttp2_frame_altsvc_init(&frame.ext, 1, origin, 0, field_value, + sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_altsvc_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* Stream does not exist; ALTSVC will be ignored. */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + nghttp2_frame_altsvc_init(&frame.ext, 1, origin, 0, field_value, + sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_altsvc_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + nghttp2_option_del(option); +} + +void test_nghttp2_session_send_headers_start_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, + (int32_t)session->next_stream_id, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + session->next_stream_id += 2; + + nghttp2_session_add_item(session, item); + CU_ASSERT(0 == nghttp2_session_send(session)); + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_headers_reply(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL)); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + nghttp2_session_add_item(session, item); + CU_ASSERT(0 == nghttp2_session_send(session)); + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_headers_frame_size_error(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_nv *nva; + size_t nvlen; + size_t vallen = NGHTTP2_HD_MAX_NV; + nghttp2_nv nv[28]; + size_t nnv = ARRLEN(nv); + size_t i; + my_user_data ud; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + for (i = 0; i < nnv; ++i) { + nv[i].name = (uint8_t *)"header"; + nv[i].namelen = strlen((const char *)nv[i].name); + nv[i].value = mem->malloc(vallen + 1, NULL); + memset(nv[i].value, '0' + (int)i, vallen); + nv[i].value[vallen] = '\0'; + nv[i].valuelen = vallen; + nv[i].flags = NGHTTP2_NV_FLAG_NONE; + } + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + nvlen = nnv; + nghttp2_nv_array_copy(&nva, nv, nvlen, mem); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, + (int32_t)session->next_stream_id, + NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen); + + session->next_stream_id += 2; + + nghttp2_session_add_item(session, item); + + ud.frame_not_send_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_FRAME_SIZE_ERROR == ud.not_sent_error); + + for (i = 0; i < nnv; ++i) { + mem->free(nv[i].value, NULL); + } + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_headers_push_reply(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL)); + open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + nghttp2_session_add_item(session, item); + CU_ASSERT(0 == session->num_outgoing_streams); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == session->num_outgoing_streams); + stream = nghttp2_session_get_stream(session, 2); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + CU_ASSERT(0 == (stream->flags & NGHTTP2_STREAM_FLAG_PUSH)); + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_rst_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + nghttp2_session_client_new(&session, &callbacks, &user_data); + open_sent_stream(session, 1); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_rst_stream_init(&frame->rst_stream, 1, NGHTTP2_PROTOCOL_ERROR); + nghttp2_session_add_item(session, item); + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_push_promise(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_stream *stream; + nghttp2_settings_entry iv; + my_user_data ud; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + open_recv_stream(session, 1); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_push_promise_init(&frame->push_promise, + NGHTTP2_FLAG_END_HEADERS, 1, + (int32_t)session->next_stream_id, NULL, 0); + + session->next_stream_id += 2; + + nghttp2_session_add_item(session, item); + + CU_ASSERT(0 == nghttp2_session_send(session)); + stream = nghttp2_session_get_stream(session, 2); + CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state); + + /* Received ENABLE_PUSH = 0 */ + iv.settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv.value = 0; + frame = mem->malloc(sizeof(nghttp2_frame), NULL); + nghttp2_frame_settings_init(&frame->settings, NGHTTP2_FLAG_NONE, + dup_iv(&iv, 1), 1); + nghttp2_session_on_settings_received(session, frame, 1); + nghttp2_frame_settings_free(&frame->settings, mem); + mem->free(frame, NULL); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_push_promise_init(&frame->push_promise, + NGHTTP2_FLAG_END_HEADERS, 1, -1, NULL, 0); + nghttp2_session_add_item(session, item); + + ud.frame_not_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_PUSH_DISABLED == ud.not_sent_error); + + nghttp2_session_del(session); + + /* PUSH_PROMISE from client is error */ + nghttp2_session_client_new(&session, &callbacks, &ud); + open_sent_stream(session, 1); + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_push_promise_init(&frame->push_promise, + NGHTTP2_FLAG_END_HEADERS, 1, -1, NULL, 0); + nghttp2_session_add_item(session, item); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 3)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_is_my_stream_id(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + nghttp2_session_server_new(&session, &callbacks, NULL); + + CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 0)); + CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 1)); + CU_ASSERT(1 == nghttp2_session_is_my_stream_id(session, 2)); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, NULL); + + CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 0)); + CU_ASSERT(1 == nghttp2_session_is_my_stream_id(session, 1)); + CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 2)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_upgrade2(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t settings_payload[128]; + size_t settings_payloadlen; + nghttp2_settings_entry iv[16]; + nghttp2_stream *stream; + nghttp2_outbound_item *item; + ssize_t rv; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 1; + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 4095; + settings_payloadlen = (size_t)nghttp2_pack_settings_payload( + settings_payload, sizeof(settings_payload), iv, 2); + + /* Check client side */ + nghttp2_session_client_new(&session, &callbacks, NULL); + CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, &callbacks)); + CU_ASSERT(1 == session->last_sent_stream_id); + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(stream != NULL); + CU_ASSERT(&callbacks == stream->stream_user_data); + CU_ASSERT(NGHTTP2_SHUT_WR == stream->shut_flags); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_SETTINGS == item->frame.hd.type); + CU_ASSERT(2 == item->frame.settings.niv); + CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == + item->frame.settings.iv[0].settings_id); + CU_ASSERT(1 == item->frame.settings.iv[0].value); + CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE == + item->frame.settings.iv[1].settings_id); + CU_ASSERT(4095 == item->frame.settings.iv[1].value); + + /* Call nghttp2_session_upgrade2() again is error */ + CU_ASSERT(NGHTTP2_ERR_PROTO == + nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, &callbacks)); + nghttp2_session_del(session); + + /* Make sure that response from server can be received */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, &callbacks)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + + nghttp2_hd_deflate_init(&deflater, mem); + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, resnv, + ARRLEN(resnv), mem); + + CU_ASSERT(0 == rv); + + buf = &bufs.head->buf; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(rv == (ssize_t)nghttp2_buf_len(buf)); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_bufs_reset(&bufs); + + /* Check server side */ + nghttp2_session_server_new(&session, &callbacks, NULL); + CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, &callbacks)); + CU_ASSERT(1 == session->last_recv_stream_id); + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(stream != NULL); + CU_ASSERT(NULL == stream->stream_user_data); + CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(1 == session->remote_settings.max_concurrent_streams); + CU_ASSERT(4095 == session->remote_settings.initial_window_size); + /* Call nghttp2_session_upgrade2() again is error */ + CU_ASSERT(NGHTTP2_ERR_PROTO == + nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, &callbacks)); + nghttp2_session_del(session); + + /* Empty SETTINGS is OK */ + settings_payloadlen = (size_t)nghttp2_pack_settings_payload( + settings_payload, sizeof(settings_payload), NULL, 0); + + nghttp2_session_client_new(&session, &callbacks, NULL); + CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, NULL)); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_reprioritize_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_stream *dep_stream; + nghttp2_priority_spec pri_spec; + int rv; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream = open_recv_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 0, 10, 0); + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(10 == stream->weight); + CU_ASSERT(&session->root == stream->dep_prev); + + /* If dependency to idle stream which is not in dependency tree yet */ + + nghttp2_priority_spec_init(&pri_spec, 3, 99, 0); + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(99 == stream->weight); + CU_ASSERT(3 == stream->dep_prev->stream_id); + + dep_stream = nghttp2_session_get_stream_raw(session, 3); + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == dep_stream->weight); + + dep_stream = open_recv_stream(session, 3); + + /* Change weight */ + pri_spec.weight = 128; + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(128 == stream->weight); + CU_ASSERT(dep_stream == stream->dep_prev); + + /* Change weight again to test short-path case */ + pri_spec.weight = 100; + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(100 == stream->weight); + CU_ASSERT(dep_stream == stream->dep_prev); + CU_ASSERT(100 == dep_stream->sum_dep_weight); + + /* Test circular dependency; stream 1 is first removed and becomes + root. Then stream 3 depends on it. */ + nghttp2_priority_spec_init(&pri_spec, 1, 1, 0); + + rv = nghttp2_session_reprioritize_stream(session, dep_stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == dep_stream->weight); + CU_ASSERT(stream == dep_stream->dep_prev); + + /* Making priority to closed stream will result in default + priority */ + session->last_recv_stream_id = 9; + + nghttp2_priority_spec_init(&pri_spec, 5, 5, 0); + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* circular dependency; in case of stream which is not a direct + descendant of root. Use exclusive dependency. */ + stream = open_recv_stream(session, 1); + stream = open_recv_stream_with_dep(session, 3, stream); + stream = open_recv_stream_with_dep(session, 5, stream); + stream = open_recv_stream_with_dep(session, 7, stream); + open_recv_stream_with_dep(session, 9, stream); + + nghttp2_priority_spec_init(&pri_spec, 7, 1, 1); + + stream = nghttp2_session_get_stream(session, 3); + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(7 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 7); + + CU_ASSERT(1 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 9); + + CU_ASSERT(3 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 5); + + CU_ASSERT(3 == stream->dep_prev->stream_id); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* circular dependency; in case of stream which is not a direct + descendant of root. Without exclusive dependency. */ + stream = open_recv_stream(session, 1); + stream = open_recv_stream_with_dep(session, 3, stream); + stream = open_recv_stream_with_dep(session, 5, stream); + stream = open_recv_stream_with_dep(session, 7, stream); + open_recv_stream_with_dep(session, 9, stream); + + nghttp2_priority_spec_init(&pri_spec, 7, 1, 0); + + stream = nghttp2_session_get_stream(session, 3); + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(7 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 7); + + CU_ASSERT(1 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 9); + + CU_ASSERT(7 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 5); + + CU_ASSERT(3 == stream->dep_prev->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_reprioritize_stream_with_idle_stream_dep(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream = open_recv_stream(session, 1); + + session->pending_local_max_concurrent_stream = 1; + + nghttp2_priority_spec_init(&pri_spec, 101, 10, 0); + + nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + /* idle stream is not counteed to max concurrent streams */ + + CU_ASSERT(10 == stream->weight); + CU_ASSERT(101 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream_raw(session, 101); + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_frame *frame; + nghttp2_frame_hd hd; + nghttp2_active_outbound_item *aob; + nghttp2_bufs *framebufs; + nghttp2_buf *buf; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2; + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + aob = &session->aob; + framebufs = &aob->framebufs; + + open_sent_stream(session, 1); + + CU_ASSERT( + 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); + + ud.block_count = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + frame = &aob->item->frame; + + buf = &framebufs->head->buf; + nghttp2_frame_unpack_frame_hd(&hd, buf->pos); + + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags); + /* aux_data.data.flags has these flags */ + CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_data_read_length_too_large(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_frame *frame; + nghttp2_frame_hd hd; + nghttp2_active_outbound_item *aob; + nghttp2_bufs *framebufs; + nghttp2_buf *buf; + size_t payloadlen; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + callbacks.read_length_callback = too_large_data_source_length_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2; + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + aob = &session->aob; + framebufs = &aob->framebufs; + + open_sent_stream(session, 1); + + CU_ASSERT( + 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); + + ud.block_count = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + frame = &aob->item->frame; + + buf = &framebufs->head->buf; + nghttp2_frame_unpack_frame_hd(&hd, buf->pos); + + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags); + CU_ASSERT(16384 == hd.length) + /* aux_data.data.flags has these flags */ + CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags); + + nghttp2_session_del(session); + + /* Check that buffers are expanded */ + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + + ud.data_source_length = NGHTTP2_MAX_FRAME_SIZE_MAX; + + session->remote_settings.max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MAX; + + open_sent_stream(session, 1); + + CU_ASSERT( + 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); + + ud.block_count = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + + aob = &session->aob; + + frame = &aob->item->frame; + + framebufs = &aob->framebufs; + + buf = &framebufs->head->buf; + nghttp2_frame_unpack_frame_hd(&hd, buf->pos); + + payloadlen = nghttp2_min(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE, + NGHTTP2_INITIAL_WINDOW_SIZE); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 1 + payloadlen == + (size_t)nghttp2_buf_cap(buf)); + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags); + CU_ASSERT(payloadlen == hd.length); + /* aux_data.data.flags has these flags */ + CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_data_read_length_smallest(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_frame *frame; + nghttp2_frame_hd hd; + nghttp2_active_outbound_item *aob; + nghttp2_bufs *framebufs; + nghttp2_buf *buf; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + callbacks.read_length_callback = smallest_length_data_source_length_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2; + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + aob = &session->aob; + framebufs = &aob->framebufs; + + open_sent_stream(session, 1); + + CU_ASSERT( + 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); + + ud.block_count = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + frame = &aob->item->frame; + + buf = &framebufs->head->buf; + nghttp2_frame_unpack_frame_hd(&hd, buf->pos); + + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags); + CU_ASSERT(1 == hd.length) + /* aux_data.data.flags has these flags */ + CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags); + + nghttp2_session_del(session); +} + +static ssize_t submit_data_twice_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + (void)session; + (void)stream_id; + (void)buf; + (void)source; + (void)user_data; + + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + return (ssize_t)nghttp2_min(len, 16); +} + +static int submit_data_twice_on_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + static int called = 0; + int rv; + nghttp2_data_provider data_prd; + (void)user_data; + + if (called == 0) { + called = 1; + + data_prd.read_callback = submit_data_twice_data_source_read_callback; + + rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, + frame->hd.stream_id, &data_prd); + CU_ASSERT(0 == rv); + } + + return 0; +} + +void test_nghttp2_submit_data_twice(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + accumulator acc; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + callbacks.on_frame_send_callback = submit_data_twice_on_frame_send_callback; + + data_prd.read_callback = submit_data_twice_data_source_read_callback; + + acc.length = 0; + ud.acc = &acc; + + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + + open_sent_stream(session, 1); + + CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, 1, &data_prd)); + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* We should have sent 2 DATA frame with 16 bytes payload each */ + CU_ASSERT(NGHTTP2_FRAME_HDLEN * 2 + 16 * 2 == acc.length); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_request_with_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = 64 * 1024 - 1; + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), + &data_prd, NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.data_source_length); + + nghttp2_session_del(session); + + /* nghttp2_submit_request() with server session is error */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + CU_ASSERT(NGHTTP2_ERR_PROTO == nghttp2_submit_request(session, NULL, reqnv, + ARRLEN(reqnv), NULL, + NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_request_without_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + accumulator acc; + nghttp2_data_provider data_prd = {{-1}, NULL}; + nghttp2_outbound_item *item; + my_user_data ud; + nghttp2_frame frame; + nghttp2_hd_inflater inflater; + nva_out out; + nghttp2_bufs bufs; + nghttp2_mem *mem; + nghttp2_priority_spec pri_spec; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + acc.length = 0; + ud.acc = &acc; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + + nghttp2_hd_inflate_init(&inflater, mem); + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), + &data_prd, NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT(item->frame.hd.flags & NGHTTP2_FLAG_END_STREAM); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length)); + + nghttp2_bufs_add(&bufs, acc.buf, acc.length); + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem); + + CU_ASSERT(ARRLEN(reqnv) == out.nvlen); + assert_nv_equal(reqnv, out.nva, out.nvlen, mem); + nghttp2_frame_headers_free(&frame.headers, mem); + nva_out_reset(&out, mem); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + + /* Try to depend on itself is error */ + nghttp2_priority_spec_init(&pri_spec, (int32_t)session->next_stream_id, 16, + 0); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_request(session, &pri_spec, reqnv, ARRLEN(reqnv), + NULL, NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_response_with_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = 64 * 1024 - 1; + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), + &data_prd)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen); + assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.data_source_length); + + nghttp2_session_del(session); + + /* Various error cases */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + /* Calling nghttp2_submit_response() with client session is error */ + CU_ASSERT(NGHTTP2_ERR_PROTO == + nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), NULL)); + + /* Stream ID <= 0 is error */ + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_response(session, 0, resnv, ARRLEN(resnv), NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_response_without_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + accumulator acc; + nghttp2_data_provider data_prd = {{-1}, NULL}; + nghttp2_outbound_item *item; + my_user_data ud; + nghttp2_frame frame; + nghttp2_hd_inflater inflater; + nva_out out; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + acc.length = 0; + ud.acc = &acc; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + + nghttp2_hd_inflate_init(&inflater, mem); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), + &data_prd)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen); + assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT(item->frame.hd.flags & NGHTTP2_FLAG_END_STREAM); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length)); + + nghttp2_bufs_add(&bufs, acc.buf, acc.length); + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem); + + CU_ASSERT(ARRLEN(resnv) == out.nvlen); + assert_nv_equal(resnv, out.nva, out.nvlen, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame.headers, mem); + nghttp2_hd_inflate_free(&inflater); + nghttp2_session_del(session); +} + +void test_nghttp2_submit_response_push_response(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + session->goaway_flags |= NGHTTP2_GOAWAY_RECV; + + CU_ASSERT(0 == + nghttp2_submit_response(session, 2, resnv, ARRLEN(resnv), NULL)); + + ud.frame_not_send_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_not_send_cb_called); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_trailer(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + accumulator acc; + nghttp2_data_provider data_prd; + nghttp2_outbound_item *item; + my_user_data ud; + nghttp2_frame frame; + nghttp2_hd_inflater inflater; + nva_out out; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + data_prd.read_callback = no_end_stream_data_source_read_callback; + nva_out_init(&out); + acc.length = 0; + ud.acc = &acc; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + + nghttp2_hd_inflate_init(&inflater, mem); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), + &data_prd)); + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(0 == + nghttp2_submit_trailer(session, 1, trailernv, ARRLEN(trailernv))); + + session->callbacks.send_callback = accumulator_send_callback; + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT(NGHTTP2_HCAT_HEADERS == item->frame.headers.cat); + CU_ASSERT(item->frame.hd.flags & NGHTTP2_FLAG_END_STREAM); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length)); + + nghttp2_bufs_add(&bufs, acc.buf, acc.length); + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem); + + CU_ASSERT(ARRLEN(trailernv) == out.nvlen); + assert_nv_equal(trailernv, out.nva, out.nvlen, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame.headers, mem); + nghttp2_hd_inflate_free(&inflater); + nghttp2_session_del(session); + + /* Specifying stream ID <= 0 is error */ + nghttp2_session_server_new(&session, &callbacks, NULL); + open_recv_stream(session, 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_trailer(session, 0, trailernv, ARRLEN(trailernv))); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_trailer(session, -1, trailernv, ARRLEN(trailernv))); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers_start_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + CU_ASSERT(1 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, + NULL, reqnv, ARRLEN(reqnv), NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM) == + item->frame.hd.flags); + CU_ASSERT(0 == (item->frame.hd.flags & NGHTTP2_FLAG_PRIORITY)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers_reply(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, resnv, ARRLEN(resnv), NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen); + assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == + item->frame.hd.flags); + + ud.frame_send_cb_called = 0; + ud.sent_frame_type = 0; + /* The transimission will be canceled because the stream 1 is not + open. */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.frame_send_cb_called); + + stream = open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, resnv, ARRLEN(resnv), NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); + CU_ASSERT(stream->shut_flags & NGHTTP2_SHUT_WR); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers_push_reply(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_stream *stream; + int foo; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + stream = open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL, + resnv, ARRLEN(resnv), &foo)); + + ud.frame_send_cb_called = 0; + ud.sent_frame_type = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + CU_ASSERT(&foo == stream->stream_user_data); + + nghttp2_session_del(session); + + /* Sending HEADERS from client against stream in reserved state is + error */ + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL, + reqnv, ARRLEN(reqnv), NULL)); + + ud.frame_send_cb_called = 0; + ud.sent_frame_type = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.frame_send_cb_called); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + accumulator acc; + nghttp2_frame frame; + nghttp2_hd_inflater inflater; + nva_out out; + nghttp2_bufs bufs; + nghttp2_mem *mem; + nghttp2_priority_spec pri_spec; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + acc.length = 0; + ud.acc = &acc; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + + nghttp2_hd_inflate_init(&inflater, mem); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, reqnv, ARRLEN(reqnv), NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == + item->frame.hd.flags); + + ud.frame_send_cb_called = 0; + ud.sent_frame_type = 0; + /* The transimission will be canceled because the stream 1 is not + open. */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.frame_send_cb_called); + + stream = open_sent_stream(session, 1); + + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, reqnv, ARRLEN(reqnv), NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); + CU_ASSERT(stream->shut_flags & NGHTTP2_SHUT_WR); + + CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length)); + + nghttp2_bufs_add(&bufs, acc.buf, acc.length); + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem); + + CU_ASSERT(ARRLEN(reqnv) == out.nvlen); + assert_nv_equal(reqnv, out.nva, out.nvlen, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_hd_inflate_free(&inflater); + + /* Try to depend on itself */ + nghttp2_priority_spec_init(&pri_spec, 3, 16, 0); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 3, &pri_spec, + reqnv, ARRLEN(reqnv), NULL)); + + session->next_stream_id = 5; + nghttp2_priority_spec_init(&pri_spec, 5, 16, 0); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, &pri_spec, + reqnv, ARRLEN(reqnv), NULL)); + + nghttp2_session_del(session); + + /* Error cases with invalid stream ID */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* Sending nghttp2_submit_headers() with stream_id == 1 and server + session is error */ + CU_ASSERT(NGHTTP2_ERR_PROTO == + nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL, reqnv, + ARRLEN(reqnv), NULL)); + + /* Sending stream ID <= 0 is error */ + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 0, NULL, resnv, + ARRLEN(resnv), NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers_continuation(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv nv[] = { + MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""), + MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""), + MAKE_NV("h1", ""), + }; + nghttp2_outbound_item *item; + uint8_t data[4096]; + size_t i; + my_user_data ud; + + memset(data, '0', sizeof(data)); + for (i = 0; i < ARRLEN(nv); ++i) { + nv[i].valuelen = sizeof(data); + nv[i].value = data; + } + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + CU_ASSERT(1 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, + NULL, nv, ARRLEN(nv), NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == + item->frame.hd.flags); + CU_ASSERT(0 == (item->frame.hd.flags & NGHTTP2_FLAG_PRIORITY)); + + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers_continuation_extra_large(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv nv[] = { + MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""), + MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""), + }; + nghttp2_outbound_item *item; + uint8_t data[16384]; + size_t i; + my_user_data ud; + nghttp2_option *opt; + + memset(data, '0', sizeof(data)); + for (i = 0; i < ARRLEN(nv); ++i) { + nv[i].valuelen = sizeof(data); + nv[i].value = data; + } + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + /* The default size of max send header block length is too small to + send these header fields. Expand it. */ + nghttp2_option_new(&opt); + nghttp2_option_set_max_send_header_block_length(opt, 102400); + + CU_ASSERT(0 == nghttp2_session_client_new2(&session, &callbacks, &ud, opt)); + CU_ASSERT(1 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, + NULL, nv, ARRLEN(nv), NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == + item->frame.hd.flags); + CU_ASSERT(0 == (item->frame.hd.flags & NGHTTP2_FLAG_PRIORITY)); + + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_session_del(session); + nghttp2_option_del(opt); +} + +void test_nghttp2_submit_priority(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + my_user_data ud; + nghttp2_priority_spec pri_spec; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + stream = open_sent_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 0, 3, 0); + + /* depends on stream 0 */ + CU_ASSERT(0 == + nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(3 == stream->weight); + + /* submit against idle stream */ + CU_ASSERT(0 == + nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 3, &pri_spec)); + + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_settings(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_settings_entry iv[7]; + nghttp2_frame ack_frame; + const int32_t UNKNOWN_ID = 1000000007; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 5; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16 * 1024; + + iv[2].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[2].value = 50; + + iv[3].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[3].value = 111; + + iv[4].settings_id = UNKNOWN_ID; + iv[4].value = 999; + + iv[5].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[5].value = 1023; + + iv[6].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[6].value = (uint32_t)NGHTTP2_MAX_WINDOW_SIZE + 1; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + nghttp2_session_server_new(&session, &callbacks, &ud); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 7)); + + /* Make sure that local settings are not changed */ + CU_ASSERT(NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS == + session->local_settings.max_concurrent_streams); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + session->local_settings.initial_window_size); + + /* Now sends without 6th one */ + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 6)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_SETTINGS == item->frame.hd.type); + + frame = &item->frame; + CU_ASSERT(6 == frame->settings.niv); + CU_ASSERT(5 == frame->settings.iv[0].value); + CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == + frame->settings.iv[0].settings_id); + + CU_ASSERT(16 * 1024 == frame->settings.iv[1].value); + CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE == + frame->settings.iv[1].settings_id); + + CU_ASSERT(UNKNOWN_ID == frame->settings.iv[4].settings_id); + CU_ASSERT(999 == frame->settings.iv[4].value); + + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + + CU_ASSERT(50 == session->pending_local_max_concurrent_stream); + + /* before receiving SETTINGS ACK, local settings have still default + values */ + CU_ASSERT(NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS == + nghttp2_session_get_local_settings( + session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + nghttp2_session_get_local_settings( + session, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE)); + + nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + nghttp2_frame_settings_free(&ack_frame.settings, mem); + + CU_ASSERT(16 * 1024 == session->local_settings.initial_window_size); + CU_ASSERT(111 == session->hd_inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(111 == session->hd_inflater.min_hd_table_bufsize_max); + CU_ASSERT(50 == session->local_settings.max_concurrent_streams); + + CU_ASSERT(50 == nghttp2_session_get_local_settings( + session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)); + CU_ASSERT(16 * 1024 == nghttp2_session_get_local_settings( + session, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE)); + + /* We just keep the last seen value */ + CU_ASSERT(50 == session->pending_local_max_concurrent_stream); + + nghttp2_session_del(session); + + /* Bail out if there are contradicting + SETTINGS_NO_RFC7540_PRIORITIES in one SETTINGS. */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv[0].value = 1; + iv[1].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv[1].value = 0; + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2)); + + nghttp2_session_del(session); + + /* Attempt to change SETTINGS_NO_RFC7540_PRIORITIES in the 2nd + SETTINGS. */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv[0].value = 1; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + + iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv[0].value = 0; + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_settings_update_local_window_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_settings_entry iv[4]; + nghttp2_stream *stream; + nghttp2_frame ack_frame; + nghttp2_mem *mem; + nghttp2_option *option; + + mem = nghttp2_mem_default(); + nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + + iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[0].value = 16 * 1024; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream = open_recv_stream(session, 1); + stream->local_window_size = NGHTTP2_INITIAL_WINDOW_SIZE + 100; + stream->recv_window_size = 32768; + + open_recv_stream(session, 3); + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(16 * 1024 + 100 == stream->local_window_size); + + stream = nghttp2_session_get_stream(session, 3); + CU_ASSERT(16 * 1024 == stream->local_window_size); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(32768 == item->frame.window_update.window_size_increment); + + nghttp2_session_del(session); + + /* Without auto-window update */ + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_window_update(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + nghttp2_option_del(option); + + stream = open_recv_stream(session, 1); + stream->local_window_size = NGHTTP2_INITIAL_WINDOW_SIZE + 100; + stream->recv_window_size = 32768; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(32768 == stream->recv_window_size); + CU_ASSERT(16 * 1024 + 100 == stream->local_window_size); + /* Check that we can handle the case where local_window_size < + recv_window_size */ + CU_ASSERT(0 == nghttp2_session_get_stream_local_window_size(session, 1)); + + nghttp2_session_del(session); + + /* Check overflow case */ + iv[0].value = 128 * 1024; + nghttp2_session_server_new(&session, &callbacks, NULL); + stream = open_recv_stream(session, 1); + stream->local_window_size = NGHTTP2_MAX_WINDOW_SIZE; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_FLOW_CONTROL_ERROR == item->frame.rst_stream.error_code); + + nghttp2_session_del(session); + nghttp2_frame_settings_free(&ack_frame.settings, mem); +} + +void test_nghttp2_submit_settings_multiple_times(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_settings_entry iv[4]; + nghttp2_frame frame; + nghttp2_inflight_settings *inflight_settings; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + /* first SETTINGS */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 100; + + iv[1].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[1].value = 0; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2)); + + inflight_settings = session->inflight_settings_head; + + CU_ASSERT(NULL != inflight_settings); + CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == + inflight_settings->iv[0].settings_id); + CU_ASSERT(100 == inflight_settings->iv[0].value); + CU_ASSERT(2 == inflight_settings->niv); + CU_ASSERT(NULL == inflight_settings->next); + + CU_ASSERT(100 == session->pending_local_max_concurrent_stream); + CU_ASSERT(0 == session->pending_enable_push); + + /* second SETTINGS */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 99; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + + inflight_settings = session->inflight_settings_head->next; + + CU_ASSERT(NULL != inflight_settings); + CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == + inflight_settings->iv[0].settings_id); + CU_ASSERT(99 == inflight_settings->iv[0].value); + CU_ASSERT(1 == inflight_settings->niv); + CU_ASSERT(NULL == inflight_settings->next); + + CU_ASSERT(99 == session->pending_local_max_concurrent_stream); + CU_ASSERT(0 == session->pending_enable_push); + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + + /* receive SETTINGS ACK */ + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + inflight_settings = session->inflight_settings_head; + + /* first inflight SETTINGS was removed */ + CU_ASSERT(NULL != inflight_settings); + CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == + inflight_settings->iv[0].settings_id); + CU_ASSERT(99 == inflight_settings->iv[0].value); + CU_ASSERT(1 == inflight_settings->niv); + CU_ASSERT(NULL == inflight_settings->next); + + CU_ASSERT(100 == session->local_settings.max_concurrent_streams); + + /* receive SETTINGS ACK again */ + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + CU_ASSERT(NULL == session->inflight_settings_head); + CU_ASSERT(99 == session->local_settings.max_concurrent_streams); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_push_promise(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + open_recv_stream(session, 1); + CU_ASSERT(2 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, + reqnv, ARRLEN(reqnv), &ud)); + + stream = nghttp2_session_get_stream(session, 2); + + CU_ASSERT(NULL != stream); + CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state); + CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2)); + + ud.frame_send_cb_called = 0; + ud.sent_frame_type = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.sent_frame_type); + + stream = nghttp2_session_get_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state); + CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2)); + + /* submit PUSH_PROMISE while associated stream is not opened */ + CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED == + nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, reqnv, + ARRLEN(reqnv), NULL)); + + /* Stream ID <= 0 is error */ + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 0, reqnv, + ARRLEN(reqnv), NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_window_update(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + stream = open_recv_stream(session, 2); + stream->recv_window_size = 4096; + + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 1024)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(1024 == item->frame.window_update.window_size_increment); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(3072 == stream->recv_window_size); + + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 4096)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(4096 == item->frame.window_update.window_size_increment); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == stream->recv_window_size); + + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 4096)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(4096 == item->frame.window_update.window_size_increment); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == stream->recv_window_size); + + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 0)); + /* It is ok if stream is closed or does not exist at the call + time */ + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 4, 4096)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_window_update_local_window_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + stream = open_recv_stream(session, 2); + stream->recv_window_size = 4096; + + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, + stream->recv_window_size + 1)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1 == stream->local_window_size); + CU_ASSERT(0 == stream->recv_window_size); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(4097 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Let's decrement local window size */ + stream->recv_window_size = 4096; + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, + -stream->local_window_size / 2)); + CU_ASSERT(32768 == stream->local_window_size); + CU_ASSERT(-28672 == stream->recv_window_size); + CU_ASSERT(32768 == stream->recv_reduction); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item == NULL); + + /* Increase local window size */ + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 16384)); + CU_ASSERT(49152 == stream->local_window_size); + CU_ASSERT(-12288 == stream->recv_window_size); + CU_ASSERT(16384 == stream->recv_reduction); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, + NGHTTP2_MAX_WINDOW_SIZE)); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Check connection-level flow control */ + session->recv_window_size = 4096; + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, + session->recv_window_size + 1)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 == + session->local_window_size); + CU_ASSERT(0 == session->recv_window_size); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(4097 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Go decrement part */ + session->recv_window_size = 4096; + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, + -session->local_window_size / 2)); + CU_ASSERT(32768 == session->local_window_size); + CU_ASSERT(-28672 == session->recv_window_size); + CU_ASSERT(32768 == session->recv_reduction); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item == NULL); + + /* Increase local window size */ + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, 16384)); + CU_ASSERT(49152 == session->local_window_size); + CU_ASSERT(-12288 == session->recv_window_size); + CU_ASSERT(16384 == session->recv_reduction); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, + NGHTTP2_MAX_WINDOW_SIZE)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_shutdown_notice(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + CU_ASSERT(0 == nghttp2_submit_shutdown_notice(session)); + + ud.frame_send_cb_called = 0; + + nghttp2_session_send(session); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_GOAWAY == ud.sent_frame_type); + CU_ASSERT((1u << 31) - 1 == session->local_last_stream_id); + + /* After another GOAWAY, nghttp2_submit_shutdown_notice() is + noop. */ + CU_ASSERT(0 == nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR)); + + ud.frame_send_cb_called = 0; + + nghttp2_session_send(session); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_GOAWAY == ud.sent_frame_type); + CU_ASSERT(0 == session->local_last_stream_id); + + CU_ASSERT(0 == nghttp2_submit_shutdown_notice(session)); + + ud.frame_send_cb_called = 0; + ud.frame_not_send_cb_called = 0; + + nghttp2_session_send(session); + + CU_ASSERT(0 == ud.frame_send_cb_called); + CU_ASSERT(0 == ud.frame_not_send_cb_called); + + nghttp2_session_del(session); + + /* Using nghttp2_submit_shutdown_notice() with client side session + is error */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == + nghttp2_submit_shutdown_notice(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_invalid_nv(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv empty_name_nv[] = {MAKE_NV("Version", "HTTP/1.1"), + MAKE_NV("", "empty name")}; + + /* Now invalid header name/value pair in HTTP/1.1 is accepted in + nghttp2 */ + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL)); + + /* nghttp2_submit_response */ + CU_ASSERT(0 == nghttp2_submit_response(session, 2, empty_name_nv, + ARRLEN(empty_name_nv), NULL)); + + /* nghttp2_submit_push_promise */ + open_recv_stream(session, 1); + + CU_ASSERT(0 < nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, + empty_name_nv, + ARRLEN(empty_name_nv), NULL)); + + nghttp2_session_del(session); + + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + + /* nghttp2_submit_request */ + CU_ASSERT(0 < nghttp2_submit_request(session, NULL, empty_name_nv, + ARRLEN(empty_name_nv), NULL, NULL)); + + /* nghttp2_submit_headers */ + CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL, + empty_name_nv, ARRLEN(empty_name_nv), + NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_extension(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + accumulator acc; + nghttp2_mem *mem; + const char data[] = "Hello World!"; + size_t len; + int32_t stream_id; + int rv; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.pack_extension_callback = pack_extension_callback; + callbacks.send_callback = accumulator_send_callback; + + nghttp2_buf_init2(&ud.scratchbuf, 4096, mem); + + nghttp2_session_client_new(&session, &callbacks, &ud); + + ud.scratchbuf.last = nghttp2_cpymem(ud.scratchbuf.last, data, sizeof(data)); + ud.acc = &acc; + + rv = nghttp2_submit_extension(session, 211, 0x01, 3, &ud.scratchbuf); + + CU_ASSERT(0 == rv); + + acc.length = 0; + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + sizeof(data) == acc.length); + + len = nghttp2_get_uint32(acc.buf) >> 8; + + CU_ASSERT(sizeof(data) == len); + CU_ASSERT(211 == acc.buf[3]); + CU_ASSERT(0x01 == acc.buf[4]); + + stream_id = (int32_t)nghttp2_get_uint32(acc.buf + 5); + + CU_ASSERT(3 == stream_id); + CU_ASSERT(0 == memcmp(data, &acc.buf[NGHTTP2_FRAME_HDLEN], sizeof(data))); + + nghttp2_session_del(session); + + /* submitting standard HTTP/2 frame is error */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_extension(session, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, + NULL); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + nghttp2_session_del(session); + nghttp2_buf_free(&ud.scratchbuf, mem); +} + +void test_nghttp2_submit_altsvc(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + int rv; + ssize_t len; + const uint8_t *data; + nghttp2_frame_hd hd; + size_t origin_len; + const uint8_t origin[] = "nghttp2.org"; + const uint8_t field_value[] = "h2=\":443\""; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin, + sizeof(origin) - 1, field_value, + sizeof(field_value) - 1); + + CU_ASSERT(0 == rv); + + ud.frame_send_cb_called = 0; + + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len == NGHTTP2_FRAME_HDLEN + 2 + sizeof(origin) - 1 + + sizeof(field_value) - 1); + + nghttp2_frame_unpack_frame_hd(&hd, data); + + CU_ASSERT(2 + sizeof(origin) - 1 + sizeof(field_value) - 1 == hd.length); + CU_ASSERT(NGHTTP2_ALTSVC == hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + + origin_len = nghttp2_get_uint16(data + NGHTTP2_FRAME_HDLEN); + + CU_ASSERT(sizeof(origin) - 1 == origin_len); + CU_ASSERT(0 == + memcmp(origin, data + NGHTTP2_FRAME_HDLEN + 2, sizeof(origin) - 1)); + CU_ASSERT(0 == memcmp(field_value, + data + NGHTTP2_FRAME_HDLEN + 2 + sizeof(origin) - 1, + hd.length - (sizeof(origin) - 1) - 2)); + + /* submitting empty origin with stream_id == 0 is error */ + rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, NULL, 0, + field_value, sizeof(field_value) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* submitting non-empty origin with stream_id != 0 is error */ + rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 1, origin, + sizeof(origin) - 1, field_value, + sizeof(field_value) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + nghttp2_session_del(session); + + /* submitting from client side session is error */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin, + sizeof(origin) - 1, field_value, + sizeof(field_value) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_origin(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + int rv; + ssize_t len; + const uint8_t *data; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + static const uint8_t examples[] = "https://examples.com"; + static const nghttp2_origin_entry ov[] = { + { + (uint8_t *)nghttp2, + sizeof(nghttp2) - 1, + }, + { + (uint8_t *)examples, + sizeof(examples) - 1, + }, + }; + nghttp2_frame frame; + nghttp2_ext_origin origin; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_send_callback = on_frame_send_callback; + + frame.ext.payload = &origin; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, ov, 2); + + CU_ASSERT(0 == rv); + + ud.frame_send_cb_called = 0; + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len > 0); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_frame_unpack_frame_hd(&frame.hd, data); + rv = nghttp2_frame_unpack_origin_payload( + &frame.ext, data + NGHTTP2_FRAME_HDLEN, (size_t)len - NGHTTP2_FRAME_HDLEN, + mem); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == frame.hd.stream_id); + CU_ASSERT(NGHTTP2_ORIGIN == frame.hd.type); + CU_ASSERT(2 == origin.nov); + CU_ASSERT(0 == memcmp(nghttp2, origin.ov[0].origin, sizeof(nghttp2) - 1)); + CU_ASSERT(sizeof(nghttp2) - 1 == origin.ov[0].origin_len); + CU_ASSERT(0 == memcmp(examples, origin.ov[1].origin, sizeof(examples) - 1)); + CU_ASSERT(sizeof(examples) - 1 == origin.ov[1].origin_len); + + nghttp2_frame_origin_free(&frame.ext, mem); + + nghttp2_session_del(session); + + /* Submitting ORIGIN frame from client session is error */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, ov, 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv); + + nghttp2_session_del(session); + + /* Submitting empty ORIGIN frame */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, NULL, 0); + + CU_ASSERT(0 == rv); + + ud.frame_send_cb_called = 0; + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len == NGHTTP2_FRAME_HDLEN); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_frame_unpack_frame_hd(&frame.hd, data); + + CU_ASSERT(NGHTTP2_ORIGIN == frame.hd.type); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_priority_update(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const uint8_t field_value[] = "i"; + my_user_data ud; + const uint8_t *data; + int rv; + nghttp2_frame frame; + nghttp2_ext_priority_update priority_update; + ssize_t len; + int32_t stream_id; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_send_callback = on_frame_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + session->pending_no_rfc7540_priorities = 1; + + stream_id = + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(1 == stream_id); + + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len > 0); + + rv = nghttp2_submit_priority_update(session, NGHTTP2_FLAG_NONE, stream_id, + field_value, sizeof(field_value) - 1); + + CU_ASSERT(0 == rv); + + frame.ext.payload = &priority_update; + + ud.frame_send_cb_called = 0; + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len > 0); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_frame_unpack_frame_hd(&frame.hd, data); + nghttp2_frame_unpack_priority_update_payload( + &frame.ext, (uint8_t *)(data + NGHTTP2_FRAME_HDLEN), + (size_t)len - NGHTTP2_FRAME_HDLEN); + + CU_ASSERT(0 == frame.hd.stream_id); + CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == frame.hd.type); + CU_ASSERT(stream_id == priority_update.stream_id); + CU_ASSERT(sizeof(field_value) - 1 == priority_update.field_value_len); + CU_ASSERT(0 == memcmp(field_value, priority_update.field_value, + sizeof(field_value) - 1)); + + nghttp2_session_del(session); + + /* Submitting PRIORITY_UPDATE frame from server session is error */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 1); + + rv = nghttp2_submit_priority_update(session, NGHTTP2_FLAG_NONE, 1, + field_value, sizeof(field_value) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv); + + nghttp2_session_del(session); + + /* Submitting PRIORITY_UPDATE with empty field_value */ + nghttp2_session_client_new(&session, &callbacks, &ud); + + stream_id = + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(1 == stream_id); + + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len > 0); + + rv = nghttp2_submit_priority_update(session, NGHTTP2_FLAG_NONE, stream_id, + NULL, 0); + + CU_ASSERT(0 == rv); + + frame.ext.payload = &priority_update; + + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len > 0); + + nghttp2_frame_unpack_frame_hd(&frame.hd, data); + nghttp2_frame_unpack_priority_update_payload( + &frame.ext, (uint8_t *)(data + NGHTTP2_FRAME_HDLEN), + (size_t)len - NGHTTP2_FRAME_HDLEN); + + CU_ASSERT(0 == frame.hd.stream_id); + CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == frame.hd.type); + CU_ASSERT(stream_id == priority_update.stream_id); + CU_ASSERT(0 == priority_update.field_value_len); + CU_ASSERT(NULL == priority_update.field_value); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_rst_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + int rv; + int32_t stream_id; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + /* Sending RST_STREAM to idle stream (local) is ignored */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + + CU_ASSERT(NULL == item); + + nghttp2_session_del(session); + + /* Sending RST_STREAM to idle stream (remote) is ignored */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + + CU_ASSERT(NULL == item); + + nghttp2_session_del(session); + + /* Sending RST_STREAM to non-idle stream (local) */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + open_sent_stream(session, 1); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(1 == item->frame.hd.stream_id); + + nghttp2_session_del(session); + + /* Sending RST_STREAM to non-idle stream (remote) */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + open_recv_stream(session, 2); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(2 == item->frame.hd.stream_id); + + nghttp2_session_del(session); + + /* Sending RST_STREAM to pending stream */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + stream_id = + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(stream_id > 0); + + item = nghttp2_outbound_queue_top(&session->ob_syn); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT(0 == item->aux_data.headers.canceled); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + item = nghttp2_outbound_queue_top(&session->ob_syn); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT(1 == item->aux_data.headers.canceled); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_open_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_priority_spec_init(&pri_spec, 0, 245, 0); + + stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_OPENED, NULL); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(0 == session->num_outgoing_streams); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + CU_ASSERT(245 == stream->weight); + CU_ASSERT(&session->root == stream->dep_prev); + CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags); + + stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_OPENING, NULL); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(1 == session->num_outgoing_streams); + CU_ASSERT(&session->root == stream->dep_prev); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags); + + stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_RESERVED, NULL); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(1 == session->num_outgoing_streams); + CU_ASSERT(&session->root == stream->dep_prev); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags); + + nghttp2_priority_spec_init(&pri_spec, 1, 17, 1); + + stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_OPENED, NULL); + CU_ASSERT(17 == stream->weight); + CU_ASSERT(1 == stream->dep_prev->stream_id); + + /* Dependency to idle stream */ + nghttp2_priority_spec_init(&pri_spec, 1000000007, 240, 1); + + stream = nghttp2_session_open_stream(session, 5, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_OPENED, NULL); + CU_ASSERT(240 == stream->weight); + CU_ASSERT(1000000007 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream_raw(session, 1000000007); + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + CU_ASSERT(&session->root == stream->dep_prev); + + /* Dependency to closed stream which is not in dependency tree */ + session->last_recv_stream_id = 7; + + nghttp2_priority_spec_init(&pri_spec, 7, 10, 0); + + stream = nghttp2_session_open_stream(session, 9, NGHTTP2_FLAG_NONE, &pri_spec, + NGHTTP2_STREAM_OPENED, NULL); + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + CU_ASSERT(&session->root == stream->dep_prev); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, NULL); + stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_RESERVED, NULL); + CU_ASSERT(0 == session->num_incoming_streams); + CU_ASSERT(0 == session->num_outgoing_streams); + CU_ASSERT(&session->root == stream->dep_prev); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + CU_ASSERT(NGHTTP2_SHUT_WR == stream->shut_flags); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_open_stream_with_idle_stream_dep(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* Dependency to idle stream */ + nghttp2_priority_spec_init(&pri_spec, 101, 245, 0); + + stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_OPENED, NULL); + + CU_ASSERT(245 == stream->weight); + CU_ASSERT(101 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream_raw(session, 101); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + + nghttp2_priority_spec_init(&pri_spec, 211, 1, 0); + + /* stream 101 was already created as idle. */ + stream = nghttp2_session_open_stream(session, 101, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_OPENED, NULL); + + CU_ASSERT(1 == stream->weight); + CU_ASSERT(211 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream_raw(session, 211); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_get_next_ob_item(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_priority_spec pri_spec; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + session->remote_settings.max_concurrent_streams = 2; + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); + CU_ASSERT(NGHTTP2_PING == + nghttp2_session_get_next_ob_item(session)->frame.hd.type); + + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, NULL, 0, NULL, NULL)); + CU_ASSERT(NGHTTP2_PING == + nghttp2_session_get_next_ob_item(session)->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + /* Incoming stream does not affect the number of outgoing max + concurrent streams. */ + open_recv_stream(session, 2); + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_MAX_WEIGHT, 0); + + CU_ASSERT(3 == + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL)); + CU_ASSERT(NGHTTP2_HEADERS == + nghttp2_session_get_next_ob_item(session)->frame.hd.type); + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(5 == + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + session->remote_settings.max_concurrent_streams = 3; + + CU_ASSERT(NGHTTP2_HEADERS == + nghttp2_session_get_next_ob_item(session)->frame.hd.type); + + nghttp2_session_del(session); + + /* Check that push reply HEADERS are queued into ob_ss_pq */ + nghttp2_session_server_new(&session, &callbacks, NULL); + session->remote_settings.max_concurrent_streams = 0; + open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 2, + NULL, NULL, 0, NULL)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(1 == nghttp2_outbound_queue_size(&session->ob_syn)); + nghttp2_session_del(session); +} + +void test_nghttp2_session_pop_next_ob_item(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_priority_spec pri_spec; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + session->remote_settings.max_concurrent_streams = 1; + + CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session)); + + nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); + + nghttp2_priority_spec_init(&pri_spec, 0, 254, 0); + + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL); + + item = nghttp2_session_pop_next_ob_item(session); + CU_ASSERT(NGHTTP2_PING == item->frame.hd.type); + nghttp2_outbound_item_free(item, mem); + mem->free(item, NULL); + + item = nghttp2_session_pop_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + nghttp2_outbound_item_free(item, mem); + mem->free(item, NULL); + + CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session)); + + /* Incoming stream does not affect the number of outgoing max + concurrent streams. */ + open_recv_stream(session, 4); + /* In-flight outgoing stream */ + open_sent_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_MAX_WEIGHT, 0); + + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL); + + CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session)); + + session->remote_settings.max_concurrent_streams = 2; + + item = nghttp2_session_pop_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + nghttp2_outbound_item_free(item, mem); + mem->free(item, NULL); + + nghttp2_session_del(session); + + /* Check that push reply HEADERS are queued into ob_ss_pq */ + nghttp2_session_server_new(&session, &callbacks, NULL); + session->remote_settings.max_concurrent_streams = 0; + open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 2, + NULL, NULL, 0, NULL)); + CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session)); + CU_ASSERT(1 == nghttp2_outbound_queue_size(&session->ob_syn)); + nghttp2_session_del(session); +} + +void test_nghttp2_session_reply_fail(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = fail_send_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = 4 * 1024; + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, NULL, 0, &data_prd)); + CU_ASSERT(NGHTTP2_ERR_CALLBACK_FAILURE == nghttp2_session_send(session)); + nghttp2_session_del(session); +} + +void test_nghttp2_session_max_concurrent_streams(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + open_recv_stream(session, 1); + + /* Check un-ACKed SETTINGS_MAX_CONCURRENT_STREAMS */ + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + session->pending_local_max_concurrent_stream = 1; + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_REFUSED_STREAM == item->frame.rst_stream.error_code); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Check ACKed SETTINGS_MAX_CONCURRENT_STREAMS */ + session->local_settings.max_concurrent_streams = 1; + frame.hd.stream_id = 5; + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + + nghttp2_frame_headers_free(&frame.headers, mem); + nghttp2_session_del(session); +} + +void test_nghttp2_session_stop_data_with_rst_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_data_provider data_prd; + nghttp2_frame frame; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.send_callback = block_count_send_callback; + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.frame_send_cb_called = 0; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 4; + + nghttp2_session_server_new(&session, &callbacks, &ud); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + nghttp2_submit_response(session, 1, NULL, 0, &data_prd); + + ud.block_count = 2; + /* Sends response HEADERS + DATA[0] */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_DATA == ud.sent_frame_type); + /* data for DATA[1] is read from data_prd but it is not sent */ + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2); + + nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_CANCEL); + CU_ASSERT(0 == nghttp2_session_on_rst_stream_received(session, &frame)); + nghttp2_frame_rst_stream_free(&frame.rst_stream); + + /* Big enough number to send all DATA frames potentially. */ + ud.block_count = 100; + /* Nothing will be sent in the following call. */ + CU_ASSERT(0 == nghttp2_session_send(session)); + /* With RST_STREAM, stream is canceled and further DATA on that + stream are not sent. */ + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2); + + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_defer_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_data_provider data_prd; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.send_callback = block_count_send_callback; + data_prd.read_callback = defer_data_source_read_callback; + + ud.frame_send_cb_called = 0; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 4; + + nghttp2_session_server_new(&session, &callbacks, &ud); + stream = open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + session->remote_window_size = 1 << 20; + stream->remote_window_size = 1 << 20; + + nghttp2_submit_response(session, 1, NULL, 0, &data_prd); + + ud.block_count = 1; + /* Sends HEADERS reply */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); + /* No data is read */ + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 4); + + ud.block_count = 1; + nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); + /* Sends PING */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_PING == ud.sent_frame_type); + + /* Resume deferred DATA */ + CU_ASSERT(0 == nghttp2_session_resume_data(session, 1)); + item = stream->item; + item->aux_data.data.data_prd.read_callback = + fixed_length_data_source_read_callback; + ud.block_count = 1; + /* Reads 2 DATA chunks */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2); + + /* Deferred again */ + item->aux_data.data.data_prd.read_callback = defer_data_source_read_callback; + /* This is needed since 16KiB block is already read and waiting to be + sent. No read_callback invocation. */ + ud.block_count = 1; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2); + + /* Resume deferred DATA */ + CU_ASSERT(0 == nghttp2_session_resume_data(session, 1)); + item->aux_data.data.data_prd.read_callback = + fixed_length_data_source_read_callback; + ud.block_count = 1; + /* Reads 2 16KiB blocks */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(ud.data_source_length == 0); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_flow_control(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_data_provider data_prd; + nghttp2_frame frame; + nghttp2_stream *stream; + int32_t new_initial_window_size; + nghttp2_settings_entry iv[1]; + nghttp2_frame settings_frame; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = fixed_bytes_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.frame_send_cb_called = 0; + ud.data_source_length = 128 * 1024; + /* Use smaller emission count so that we can check outbound flow + control window calculation is correct. */ + ud.fixed_sendlen = 2 * 1024; + + /* Initial window size to 64KiB - 1*/ + nghttp2_session_client_new(&session, &callbacks, &ud); + /* Change it to 64KiB for easy calculation */ + session->remote_window_size = 64 * 1024; + session->remote_settings.initial_window_size = 64 * 1024; + + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); + + /* Sends 64KiB - 1 data */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(64 * 1024 == ud.data_source_length); + + /* Back 32KiB in stream window */ + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1, + 32 * 1024); + nghttp2_session_on_window_update_received(session, &frame); + + /* Send nothing because of connection-level window */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(64 * 1024 == ud.data_source_length); + + /* Back 32KiB in connection-level window */ + frame.hd.stream_id = 0; + nghttp2_session_on_window_update_received(session, &frame); + + /* Sends another 32KiB data */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(32 * 1024 == ud.data_source_length); + + stream = nghttp2_session_get_stream(session, 1); + /* Change initial window size to 16KiB. The window_size becomes + negative. */ + new_initial_window_size = 16 * 1024; + stream->remote_window_size = + new_initial_window_size - + ((int32_t)session->remote_settings.initial_window_size - + stream->remote_window_size); + session->remote_settings.initial_window_size = + (uint32_t)new_initial_window_size; + CU_ASSERT(-48 * 1024 == stream->remote_window_size); + + /* Back 48KiB to stream window */ + frame.hd.stream_id = 1; + frame.window_update.window_size_increment = 48 * 1024; + nghttp2_session_on_window_update_received(session, &frame); + + /* Nothing is sent because window_size is 0 */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(32 * 1024 == ud.data_source_length); + + /* Back 16KiB in stream window */ + frame.hd.stream_id = 1; + frame.window_update.window_size_increment = 16 * 1024; + nghttp2_session_on_window_update_received(session, &frame); + + /* Back 24KiB in connection-level window */ + frame.hd.stream_id = 0; + frame.window_update.window_size_increment = 24 * 1024; + nghttp2_session_on_window_update_received(session, &frame); + + /* Sends another 16KiB data */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(16 * 1024 == ud.data_source_length); + + /* Increase initial window size to 32KiB */ + iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[0].value = 32 * 1024; + + nghttp2_frame_settings_init(&settings_frame.settings, NGHTTP2_FLAG_NONE, + dup_iv(iv, 1), 1); + nghttp2_session_on_settings_received(session, &settings_frame, 1); + nghttp2_frame_settings_free(&settings_frame.settings, mem); + + /* Sends another 8KiB data */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(8 * 1024 == ud.data_source_length); + + /* Back 8KiB in connection-level window */ + frame.hd.stream_id = 0; + frame.window_update.window_size_increment = 8 * 1024; + nghttp2_session_on_window_update_received(session, &frame); + + /* Sends last 8KiB data */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.data_source_length); + CU_ASSERT(nghttp2_session_get_stream(session, 1)->shut_flags & + NGHTTP2_SHUT_WR); + + nghttp2_frame_window_update_free(&frame.window_update); + nghttp2_session_del(session); +} + +void test_nghttp2_session_flow_control_data_recv(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t data[64 * 1024 + 16]; + nghttp2_frame_hd hd; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* Initial window size to 64KiB - 1*/ + nghttp2_session_client_new(&session, &callbacks, NULL); + + stream = open_sent_stream(session, 1); + + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); + + session->local_window_size = NGHTTP2_MAX_PAYLOADLEN; + stream->local_window_size = NGHTTP2_MAX_PAYLOADLEN; + + /* Create DATA frame */ + memset(data, 0, sizeof(data)); + nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_PAYLOADLEN, NGHTTP2_DATA, + NGHTTP2_FLAG_END_STREAM, 1); + + nghttp2_frame_pack_frame_hd(data, &hd); + CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN + NGHTTP2_FRAME_HDLEN == + nghttp2_session_mem_recv( + session, data, NGHTTP2_MAX_PAYLOADLEN + NGHTTP2_FRAME_HDLEN)); + + item = nghttp2_session_get_next_ob_item(session); + /* Since this is the last frame, stream-level WINDOW_UPDATE is not + issued, but connection-level is. */ + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(0 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN == + item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Receive DATA for closed stream. They are still subject to under + connection-level flow control, since this situation arises when + RST_STREAM is issued by the remote, but the local side keeps + sending DATA frames. Without calculating connection-level window, + the subsequent flow control gets confused. */ + CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN + NGHTTP2_FRAME_HDLEN == + nghttp2_session_mem_recv( + session, data, NGHTTP2_MAX_PAYLOADLEN + NGHTTP2_FRAME_HDLEN)); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(0 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN == + item->frame.window_update.window_size_increment); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_flow_control_data_with_padding_recv(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t data[1024]; + nghttp2_frame_hd hd; + nghttp2_stream *stream; + nghttp2_option *option; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_option_new(&option); + /* Disable auto window update so that we can check padding is + consumed automatically */ + nghttp2_option_set_no_auto_window_update(option, 1); + + /* Initial window size to 64KiB - 1*/ + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + nghttp2_option_del(option); + + stream = open_sent_stream(session, 1); + + /* Create DATA frame */ + memset(data, 0, sizeof(data)); + nghttp2_frame_hd_init(&hd, 357, NGHTTP2_DATA, NGHTTP2_FLAG_PADDED, 1); + + nghttp2_frame_pack_frame_hd(data, &hd); + /* Set Pad Length field, which itself is padding */ + data[NGHTTP2_FRAME_HDLEN] = 255; + + CU_ASSERT( + (ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length) == + nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + hd.length)); + + CU_ASSERT((int32_t)hd.length == session->recv_window_size); + CU_ASSERT((int32_t)hd.length == stream->recv_window_size); + CU_ASSERT(256 == session->consumed_size); + CU_ASSERT(256 == stream->consumed_size); + CU_ASSERT(357 == session->recv_window_size); + CU_ASSERT(357 == stream->recv_window_size); + + /* Receive the same DATA frame, but in 2 parts: first 9 + 1 + 102 + bytes which includes 1st padding byte, and remainder */ + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 103) == + nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 103)); + CU_ASSERT(258 == session->consumed_size); + CU_ASSERT(258 == stream->consumed_size); + CU_ASSERT(460 == session->recv_window_size); + CU_ASSERT(460 == stream->recv_window_size); + + /* 357 - 103 = 254 bytes left */ + CU_ASSERT(254 == nghttp2_session_mem_recv(session, data, 254)); + CU_ASSERT(512 == session->consumed_size); + CU_ASSERT(512 == stream->consumed_size); + CU_ASSERT(714 == session->recv_window_size); + CU_ASSERT(714 == stream->recv_window_size); + + /* Receive the same DATA frame, but in 2 parts: first 9 = 1 + 101 + bytes which only includes data without padding, 2nd part is + padding only */ + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 102) == + nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 102)); + CU_ASSERT(513 == session->consumed_size); + CU_ASSERT(513 == stream->consumed_size); + CU_ASSERT(816 == session->recv_window_size); + CU_ASSERT(816 == stream->recv_window_size); + + /* 357 - 102 = 255 bytes left */ + CU_ASSERT(255 == nghttp2_session_mem_recv(session, data, 255)); + CU_ASSERT(768 == session->consumed_size); + CU_ASSERT(768 == stream->consumed_size); + CU_ASSERT(1071 == session->recv_window_size); + CU_ASSERT(1071 == stream->recv_window_size); + + /* Receive the same DATA frame, but in 2 parts: first 9 = 1 + 50 + bytes which includes byte up to middle of data, 2nd part is the + remainder */ + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 51) == + nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 51)); + CU_ASSERT(769 == session->consumed_size); + CU_ASSERT(769 == stream->consumed_size); + CU_ASSERT(1122 == session->recv_window_size); + CU_ASSERT(1122 == stream->recv_window_size); + + /* 357 - 51 = 306 bytes left */ + CU_ASSERT(306 == nghttp2_session_mem_recv(session, data, 306)); + CU_ASSERT(1024 == session->consumed_size); + CU_ASSERT(1024 == stream->consumed_size); + CU_ASSERT(1428 == session->recv_window_size); + CU_ASSERT(1428 == stream->recv_window_size); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_data_read_temporal_failure(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_data_provider data_prd; + nghttp2_frame frame; + nghttp2_stream *stream; + size_t data_size = 128 * 1024; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.data_source_length = data_size; + + /* Initial window size is 64KiB - 1 */ + nghttp2_session_client_new(&session, &callbacks, &ud); + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); + + /* Sends NGHTTP2_INITIAL_WINDOW_SIZE data, assuming, it is equal to + or smaller than NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(data_size - NGHTTP2_INITIAL_WINDOW_SIZE == ud.data_source_length); + + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(NGHTTP2_DATA == stream->item->frame.hd.type); + + stream->item->aux_data.data.data_prd.read_callback = + temporal_failure_data_source_read_callback; + + /* Back NGHTTP2_INITIAL_WINDOW_SIZE to both connection-level and + stream-wise window */ + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_INITIAL_WINDOW_SIZE); + nghttp2_session_on_window_update_received(session, &frame); + frame.hd.stream_id = 0; + nghttp2_session_on_window_update_received(session, &frame); + nghttp2_frame_window_update_free(&frame.window_update); + + /* Sending data will fail (soft fail) and treated as stream error */ + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(data_size - NGHTTP2_INITIAL_WINDOW_SIZE == ud.data_source_length); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_RST_STREAM == ud.sent_frame_type); + + data_prd.read_callback = fail_data_source_read_callback; + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); + /* Sending data will fail (hard fail) and session tear down */ + CU_ASSERT(NGHTTP2_ERR_CALLBACK_FAILURE == nghttp2_session_send(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_stream_close(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_stream_close_callback = on_stream_close_callback; + user_data.stream_close_cb_called = 0; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + stream = + open_sent_stream3(session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default, + NGHTTP2_STREAM_OPENED, &user_data); + CU_ASSERT(stream != NULL); + CU_ASSERT(nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR) == 0); + CU_ASSERT(user_data.stream_close_cb_called == 1); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_ctrl_not_send(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + callbacks.send_callback = null_send_callback; + user_data.frame_not_send_cb_called = 0; + user_data.not_sent_frame_type = 0; + user_data.not_sent_error = 0; + + nghttp2_session_server_new(&session, &callbacks, &user_data); + stream = + open_recv_stream3(session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default, + NGHTTP2_STREAM_OPENING, &user_data); + + /* Check response HEADERS */ + /* Send bogus stream ID */ + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 3, + NULL, NULL, 0, NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == user_data.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED == user_data.not_sent_error); + + user_data.frame_not_send_cb_called = 0; + /* Shutdown transmission */ + stream->shut_flags |= NGHTTP2_SHUT_WR; + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, NULL, 0, NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == user_data.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_STREAM_SHUT_WR == user_data.not_sent_error); + + stream->shut_flags = NGHTTP2_SHUT_NONE; + user_data.frame_not_send_cb_called = 0; + /* Queue RST_STREAM */ + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, NULL, 0, NULL)); + CU_ASSERT(0 == nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_INTERNAL_ERROR)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == user_data.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSING == user_data.not_sent_error); + + nghttp2_session_del(session); + + /* Check request HEADERS */ + user_data.frame_not_send_cb_called = 0; + CU_ASSERT(nghttp2_session_client_new(&session, &callbacks, &user_data) == 0); + /* Maximum Stream ID is reached */ + session->next_stream_id = (1u << 31) + 1; + CU_ASSERT(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE == + nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, NULL, + NULL, 0, NULL)); + + user_data.frame_not_send_cb_called = 0; + /* GOAWAY received */ + session->goaway_flags |= NGHTTP2_GOAWAY_RECV; + session->next_stream_id = 9; + + CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, + NULL, NULL, 0, NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == user_data.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_START_STREAM_NOT_ALLOWED == user_data.not_sent_error); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_get_outbound_queue_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + CU_ASSERT(0 == nghttp2_session_get_outbound_queue_size(session)); + + CU_ASSERT(0 == nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL)); + CU_ASSERT(1 == nghttp2_session_get_outbound_queue_size(session)); + + CU_ASSERT(0 == nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, 2, + NGHTTP2_NO_ERROR, NULL, 0)); + CU_ASSERT(2 == nghttp2_session_get_outbound_queue_size(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_get_effective_local_window_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + + stream = open_sent_stream(session, 1); + + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE == + nghttp2_session_get_effective_local_window_size(session)); + CU_ASSERT(0 == nghttp2_session_get_effective_recv_data_length(session)); + + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + nghttp2_session_get_stream_effective_local_window_size(session, 1)); + CU_ASSERT(0 == + nghttp2_session_get_stream_effective_recv_data_length(session, 1)); + + /* Check connection flow control */ + session->recv_window_size = 100; + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, 1100); + + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 == + nghttp2_session_get_effective_local_window_size(session)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 == + nghttp2_session_get_local_window_size(session)); + CU_ASSERT(0 == nghttp2_session_get_effective_recv_data_length(session)); + + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, -50); + /* Now session->recv_window_size = -50 */ + CU_ASSERT(-50 == session->recv_window_size); + CU_ASSERT(50 == session->recv_reduction); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 950 == + nghttp2_session_get_effective_local_window_size(session)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 == + nghttp2_session_get_local_window_size(session)); + CU_ASSERT(0 == nghttp2_session_get_effective_recv_data_length(session)); + + session->recv_window_size += 50; + + /* Now session->recv_window_size = 0 */ + + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 950 == + nghttp2_session_get_local_window_size(session)); + + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, 100); + CU_ASSERT(50 == session->recv_window_size); + CU_ASSERT(0 == session->recv_reduction); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1050 == + nghttp2_session_get_effective_local_window_size(session)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 == + nghttp2_session_get_local_window_size(session)); + CU_ASSERT(50 == nghttp2_session_get_effective_recv_data_length(session)); + + /* Check stream flow control */ + stream->recv_window_size = 100; + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 1, 1100); + + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 == + nghttp2_session_get_stream_effective_local_window_size(session, 1)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 == + nghttp2_session_get_stream_local_window_size(session, 1)); + CU_ASSERT(0 == + nghttp2_session_get_stream_effective_recv_data_length(session, 1)); + + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 1, -50); + /* Now stream->recv_window_size = -50 */ + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 950 == + nghttp2_session_get_stream_effective_local_window_size(session, 1)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 == + nghttp2_session_get_stream_local_window_size(session, 1)); + CU_ASSERT(0 == + nghttp2_session_get_stream_effective_recv_data_length(session, 1)); + + stream->recv_window_size += 50; + /* Now stream->recv_window_size = 0 */ + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 1, 100); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1050 == + nghttp2_session_get_stream_effective_local_window_size(session, 1)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 == + nghttp2_session_get_stream_local_window_size(session, 1)); + CU_ASSERT(50 == + nghttp2_session_get_stream_effective_recv_data_length(session, 1)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_set_option(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_option *option; + nghttp2_hd_deflater *deflater; + int rv; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* Test for nghttp2_option_set_no_auto_window_update */ + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_window_update(option, 1); + + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE); + + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* Test for nghttp2_option_set_peer_max_concurrent_streams */ + nghttp2_option_new(&option); + nghttp2_option_set_peer_max_concurrent_streams(option, 100); + + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + CU_ASSERT(100 == session->remote_settings.max_concurrent_streams); + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* Test for nghttp2_option_set_max_reserved_remote_streams */ + nghttp2_option_new(&option); + nghttp2_option_set_max_reserved_remote_streams(option, 99); + + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + CU_ASSERT(99 == session->max_incoming_reserved_streams); + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* Test for nghttp2_option_set_no_auto_ping_ack */ + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_ping_ack(option, 1); + + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_PING_ACK); + + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* Test for nghttp2_option_set_max_deflate_dynamic_table_size */ + nghttp2_option_new(&option); + nghttp2_option_set_max_deflate_dynamic_table_size(option, 0); + + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + deflater = &session->hd_deflater; + + rv = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(1 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == deflater->deflate_hd_table_bufsize_max); + CU_ASSERT(0 == deflater->ctx.hd_table_bufsize); + + nghttp2_session_del(session); + nghttp2_option_del(option); +} + +void test_nghttp2_session_data_backoff_by_high_pri_frame(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_data_provider data_prd; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.frame_send_cb_called = 0; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 4; + + nghttp2_session_client_new(&session, &callbacks, &ud); + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); + + session->remote_window_size = 1 << 20; + + ud.block_count = 2; + /* Sends request HEADERS + DATA[0] */ + CU_ASSERT(0 == nghttp2_session_send(session)); + + stream = nghttp2_session_get_stream(session, 1); + stream->remote_window_size = 1 << 20; + + CU_ASSERT(NGHTTP2_DATA == ud.sent_frame_type); + /* data for DATA[1] is read from data_prd but it is not sent */ + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2); + + nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); + ud.block_count = 2; + /* Sends DATA[1] + PING, PING is interleaved in DATA sequence */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_PING == ud.sent_frame_type); + /* data for DATA[2] is read from data_prd but it is not sent */ + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN); + + ud.block_count = 2; + /* Sends DATA[2..3] */ + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(stream->shut_flags & NGHTTP2_SHUT_WR); + + nghttp2_session_del(session); +} + +static void check_session_recv_data_with_padding(nghttp2_bufs *bufs, + size_t datalen, + nghttp2_mem *mem) { + nghttp2_session *session; + my_user_data ud; + nghttp2_session_callbacks callbacks; + uint8_t *in; + size_t inlen; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 1); + + inlen = (size_t)nghttp2_bufs_remove(bufs, &in); + + ud.frame_recv_cb_called = 0; + ud.data_chunk_len = 0; + + CU_ASSERT((ssize_t)inlen == nghttp2_session_mem_recv(session, in, inlen)); + + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(datalen == ud.data_chunk_len); + + mem->free(in, NULL); + nghttp2_session_del(session); +} + +void test_nghttp2_session_pack_data_with_padding(void) { + nghttp2_session *session; + my_user_data ud; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + nghttp2_frame *frame; + size_t datalen = 55; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = block_count_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.select_padding_callback = select_padding_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + ud.padlen = 63; + + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); + ud.block_count = 1; + ud.data_source_length = datalen; + /* Sends HEADERS */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); + + frame = &session->aob.item->frame; + + CU_ASSERT(ud.padlen == frame->data.padlen); + CU_ASSERT(frame->hd.flags & NGHTTP2_FLAG_PADDED); + + /* Check reception of this DATA frame */ + check_session_recv_data_with_padding(&session->aob.framebufs, datalen, mem); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_pack_headers_with_padding(void) { + nghttp2_session *session, *sv_session; + accumulator acc; + my_user_data ud; + nghttp2_session_callbacks callbacks; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = accumulator_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.select_padding_callback = select_padding_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + acc.length = 0; + ud.acc = &acc; + + nghttp2_session_client_new(&session, &callbacks, &ud); + nghttp2_session_server_new(&sv_session, &callbacks, &ud); + + ud.padlen = 163; + + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), + NULL, NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(acc.length < NGHTTP2_MAX_PAYLOADLEN); + ud.frame_recv_cb_called = 0; + CU_ASSERT((ssize_t)acc.length == + nghttp2_session_mem_recv(sv_session, acc.buf, acc.length)); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(sv_session)); + + nghttp2_session_del(sv_session); + nghttp2_session_del(session); +} + +void test_nghttp2_pack_settings_payload(void) { + nghttp2_settings_entry iv[2]; + uint8_t buf[64]; + ssize_t len; + nghttp2_settings_entry *resiv; + size_t resniv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 1023; + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 4095; + + len = nghttp2_pack_settings_payload(buf, sizeof(buf), iv, 2); + CU_ASSERT(2 * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH == len); + CU_ASSERT(0 == nghttp2_frame_unpack_settings_payload2(&resiv, &resniv, buf, + (size_t)len, mem)); + CU_ASSERT(2 == resniv); + CU_ASSERT(NGHTTP2_SETTINGS_HEADER_TABLE_SIZE == resiv[0].settings_id); + CU_ASSERT(1023 == resiv[0].value); + CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE == resiv[1].settings_id); + CU_ASSERT(4095 == resiv[1].value); + + mem->free(resiv, NULL); + + len = nghttp2_pack_settings_payload(buf, 9 /* too small */, iv, 2); + CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == len); +} + +#define check_stream_dep_sib(STREAM, DEP_PREV, DEP_NEXT, SIB_PREV, SIB_NEXT) \ + do { \ + CU_ASSERT(DEP_PREV == STREAM->dep_prev); \ + CU_ASSERT(DEP_NEXT == STREAM->dep_next); \ + CU_ASSERT(SIB_PREV == STREAM->sib_prev); \ + CU_ASSERT(SIB_NEXT == STREAM->sib_next); \ + } while (0) + +/* nghttp2_stream_dep_add() and its families functions should be + tested in nghttp2_stream_test.c, but it is easier to use + nghttp2_session_open_stream(). Therefore, we test them here. */ +void test_nghttp2_session_stream_dep_add(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *root; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + + c = open_stream_with_dep(session, 5, a); + b = open_stream_with_dep(session, 3, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * b--c + * | + * d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + + check_stream_dep_sib(a, root, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, c); + check_stream_dep_sib(c, a, d, b, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + CU_ASSERT(a == session->root.dep_next); + + e = open_stream_with_dep_excl(session, 9, a); + + /* a + * | + * e + * | + * b--c + * | + * d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == e->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + + check_stream_dep_sib(a, root, e, NULL, NULL); + check_stream_dep_sib(e, a, b, NULL, NULL); + check_stream_dep_sib(b, e, NULL, NULL, c); + check_stream_dep_sib(c, e, d, b, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + CU_ASSERT(a == session->root.dep_next); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_dep_remove(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *f, *root; + + memset(&callbacks, 0, sizeof(callbacks)); + + /* Remove root */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + nghttp2_stream_dep_remove(a); + + /* becomes: + * c b + * | + * d + */ + + CU_ASSERT(0 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + + check_stream_dep_sib(a, NULL, NULL, NULL, NULL); + check_stream_dep_sib(b, root, NULL, c, NULL); + check_stream_dep_sib(c, root, d, NULL, b); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + CU_ASSERT(c == session->root.dep_next); + + nghttp2_session_del(session); + + /* Remove right most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + nghttp2_stream_dep_remove(b); + + /* becomes: + * a + * | + * c + * | + * d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + + check_stream_dep_sib(a, root, c, NULL, NULL); + check_stream_dep_sib(b, NULL, NULL, NULL, NULL); + check_stream_dep_sib(c, a, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + CU_ASSERT(a == session->root.dep_next); + + nghttp2_session_del(session); + + /* Remove left most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + e = open_stream_with_dep(session, 9, c); + + /* a + * | + * c--b + * | + * e--d + */ + + nghttp2_stream_dep_remove(c); + + /* becomes: + * a + * | + * e--d--b + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(0 == c->sum_dep_weight); + CU_ASSERT(0 == e->sum_dep_weight); + + check_stream_dep_sib(a, root, e, NULL, NULL); + check_stream_dep_sib(b, a, NULL, d, NULL); + check_stream_dep_sib(c, NULL, NULL, NULL, NULL); + check_stream_dep_sib(d, a, NULL, e, b); + check_stream_dep_sib(e, a, NULL, NULL, d); + + nghttp2_session_del(session); + + /* Remove middle stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, a); + e = open_stream_with_dep(session, 9, c); + f = open_stream_with_dep(session, 11, c); + + /* a + * | + * d--c--b + * | + * f--e + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 3 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(0 == e->sum_dep_weight); + CU_ASSERT(0 == f->sum_dep_weight); + + nghttp2_stream_dep_remove(c); + + /* becomes: + * a + * | + * d--f--e--b + */ + + /* c's weight 16 is distributed evenly to e and f. Each weight of e + and f becomes 8. */ + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 + 8 * 2 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(0 == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(0 == e->sum_dep_weight); + CU_ASSERT(0 == f->sum_dep_weight); + + check_stream_dep_sib(a, root, d, NULL, NULL); + check_stream_dep_sib(b, a, NULL, e, NULL); + check_stream_dep_sib(c, NULL, NULL, NULL, NULL); + check_stream_dep_sib(e, a, NULL, f, b); + check_stream_dep_sib(f, a, NULL, d, e); + check_stream_dep_sib(d, a, NULL, NULL, f); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_dep_add_subtree(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *f, *root; + + memset(&callbacks, 0, sizeof(callbacks)); + + /* dep_stream has dep_next */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + e = open_stream(session, 9); + f = open_stream_with_dep(session, 11, e); + + /* a e + * | | + * c--b f + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(e); + nghttp2_stream_dep_add_subtree(a, e); + + /* becomes + * a + * | + * e--c--b + * | | + * f d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 3 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == e->sum_dep_weight); + CU_ASSERT(0 == f->sum_dep_weight); + + check_stream_dep_sib(a, root, e, NULL, NULL); + check_stream_dep_sib(b, a, NULL, c, NULL); + check_stream_dep_sib(c, a, d, e, b); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + check_stream_dep_sib(e, a, f, NULL, c); + check_stream_dep_sib(f, e, NULL, NULL, NULL); + + nghttp2_session_del(session); + + /* dep_stream has dep_next and now we insert subtree */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + e = open_stream(session, 9); + f = open_stream_with_dep(session, 11, e); + + /* a e + * | | + * c--b f + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(e); + nghttp2_stream_dep_insert_subtree(a, e); + + /* becomes + * a + * | + * e + * | + * f--c--b + * | + * d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 3 == e->sum_dep_weight); + CU_ASSERT(0 == f->sum_dep_weight); + + check_stream_dep_sib(a, root, e, NULL, NULL); + check_stream_dep_sib(e, a, f, NULL, NULL); + check_stream_dep_sib(f, e, NULL, NULL, c); + check_stream_dep_sib(b, e, NULL, c, NULL); + check_stream_dep_sib(c, e, d, f, b); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_dep_remove_subtree(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *root; + + memset(&callbacks, 0, sizeof(callbacks)); + + /* Remove left most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(c); + + /* becomes + * a c + * | | + * b d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + + check_stream_dep_sib(a, root, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, NULL); + check_stream_dep_sib(c, NULL, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + nghttp2_session_del(session); + + /* Remove right most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(b); + + /* becomes + * a b + * | + * c + * | + * d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + + check_stream_dep_sib(a, root, c, NULL, NULL); + check_stream_dep_sib(c, a, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + check_stream_dep_sib(b, NULL, NULL, NULL, NULL); + + nghttp2_session_del(session); + + /* Remove middle stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + e = open_stream_with_dep(session, 9, a); + c = open_stream_with_dep(session, 5, a); + b = open_stream_with_dep(session, 3, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * b--c--e + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(c); + + /* becomes + * a c + * | | + * b--e d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(0 == e->sum_dep_weight); + + check_stream_dep_sib(a, root, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, e); + check_stream_dep_sib(e, a, NULL, b, NULL); + check_stream_dep_sib(c, NULL, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *root; + nghttp2_outbound_item *db, *dc; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + + c = open_stream(session, 5); + + /* a c + * | + * b + */ + + nghttp2_stream_dep_remove_subtree(c); + CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c)); + + /* + * c + * | + * a + * | + * b + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + + check_stream_dep_sib(c, root, a, NULL, NULL); + check_stream_dep_sib(a, c, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, NULL); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream(session, 3); + c = open_stream(session, 5); + + /* + * a b c + */ + + nghttp2_stream_dep_remove_subtree(c); + CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c)); + + /* + * c + * | + * b--a + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == c->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(0 == a->sum_dep_weight); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + + check_stream_dep_sib(c, root, b, NULL, NULL); + check_stream_dep_sib(b, c, NULL, NULL, a); + check_stream_dep_sib(a, c, NULL, b, NULL); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + + c = open_stream(session, 5); + d = open_stream_with_dep(session, 7, c); + + /* a c + * | | + * b d + */ + + nghttp2_stream_dep_remove_subtree(c); + CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c)); + + /* + * c + * | + * d--a + * | + * b + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + check_stream_dep_sib(c, root, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, a); + check_stream_dep_sib(a, c, b, d, NULL); + check_stream_dep_sib(b, a, NULL, NULL, NULL); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + + c = open_stream(session, 5); + d = open_stream_with_dep(session, 7, c); + + /* a c + * | | + * b d + */ + + db = create_data_ob_item(mem); + + nghttp2_stream_attach_item(b, db); + + nghttp2_stream_dep_remove_subtree(c); + CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c)); + + /* + * c + * | + * d--a + * | + * b + */ + + CU_ASSERT(c->queued); + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + check_stream_dep_sib(c, root, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, a); + check_stream_dep_sib(a, c, b, d, NULL); + check_stream_dep_sib(b, a, NULL, NULL, NULL); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + + c = open_stream(session, 5); + d = open_stream_with_dep(session, 7, c); + + /* a c + * | | + * b d + */ + + db = create_data_ob_item(mem); + dc = create_data_ob_item(mem); + + nghttp2_stream_attach_item(b, db); + nghttp2_stream_attach_item(c, dc); + + nghttp2_stream_dep_remove_subtree(c); + CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c)); + + /* + * c + * | + * d--a + * | + * b + */ + + CU_ASSERT(c->queued); + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!d->queued); + + check_stream_dep_sib(c, root, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, a); + check_stream_dep_sib(a, c, b, d, NULL); + check_stream_dep_sib(b, a, NULL, NULL, NULL); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_attach_item(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e; + nghttp2_outbound_item *da, *db, *dc, *dd; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + db = create_data_ob_item(mem); + + nghttp2_stream_attach_item(b, db); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + + /* Attach item to c */ + dc = create_data_ob_item(mem); + + nghttp2_stream_attach_item(c, dc); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + + /* Attach item to a */ + da = create_data_ob_item(mem); + + nghttp2_stream_attach_item(a, da); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + + /* Detach item from a */ + nghttp2_stream_detach_item(a); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + + /* Attach item to d */ + dd = create_data_ob_item(mem); + + nghttp2_stream_attach_item(d, dd); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + + /* Detach item from c */ + nghttp2_stream_detach_item(c); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + + /* Detach item from b */ + nghttp2_stream_detach_item(b); + + CU_ASSERT(a->queued); + CU_ASSERT(!b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + + /* exercises insertion */ + e = open_stream_with_dep_excl(session, 9, a); + + /* a + * | + * e + * | + * c--b + * | + * d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + /* exercises deletion */ + nghttp2_stream_dep_remove(e); + + /* a + * | + * c--b + * | + * d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(!b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + /* e's weight 16 is distributed equally among c and b, both now have + weight 8 each. */ + CU_ASSERT(8 == b->weight); + CU_ASSERT(8 == c->weight); + + /* da, db, dc have been detached */ + nghttp2_outbound_item_free(da, mem); + nghttp2_outbound_item_free(db, mem); + nghttp2_outbound_item_free(dc, mem); + free(da); + free(db); + free(dc); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + da = create_data_ob_item(mem); + db = create_data_ob_item(mem); + dc = create_data_ob_item(mem); + + nghttp2_stream_attach_item(a, da); + nghttp2_stream_attach_item(b, db); + nghttp2_stream_attach_item(c, dc); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + /* Detach item from a */ + nghttp2_stream_detach_item(a); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + /* da has been detached */ + nghttp2_outbound_item_free(da, mem); + free(da); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_attach_item_subtree(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *f; + nghttp2_outbound_item *da, *db, *dd, *de; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + e = open_stream_with_dep_weight(session, 9, 32, &session->root); + f = open_stream_with_dep(session, 11, e); + + /* + * a e + * | | + * c--b f + * | + * d + */ + + de = create_data_ob_item(mem); + + nghttp2_stream_attach_item(e, de); + + db = create_data_ob_item(mem); + + nghttp2_stream_attach_item(b, db); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Insert subtree e under a */ + + nghttp2_stream_dep_remove_subtree(e); + nghttp2_stream_dep_insert_subtree(a, e); + + /* + * a + * | + * e + * | + * f--c--b + * | + * d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree b */ + + nghttp2_stream_dep_remove_subtree(b); + + CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, b)); + + /* + * a b + * | + * e + * | + * f--c + * | + * d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree a, and add it to root again */ + + nghttp2_stream_dep_remove_subtree(a); + + CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, a)); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree c */ + + nghttp2_stream_dep_remove_subtree(c); + + CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, c)); + + /* + * a b c + * | | + * e d + * | + * f + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + dd = create_data_ob_item(mem); + + nghttp2_stream_attach_item(d, dd); + + /* Add subtree c to a */ + + nghttp2_stream_dep_remove_subtree(c); + nghttp2_stream_dep_add_subtree(a, c); + + /* + * a b + * | + * c--e + * | | + * d f + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Insert b under a */ + + nghttp2_stream_dep_remove_subtree(b); + nghttp2_stream_dep_insert_subtree(a, b); + + /* + * a + * | + * b + * | + * c--e + * | | + * d f + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(2 == nghttp2_pq_size(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree b */ + + nghttp2_stream_dep_remove_subtree(b); + CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, b)); + + /* + * b a + * | + * e--c + * | | + * f d + */ + + CU_ASSERT(!a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(2 == nghttp2_pq_size(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree c, and detach item from b, and then re-add + subtree c under b */ + + nghttp2_stream_dep_remove_subtree(c); + nghttp2_stream_detach_item(b); + nghttp2_stream_dep_add_subtree(b, c); + + /* + * b a + * | + * e--c + * | | + * f d + */ + + CU_ASSERT(!a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(2 == nghttp2_pq_size(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Attach data to a, and add subtree a under b */ + + da = create_data_ob_item(mem); + nghttp2_stream_attach_item(a, da); + nghttp2_stream_dep_remove_subtree(a); + nghttp2_stream_dep_add_subtree(b, a); + + /* + * b + * | + * a--e--c + * | | + * f d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(3 == nghttp2_pq_size(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree c, and add under f */ + nghttp2_stream_dep_remove_subtree(c); + nghttp2_stream_dep_insert_subtree(f, c); + + /* + * b + * | + * a--e + * | + * f + * | + * c + * | + * d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(f->queued); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(2 == nghttp2_pq_size(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&e->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&f->obq)); + + /* db has been detached */ + nghttp2_outbound_item_free(db, mem); + free(db); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_get_state(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_mem *mem; + nghttp2_hd_deflater deflater; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_stream *stream; + ssize_t rv; + nghttp2_data_provider data_prd; + nghttp2_frame frame; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + memset(&data_prd, 0, sizeof(data_prd)); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_hd_deflate_init(&deflater, mem); + + CU_ASSERT(NGHTTP2_STREAM_STATE_IDLE == + nghttp2_stream_get_state(nghttp2_session_get_root_stream(session))); + + /* stream 1 HEADERS; without END_STREAM flag set */ + pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 1); + + CU_ASSERT(NULL != stream); + CU_ASSERT(1 == stream->stream_id); + CU_ASSERT(NGHTTP2_STREAM_STATE_OPEN == nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + /* stream 3 HEADERS; with END_STREAM flag set */ + pack_headers(&bufs, &deflater, 3, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 3); + + CU_ASSERT(NULL != stream); + CU_ASSERT(3 == stream->stream_id); + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE == + nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + /* Respond to stream 1 */ + nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL == + nghttp2_stream_get_state(stream)); + + /* Respond to stream 3 */ + nghttp2_submit_response(session, 3, resnv, ARRLEN(resnv), NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 3); + + CU_ASSERT(NGHTTP2_STREAM_STATE_CLOSED == nghttp2_stream_get_state(stream)); + + /* stream 5 HEADERS; with END_STREAM flag set */ + pack_headers(&bufs, &deflater, 5, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + nghttp2_bufs_reset(&bufs); + + /* Push stream 2 associated to stream 5 */ + rv = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 5, reqnv, + ARRLEN(reqnv), NULL); + + CU_ASSERT(2 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_LOCAL == + nghttp2_stream_get_state(stream)); + + /* Send response to push stream 2 with END_STREAM set */ + nghttp2_submit_response(session, 2, resnv, ARRLEN(resnv), NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 2); + + /* At server, pushed stream object is not retained after closed */ + CU_ASSERT(NULL == stream); + + /* Push stream 4 associated to stream 5 */ + rv = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 5, reqnv, + ARRLEN(reqnv), NULL); + + CU_ASSERT(4 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 4); + + CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_LOCAL == + nghttp2_stream_get_state(stream)); + + /* Send response to push stream 4 without closing */ + data_prd.read_callback = defer_data_source_read_callback; + + nghttp2_submit_response(session, 4, resnv, ARRLEN(resnv), &data_prd); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 4); + + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE == + nghttp2_stream_get_state(stream)); + + /* Create idle stream by PRIORITY frame */ + nghttp2_frame_priority_init(&frame.priority, 7, &pri_spec_default); + + rv = nghttp2_frame_pack_priority(&bufs, &frame.priority); + + CU_ASSERT(0 == rv); + + nghttp2_frame_priority_free(&frame.priority); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 7); + + CU_ASSERT(NGHTTP2_STREAM_STATE_IDLE == nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Test for client side */ + + nghttp2_session_client_new(&session, &callbacks, NULL); + nghttp2_hd_deflate_init(&deflater, mem); + + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + /* Receive PUSH_PROMISE 2 associated to stream 1 */ + pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_REMOTE == + nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + /* Receive push response for stream 2 without END_STREAM set */ + pack_headers(&bufs, &deflater, 2, NGHTTP2_FLAG_END_HEADERS, resnv, + ARRLEN(resnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL == + nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_stream_get_something(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + + CU_ASSERT(nghttp2_session_get_root_stream(session) == + nghttp2_stream_get_parent(a)); + CU_ASSERT(NULL == nghttp2_stream_get_previous_sibling(a)); + CU_ASSERT(NULL == nghttp2_stream_get_next_sibling(a)); + CU_ASSERT(NULL == nghttp2_stream_get_first_child(a)); + + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep_weight(session, 5, 11, a); + + CU_ASSERT(a == nghttp2_stream_get_parent(c)); + CU_ASSERT(a == nghttp2_stream_get_parent(b)); + + CU_ASSERT(c == nghttp2_stream_get_first_child(a)); + + CU_ASSERT(b == nghttp2_stream_get_next_sibling(c)); + CU_ASSERT(c == nghttp2_stream_get_previous_sibling(b)); + + CU_ASSERT(27 == nghttp2_stream_get_sum_dependency_weight(a)); + + CU_ASSERT(11 == nghttp2_stream_get_weight(c)); + CU_ASSERT(5 == nghttp2_stream_get_stream_id(c)); + CU_ASSERT(0 == nghttp2_stream_get_stream_id(&session->root)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_find_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + open_recv_stream(session, 1); + + stream = nghttp2_session_find_stream(session, 1); + + CU_ASSERT(NULL != stream); + CU_ASSERT(1 == stream->stream_id); + + stream = nghttp2_session_find_stream(session, 0); + + CU_ASSERT(&session->root == stream); + CU_ASSERT(0 == stream->stream_id); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NULL == stream); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_keep_closed_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const size_t max_concurrent_streams = 5; + nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, + (uint32_t)max_concurrent_streams}; + size_t i; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + for (i = 0; i < max_concurrent_streams; ++i) { + open_recv_stream(session, (int32_t)i * 2 + 1); + } + + CU_ASSERT(0 == session->num_closed_streams); + + nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR); + + CU_ASSERT(1 == session->num_closed_streams); + CU_ASSERT(1 == session->closed_stream_tail->stream_id); + CU_ASSERT(session->closed_stream_tail == session->closed_stream_head); + + nghttp2_session_close_stream(session, 5, NGHTTP2_NO_ERROR); + + CU_ASSERT(2 == session->num_closed_streams); + CU_ASSERT(5 == session->closed_stream_tail->stream_id); + CU_ASSERT(1 == session->closed_stream_head->stream_id); + CU_ASSERT(session->closed_stream_head == + session->closed_stream_tail->closed_prev); + CU_ASSERT(NULL == session->closed_stream_tail->closed_next); + CU_ASSERT(session->closed_stream_tail == + session->closed_stream_head->closed_next); + CU_ASSERT(NULL == session->closed_stream_head->closed_prev); + + open_recv_stream(session, 11); + nghttp2_session_adjust_closed_stream(session); + + CU_ASSERT(1 == session->num_closed_streams); + CU_ASSERT(5 == session->closed_stream_tail->stream_id); + CU_ASSERT(session->closed_stream_tail == session->closed_stream_head); + CU_ASSERT(NULL == session->closed_stream_head->closed_prev); + CU_ASSERT(NULL == session->closed_stream_head->closed_next); + + open_recv_stream(session, 13); + nghttp2_session_adjust_closed_stream(session); + + CU_ASSERT(0 == session->num_closed_streams); + CU_ASSERT(NULL == session->closed_stream_tail); + CU_ASSERT(NULL == session->closed_stream_head); + + nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR); + + CU_ASSERT(1 == session->num_closed_streams); + CU_ASSERT(3 == session->closed_stream_head->stream_id); + + /* server initiated stream is not counted to max concurrent limit */ + open_sent_stream(session, 2); + nghttp2_session_adjust_closed_stream(session); + + CU_ASSERT(1 == session->num_closed_streams); + CU_ASSERT(3 == session->closed_stream_head->stream_id); + + nghttp2_session_close_stream(session, 2, NGHTTP2_NO_ERROR); + + CU_ASSERT(1 == session->num_closed_streams); + CU_ASSERT(3 == session->closed_stream_head->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_keep_idle_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const size_t max_concurrent_streams = 1; + nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, + (uint32_t)max_concurrent_streams}; + int i; + int32_t stream_id; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + /* We at least allow NGHTTP2_MIN_IDLE_STREAM idle streams even if + max concurrent streams is very low. */ + for (i = 0; i < NGHTTP2_MIN_IDLE_STREAMS; ++i) { + open_recv_stream2(session, i * 2 + 1, NGHTTP2_STREAM_IDLE); + nghttp2_session_adjust_idle_stream(session); + } + + CU_ASSERT(NGHTTP2_MIN_IDLE_STREAMS == session->num_idle_streams); + + stream_id = (NGHTTP2_MIN_IDLE_STREAMS - 1) * 2 + 1; + CU_ASSERT(1 == session->idle_stream_head->stream_id); + CU_ASSERT(stream_id == session->idle_stream_tail->stream_id); + + stream_id += 2; + + open_recv_stream2(session, stream_id, NGHTTP2_STREAM_IDLE); + nghttp2_session_adjust_idle_stream(session); + + CU_ASSERT(NGHTTP2_MIN_IDLE_STREAMS == session->num_idle_streams); + CU_ASSERT(3 == session->idle_stream_head->stream_id); + CU_ASSERT(stream_id == session->idle_stream_tail->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_detach_idle_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + int i; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + for (i = 1; i <= 3; ++i) { + nghttp2_session_open_stream(session, i, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, NGHTTP2_STREAM_IDLE, NULL); + } + + CU_ASSERT(3 == session->num_idle_streams); + + /* Detach middle stream */ + stream = nghttp2_session_get_stream_raw(session, 2); + + CU_ASSERT(session->idle_stream_head == stream->closed_prev); + CU_ASSERT(session->idle_stream_tail == stream->closed_next); + CU_ASSERT(stream == session->idle_stream_head->closed_next); + CU_ASSERT(stream == session->idle_stream_tail->closed_prev); + + nghttp2_session_detach_idle_stream(session, stream); + + CU_ASSERT(2 == session->num_idle_streams); + + CU_ASSERT(NULL == stream->closed_prev); + CU_ASSERT(NULL == stream->closed_next); + + CU_ASSERT(session->idle_stream_head == + session->idle_stream_tail->closed_prev); + CU_ASSERT(session->idle_stream_tail == + session->idle_stream_head->closed_next); + + /* Detach head stream */ + stream = session->idle_stream_head; + + nghttp2_session_detach_idle_stream(session, stream); + + CU_ASSERT(1 == session->num_idle_streams); + + CU_ASSERT(session->idle_stream_head == session->idle_stream_tail); + CU_ASSERT(NULL == session->idle_stream_head->closed_prev); + CU_ASSERT(NULL == session->idle_stream_head->closed_next); + + /* Detach last stream */ + + stream = session->idle_stream_head; + + nghttp2_session_detach_idle_stream(session, stream); + + CU_ASSERT(0 == session->num_idle_streams); + + CU_ASSERT(NULL == session->idle_stream_head); + CU_ASSERT(NULL == session->idle_stream_tail); + + for (i = 4; i <= 5; ++i) { + nghttp2_session_open_stream(session, i, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, NGHTTP2_STREAM_IDLE, NULL); + } + + CU_ASSERT(2 == session->num_idle_streams); + + /* Detach tail stream */ + + stream = session->idle_stream_tail; + + nghttp2_session_detach_idle_stream(session, stream); + + CU_ASSERT(1 == session->num_idle_streams); + + CU_ASSERT(session->idle_stream_head == session->idle_stream_tail); + CU_ASSERT(NULL == session->idle_stream_head->closed_prev); + CU_ASSERT(NULL == session->idle_stream_head->closed_next); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_large_dep_tree(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + size_t i; + nghttp2_stream *dep_stream = NULL; + nghttp2_stream *stream; + int32_t stream_id; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream_id = 1; + for (i = 0; i < 250; ++i, stream_id += 2) { + dep_stream = open_stream_with_dep(session, stream_id, dep_stream); + } + + stream_id = 1; + for (i = 0; i < 250; ++i, stream_id += 2) { + stream = nghttp2_session_get_stream(session, stream_id); + CU_ASSERT(nghttp2_stream_dep_find_ancestor(stream, &session->root)); + CU_ASSERT(nghttp2_stream_in_dep_tree(stream)); + } + + nghttp2_session_del(session); +} + +void test_nghttp2_session_graceful_shutdown(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.on_stream_close_callback = on_stream_close_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 301); + open_sent_stream(session, 302); + open_recv_stream(session, 309); + open_recv_stream(session, 311); + open_recv_stream(session, 319); + + CU_ASSERT(0 == nghttp2_submit_shutdown_notice(session)); + + ud.frame_send_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT((1u << 31) - 1 == session->local_last_stream_id); + + CU_ASSERT(0 == nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, 311, + NGHTTP2_NO_ERROR, NULL, 0)); + + ud.frame_send_cb_called = 0; + ud.stream_close_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(311 == session->local_last_stream_id); + CU_ASSERT(1 == ud.stream_close_cb_called); + + CU_ASSERT(0 == + nghttp2_session_terminate_session2(session, 301, NGHTTP2_NO_ERROR)); + + ud.frame_send_cb_called = 0; + ud.stream_close_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(301 == session->local_last_stream_id); + CU_ASSERT(2 == ud.stream_close_cb_called); + + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 301)); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 302)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 309)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 311)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 319)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_header_temporal_failure(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_hd_deflater deflater; + nghttp2_nv nv[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")}; + nghttp2_nv *nva; + size_t hdpos; + ssize_t rv; + nghttp2_frame frame; + nghttp2_frame_hd hd; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.on_header_callback = temporal_failure_on_header_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + frame_pack_bufs_init(&bufs); + + nghttp2_hd_deflate_init(&deflater, mem); + + nghttp2_nv_array_copy(&nva, reqnv, ARRLEN(reqnv), mem); + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_STREAM, 1, + NGHTTP2_HCAT_REQUEST, NULL, nva, ARRLEN(reqnv)); + nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + nghttp2_frame_headers_free(&frame.headers, mem); + + /* We are going to create CONTINUATION. First serialize header + block, and then frame header. */ + hdpos = nghttp2_bufs_len(&bufs); + + buf = &bufs.head->buf; + buf->last += NGHTTP2_FRAME_HDLEN; + + nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, &nv[1], 1); + + nghttp2_frame_hd_init(&hd, + nghttp2_bufs_len(&bufs) - hdpos - NGHTTP2_FRAME_HDLEN, + NGHTTP2_CONTINUATION, NGHTTP2_FLAG_END_HEADERS, 1); + + nghttp2_frame_pack_frame_hd(&buf->pos[hdpos], &hd); + + ud.header_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.header_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(1 == item->frame.hd.stream_id); + + /* Make sure no header decompression error occurred */ + CU_ASSERT(NGHTTP2_GOAWAY_NONE == session->goaway_flags); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_bufs_reset(&bufs); + + /* Check for PUSH_PROMISE */ + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_session_client_new(&session, &callbacks, &ud); + + open_sent_stream(session, 1); + + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, + reqnv, ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + ud.header_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.header_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(2 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_INTERNAL_ERROR == item->frame.rst_stream.error_code); + + nghttp2_session_del(session); + nghttp2_hd_deflate_free(&deflater); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_recv_client_magic(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + ssize_t rv; + nghttp2_frame ping_frame; + uint8_t buf[16]; + + /* enable global nghttp2_enable_strict_preface here */ + nghttp2_enable_strict_preface = 1; + + memset(&callbacks, 0, sizeof(callbacks)); + + /* Check success case */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + rv = nghttp2_session_mem_recv(session, (const uint8_t *)NGHTTP2_CLIENT_MAGIC, + NGHTTP2_CLIENT_MAGIC_LEN); + + CU_ASSERT(rv == NGHTTP2_CLIENT_MAGIC_LEN); + CU_ASSERT(NGHTTP2_IB_READ_FIRST_SETTINGS == session->iframe.state); + + /* Receiving PING is error because we want SETTINGS. */ + nghttp2_frame_ping_init(&ping_frame.ping, NGHTTP2_FLAG_NONE, NULL); + + nghttp2_frame_pack_frame_hd(buf, &ping_frame.ping.hd); + + rv = nghttp2_session_mem_recv(session, buf, NGHTTP2_FRAME_HDLEN); + CU_ASSERT(NGHTTP2_FRAME_HDLEN == rv); + CU_ASSERT(NGHTTP2_IB_IGN_ALL == session->iframe.state); + CU_ASSERT(0 == session->iframe.payloadleft); + + nghttp2_frame_ping_free(&ping_frame.ping); + + nghttp2_session_del(session); + + /* Check bad case */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* Feed magic with one byte less */ + rv = nghttp2_session_mem_recv(session, (const uint8_t *)NGHTTP2_CLIENT_MAGIC, + NGHTTP2_CLIENT_MAGIC_LEN - 1); + + CU_ASSERT(rv == NGHTTP2_CLIENT_MAGIC_LEN - 1); + CU_ASSERT(NGHTTP2_IB_READ_CLIENT_MAGIC == session->iframe.state); + CU_ASSERT(1 == session->iframe.payloadleft); + + rv = nghttp2_session_mem_recv(session, (const uint8_t *)"\0", 1); + + CU_ASSERT(NGHTTP2_ERR_BAD_CLIENT_MAGIC == rv); + + nghttp2_session_del(session); + + /* disable global nghttp2_enable_strict_preface here */ + nghttp2_enable_strict_preface = 0; +} + +void test_nghttp2_session_delete_data_item(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a; + nghttp2_data_provider prd; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_recv_stream(session, 1); + open_recv_stream_with_dep(session, 3, a); + + /* We don't care about these members, since we won't send data */ + prd.source.ptr = NULL; + prd.read_callback = fail_data_source_read_callback; + + CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, 1, &prd)); + CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, 3, &prd)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_open_idle_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_stream *opened_stream; + nghttp2_priority_spec pri_spec; + nghttp2_frame frame; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_priority_spec_init(&pri_spec, 0, 3, 0); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(NULL == stream->closed_prev); + CU_ASSERT(NULL == stream->closed_next); + CU_ASSERT(1 == session->num_idle_streams); + CU_ASSERT(session->idle_stream_head == stream); + CU_ASSERT(session->idle_stream_tail == stream); + + opened_stream = open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + CU_ASSERT(stream == opened_stream); + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + CU_ASSERT(0 == session->num_idle_streams); + CU_ASSERT(NULL == session->idle_stream_head); + CU_ASSERT(NULL == session->idle_stream_tail); + + nghttp2_frame_priority_free(&frame.priority); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_cancel_reserved_remote(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_frame frame; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + stream = open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2, NGHTTP2_CANCEL); + + CU_ASSERT(NGHTTP2_STREAM_CLOSING == stream->state); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nvlen = ARRLEN(resnv); + nghttp2_nv_array_copy(&nva, resnv, nvlen, mem); + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_PUSH_RESPONSE, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + /* stream is not dangling, so assign NULL */ + stream = NULL; + + /* No RST_STREAM or GOAWAY is generated since stream should be in + NGHTTP2_STREAM_CLOSING and push response should be ignored. */ + CU_ASSERT(0 == nghttp2_outbound_queue_size(&session->ob_reg)); + + /* Check that we can receive push response HEADERS while RST_STREAM + is just queued. */ + open_recv_stream2(session, 4, NGHTTP2_STREAM_RESERVED); + + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2, NGHTTP2_CANCEL); + + nghttp2_bufs_reset(&bufs); + + frame.hd.stream_id = 4; + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(1 == nghttp2_outbound_queue_size(&session->ob_reg)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_reset_pending_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + int32_t stream_id; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + callbacks.on_stream_close_callback = on_stream_close_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + stream_id = nghttp2_submit_request(session, NULL, NULL, 0, NULL, NULL); + CU_ASSERT(stream_id >= 1); + + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, + NGHTTP2_CANCEL); + + session->remote_settings.max_concurrent_streams = 0; + + /* RST_STREAM cancels pending HEADERS and is not actually sent. */ + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(0 == ud.frame_send_cb_called); + + stream = nghttp2_session_get_stream(session, stream_id); + + CU_ASSERT(NULL == stream); + + /* See HEADERS is not sent. on_stream_close is called just like + transmission failure. */ + session->remote_settings.max_concurrent_streams = 1; + + ud.frame_not_send_cb_called = 0; + ud.stream_close_error_code = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.not_sent_frame_type); + CU_ASSERT(NGHTTP2_CANCEL == ud.stream_close_error_code); + + stream = nghttp2_session_get_stream(session, stream_id); + + CU_ASSERT(NULL == stream); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_data_callback(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + accumulator acc; + nghttp2_frame_hd hd; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + callbacks.send_data_callback = send_data_callback; + + data_prd.read_callback = no_copy_data_source_read_callback; + + acc.length = 0; + ud.acc = &acc; + + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + open_sent_stream(session, 1); + + nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT((NGHTTP2_FRAME_HDLEN + NGHTTP2_DATA_PAYLOADLEN) * 2 == acc.length); + + nghttp2_frame_unpack_frame_hd(&hd, acc.buf); + + CU_ASSERT(16384 == hd.length); + CU_ASSERT(NGHTTP2_DATA == hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + + nghttp2_frame_unpack_frame_hd(&hd, acc.buf + NGHTTP2_FRAME_HDLEN + hd.length); + + CU_ASSERT(16384 == hd.length); + CU_ASSERT(NGHTTP2_DATA == hd.type); + CU_ASSERT(NGHTTP2_FLAG_END_STREAM == hd.flags); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_begin_headers_temporal_failure(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_bufs bufs; + nghttp2_mem *mem; + ssize_t rv; + nghttp2_hd_deflater deflater; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + nghttp2_hd_deflate_init(&deflater, mem); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_begin_headers_callback = + temporal_failure_on_begin_headers_callback; + callbacks.on_header_callback = on_header_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.send_callback = null_send_callback; + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + ud.header_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.header_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(1 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_INTERNAL_ERROR == item->frame.rst_stream.error_code); + + nghttp2_session_del(session); + nghttp2_hd_deflate_free(&deflater); + + nghttp2_bufs_reset(&bufs); + /* check for PUSH_PROMISE */ + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_session_client_new(&session, &callbacks, &ud); + + open_sent_stream(session, 1); + + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, + reqnv, ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + ud.header_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.header_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(2 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_INTERNAL_ERROR == item->frame.rst_stream.error_code); + + nghttp2_session_del(session); + nghttp2_hd_deflate_free(&deflater); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_defer_then_close(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider prd; + int rv; + const uint8_t *datap; + ssize_t datalen; + nghttp2_frame frame; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + prd.read_callback = defer_data_source_read_callback; + + rv = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), &prd, NULL); + CU_ASSERT(rv > 0); + + /* This sends HEADERS */ + datalen = nghttp2_session_mem_send(session, &datap); + + CU_ASSERT(datalen > 0); + + /* This makes DATA item deferred */ + datalen = nghttp2_session_mem_send(session, &datap); + + CU_ASSERT(datalen == 0); + + nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_CANCEL); + + /* Assertion failure; GH-264 */ + rv = nghttp2_session_on_rst_stream_received(session, &frame); + + CU_ASSERT(rv == 0); + + nghttp2_session_del(session); +} + +static int submit_response_on_stream_close(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, + void *user_data) { + nghttp2_data_provider data_prd; + (void)error_code; + (void)user_data; + + data_prd.read_callback = temporal_failure_data_source_read_callback; + + // Attempt to submit response or data to the stream being closed + switch (stream_id) { + case 1: + CU_ASSERT(0 == nghttp2_submit_response(session, stream_id, resnv, + ARRLEN(resnv), &data_prd)); + break; + case 3: + CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, stream_id, + &data_prd)); + break; + } + + return 0; +} + +void test_nghttp2_session_detach_item_from_closed_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + + memset(&callbacks, 0, sizeof(callbacks)); + + callbacks.send_callback = null_send_callback; + callbacks.on_stream_close_callback = submit_response_on_stream_close; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + open_recv_stream(session, 1); + open_recv_stream(session, 3); + + nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR); + nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_flooding(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_frame frame; + nghttp2_mem *mem; + size_t i; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(callbacks)); + + /* PING ACK */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL); + nghttp2_frame_pack_ping(&bufs, &frame.ping); + nghttp2_frame_ping_free(&frame.ping); + + buf = &bufs.head->buf; + + for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) { + CU_ASSERT( + (ssize_t)nghttp2_buf_len(buf) == + nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); + } + + CU_ASSERT(NGHTTP2_ERR_FLOODED == + nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); + + nghttp2_session_del(session); + + /* SETTINGS ACK */ + nghttp2_bufs_reset(&bufs); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0); + nghttp2_frame_pack_settings(&bufs, &frame.settings); + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + + for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) { + CU_ASSERT( + (ssize_t)nghttp2_buf_len(buf) == + nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); + } + + CU_ASSERT(NGHTTP2_ERR_FLOODED == + nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); + + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_change_stream_priority(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream1, *stream2, *stream3, *stream5; + nghttp2_priority_spec pri_spec; + int rv; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream1 = open_recv_stream(session, 1); + stream3 = open_recv_stream_with_dep_weight(session, 3, 199, stream1); + stream2 = open_sent_stream_with_dep_weight(session, 2, 101, stream3); + + nghttp2_priority_spec_init(&pri_spec, 1, 256, 0); + + rv = nghttp2_session_change_stream_priority(session, 2, &pri_spec); + + CU_ASSERT(0 == rv); + + CU_ASSERT(stream1 == stream2->dep_prev); + CU_ASSERT(256 == stream2->weight); + + /* Cannot change stream which does not exist */ + rv = nghttp2_session_change_stream_priority(session, 5, &pri_spec); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* It is an error to depend on itself */ + rv = nghttp2_session_change_stream_priority(session, 1, &pri_spec); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* It is an error to change priority of root stream (0) */ + rv = nghttp2_session_change_stream_priority(session, 0, &pri_spec); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* Depends on the non-existing idle stream. This creates that idle + stream. */ + nghttp2_priority_spec_init(&pri_spec, 5, 9, 1); + + rv = nghttp2_session_change_stream_priority(session, 2, &pri_spec); + + CU_ASSERT(0 == rv); + + stream5 = nghttp2_session_get_stream_raw(session, 5); + + CU_ASSERT(NULL != stream5); + CU_ASSERT(&session->root == stream5->dep_prev); + CU_ASSERT(stream5 == stream2->dep_prev); + CU_ASSERT(9 == stream2->weight); + + nghttp2_session_del(session); + + /* Check that this works in client session too */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + stream1 = open_sent_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 5, 9, 1); + + rv = nghttp2_session_change_stream_priority(session, 1, &pri_spec); + + CU_ASSERT(0 == rv); + + stream5 = nghttp2_session_get_stream_raw(session, 5); + + CU_ASSERT(NULL != stream5); + CU_ASSERT(&session->root == stream5->dep_prev); + CU_ASSERT(stream5 == stream1->dep_prev); + CU_ASSERT(9 == stream1->weight); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_change_extpri_stream_priority(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + nghttp2_option *option; + nghttp2_extension frame; + nghttp2_ext_priority_update priority_update; + nghttp2_extpri extpri; + nghttp2_stream *stream; + static const uint8_t field_value[] = "u=2"; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + frame_pack_bufs_init(&bufs); + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, + NGHTTP2_PRIORITY_UPDATE); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + session->pending_no_rfc7540_priorities = 1; + + open_recv_stream(session, 1); + + extpri.urgency = NGHTTP2_EXTPRI_URGENCY_LOW + 1; + extpri.inc = 1; + + rv = nghttp2_session_change_extpri_stream_priority( + session, 1, &extpri, /* ignore_client_signal = */ 0); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW == + nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + /* Client can still update stream priority. */ + frame.payload = &priority_update; + nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value, + sizeof(field_value) - 1); + nghttp2_frame_pack_priority_update(&bufs, &frame); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(2 == stream->extpri); + + /* Start to ignore client priority signal for this stream. */ + rv = nghttp2_session_change_extpri_stream_priority( + session, 1, &extpri, /* ignore_client_signal = */ 1); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW == + nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW == + nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_session_del(session); + nghttp2_option_del(option); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_create_idle_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream2, *stream4, *stream8, *stream10; + nghttp2_priority_spec pri_spec; + int rv; + int i; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream2 = open_sent_stream(session, 2); + + nghttp2_priority_spec_init(&pri_spec, 2, 111, 1); + + rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec); + + CU_ASSERT(0 == rv); + + stream4 = nghttp2_session_get_stream_raw(session, 4); + + CU_ASSERT(4 == stream4->stream_id); + CU_ASSERT(111 == stream4->weight); + CU_ASSERT(stream2 == stream4->dep_prev); + CU_ASSERT(stream4 == stream2->dep_next); + + /* If pri_spec->stream_id does not exist, and it is idle stream, it + is created too */ + nghttp2_priority_spec_init(&pri_spec, 10, 109, 0); + + rv = nghttp2_session_create_idle_stream(session, 8, &pri_spec); + + CU_ASSERT(0 == rv); + + stream8 = nghttp2_session_get_stream_raw(session, 8); + stream10 = nghttp2_session_get_stream_raw(session, 10); + + CU_ASSERT(8 == stream8->stream_id); + CU_ASSERT(109 == stream8->weight); + CU_ASSERT(10 == stream10->stream_id); + CU_ASSERT(16 == stream10->weight); + CU_ASSERT(stream10 == stream8->dep_prev); + CU_ASSERT(&session->root == stream10->dep_prev); + + /* It is an error to attempt to create already existing idle + stream */ + rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* It is an error to depend on itself */ + pri_spec.stream_id = 6; + + rv = nghttp2_session_create_idle_stream(session, 6, &pri_spec); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* It is an error to create root stream (0) as idle stream */ + rv = nghttp2_session_create_idle_stream(session, 0, &pri_spec); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* It is an error to create non-idle stream */ + session->last_sent_stream_id = 20; + pri_spec.stream_id = 2; + + rv = nghttp2_session_create_idle_stream(session, 18, &pri_spec); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + nghttp2_session_del(session); + + /* Check that this works in client session too */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_priority_spec_init(&pri_spec, 4, 99, 1); + + rv = nghttp2_session_create_idle_stream(session, 2, &pri_spec); + + CU_ASSERT(0 == rv); + + stream4 = nghttp2_session_get_stream_raw(session, 4); + stream2 = nghttp2_session_get_stream_raw(session, 2); + + CU_ASSERT(NULL != stream4); + CU_ASSERT(NULL != stream2); + CU_ASSERT(&session->root == stream4->dep_prev); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream4->weight); + CU_ASSERT(stream4 == stream2->dep_prev); + CU_ASSERT(99 == stream2->weight); + + nghttp2_session_del(session); + + /* Check that idle stream is reduced when nghttp2_session_send() is + called. */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + session->local_settings.max_concurrent_streams = 30; + + nghttp2_priority_spec_init(&pri_spec, 0, 16, 0); + for (i = 0; i < 100; ++i) { + rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec); + + CU_ASSERT(0 == rv); + + nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0); + } + + CU_ASSERT(100 == session->num_idle_streams); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(30 == session->num_idle_streams); + CU_ASSERT(141 == session->idle_stream_head->stream_id); + + nghttp2_session_del(session); + + /* Check that idle stream is reduced when nghttp2_session_mem_recv() is + called. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + session->local_settings.max_concurrent_streams = 30; + + nghttp2_priority_spec_init(&pri_spec, 0, 16, 0); + for (i = 0; i < 100; ++i) { + rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec); + + CU_ASSERT(0 == rv); + + nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0); + } + + CU_ASSERT(100 == session->num_idle_streams); + CU_ASSERT(0 == nghttp2_session_mem_recv(session, NULL, 0)); + CU_ASSERT(30 == session->num_idle_streams); + CU_ASSERT(141 == session->idle_stream_head->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_repeated_priority_change(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + nghttp2_priority_spec pri_spec; + int32_t stream_id, last_stream_id; + int32_t max_streams = 20; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + session->local_settings.max_concurrent_streams = (uint32_t)max_streams; + + /* 1 -> 0 */ + nghttp2_priority_spec_init(&pri_spec, 0, 16, 0); + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + nghttp2_frame_priority_free(&frame.priority); + + last_stream_id = max_streams * 2 + 1; + + for (stream_id = 3; stream_id < last_stream_id; stream_id += 2) { + /* 1 -> stream_id */ + nghttp2_priority_spec_init(&pri_spec, stream_id, 16, 0); + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + nghttp2_frame_priority_free(&frame.priority); + } + + CU_ASSERT(20 == session->num_idle_streams); + CU_ASSERT(1 == session->idle_stream_head->stream_id); + + /* 1 -> last_stream_id */ + nghttp2_priority_spec_init(&pri_spec, last_stream_id, 16, 0); + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + nghttp2_frame_priority_free(&frame.priority); + + CU_ASSERT(20 == session->num_idle_streams); + CU_ASSERT(3 == session->idle_stream_head->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_repeated_priority_submission(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_priority_spec pri_spec; + int32_t stream_id, last_stream_id; + uint32_t max_streams = NGHTTP2_MIN_IDLE_STREAMS; + + memset(&callbacks, 0, sizeof(callbacks)); + + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + session->local_settings.max_concurrent_streams = max_streams; + + /* 1 -> 0 */ + nghttp2_priority_spec_init(&pri_spec, 0, 16, 0); + + CU_ASSERT(0 == + nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec)); + + last_stream_id = (int32_t)(max_streams * 2 + 1); + + for (stream_id = 3; stream_id < last_stream_id; stream_id += 2) { + /* 1 -> stream_id */ + nghttp2_priority_spec_init(&pri_spec, stream_id, 16, 0); + + CU_ASSERT( + 0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec)); + } + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(max_streams == session->num_idle_streams); + CU_ASSERT(1 == session->idle_stream_head->stream_id); + + /* 1 -> last_stream_id */ + nghttp2_priority_spec_init(&pri_spec, last_stream_id, 16, 0); + + CU_ASSERT(0 == + nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec)); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(max_streams == session->num_idle_streams); + CU_ASSERT(3 == session->idle_stream_head->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_set_local_window_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + stream = open_sent_stream(session, 1); + stream->recv_window_size = 4096; + + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 65536)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 == + stream->local_window_size); + CU_ASSERT(4096 == stream->recv_window_size); + CU_ASSERT(65536 - 4096 == + nghttp2_session_get_stream_local_window_size(session, 1)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(1 == item->frame.window_update.hd.stream_id); + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Go decrement part */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 32768)); + CU_ASSERT(32768 == stream->local_window_size); + CU_ASSERT(-28672 == stream->recv_window_size); + CU_ASSERT(32768 == stream->recv_reduction); + CU_ASSERT(65536 - 4096 == + nghttp2_session_get_stream_local_window_size(session, 1)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item == NULL); + + /* Increase local window size */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 49152)); + CU_ASSERT(49152 == stream->local_window_size); + CU_ASSERT(-12288 == stream->recv_window_size); + CU_ASSERT(16384 == stream->recv_reduction); + CU_ASSERT(65536 - 4096 == + nghttp2_session_get_stream_local_window_size(session, 1)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + /* Increase local window again */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 65537)); + CU_ASSERT(65537 == stream->local_window_size); + CU_ASSERT(4096 == stream->recv_window_size); + CU_ASSERT(0 == stream->recv_reduction); + CU_ASSERT(65537 - 4096 == + nghttp2_session_get_stream_local_window_size(session, 1)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Check connection-level flow control */ + session->recv_window_size = 4096; + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 65536)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 == + session->local_window_size); + CU_ASSERT(4096 == session->recv_window_size); + CU_ASSERT(65536 - 4096 == nghttp2_session_get_local_window_size(session)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(0 == item->frame.window_update.hd.stream_id); + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Go decrement part */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 32768)); + CU_ASSERT(32768 == session->local_window_size); + CU_ASSERT(-28672 == session->recv_window_size); + CU_ASSERT(32768 == session->recv_reduction); + CU_ASSERT(65536 - 4096 == nghttp2_session_get_local_window_size(session)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item == NULL); + + /* Increase local window size */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 49152)); + CU_ASSERT(49152 == session->local_window_size); + CU_ASSERT(-12288 == session->recv_window_size); + CU_ASSERT(16384 == session->recv_reduction); + CU_ASSERT(65536 - 4096 == nghttp2_session_get_local_window_size(session)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + /* Increase local window again */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 65537)); + CU_ASSERT(65537 == session->local_window_size); + CU_ASSERT(4096 == session->recv_window_size); + CU_ASSERT(0 == session->recv_reduction); + CU_ASSERT(65537 - 4096 == nghttp2_session_get_local_window_size(session)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_session_del(session); + + /* Make sure that nghttp2_session_set_local_window_size submits + WINDOW_UPDATE if necessary to increase stream-level window. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + stream = open_sent_stream(session, 1); + stream->recv_window_size = NGHTTP2_INITIAL_WINDOW_SIZE; + + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 0)); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(0 == nghttp2_session_get_stream_local_window_size(session, 1)); + /* This should submit WINDOW_UPDATE frame because stream-level + receiving window is now full. */ + CU_ASSERT(0 == + nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_INITIAL_WINDOW_SIZE)); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + nghttp2_session_get_stream_local_window_size(session, 1)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(1 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + item->frame.window_update.window_size_increment); + + nghttp2_session_del(session); + + /* Make sure that nghttp2_session_set_local_window_size submits + WINDOW_UPDATE if necessary to increase connection-level + window. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + session->recv_window_size = NGHTTP2_INITIAL_WINDOW_SIZE; + + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 0)); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(0 == nghttp2_session_get_local_window_size(session)); + /* This should submit WINDOW_UPDATE frame because connection-level + receiving window is now full. */ + CU_ASSERT(0 == + nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 0, + NGHTTP2_INITIAL_WINDOW_SIZE)); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + nghttp2_session_get_local_window_size(session)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(0 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + item->frame.window_update.window_size_increment); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_cancel_from_before_frame_send(void) { + int rv; + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_settings_entry iv; + nghttp2_data_provider data_prd; + int32_t stream_id; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + + callbacks.before_frame_send_callback = cancel_before_frame_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + iv.settings_id = 0; + iv.value = 1000000009; + + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + CU_ASSERT(0 == rv); + + ud.frame_send_cb_called = 0; + ud.before_frame_send_cb_called = 0; + ud.frame_not_send_cb_called = 0; + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_send_cb_called); + CU_ASSERT(1 == ud.before_frame_send_cb_called); + CU_ASSERT(1 == ud.frame_not_send_cb_called); + + data_prd.source.ptr = NULL; + data_prd.read_callback = temporal_failure_data_source_read_callback; + + stream_id = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), + &data_prd, NULL); + + CU_ASSERT(stream_id > 0); + + ud.frame_send_cb_called = 0; + ud.before_frame_send_cb_called = 0; + ud.frame_not_send_cb_called = 0; + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_send_cb_called); + CU_ASSERT(1 == ud.before_frame_send_cb_called); + CU_ASSERT(1 == ud.frame_not_send_cb_called); + + stream = nghttp2_session_get_stream_raw(session, stream_id); + + CU_ASSERT(NULL == stream); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 1); + + stream_id = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, reqnv, + ARRLEN(reqnv), NULL); + + CU_ASSERT(stream_id > 0); + + ud.frame_send_cb_called = 0; + ud.before_frame_send_cb_called = 0; + ud.frame_not_send_cb_called = 0; + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_send_cb_called); + CU_ASSERT(1 == ud.before_frame_send_cb_called); + CU_ASSERT(1 == ud.frame_not_send_cb_called); + + stream = nghttp2_session_get_stream_raw(session, stream_id); + + CU_ASSERT(NULL == stream); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_too_many_settings(void) { + nghttp2_session *session; + nghttp2_option *option; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_settings_entry iv[3]; + nghttp2_mem *mem; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.send_callback = null_send_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_max_settings(option, 1); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + CU_ASSERT(1 == session->max_settings); + + nghttp2_option_del(option); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 3000; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16384; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2), + 2); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_bufs_reset(&bufs); + nghttp2_bufs_free(&bufs); + nghttp2_session_del(session); +} + +static void +prepare_session_removed_closed_stream(nghttp2_session *session, + nghttp2_hd_deflater *deflater) { + int rv; + nghttp2_settings_entry iv; + nghttp2_bufs bufs; + nghttp2_mem *mem; + ssize_t nread; + int i; + nghttp2_stream *stream; + nghttp2_frame_hd hd; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + iv.settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv.value = 2; + + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + for (i = 1; i <= 3; i += 2) { + rv = pack_headers(&bufs, deflater, i, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv, + ARRLEN(reqnv), mem); + + CU_ASSERT(0 == rv); + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + + nghttp2_bufs_reset(&bufs); + } + + nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR); + + rv = pack_headers(&bufs, deflater, 5, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv, + ARRLEN(reqnv), mem); + + CU_ASSERT(0 == rv); + + /* Receiving stream 5 will erase stream 3 from closed stream list */ + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + + stream = nghttp2_session_get_stream_raw(session, 3); + + CU_ASSERT(NULL == stream); + + /* Since the current max concurrent streams is + NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS, receiving frame on stream + 3 is ignored. */ + nghttp2_bufs_reset(&bufs); + rv = pack_headers(&bufs, deflater, 3, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + trailernv, ARRLEN(trailernv), mem); + + CU_ASSERT(0 == rv); + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 3); + nghttp2_bufs_reset(&bufs); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + /* Now server receives SETTINGS ACK */ + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_SETTINGS, NGHTTP2_FLAG_ACK, 0); + nghttp2_bufs_reset(&bufs); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_removed_closed_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + int rv; + nghttp2_hd_deflater deflater; + nghttp2_bufs bufs; + nghttp2_mem *mem; + ssize_t nread; + nghttp2_frame_hd hd; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(callbacks)); + + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* Now local max concurrent streams is still unlimited, pending max + concurrent streams is now 2. */ + + nghttp2_hd_deflate_init(&deflater, mem); + + prepare_session_removed_closed_stream(session, &deflater); + + /* Now current max concurrent streams is 2. Receiving frame on + stream 3 is ignored because we have no stream object for stream + 3. */ + nghttp2_bufs_reset(&bufs); + rv = pack_headers(&bufs, &deflater, 3, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + trailernv, ARRLEN(trailernv), mem); + + CU_ASSERT(0 == rv); + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL == item); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_hd_deflate_init(&deflater, mem); + /* Same setup, and then receive DATA instead of HEADERS */ + + prepare_session_removed_closed_stream(session, &deflater); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 3); + nghttp2_bufs_reset(&bufs); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL == item); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +static ssize_t pause_once_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + my_user_data *ud = user_data; + if (ud->data_source_read_cb_paused == 0) { + ++ud->data_source_read_cb_paused; + return NGHTTP2_ERR_PAUSE; + } + + return fixed_length_data_source_read_callback(session, stream_id, buf, len, + data_flags, source, user_data); +} + +void test_nghttp2_session_pause_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + data_prd.read_callback = pause_once_data_source_read_callback; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 1); + + CU_ASSERT( + 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); + + ud.frame_send_cb_called = 0; + ud.data_source_read_cb_paused = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.frame_send_cb_called); + CU_ASSERT(NULL == session->aob.item); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_DATA == ud.sent_frame_type); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_no_closed_streams(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_option *option; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_option_new(&option); + nghttp2_option_set_no_closed_streams(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + open_recv_stream(session, 1); + + nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == session->num_closed_streams); + + nghttp2_session_del(session); + nghttp2_option_del(option); +} + +void test_nghttp2_session_set_stream_user_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + int32_t stream_id; + int user_data1, user_data2; + int rv; + const uint8_t *datap; + ssize_t datalen; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_client_new(&session, &callbacks, NULL); + + stream_id = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, + &user_data1); + + rv = nghttp2_session_set_stream_user_data(session, stream_id, &user_data2); + + CU_ASSERT(0 == rv); + + datalen = nghttp2_session_mem_send(session, &datap); + + CU_ASSERT(datalen > 0); + + CU_ASSERT(&user_data2 == + nghttp2_session_get_stream_user_data(session, stream_id)); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_session_set_stream_user_data(session, 2, NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_no_rfc7540_priorities(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + nghttp2_settings_entry iv; + nghttp2_priority_spec pri_spec; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* Do not use a dependency tree if SETTINGS_NO_RFC7540_PRIORITIES = + 1. */ + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.data_source_length = 128 * 1024; + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 1; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), + &data_prd)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen); + assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == nghttp2_pq_size( + &session->sched[NGHTTP2_EXTPRI_DEFAULT_URGENCY].ob_data)); + CU_ASSERT(nghttp2_pq_empty(&session->root.obq)); + + nghttp2_session_del(session); + + /* Priorities are sent as is before client receives + SETTINGS_NO_RFC7540_PRIORITIES = 1 from server. */ + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 1; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1)); + + pri_spec.stream_id = 5; + pri_spec.weight = 111; + pri_spec.exclusive = 1; + + CU_ASSERT(1 == nghttp2_submit_request(session, &pri_spec, reqnv, + ARRLEN(reqnv), NULL, NULL)); + + item = nghttp2_outbound_queue_top(&session->ob_syn); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT(pri_spec.stream_id == item->frame.headers.pri_spec.stream_id); + CU_ASSERT(pri_spec.weight == item->frame.headers.pri_spec.weight); + CU_ASSERT(pri_spec.exclusive == item->frame.headers.pri_spec.exclusive); + + nghttp2_session_del(session); + + /* Priorities are defaulted if client received + SETTINGS_NO_RFC7540_PRIORITIES = 1 from server. */ + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 1; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1)); + + session->remote_settings.no_rfc7540_priorities = 1; + + pri_spec.stream_id = 5; + pri_spec.weight = 111; + pri_spec.exclusive = 1; + + CU_ASSERT(1 == nghttp2_submit_request(session, &pri_spec, reqnv, + ARRLEN(reqnv), NULL, NULL)); + + item = nghttp2_outbound_queue_top(&session->ob_syn); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT(nghttp2_priority_spec_check_default(&item->frame.headers.pri_spec)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_server_fallback_rfc7540_priorities(void) { + nghttp2_session *session; + nghttp2_option *option; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + nghttp2_settings_entry iv; + nghttp2_mem *mem; + nghttp2_hd_deflater deflater; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_priority_spec pri_spec; + nghttp2_stream *anchor_stream, *stream; + my_user_data ud; + nghttp2_ext_priority_update priority_update; + static const uint8_t field_value[] = "u=0"; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_server_fallback_rfc7540_priorities(option, 1); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 1; + + /* Server falls back to RFC 7540 priorities. */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0); + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == session->fallback_rfc7540_priorities); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_priority_spec_init(&pri_spec, 3, 111, 1); + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen); + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + anchor_stream = nghttp2_session_get_stream_raw(session, 3); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == anchor_stream->state); + CU_ASSERT( + !(anchor_stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)); + CU_ASSERT(&session->root == anchor_stream->dep_prev); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + CU_ASSERT(!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)); + CU_ASSERT(anchor_stream == stream->dep_prev); + + /* Make sure that PRIORITY frame updates stream priority. */ + nghttp2_priority_spec_init(&pri_spec, 5, 1, 0); + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_priority(&bufs, &frame.priority); + + CU_ASSERT(0 == rv); + + nghttp2_frame_priority_free(&frame.priority); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + anchor_stream = nghttp2_session_get_stream_raw(session, 5); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == anchor_stream->state); + CU_ASSERT(&session->root == anchor_stream->dep_prev); + CU_ASSERT(anchor_stream == stream->dep_prev); + + /* Make sure that PRIORITY_UPDATE frame is ignored. */ + frame.ext.payload = &priority_update; + nghttp2_frame_priority_update_init(&frame.ext, 1, (uint8_t *)field_value, + sizeof(field_value) - 1); + nghttp2_bufs_reset(&bufs); + nghttp2_frame_pack_priority_update(&bufs, &frame.ext); + + ud.frame_recv_cb_called = 0; + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == stream->extpri); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Server does not fallback to RFC 7540 priorities. */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 0; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, + dup_iv(&iv, 1), 1); + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == session->fallback_rfc7540_priorities); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_priority_spec_init(&pri_spec, 3, 111, 1); + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen); + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(NULL == nghttp2_session_get_stream_raw(session, 3)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + CU_ASSERT(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_option_del(option); + nghttp2_bufs_free(&bufs); +} + +static void check_nghttp2_http_recv_headers_fail( + nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id, + int stream_state, const nghttp2_nv *nva, size_t nvlen) { + nghttp2_mem *mem; + ssize_t rv; + nghttp2_outbound_item *item; + nghttp2_bufs bufs; + my_user_data *ud; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + ud = session->user_data; + + if (stream_state != -1) { + if (nghttp2_session_is_my_stream_id(session, stream_id)) { + open_sent_stream2(session, stream_id, (nghttp2_stream_state)stream_state); + } else { + open_recv_stream2(session, stream_id, (nghttp2_stream_state)stream_state); + } + } + + rv = pack_headers(&bufs, deflater, stream_id, NGHTTP2_FLAG_END_HEADERS, nva, + nvlen, mem); + CU_ASSERT(0 == rv); + + ud->invalid_frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(1 == ud->invalid_frame_recv_cb_called); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_free(&bufs); +} + +static void check_nghttp2_http_recv_headers_ok( + nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id, + int stream_state, const nghttp2_nv *nva, size_t nvlen) { + nghttp2_mem *mem; + ssize_t rv; + nghttp2_bufs bufs; + my_user_data *ud; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + ud = session->user_data; + + if (stream_state != -1) { + if (nghttp2_session_is_my_stream_id(session, stream_id)) { + open_sent_stream2(session, stream_id, (nghttp2_stream_state)stream_state); + } else { + open_recv_stream2(session, stream_id, (nghttp2_stream_state)stream_state); + } + } + + rv = pack_headers(&bufs, deflater, stream_id, NGHTTP2_FLAG_END_HEADERS, nva, + nvlen, mem); + CU_ASSERT(0 == rv); + + ud->frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(1 == ud->frame_recv_cb_called); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_mandatory_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + my_user_data ud; + /* test case for response */ + const nghttp2_nv nostatus_resnv[] = {MAKE_NV("server", "foo")}; + const nghttp2_nv dupstatus_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV(":status", "200")}; + const nghttp2_nv badpseudo_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV(":scheme", "https")}; + const nghttp2_nv latepseudo_resnv[] = {MAKE_NV("server", "foo"), + MAKE_NV(":status", "200")}; + const nghttp2_nv badstatus_resnv[] = {MAKE_NV(":status", "2000")}; + const nghttp2_nv badcl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "-1")}; + const nghttp2_nv dupcl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "0"), + MAKE_NV("content-length", "0")}; + const nghttp2_nv badhd_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("connection", "close")}; + const nghttp2_nv cl1xx_resnv[] = {MAKE_NV(":status", "100"), + MAKE_NV("content-length", "0")}; + const nghttp2_nv cl204_resnv[] = {MAKE_NV(":status", "204"), + MAKE_NV("content-length", "0")}; + const nghttp2_nv clnonzero204_resnv[] = {MAKE_NV(":status", "204"), + MAKE_NV("content-length", "100")}; + const nghttp2_nv status101_resnv[] = {MAKE_NV(":status", "101")}; + const nghttp2_nv unexpectedhost_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("host", "/localhost")}; + + /* test case for request */ + const nghttp2_nv nopath_reqnv[] = {MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost")}; + const nghttp2_nv earlyconnect_reqnv[] = { + MAKE_NV(":method", "CONNECT"), MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), MAKE_NV(":authority", "localhost")}; + const nghttp2_nv lateconnect_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost")}; + const nghttp2_nv duppath_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV(":path", "/")}; + const nghttp2_nv badcl_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("content-length", "-1")}; + const nghttp2_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")}; + const nghttp2_nv badhd_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("connection", "close")}; + const nghttp2_nv badauthority_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "\x0d\x0alocalhost"), MAKE_NV(":path", "/")}; + const nghttp2_nv badhdbtw_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV("foo", "\x0d\x0a"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":path", "/")}; + const nghttp2_nv asteriskget1_reqnv[] = { + MAKE_NV(":path", "*"), MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":method", "GET")}; + const nghttp2_nv asteriskget2_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "GET"), MAKE_NV(":path", "*")}; + const nghttp2_nv asteriskoptions1_reqnv[] = { + MAKE_NV(":path", "*"), MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":method", "OPTIONS")}; + const nghttp2_nv asteriskoptions2_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "OPTIONS"), MAKE_NV(":path", "*")}; + const nghttp2_nv connectproto_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket")}; + const nghttp2_nv connectprotoget_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket")}; + const nghttp2_nv connectprotonopath_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":protocol", "websocket")}; + const nghttp2_nv connectprotonoauth_reqnv[] = { + MAKE_NV(":scheme", "http"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV("host", "localhost"), + MAKE_NV(":protocol", "websocket")}; + const nghttp2_nv regularconnect_reqnv[] = { + MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost")}; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* response header lacks :status */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 1, + NGHTTP2_STREAM_OPENING, nostatus_resnv, + ARRLEN(nostatus_resnv)); + + /* response header has 2 :status */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 3, + NGHTTP2_STREAM_OPENING, dupstatus_resnv, + ARRLEN(dupstatus_resnv)); + + /* response header has bad pseudo header :scheme */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 5, + NGHTTP2_STREAM_OPENING, badpseudo_resnv, + ARRLEN(badpseudo_resnv)); + + /* response header has :status after regular header field */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 7, + NGHTTP2_STREAM_OPENING, latepseudo_resnv, + ARRLEN(latepseudo_resnv)); + + /* response header has bad status code */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 9, + NGHTTP2_STREAM_OPENING, badstatus_resnv, + ARRLEN(badstatus_resnv)); + + /* response header has bad content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 11, + NGHTTP2_STREAM_OPENING, badcl_resnv, + ARRLEN(badcl_resnv)); + + /* response header has multiple content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 13, + NGHTTP2_STREAM_OPENING, dupcl_resnv, + ARRLEN(dupcl_resnv)); + + /* response header has disallowed header field */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 15, + NGHTTP2_STREAM_OPENING, badhd_resnv, + ARRLEN(badhd_resnv)); + + /* response header has content-length with 100 status code */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 17, + NGHTTP2_STREAM_OPENING, cl1xx_resnv, + ARRLEN(cl1xx_resnv)); + + /* response header has 0 content-length with 204 status code */ + check_nghttp2_http_recv_headers_ok(session, &deflater, 19, + NGHTTP2_STREAM_OPENING, cl204_resnv, + ARRLEN(cl204_resnv)); + + /* response header has nonzero content-length with 204 status + code */ + check_nghttp2_http_recv_headers_fail( + session, &deflater, 21, NGHTTP2_STREAM_OPENING, clnonzero204_resnv, + ARRLEN(clnonzero204_resnv)); + + /* status code 101 should not be used in HTTP/2 because it is used + for HTTP Upgrade which HTTP/2 removes. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 23, + NGHTTP2_STREAM_OPENING, status101_resnv, + ARRLEN(status101_resnv)); + + /* Specific characters check for host field in response header + should not be done as its use is undefined. */ + check_nghttp2_http_recv_headers_ok( + session, &deflater, 25, NGHTTP2_STREAM_OPENING, unexpectedhost_resnv, + ARRLEN(unexpectedhost_resnv)); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + /* check server side */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* request header has no :path */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 1, -1, nopath_reqnv, + ARRLEN(nopath_reqnv)); + + /* request header has CONNECT method, but followed by :path */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 3, -1, + earlyconnect_reqnv, + ARRLEN(earlyconnect_reqnv)); + + /* request header has CONNECT method following :path */ + check_nghttp2_http_recv_headers_fail( + session, &deflater, 5, -1, lateconnect_reqnv, ARRLEN(lateconnect_reqnv)); + + /* request header has multiple :path */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 7, -1, duppath_reqnv, + ARRLEN(duppath_reqnv)); + + /* request header has bad content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 9, -1, badcl_reqnv, + ARRLEN(badcl_reqnv)); + + /* request header has multiple content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 11, -1, dupcl_reqnv, + ARRLEN(dupcl_reqnv)); + + /* request header has disallowed header field */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 13, -1, badhd_reqnv, + ARRLEN(badhd_reqnv)); + + /* request header has :authority header field containing illegal + characters */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 15, -1, + badauthority_reqnv, + ARRLEN(badauthority_reqnv)); + + /* request header has regular header field containing illegal + character before all mandatory header fields are seen. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 17, -1, + badhdbtw_reqnv, ARRLEN(badhdbtw_reqnv)); + + /* request header has "*" in :path header field while method is GET. + :path is received before :method */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 19, -1, + asteriskget1_reqnv, + ARRLEN(asteriskget1_reqnv)); + + /* request header has "*" in :path header field while method is GET. + :method is received before :path */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 21, -1, + asteriskget2_reqnv, + ARRLEN(asteriskget2_reqnv)); + + /* OPTIONS method can include "*" in :path header field. :path is + received before :method. */ + check_nghttp2_http_recv_headers_ok(session, &deflater, 23, -1, + asteriskoptions1_reqnv, + ARRLEN(asteriskoptions1_reqnv)); + + /* OPTIONS method can include "*" in :path header field. :method is + received before :path. */ + check_nghttp2_http_recv_headers_ok(session, &deflater, 25, -1, + asteriskoptions2_reqnv, + ARRLEN(asteriskoptions2_reqnv)); + + /* :protocol is not allowed unless it is enabled by the local + endpoint. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 27, -1, + connectproto_reqnv, + ARRLEN(connectproto_reqnv)); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + /* enable SETTINGS_CONNECT_PROTOCOL */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + session->pending_enable_connect_protocol = 1; + + nghttp2_hd_deflate_init(&deflater, mem); + + /* :protocol is allowed if SETTINGS_CONNECT_PROTOCOL is enabled by + the local endpoint. */ + check_nghttp2_http_recv_headers_ok(session, &deflater, 1, -1, + connectproto_reqnv, + ARRLEN(connectproto_reqnv)); + + /* :protocol is only allowed with CONNECT method. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 3, -1, + connectprotoget_reqnv, + ARRLEN(connectprotoget_reqnv)); + + /* CONNECT method with :protocol requires :path. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 5, -1, + connectprotonopath_reqnv, + ARRLEN(connectprotonopath_reqnv)); + + /* CONNECT method with :protocol requires :authority. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 7, -1, + connectprotonoauth_reqnv, + ARRLEN(connectprotonoauth_reqnv)); + + /* regular CONNECT method should succeed with + SETTINGS_CONNECT_PROTOCOL */ + check_nghttp2_http_recv_headers_ok(session, &deflater, 9, -1, + regularconnect_reqnv, + ARRLEN(regularconnect_reqnv)); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); +} + +void test_nghttp2_http_content_length(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + nghttp2_stream *stream; + const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("te", "trailers"), + MAKE_NV("content-length", "9000000000")}; + const nghttp2_nv cl_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")}; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + stream = open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_resnv, + ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(9000000000LL == stream->content_length); + CU_ASSERT(200 == stream->status_code); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_reset(&bufs); + + /* check server side */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_reqnv, + ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(9000000000LL == stream->content_length); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_content_length_mismatch(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv cl_reqnv[] = { + MAKE_NV(":path", "/"), MAKE_NV(":method", "PUT"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":scheme", "https"), + MAKE_NV("content-length", "20")}; + const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "20")}; + nghttp2_outbound_item *item; + nghttp2_frame_hd hd; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* header says content-length: 20, but HEADERS has END_STREAM flag set */ + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + cl_reqnv, ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* header says content-length: 20, but DATA has 0 byte */ + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, cl_reqnv, + ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 3); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* header says content-length: 20, but DATA has 21 bytes */ + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, cl_reqnv, + ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 21, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 5); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN + 21; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + /* Check for client */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* header says content-length: 20, but HEADERS has END_STREAM flag set */ + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + cl_resnv, ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + /* After sending RST_STREAM, stream must be closed */ + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1)); + + nghttp2_bufs_reset(&bufs); + + /* header says content-length: 20, but DATA has 0 byte */ + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, cl_resnv, + ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 3); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 3)); + CU_ASSERT(0 == nghttp2_session_send(session)); + /* After sending RST_STREAM, stream must be closed */ + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 3)); + + nghttp2_bufs_reset(&bufs); + + /* header says content-length: 20, but DATA has 21 bytes */ + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, cl_resnv, + ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 21, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 5); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN + 21; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 5)); + CU_ASSERT(0 == nghttp2_session_send(session)); + /* After sending RST_STREAM, stream must be closed */ + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 5)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); +} + +void test_nghttp2_http_non_final_response(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv nonfinal_resnv[] = { + MAKE_NV(":status", "100"), + }; + nghttp2_outbound_item *item; + nghttp2_frame_hd hd; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* non-final HEADERS with END_STREAM is illegal */ + open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by non-empty DATA is illegal */ + open_sent_stream2(session, 3, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 10, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 3); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN + 10; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by empty DATA (without END_STREAM) is + ok */ + open_sent_stream2(session, 5, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 5); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by empty DATA (with END_STREAM) is + illegal */ + open_sent_stream2(session, 7, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 7, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 7); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by final HEADERS is OK */ + open_sent_stream2(session, 9, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 9, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 9, NGHTTP2_FLAG_END_HEADERS, resnv, + ARRLEN(resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_trailer_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv trailer_reqnv[] = { + MAKE_NV("foo", "bar"), + }; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* good trailer header */ + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + trailer_reqnv, ARRLEN(trailer_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + /* trailer header without END_STREAM is illegal */ + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, + trailer_reqnv, ARRLEN(trailer_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* trailer header including pseudo header field is illegal */ + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_ignore_regular_header(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + my_user_data ud; + const nghttp2_nv bad_reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), + MAKE_NV("foo", "\x0zzz"), + MAKE_NV("bar", "buzz"), + }; + const nghttp2_nv bad_ansnv[] = { + MAKE_NV(":authority", "localhost"), MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), MAKE_NV(":method", "GET"), MAKE_NV("bar", "buzz")}; + size_t proclen; + size_t i; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_header_callback = pause_on_header_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + bad_reqnv, ARRLEN(bad_reqnv), mem); + + CU_ASSERT_FATAL(0 == rv); + + nghttp2_hd_deflate_free(&deflater); + + proclen = 0; + + for (i = 0; i < 4; ++i) { + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen, + nghttp2_buf_len(&bufs.head->buf) - proclen); + CU_ASSERT_FATAL(rv > 0); + proclen += (size_t)rv; + CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[i], &ud.nv)); + } + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen, + nghttp2_buf_len(&bufs.head->buf) - proclen); + CU_ASSERT_FATAL(rv > 0); + /* Without on_invalid_frame_recv_callback, bad header causes stream + reset */ + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + proclen += (size_t)rv; + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == proclen); + + nghttp2_session_del(session); + + /* use on_invalid_header_callback */ + callbacks.on_invalid_header_callback = pause_on_invalid_header_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + proclen = 0; + + ud.invalid_header_cb_called = 0; + + for (i = 0; i < 4; ++i) { + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen, + nghttp2_buf_len(&bufs.head->buf) - proclen); + CU_ASSERT_FATAL(rv > 0); + proclen += (size_t)rv; + CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[i], &ud.nv)); + } + + CU_ASSERT(0 == ud.invalid_header_cb_called); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen, + nghttp2_buf_len(&bufs.head->buf) - proclen); + + CU_ASSERT_FATAL(rv > 0); + CU_ASSERT(1 == ud.invalid_header_cb_called); + CU_ASSERT(nghttp2_nv_equal(&bad_reqnv[4], &ud.nv)); + + proclen += (size_t)rv; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen, + nghttp2_buf_len(&bufs.head->buf) - proclen); + + CU_ASSERT(rv > 0); + CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[4], &ud.nv)); + + nghttp2_session_del(session); + + /* make sure that we can reset stream from + on_invalid_header_callback */ + callbacks.on_header_callback = on_header_callback; + callbacks.on_invalid_header_callback = reset_on_invalid_header_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(rv == (ssize_t)nghttp2_buf_len(&bufs.head->buf)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(1 == item->frame.hd.stream_id); + + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_ignore_content_length(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "304"), + MAKE_NV("content-length", "20")}; + const nghttp2_nv conn_reqnv[] = {MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "CONNECT"), + MAKE_NV("content-length", "999999")}; + const nghttp2_nv conn_cl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "0")}; + nghttp2_stream *stream; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* If status 304, content-length must be ignored */ + open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + cl_resnv, ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + /* Content-Length in 200 response to CONNECT is ignored */ + stream = open_sent_stream2(session, 3, NGHTTP2_STREAM_OPENING); + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT; + + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, + conn_cl_resnv, ARRLEN(conn_cl_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(-1 == stream->content_length); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* If request method is CONNECT, content-length must be ignored */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, conn_reqnv, + ARRLEN(conn_reqnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(-1 == stream->content_length); + CU_ASSERT((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) > 0); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_record_request_method(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const nghttp2_nv conn_reqnv[] = {MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost")}; + const nghttp2_nv conn_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "9999")}; + nghttp2_stream *stream; + ssize_t rv; + nghttp2_bufs bufs; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, conn_reqnv, + ARRLEN(conn_reqnv), NULL, NULL)); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_HTTP_FLAG_METH_CONNECT == stream->http_flags); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, conn_resnv, + ARRLEN(conn_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT((NGHTTP2_HTTP_FLAG_METH_CONNECT & stream->http_flags) > 0); + CU_ASSERT(-1 == stream->content_length); + + /* content-length is ignored in 200 response to a CONNECT request */ + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL == item); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_push_promise(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + nghttp2_stream *stream; + const nghttp2_nv bad_reqnv[] = {MAKE_NV(":method", "GET")}; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* good PUSH_PROMISE case */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, + reqnv, ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + stream = nghttp2_session_get_stream(session, 2); + CU_ASSERT(NULL != stream); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 2, NGHTTP2_FLAG_END_HEADERS, resnv, + ARRLEN(resnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + CU_ASSERT(200 == stream->status_code); + + nghttp2_bufs_reset(&bufs); + + /* PUSH_PROMISE lacks mandatory header */ + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 4, + bad_reqnv, ARRLEN(bad_reqnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(4 == item->frame.hd.stream_id); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_head_method_upgrade_workaround(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "1000000007")}; + nghttp2_bufs bufs; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + ssize_t rv; + nghttp2_stream *stream; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + nghttp2_session_upgrade(session, NULL, 0, NULL); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_resnv, + ARRLEN(cl_resnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(-1 == stream->content_length); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv ws_reqnv[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":scheme", "https"), + MAKE_NV("foo", "bar "), + }; + nghttp2_outbound_item *item; + nghttp2_option *option; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* By default, the leading and trailing white spaces validation is + enabled as per RFC 9113. */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + ws_reqnv, ARRLEN(ws_reqnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Turn off the validation */ + nghttp2_option_new(&option); + nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + ws_reqnv, ARRLEN(ws_reqnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL == item); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_option_del(option); + + nghttp2_bufs_free(&bufs); +} diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h new file mode 100644 index 0000000..e776813 --- /dev/null +++ b/tests/nghttp2_session_test.h @@ -0,0 +1,183 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_SESSION_TEST_H +#define NGHTTP2_SESSION_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_session_recv(void); +void test_nghttp2_session_recv_invalid_stream_id(void); +void test_nghttp2_session_recv_invalid_frame(void); +void test_nghttp2_session_recv_eof(void); +void test_nghttp2_session_recv_data(void); +void test_nghttp2_session_recv_data_no_auto_flow_control(void); +void test_nghttp2_session_recv_continuation(void); +void test_nghttp2_session_recv_headers_with_priority(void); +void test_nghttp2_session_recv_headers_with_padding(void); +void test_nghttp2_session_recv_headers_early_response(void); +void test_nghttp2_session_recv_headers_for_closed_stream(void); +void test_nghttp2_session_recv_headers_with_extpri(void); +void test_nghttp2_session_server_recv_push_response(void); +void test_nghttp2_session_recv_premature_headers(void); +void test_nghttp2_session_recv_unknown_frame(void); +void test_nghttp2_session_recv_unexpected_continuation(void); +void test_nghttp2_session_recv_settings_header_table_size(void); +void test_nghttp2_session_recv_too_large_frame_length(void); +void test_nghttp2_session_recv_extension(void); +void test_nghttp2_session_recv_altsvc(void); +void test_nghttp2_session_recv_origin(void); +void test_nghttp2_session_recv_priority_update(void); +void test_nghttp2_session_continue(void); +void test_nghttp2_session_add_frame(void); +void test_nghttp2_session_on_request_headers_received(void); +void test_nghttp2_session_on_response_headers_received(void); +void test_nghttp2_session_on_headers_received(void); +void test_nghttp2_session_on_push_response_headers_received(void); +void test_nghttp2_session_on_priority_received(void); +void test_nghttp2_session_on_rst_stream_received(void); +void test_nghttp2_session_on_settings_received(void); +void test_nghttp2_session_on_push_promise_received(void); +void test_nghttp2_session_on_ping_received(void); +void test_nghttp2_session_on_goaway_received(void); +void test_nghttp2_session_on_window_update_received(void); +void test_nghttp2_session_on_data_received(void); +void test_nghttp2_session_on_data_received_fail_fast(void); +void test_nghttp2_session_on_altsvc_received(void); +void test_nghttp2_session_send_headers_start_stream(void); +void test_nghttp2_session_send_headers_reply(void); +void test_nghttp2_session_send_headers_frame_size_error(void); +void test_nghttp2_session_send_headers_push_reply(void); +void test_nghttp2_session_send_rst_stream(void); +void test_nghttp2_session_send_push_promise(void); +void test_nghttp2_session_is_my_stream_id(void); +void test_nghttp2_session_upgrade2(void); +void test_nghttp2_session_reprioritize_stream(void); +void test_nghttp2_session_reprioritize_stream_with_idle_stream_dep(void); +void test_nghttp2_submit_data(void); +void test_nghttp2_submit_data_read_length_too_large(void); +void test_nghttp2_submit_data_read_length_smallest(void); +void test_nghttp2_submit_data_twice(void); +void test_nghttp2_submit_request_with_data(void); +void test_nghttp2_submit_request_without_data(void); +void test_nghttp2_submit_response_with_data(void); +void test_nghttp2_submit_response_without_data(void); +void test_nghttp2_submit_response_push_response(void); +void test_nghttp2_submit_trailer(void); +void test_nghttp2_submit_headers_start_stream(void); +void test_nghttp2_submit_headers_reply(void); +void test_nghttp2_submit_headers_push_reply(void); +void test_nghttp2_submit_headers(void); +void test_nghttp2_submit_headers_continuation(void); +void test_nghttp2_submit_headers_continuation_extra_large(void); +void test_nghttp2_submit_priority(void); +void test_nghttp2_submit_settings(void); +void test_nghttp2_submit_settings_update_local_window_size(void); +void test_nghttp2_submit_settings_multiple_times(void); +void test_nghttp2_submit_push_promise(void); +void test_nghttp2_submit_window_update(void); +void test_nghttp2_submit_window_update_local_window_size(void); +void test_nghttp2_submit_shutdown_notice(void); +void test_nghttp2_submit_invalid_nv(void); +void test_nghttp2_submit_extension(void); +void test_nghttp2_submit_altsvc(void); +void test_nghttp2_submit_origin(void); +void test_nghttp2_submit_priority_update(void); +void test_nghttp2_submit_rst_stream(void); +void test_nghttp2_session_open_stream(void); +void test_nghttp2_session_open_stream_with_idle_stream_dep(void); +void test_nghttp2_session_get_next_ob_item(void); +void test_nghttp2_session_pop_next_ob_item(void); +void test_nghttp2_session_reply_fail(void); +void test_nghttp2_session_max_concurrent_streams(void); +void test_nghttp2_session_stop_data_with_rst_stream(void); +void test_nghttp2_session_defer_data(void); +void test_nghttp2_session_flow_control(void); +void test_nghttp2_session_flow_control_data_recv(void); +void test_nghttp2_session_flow_control_data_with_padding_recv(void); +void test_nghttp2_session_data_read_temporal_failure(void); +void test_nghttp2_session_on_stream_close(void); +void test_nghttp2_session_on_ctrl_not_send(void); +void test_nghttp2_session_get_outbound_queue_size(void); +void test_nghttp2_session_get_effective_local_window_size(void); +void test_nghttp2_session_set_option(void); +void test_nghttp2_session_data_backoff_by_high_pri_frame(void); +void test_nghttp2_session_pack_data_with_padding(void); +void test_nghttp2_session_pack_headers_with_padding(void); +void test_nghttp2_pack_settings_payload(void); +void test_nghttp2_session_stream_dep_add(void); +void test_nghttp2_session_stream_dep_remove(void); +void test_nghttp2_session_stream_dep_add_subtree(void); +void test_nghttp2_session_stream_dep_remove_subtree(void); +void test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us(void); +void test_nghttp2_session_stream_attach_item(void); +void test_nghttp2_session_stream_attach_item_subtree(void); +void test_nghttp2_session_stream_get_state(void); +void test_nghttp2_session_stream_get_something(void); +void test_nghttp2_session_find_stream(void); +void test_nghttp2_session_keep_closed_stream(void); +void test_nghttp2_session_keep_idle_stream(void); +void test_nghttp2_session_detach_idle_stream(void); +void test_nghttp2_session_large_dep_tree(void); +void test_nghttp2_session_graceful_shutdown(void); +void test_nghttp2_session_on_header_temporal_failure(void); +void test_nghttp2_session_recv_client_magic(void); +void test_nghttp2_session_delete_data_item(void); +void test_nghttp2_session_open_idle_stream(void); +void test_nghttp2_session_cancel_reserved_remote(void); +void test_nghttp2_session_reset_pending_headers(void); +void test_nghttp2_session_send_data_callback(void); +void test_nghttp2_session_on_begin_headers_temporal_failure(void); +void test_nghttp2_session_defer_then_close(void); +void test_nghttp2_session_detach_item_from_closed_stream(void); +void test_nghttp2_session_flooding(void); +void test_nghttp2_session_change_stream_priority(void); +void test_nghttp2_session_change_extpri_stream_priority(void); +void test_nghttp2_session_create_idle_stream(void); +void test_nghttp2_session_repeated_priority_change(void); +void test_nghttp2_session_repeated_priority_submission(void); +void test_nghttp2_session_set_local_window_size(void); +void test_nghttp2_session_cancel_from_before_frame_send(void); +void test_nghttp2_session_too_many_settings(void); +void test_nghttp2_session_removed_closed_stream(void); +void test_nghttp2_session_pause_data(void); +void test_nghttp2_session_no_closed_streams(void); +void test_nghttp2_session_set_stream_user_data(void); +void test_nghttp2_session_no_rfc7540_priorities(void); +void test_nghttp2_session_server_fallback_rfc7540_priorities(void); +void test_nghttp2_http_mandatory_headers(void); +void test_nghttp2_http_content_length(void); +void test_nghttp2_http_content_length_mismatch(void); +void test_nghttp2_http_non_final_response(void); +void test_nghttp2_http_trailer_headers(void); +void test_nghttp2_http_ignore_regular_header(void); +void test_nghttp2_http_ignore_content_length(void); +void test_nghttp2_http_record_request_method(void); +void test_nghttp2_http_push_promise(void); +void test_nghttp2_http_head_method_upgrade_workaround(void); +void test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation(void); + +#endif /* NGHTTP2_SESSION_TEST_H */ diff --git a/tests/nghttp2_stream_test.c b/tests/nghttp2_stream_test.c new file mode 100644 index 0000000..9b572d0 --- /dev/null +++ b/tests/nghttp2_stream_test.c @@ -0,0 +1,29 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_stream_test.h" + +#include <CUnit/CUnit.h> + +#include "nghttp2_stream.h" diff --git a/tests/nghttp2_stream_test.h b/tests/nghttp2_stream_test.h new file mode 100644 index 0000000..ad7be64 --- /dev/null +++ b/tests/nghttp2_stream_test.h @@ -0,0 +1,32 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_STREAM_TEST_H +#define NGHTTP2_STREAM_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#endif /* NGHTTP2_STREAM_TEST_H */ diff --git a/tests/nghttp2_test_helper.c b/tests/nghttp2_test_helper.c new file mode 100644 index 0000000..0912a30 --- /dev/null +++ b/tests/nghttp2_test_helper.c @@ -0,0 +1,436 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_test_helper.h" + +#include <assert.h> + +#include <CUnit/CUnit.h> + +#include "nghttp2_helper.h" +#include "nghttp2_priority_spec.h" + +int unpack_framebuf(nghttp2_frame *frame, nghttp2_bufs *bufs) { + nghttp2_buf *buf; + + /* Assuming we have required data in first buffer. We don't decode + header block so, we don't mind its space */ + buf = &bufs->head->buf; + return unpack_frame(frame, buf->pos, nghttp2_buf_len(buf)); +} + +int unpack_frame(nghttp2_frame *frame, const uint8_t *in, size_t len) { + int rv = 0; + const uint8_t *payload = in + NGHTTP2_FRAME_HDLEN; + size_t payloadlen = len - NGHTTP2_FRAME_HDLEN; + size_t payloadoff; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + nghttp2_frame_unpack_frame_hd(&frame->hd, in); + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + payloadoff = ((frame->hd.flags & NGHTTP2_FLAG_PADDED) > 0); + rv = nghttp2_frame_unpack_headers_payload(&frame->headers, + payload + payloadoff); + break; + case NGHTTP2_PRIORITY: + nghttp2_frame_unpack_priority_payload(&frame->priority, payload); + break; + case NGHTTP2_RST_STREAM: + nghttp2_frame_unpack_rst_stream_payload(&frame->rst_stream, payload); + break; + case NGHTTP2_SETTINGS: + rv = nghttp2_frame_unpack_settings_payload2( + &frame->settings.iv, &frame->settings.niv, payload, payloadlen, mem); + break; + case NGHTTP2_PUSH_PROMISE: + rv = nghttp2_frame_unpack_push_promise_payload(&frame->push_promise, + payload); + break; + case NGHTTP2_PING: + nghttp2_frame_unpack_ping_payload(&frame->ping, payload); + break; + case NGHTTP2_GOAWAY: + nghttp2_frame_unpack_goaway_payload2(&frame->goaway, payload, payloadlen, + mem); + break; + case NGHTTP2_WINDOW_UPDATE: + nghttp2_frame_unpack_window_update_payload(&frame->window_update, payload); + break; + case NGHTTP2_ALTSVC: + assert(payloadlen > 2); + nghttp2_frame_unpack_altsvc_payload2(&frame->ext, payload, payloadlen, mem); + break; + case NGHTTP2_ORIGIN: + rv = nghttp2_frame_unpack_origin_payload(&frame->ext, payload, payloadlen, + mem); + break; + case NGHTTP2_PRIORITY_UPDATE: + assert(payloadlen >= 4); + nghttp2_frame_unpack_priority_update_payload( + &frame->ext, (uint8_t *)payload, payloadlen); + break; + default: + /* Must not be reachable */ + assert(0); + } + return rv; +} + +int strmemeq(const char *a, const uint8_t *b, size_t bn) { + const uint8_t *c; + if (!a || !b) { + return 0; + } + c = b + bn; + for (; *a && b != c && *a == *b; ++a, ++b) + ; + return !*a && b == c; +} + +int nvnameeq(const char *a, nghttp2_nv *nv) { + return strmemeq(a, nv->name, nv->namelen); +} + +int nvvalueeq(const char *a, nghttp2_nv *nv) { + return strmemeq(a, nv->value, nv->valuelen); +} + +void nva_out_init(nva_out *out) { + memset(out->nva, 0, sizeof(out->nva)); + out->nvlen = 0; +} + +void nva_out_reset(nva_out *out, nghttp2_mem *mem) { + size_t i; + for (i = 0; i < out->nvlen; ++i) { + mem->free(out->nva[i].name, NULL); + mem->free(out->nva[i].value, NULL); + } + memset(out->nva, 0, sizeof(out->nva)); + out->nvlen = 0; +} + +void add_out(nva_out *out, nghttp2_nv *nv, nghttp2_mem *mem) { + nghttp2_nv *onv = &out->nva[out->nvlen]; + if (nv->namelen) { + onv->name = mem->malloc(nv->namelen, NULL); + memcpy(onv->name, nv->name, nv->namelen); + } else { + onv->name = NULL; + } + if (nv->valuelen) { + onv->value = mem->malloc(nv->valuelen, NULL); + memcpy(onv->value, nv->value, nv->valuelen); + } else { + onv->value = NULL; + } + onv->namelen = nv->namelen; + onv->valuelen = nv->valuelen; + + onv->flags = nv->flags; + + ++out->nvlen; +} + +ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out, + nghttp2_bufs *bufs, size_t offset, nghttp2_mem *mem) { + ssize_t rv; + nghttp2_nv nv; + int inflate_flags; + nghttp2_buf_chain *ci; + nghttp2_buf *buf; + nghttp2_buf bp; + int fin; + size_t processed; + + processed = 0; + + for (ci = bufs->head; ci; ci = ci->next) { + buf = &ci->buf; + fin = nghttp2_buf_len(buf) == 0 || ci->next == NULL; + bp = *buf; + + if (offset) { + size_t n; + + n = nghttp2_min(offset, nghttp2_buf_len(&bp)); + bp.pos += n; + offset -= n; + } + + for (;;) { + inflate_flags = 0; + rv = nghttp2_hd_inflate_hd2(inflater, &nv, &inflate_flags, bp.pos, + nghttp2_buf_len(&bp), fin); + + if (rv < 0) { + return rv; + } + + bp.pos += rv; + processed += (size_t)rv; + + if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + if (out) { + add_out(out, &nv, mem); + } + } + if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + break; + } + if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && + nghttp2_buf_len(&bp) == 0) { + break; + } + } + } + + nghttp2_hd_inflate_end_headers(inflater); + + return (ssize_t)processed; +} + +int pack_headers(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, uint8_t flags, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem) { + nghttp2_nv *dnva; + nghttp2_frame frame; + int rv; + + nghttp2_nv_array_copy(&dnva, nva, nvlen, mem); + + nghttp2_frame_headers_init(&frame.headers, flags, stream_id, + NGHTTP2_HCAT_HEADERS, NULL, dnva, nvlen); + rv = nghttp2_frame_pack_headers(bufs, &frame.headers, deflater); + + nghttp2_frame_headers_free(&frame.headers, mem); + + return rv; +} + +int pack_push_promise(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, uint8_t flags, + int32_t promised_stream_id, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem) { + nghttp2_nv *dnva; + nghttp2_frame frame; + int rv; + + nghttp2_nv_array_copy(&dnva, nva, nvlen, mem); + + nghttp2_frame_push_promise_init(&frame.push_promise, flags, stream_id, + promised_stream_id, dnva, nvlen); + rv = nghttp2_frame_pack_push_promise(bufs, &frame.push_promise, deflater); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + + return rv; +} + +int frame_pack_bufs_init(nghttp2_bufs *bufs) { + /* 1 for Pad Length */ + return nghttp2_bufs_init2(bufs, 4096, 16, NGHTTP2_FRAME_HDLEN + 1, + nghttp2_mem_default()); +} + +void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size) { + /* 1 for Pad Length */ + nghttp2_bufs_init2(bufs, chunk_size, 16, NGHTTP2_FRAME_HDLEN + 1, + nghttp2_mem_default()); +} + +static nghttp2_stream *open_stream_with_all(nghttp2_session *session, + int32_t stream_id, int32_t weight, + uint8_t exclusive, + nghttp2_stream *dep_stream) { + nghttp2_priority_spec pri_spec; + int32_t dep_stream_id; + + if (dep_stream) { + dep_stream_id = dep_stream->stream_id; + } else { + dep_stream_id = 0; + } + + nghttp2_priority_spec_init(&pri_spec, dep_stream_id, weight, exclusive); + + return nghttp2_session_open_stream(session, stream_id, + NGHTTP2_STREAM_FLAG_NONE, &pri_spec, + NGHTTP2_STREAM_OPENED, NULL); +} + +nghttp2_stream *open_stream(nghttp2_session *session, int32_t stream_id) { + return open_stream_with_all(session, stream_id, NGHTTP2_DEFAULT_WEIGHT, 0, + NULL); +} + +nghttp2_stream *open_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream) { + return open_stream_with_all(session, stream_id, NGHTTP2_DEFAULT_WEIGHT, 0, + dep_stream); +} + +nghttp2_stream *open_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, int32_t weight, + nghttp2_stream *dep_stream) { + return open_stream_with_all(session, stream_id, weight, 0, dep_stream); +} + +nghttp2_stream *open_stream_with_dep_excl(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream) { + return open_stream_with_all(session, stream_id, NGHTTP2_DEFAULT_WEIGHT, 1, + dep_stream); +} + +nghttp2_outbound_item *create_data_ob_item(nghttp2_mem *mem) { + nghttp2_outbound_item *item; + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + memset(item, 0, sizeof(nghttp2_outbound_item)); + + return item; +} + +nghttp2_stream *open_sent_stream(nghttp2_session *session, int32_t stream_id) { + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0); + return open_sent_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec, + NGHTTP2_STREAM_OPENED, NULL); +} + +nghttp2_stream *open_sent_stream2(nghttp2_session *session, int32_t stream_id, + nghttp2_stream_state initial_state) { + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0); + return open_sent_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec, + initial_state, NULL); +} + +nghttp2_stream *open_sent_stream3(nghttp2_session *session, int32_t stream_id, + uint8_t flags, + nghttp2_priority_spec *pri_spec_in, + nghttp2_stream_state initial_state, + void *stream_user_data) { + nghttp2_stream *stream; + + assert(nghttp2_session_is_my_stream_id(session, stream_id)); + + stream = nghttp2_session_open_stream(session, stream_id, flags, pri_spec_in, + initial_state, stream_user_data); + session->last_sent_stream_id = + nghttp2_max(session->last_sent_stream_id, stream_id); + session->next_stream_id = + nghttp2_max(session->next_stream_id, (uint32_t)stream_id + 2); + + return stream; +} + +nghttp2_stream *open_sent_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream) { + return open_sent_stream_with_dep_weight(session, stream_id, + NGHTTP2_DEFAULT_WEIGHT, dep_stream); +} + +nghttp2_stream *open_sent_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, + int32_t weight, + nghttp2_stream *dep_stream) { + nghttp2_stream *stream; + + assert(nghttp2_session_is_my_stream_id(session, stream_id)); + + stream = open_stream_with_all(session, stream_id, weight, 0, dep_stream); + + session->last_sent_stream_id = + nghttp2_max(session->last_sent_stream_id, stream_id); + session->next_stream_id = + nghttp2_max(session->next_stream_id, (uint32_t)stream_id + 2); + + return stream; +} + +nghttp2_stream *open_recv_stream(nghttp2_session *session, int32_t stream_id) { + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0); + return open_recv_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec, + NGHTTP2_STREAM_OPENED, NULL); +} + +nghttp2_stream *open_recv_stream2(nghttp2_session *session, int32_t stream_id, + nghttp2_stream_state initial_state) { + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0); + return open_recv_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec, + initial_state, NULL); +} + +nghttp2_stream *open_recv_stream3(nghttp2_session *session, int32_t stream_id, + uint8_t flags, + nghttp2_priority_spec *pri_spec_in, + nghttp2_stream_state initial_state, + void *stream_user_data) { + nghttp2_stream *stream; + + assert(!nghttp2_session_is_my_stream_id(session, stream_id)); + + stream = nghttp2_session_open_stream(session, stream_id, flags, pri_spec_in, + initial_state, stream_user_data); + session->last_recv_stream_id = + nghttp2_max(session->last_recv_stream_id, stream_id); + + return stream; +} + +nghttp2_stream *open_recv_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream) { + return open_recv_stream_with_dep_weight(session, stream_id, + NGHTTP2_DEFAULT_WEIGHT, dep_stream); +} + +nghttp2_stream *open_recv_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, + int32_t weight, + nghttp2_stream *dep_stream) { + nghttp2_stream *stream; + + assert(!nghttp2_session_is_my_stream_id(session, stream_id)); + + stream = open_stream_with_all(session, stream_id, weight, 0, dep_stream); + + session->last_recv_stream_id = + nghttp2_max(session->last_recv_stream_id, stream_id); + + return stream; +} diff --git a/tests/nghttp2_test_helper.h b/tests/nghttp2_test_helper.h new file mode 100644 index 0000000..c66298a --- /dev/null +++ b/tests/nghttp2_test_helper.h @@ -0,0 +1,158 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_TEST_HELPER_H +#define NGHTTP2_TEST_HELPER_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include "nghttp2_frame.h" +#include "nghttp2_hd.h" +#include "nghttp2_session.h" + +#define MAKE_NV(NAME, VALUE) \ + { \ + (uint8_t *)(NAME), (uint8_t *)(VALUE), sizeof((NAME)) - 1, \ + sizeof((VALUE)) - 1, NGHTTP2_NV_FLAG_NONE \ + } +#define ARRLEN(ARR) (sizeof(ARR) / sizeof(ARR[0])) + +#define assert_nv_equal(A, B, len, mem) \ + do { \ + size_t alloclen = sizeof(nghttp2_nv) * len; \ + const nghttp2_nv *sa = A, *sb = B; \ + nghttp2_nv *a = mem->malloc(alloclen, NULL); \ + nghttp2_nv *b = mem->malloc(alloclen, NULL); \ + ssize_t i_; \ + memcpy(a, sa, alloclen); \ + memcpy(b, sb, alloclen); \ + nghttp2_nv_array_sort(a, len); \ + nghttp2_nv_array_sort(b, len); \ + for (i_ = 0; i_ < (ssize_t)len; ++i_) { \ + CU_ASSERT(nghttp2_nv_equal(&a[i_], &b[i_])); \ + } \ + mem->free(b, NULL); \ + mem->free(a, NULL); \ + } while (0); + +int unpack_framebuf(nghttp2_frame *frame, nghttp2_bufs *bufs); + +int unpack_frame(nghttp2_frame *frame, const uint8_t *in, size_t len); + +int strmemeq(const char *a, const uint8_t *b, size_t bn); + +int nvnameeq(const char *a, nghttp2_nv *nv); + +int nvvalueeq(const char *a, nghttp2_nv *nv); + +typedef struct { + nghttp2_nv nva[256]; + size_t nvlen; +} nva_out; + +void nva_out_init(nva_out *out); +void nva_out_reset(nva_out *out, nghttp2_mem *mem); + +void add_out(nva_out *out, nghttp2_nv *nv, nghttp2_mem *mem); + +ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out, + nghttp2_bufs *bufs, size_t offset, nghttp2_mem *mem); + +int pack_headers(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, uint8_t flags, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem); + +int pack_push_promise(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, uint8_t flags, + int32_t promised_stream_id, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem); + +int frame_pack_bufs_init(nghttp2_bufs *bufs); + +void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size); + +nghttp2_stream *open_stream(nghttp2_session *session, int32_t stream_id); + +nghttp2_stream *open_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, int32_t weight, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_stream_with_dep_excl(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_outbound_item *create_data_ob_item(nghttp2_mem *mem); + +/* Opens stream. This stream is assumed to be sent from |session|, + and session->last_sent_stream_id and session->next_stream_id will + be adjusted accordingly. */ +nghttp2_stream *open_sent_stream(nghttp2_session *session, int32_t stream_id); + +nghttp2_stream *open_sent_stream2(nghttp2_session *session, int32_t stream_id, + nghttp2_stream_state initial_state); + +nghttp2_stream *open_sent_stream3(nghttp2_session *session, int32_t stream_id, + uint8_t flags, + nghttp2_priority_spec *pri_spec_in, + nghttp2_stream_state initial_state, + void *stream_user_data); + +nghttp2_stream *open_sent_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_sent_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, + int32_t weight, + nghttp2_stream *dep_stream); + +/* Opens stream. This stream is assumed to be received by |session|, + and session->last_recv_stream_id will be adjusted accordingly. */ +nghttp2_stream *open_recv_stream(nghttp2_session *session, int32_t stream_id); + +nghttp2_stream *open_recv_stream2(nghttp2_session *session, int32_t stream_id, + nghttp2_stream_state initial_state); + +nghttp2_stream *open_recv_stream3(nghttp2_session *session, int32_t stream_id, + uint8_t flags, + nghttp2_priority_spec *pri_spec_in, + nghttp2_stream_state initial_state, + void *stream_user_data); + +nghttp2_stream *open_recv_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_recv_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, + int32_t weight, + nghttp2_stream *dep_stream); + +#endif /* NGHTTP2_TEST_HELPER_H */ diff --git a/tests/testdata/Makefile.am b/tests/testdata/Makefile.am new file mode 100644 index 0000000..ee38113 --- /dev/null +++ b/tests/testdata/Makefile.am @@ -0,0 +1,23 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# 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 = cacert.pem index.html privkey.pem diff --git a/tests/testdata/cacert.pem b/tests/testdata/cacert.pem new file mode 100644 index 0000000..e462790 --- /dev/null +++ b/tests/testdata/cacert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICKTCCAdOgAwIBAgIJAIsolheWrwMZMA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTENMAsGA1UEBwwEQ2l0eTESMBAGA1UECgwJU3Bk +eSBUZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHTAbBgkqhkiG9w0BCQEWDnNwZHlA +bG9jYWxob3N0MB4XDTEyMDMwMTE5MTI0NVoXDTIzMDUxOTE5MTI0NVowcDELMAkG +A1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQHDARDaXR5MRIwEAYDVQQKDAlT +cGR5IFRlc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYOc3Bk +eUBsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAw/2MgzAdlJDm29qH +ZlAibgs9mH+8keOtsRrb4B1PiCcZoHvN9eCVZ4WnzT+0zhHF+nO3YfwVFVC3w7TF +7fLB3QIDAQABo1AwTjAdBgNVHQ4EFgQUVP2Jw9RX6BB76aV5x2qk5qsrAIQwHwYD +VR0jBBgwFoAUVP2Jw9RX6BB76aV5x2qk5qsrAIQwDAYDVR0TBAUwAwEB/zANBgkq +hkiG9w0BAQUFAANBAKd9M5FzQLEZW1KPe9/XNZlgxZ2g3EC5Krxo5I4Ul3MnIYS9 +u4K8t/iprhgOzjFH6+8LVk9v0Za+gU+K43CpUo4= +-----END CERTIFICATE----- diff --git a/tests/testdata/index.html b/tests/testdata/index.html new file mode 100644 index 0000000..cdd75fc --- /dev/null +++ b/tests/testdata/index.html @@ -0,0 +1 @@ +<html><body>small</body></html> diff --git a/tests/testdata/privkey.pem b/tests/testdata/privkey.pem new file mode 100644 index 0000000..0ab825b --- /dev/null +++ b/tests/testdata/privkey.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAMP9jIMwHZSQ5tvah2ZQIm4LPZh/vJHjrbEa2+AdT4gnGaB7zfXg +lWeFp80/tM4Rxfpzt2H8FRVQt8O0xe3ywd0CAwEAAQJBAIQ8PGP/QNYOdlT8OsLj +aneJCgQsm1Rro7ONBbFO1WxslvA6+uJsx4Rs8zLiS8cyqmJ/lmGa7zhwYSOvFQPa +XgECIQDgIcgM/2C67peTm1diKKIoGVVKFCfdRi+Dje6mTl2TQQIhAN/bcFWbG73j +cUVlIsr9Wk1dJzjPPWKeyirF1qd/WbOdAiEApTsCOeLCssxV3jF02B5QfPNAFx6I +zO2C9Z7awque/IECIGCHW3VOoTPMs7dc2Rf3D810cclJdArmtf6juOAZRjDxAiBS +AC+H685IBJ99N5nCbF9NWYIVSkuiKVQ8POYVZX+0Jg== +-----END RSA PRIVATE KEY----- |