summaryrefslogtreecommitdiffstats
path: root/debian/vendor-h2o/t/00unit/lib/http2
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--debian/vendor-h2o/t/00unit/lib/http2/cache_digests.c78
-rw-r--r--debian/vendor-h2o/t/00unit/lib/http2/casper.c133
-rw-r--r--debian/vendor-h2o/t/00unit/lib/http2/hpack.c511
-rw-r--r--debian/vendor-h2o/t/00unit/lib/http2/scheduler.c563
4 files changed, 1285 insertions, 0 deletions
diff --git a/debian/vendor-h2o/t/00unit/lib/http2/cache_digests.c b/debian/vendor-h2o/t/00unit/lib/http2/cache_digests.c
new file mode 100644
index 0000000..3e55d17
--- /dev/null
+++ b/debian/vendor-h2o/t/00unit/lib/http2/cache_digests.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * 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 <string.h>
+#include "../../test.h"
+#include "../../../../lib/http2/cache_digests.c"
+
+static void test_calc_hash(void)
+{
+ ok(calc_hash(H2O_STRLIT("https://example.com/style.css"), H2O_STRLIT("")) == 0xbaf9e86f03330860);
+ ok(calc_hash(H2O_STRLIT("https://example.com/style.css"), H2O_STRLIT("\"deadbeef\"")) == 0xa53eb398509042d7);
+}
+
+static void test_decode(void)
+{
+ h2o_cache_digests_t *digests = NULL;
+
+ h2o_cache_digests_load_header(&digests, H2O_STRLIT("AeLA"));
+ ok(digests != NULL);
+ if (digests == NULL)
+ return;
+ ok(digests->fresh.url_only.size == 1);
+ ok(digests->fresh.url_and_etag.size == 0);
+ ok(digests->fresh.url_only.entries[0].capacity_bits == 7);
+ ok(digests->fresh.url_only.entries[0].keys.size == 1);
+ ok(digests->fresh.url_only.entries[0].keys.entries[0] == 0x0b);
+ ok(!digests->fresh.complete);
+
+ ok(h2o_cache_digests_lookup_by_url(digests, H2O_STRLIT("https://127.0.0.1.xip.io:8081/cache-digests.cgi/hello.js")) ==
+ H2O_CACHE_DIGESTS_STATE_FRESH);
+ ok(h2o_cache_digests_lookup_by_url(digests, H2O_STRLIT("https://127.0.0.1.xip.io:8081/notfound.js")) ==
+ H2O_CACHE_DIGESTS_STATE_UNKNOWN);
+
+ h2o_cache_digests_load_header(&digests, H2O_STRLIT("FOO; stale, AcA; validators; complete"));
+ ok(digests->fresh.url_only.size == 1);
+ ok(digests->fresh.url_and_etag.size == 1);
+ ok(digests->fresh.url_and_etag.entries[0].capacity_bits == 7);
+ ok(digests->fresh.url_and_etag.entries[0].keys.size == 0);
+ ok(digests->fresh.complete);
+
+ ok(h2o_cache_digests_lookup_by_url(digests, H2O_STRLIT("https://127.0.0.1.xip.io:8081/notfound.js")) ==
+ H2O_CACHE_DIGESTS_STATE_NOT_CACHED);
+ ok(h2o_cache_digests_lookup_by_url(digests, H2O_STRLIT("https://127.0.0.1.xip.io:8081/cache-digests.cgi/hello.js")) ==
+ H2O_CACHE_DIGESTS_STATE_FRESH);
+
+ h2o_cache_digests_load_header(&digests, H2O_STRLIT("AcA; reset"));
+ ok(digests->fresh.url_only.size == 1);
+ ok(digests->fresh.url_and_etag.size == 0);
+ ok(digests->fresh.url_only.entries[0].capacity_bits == 7);
+ ok(digests->fresh.url_only.entries[0].keys.size == 0);
+ ok(!digests->fresh.complete);
+
+ h2o_cache_digests_destroy(digests);
+}
+
+void test_lib__http2__cache_digests(void)
+{
+ subtest("calc_hash", test_calc_hash);
+ subtest("test_decode", test_decode);
+}
diff --git a/debian/vendor-h2o/t/00unit/lib/http2/casper.c b/debian/vendor-h2o/t/00unit/lib/http2/casper.c
new file mode 100644
index 0000000..5b2a43a
--- /dev/null
+++ b/debian/vendor-h2o/t/00unit/lib/http2/casper.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2015 DeNA Co., Ltd., Kazuho Oku
+ *
+ * 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 <string.h>
+#include "../../test.h"
+#include "../../../../lib/http2/casper.c"
+
+static size_t get_end_of_cookie_value(char *cookie, size_t cookie_len)
+{
+ size_t i;
+ for (i = 0; i != cookie_len; ++i)
+ if (cookie[i] == ';')
+ break;
+ return i;
+}
+
+static void test_calc_key(void)
+{
+ h2o_http2_casper_t *casper = h2o_http2_casper_create(13, 6);
+
+ unsigned key = calc_key(casper, H2O_STRLIT("/index.html")), expected;
+ memcpy(&expected, "\x14\xfe\x45\x59", 4);
+ expected &= (1 << 13) - 1;
+ ok(key == expected);
+
+ h2o_http2_casper_destroy(casper);
+}
+
+static void test_lookup(void)
+{
+ h2o_http2_casper_t *casper;
+ casper = h2o_http2_casper_create(13, 6);
+
+ ok(h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.html"), 0) == 0);
+ ok(h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.html"), 1) == 0);
+ ok(casper->keys.size == 1);
+ ok(h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.html"), 0) == 1);
+ ok(h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.html"), 1) == 1);
+ ok(casper->keys.size == 1);
+
+ h2o_http2_casper_destroy(casper);
+}
+
+static void test_cookie(void)
+{
+ h2o_http2_casper_t *casper;
+
+ casper = h2o_http2_casper_create(13, 6);
+
+ h2o_iovec_t cookie = h2o_http2_casper_get_cookie(casper);
+ ok(cookie.base == NULL);
+ ok(cookie.len == 0);
+
+ h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.html"), 1);
+ cookie = h2o_http2_casper_get_cookie(casper);
+ ok(cookie.len != 0);
+ cookie = h2o_strdup(NULL, cookie.base, cookie.len);
+ h2o_http2_casper_destroy(casper);
+ casper = h2o_http2_casper_create(13, 6);
+
+ h2o_http2_casper_consume_cookie(casper, cookie.base, get_end_of_cookie_value(cookie.base, cookie.len));
+ ok(h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.html"), 0) == 1);
+ ok(h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.php"), 0) == 0);
+ h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.php"), 1);
+
+ h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.html"), 1);
+ cookie = h2o_http2_casper_get_cookie(casper);
+ ok(cookie.len != 0);
+ cookie = h2o_strdup(NULL, cookie.base, cookie.len);
+
+ h2o_http2_casper_destroy(casper);
+ casper = h2o_http2_casper_create(13, 6);
+
+ h2o_http2_casper_consume_cookie(casper, cookie.base, get_end_of_cookie_value(cookie.base, cookie.len));
+ ok(h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.html"), 0) == 1);
+ ok(h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.php"), 0) == 1);
+
+ h2o_http2_casper_destroy(casper);
+}
+
+static void test_cookie_merge(void)
+{
+ h2o_http2_casper_t *casper;
+
+ casper = h2o_http2_casper_create(13, 6);
+ h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.html"), 1);
+ h2o_iovec_t cookie1 = h2o_http2_casper_get_cookie(casper);
+ cookie1 = h2o_strdup(NULL, cookie1.base, cookie1.len);
+ h2o_http2_casper_destroy(casper);
+
+ casper = h2o_http2_casper_create(13, 6);
+ h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.php"), 1);
+ h2o_iovec_t cookie2 = h2o_http2_casper_get_cookie(casper);
+ cookie2 = h2o_strdup(NULL, cookie2.base, cookie2.len);
+ h2o_http2_casper_destroy(casper);
+
+ casper = h2o_http2_casper_create(13, 6);
+ h2o_http2_casper_consume_cookie(casper, cookie1.base, get_end_of_cookie_value(cookie1.base, cookie1.len));
+ h2o_http2_casper_consume_cookie(casper, cookie1.base, get_end_of_cookie_value(cookie1.base, cookie1.len));
+ ok(casper->keys.size == 1);
+ ok(h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.html"), 0) == 1);
+ h2o_http2_casper_consume_cookie(casper, cookie2.base, get_end_of_cookie_value(cookie2.base, cookie2.len));
+ ok(casper->keys.size == 2);
+ ok(h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.html"), 0) == 1);
+ ok(h2o_http2_casper_lookup(casper, H2O_STRLIT("/index.php"), 0) == 1);
+ h2o_http2_casper_destroy(casper);
+}
+
+void test_lib__http2__casper(void)
+{
+ subtest("calc_key", test_calc_key);
+ subtest("test_lookup", test_lookup);
+ subtest("cookie", test_cookie);
+ subtest("cookie-merge", test_cookie_merge);
+}
diff --git a/debian/vendor-h2o/t/00unit/lib/http2/hpack.c b/debian/vendor-h2o/t/00unit/lib/http2/hpack.c
new file mode 100644
index 0000000..059df82
--- /dev/null
+++ b/debian/vendor-h2o/t/00unit/lib/http2/hpack.c
@@ -0,0 +1,511 @@
+/*
+ * Copyright (c) 2014 DeNA Co., Ltd.
+ *
+ * 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 <stdarg.h>
+#include "../../test.h"
+#include "../../../../lib/http2/hpack.c"
+
+static void test_request(h2o_iovec_t first_req, h2o_iovec_t second_req, h2o_iovec_t third_req)
+{
+ h2o_hpack_header_table_t header_table;
+ h2o_req_t req;
+ h2o_iovec_t in;
+ int r, pseudo_headers_map;
+ size_t content_length;
+ const char *err_desc = NULL;
+
+ memset(&header_table, 0, sizeof(header_table));
+ header_table.hpack_capacity = 4096;
+
+ memset(&req, 0, sizeof(req));
+ h2o_mem_init_pool(&req.pool);
+ in = first_req;
+ r = h2o_hpack_parse_headers(&req, &header_table, (const uint8_t *)in.base, in.len, &pseudo_headers_map, &content_length, NULL,
+ &err_desc);
+ ok(r == 0);
+ ok(req.input.authority.len == 15);
+ ok(memcmp(req.input.authority.base, H2O_STRLIT("www.example.com")) == 0);
+ ok(req.input.method.len == 3);
+ ok(memcmp(req.input.method.base, H2O_STRLIT("GET")) == 0);
+ ok(req.input.path.len == 1);
+ ok(memcmp(req.input.path.base, H2O_STRLIT("/")) == 0);
+ ok(req.input.scheme == &H2O_URL_SCHEME_HTTP);
+ ok(req.headers.size == 0);
+
+ h2o_mem_clear_pool(&req.pool);
+
+ memset(&req, 0, sizeof(req));
+ h2o_mem_init_pool(&req.pool);
+ in = second_req;
+ r = h2o_hpack_parse_headers(&req, &header_table, (const uint8_t *)in.base, in.len, &pseudo_headers_map, &content_length, NULL,
+ &err_desc);
+ ok(r == 0);
+ ok(req.input.authority.len == 15);
+ ok(memcmp(req.input.authority.base, H2O_STRLIT("www.example.com")) == 0);
+ ok(req.input.method.len == 3);
+ ok(memcmp(req.input.method.base, H2O_STRLIT("GET")) == 0);
+ ok(req.input.path.len == 1);
+ ok(memcmp(req.input.path.base, H2O_STRLIT("/")) == 0);
+ ok(req.input.scheme == &H2O_URL_SCHEME_HTTP);
+ ok(req.headers.size == 1);
+ ok(h2o_memis(req.headers.entries[0].name->base, req.headers.entries[0].name->len, H2O_STRLIT("cache-control")));
+ ok(h2o_lcstris(req.headers.entries[0].value.base, req.headers.entries[0].value.len, H2O_STRLIT("no-cache")));
+
+ h2o_mem_clear_pool(&req.pool);
+
+ memset(&req, 0, sizeof(req));
+ h2o_mem_init_pool(&req.pool);
+ in = third_req;
+ r = h2o_hpack_parse_headers(&req, &header_table, (const uint8_t *)in.base, in.len, &pseudo_headers_map, &content_length, NULL,
+ &err_desc);
+ ok(r == 0);
+ ok(req.input.authority.len == 15);
+ ok(memcmp(req.input.authority.base, H2O_STRLIT("www.example.com")) == 0);
+ ok(req.input.method.len == 3);
+ ok(memcmp(req.input.method.base, H2O_STRLIT("GET")) == 0);
+ ok(req.input.path.len == 11);
+ ok(memcmp(req.input.path.base, H2O_STRLIT("/index.html")) == 0);
+ ok(req.input.scheme == &H2O_URL_SCHEME_HTTPS);
+ ok(req.headers.size == 1);
+ ok(h2o_memis(req.headers.entries[0].name->base, req.headers.entries[0].name->len, H2O_STRLIT("custom-key")));
+ ok(h2o_lcstris(req.headers.entries[0].value.base, req.headers.entries[0].value.len, H2O_STRLIT("custom-value")));
+
+ h2o_hpack_dispose_header_table(&header_table);
+ h2o_mem_clear_pool(&req.pool);
+}
+
+static void check_flatten(h2o_hpack_header_table_t *header_table, h2o_res_t *res, const char *expected, size_t expected_len)
+{
+ h2o_buffer_t *buf;
+ h2o_http2_frame_t frame;
+ const char *err_desc;
+
+ h2o_buffer_init(&buf, &h2o_socket_buffer_prototype);
+ h2o_hpack_flatten_response(&buf, header_table, 1, H2O_HTTP2_SETTINGS_DEFAULT.max_frame_size, res, NULL, NULL, SIZE_MAX);
+
+ ok(h2o_http2_decode_frame(&frame, (uint8_t *)buf->bytes, buf->size, &H2O_HTTP2_SETTINGS_DEFAULT, &err_desc) > 0);
+ ok(h2o_memis(frame.payload, frame.length, expected, expected_len));
+
+ h2o_buffer_dispose(&buf);
+}
+
+static void test_hpack(void)
+{
+ h2o_mem_pool_t pool;
+ const char *err_desc;
+
+ h2o_mem_init_pool(&pool);
+
+ note("decode_int");
+ {
+ h2o_iovec_t in;
+ const uint8_t *p;
+ int32_t out;
+#define TEST(input, output) \
+ in = h2o_iovec_init(H2O_STRLIT(input)); \
+ p = (const uint8_t *)in.base; \
+ out = decode_int(&p, p + in.len, 7); \
+ ok(out == output); \
+ ok(p == (const uint8_t *)in.base + in.len);
+ TEST("\x00", 0);
+ TEST("\x03", 3);
+ TEST("\x81", 1);
+ TEST("\x7f\x00", 127);
+ TEST("\x7f\x01", 128);
+ TEST("\x7f\x7f", 254);
+ TEST("\x7f\x81\x00", 128);
+ TEST("\x7f\x80\x01", 255);
+ TEST("\x7f\xff\xff\xff\x7f", 0xfffffff + 127);
+ /* failures */
+ TEST("", -1);
+ TEST("\x7f", -1);
+ TEST("\x7f\xff", -1);
+ TEST("\x7f\xff\xff\xff\xff", -1);
+#undef TEST
+ }
+
+ note("encode_int");
+ {
+ uint8_t buf[16];
+ size_t len;
+#define TEST(encoded, value) \
+ memset(buf, 0, sizeof(buf)); \
+ len = encode_int(buf, value, 7) - buf; \
+ ok(len == sizeof(encoded) - 1); \
+ ok(memcmp(buf, encoded, sizeof(encoded) - 1) == 0);
+ TEST("\x00", 0);
+ TEST("\x03", 3);
+ TEST("\x7e", 126);
+ TEST("\x7f\x00", 127);
+ TEST("\x7f\x01", 128);
+ TEST("\x7f\x7f", 254);
+ TEST("\x7f\x80\x01", 255);
+ TEST("\x7f\xff\xff\xff\x7f", 0xfffffff + 127);
+#undef TEST
+ }
+
+ note("decode_huffman");
+ {
+ h2o_iovec_t huffcode = {H2O_STRLIT("\xf1\xe3\xc2\xe5\xf2\x3a\x6b\xa0\xab\x90\xf4\xff")};
+ uint8_t flags = 0;
+ h2o_iovec_t *decoded = decode_huffman(&pool, (const uint8_t *)huffcode.base, huffcode.len, &flags);
+ ok(decoded->len == sizeof("www.example.com") - 1);
+ ok(strcmp(decoded->base, "www.example.com") == 0);
+ ok(flags == 0);
+ }
+ h2o_mem_clear_pool(&pool);
+
+ note("decode_string_bogus");
+ {
+ char *str = "\x8c\xf1\xe3\xc2\xe5\xf2\x3a\x6b\xa0\xab\x90\xf4\xff";
+ const uint8_t *buf;
+ const char *errstr = NULL;
+ size_t len;
+ len = strlen(str);
+ buf = (const uint8_t *)str;
+ /* since we're only passing one byte, decode_string should fail */
+ h2o_iovec_t *decoded = decode_string(&pool, &buf, &buf[1], 0, &errstr);
+ ok(decoded == NULL);
+ }
+ h2o_mem_clear_pool(&pool);
+
+ note("decode_header (literal header field with indexing)");
+ {
+ struct st_h2o_decode_header_result_t result;
+ h2o_hpack_header_table_t header_table;
+ h2o_iovec_t in;
+ int r;
+
+ memset(&header_table, 0, sizeof(header_table));
+ header_table.hpack_capacity = 4096;
+ in = h2o_iovec_init(
+ H2O_STRLIT("\x40\x0a\x63\x75\x73\x74\x6f\x6d\x2d\x6b\x65\x79\x0d\x63\x75\x73\x74\x6f\x6d\x2d\x68\x65\x61\x64\x65\x72"));
+ const uint8_t *p = (const uint8_t *)in.base;
+ err_desc = NULL;
+ r = decode_header(&pool, &result, &header_table, &p, p + in.len, &err_desc);
+ ok(r == 0);
+ ok(result.name->len == 10);
+ ok(strcmp(result.name->base, "custom-key") == 0);
+ ok(result.value->len == 13);
+ ok(strcmp(result.value->base, "custom-header") == 0);
+ ok(header_table.hpack_size == 55);
+ }
+ h2o_mem_clear_pool(&pool);
+
+ note("decode_header (literal header field without indexing)");
+ {
+ struct st_h2o_decode_header_result_t result;
+ h2o_hpack_header_table_t header_table;
+ h2o_iovec_t in;
+ int r;
+
+ memset(&header_table, 0, sizeof(header_table));
+ header_table.hpack_capacity = 4096;
+ in = h2o_iovec_init(H2O_STRLIT("\x04\x0c\x2f\x73\x61\x6d\x70\x6c\x65\x2f\x70\x61\x74\x68"));
+ const uint8_t *p = (const uint8_t *)in.base;
+ err_desc = NULL;
+ r = decode_header(&pool, &result, &header_table, &p, p + in.len, &err_desc);
+ ok(r == 0);
+ ok(result.name == &H2O_TOKEN_PATH->buf);
+ ok(result.value->len == 12);
+ ok(strcmp(result.value->base, "/sample/path") == 0);
+ ok(header_table.hpack_size == 0);
+ }
+ h2o_mem_clear_pool(&pool);
+
+ note("decode_header (literal header field never indexed)");
+ {
+ struct st_h2o_decode_header_result_t result;
+ h2o_hpack_header_table_t header_table;
+ h2o_iovec_t in;
+ int r;
+
+ memset(&header_table, 0, sizeof(header_table));
+ header_table.hpack_capacity = 4096;
+ in = h2o_iovec_init(H2O_STRLIT("\x10\x08\x70\x61\x73\x73\x77\x6f\x72\x64\x06\x73\x65\x63\x72\x65\x74"));
+ const uint8_t *p = (const uint8_t *)in.base;
+ err_desc = NULL;
+ r = decode_header(&pool, &result, &header_table, &p, p + in.len, &err_desc);
+ ok(r == 0);
+ ok(result.name->len == 8);
+ ok(strcmp(result.name->base, "password") == 0);
+ ok(result.value->len == 6);
+ ok(strcmp(result.value->base, "secret") == 0);
+ ok(header_table.hpack_size == 0);
+ }
+ h2o_mem_clear_pool(&pool);
+
+ note("decode_header (indexed header field)");
+ {
+ struct st_h2o_decode_header_result_t result;
+ h2o_hpack_header_table_t header_table;
+ h2o_iovec_t in;
+ int r;
+
+ memset(&header_table, 0, sizeof(header_table));
+ header_table.hpack_capacity = 4096;
+ in = h2o_iovec_init(H2O_STRLIT("\x82"));
+ const uint8_t *p = (const uint8_t *)in.base;
+ err_desc = NULL;
+ r = decode_header(&pool, &result, &header_table, &p, p + in.len, &err_desc);
+ ok(r == 0);
+ ok(result.name == &H2O_TOKEN_METHOD->buf);
+ ok(result.value->len == 3);
+ ok(strcmp(result.value->base, "GET") == 0);
+ ok(header_table.hpack_size == 0);
+ }
+ h2o_mem_clear_pool(&pool);
+
+ note("request examples without huffman coding");
+ test_request(h2o_iovec_init(H2O_STRLIT("\x82\x86\x84\x41\x0f\x77\x77\x77\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d")),
+ h2o_iovec_init(H2O_STRLIT("\x82\x86\x84\xbe\x58\x08\x6e\x6f\x2d\x63\x61\x63\x68\x65")),
+ h2o_iovec_init(H2O_STRLIT("\x82\x87\x85\xbf\x40\x0a\x63\x75\x73\x74\x6f\x6d\x2d\x6b\x65\x79\x0c\x63\x75\x73\x74"
+ "\x6f\x6d\x2d\x76\x61\x6c\x75\x65")));
+
+ note("request examples with huffman coding");
+ test_request(h2o_iovec_init(H2O_STRLIT("\x82\x86\x84\x41\x8c\xf1\xe3\xc2\xe5\xf2\x3a\x6b\xa0\xab\x90\xf4\xff")),
+ h2o_iovec_init(H2O_STRLIT("\x82\x86\x84\xbe\x58\x86\xa8\xeb\x10\x64\x9c\xbf")),
+ h2o_iovec_init(H2O_STRLIT(
+ "\x82\x87\x85\xbf\x40\x88\x25\xa8\x49\xe9\x5b\xa9\x7d\x7f\x89\x25\xa8\x49\xe9\x5b\xb8\xe8\xb4\xbf")));
+
+ note("encode_huffman");
+ {
+ h2o_iovec_t huffcode = {H2O_STRLIT("\xf1\xe3\xc2\xe5\xf2\x3a\x6b\xa0\xab\x90\xf4\xff")};
+ char buf[sizeof("www.example.com")];
+ size_t l = encode_huffman((uint8_t *)buf, (uint8_t *)H2O_STRLIT("www.example.com"));
+ ok(l == huffcode.len);
+ ok(memcmp(buf, huffcode.base, huffcode.len) == 0);
+ }
+
+ note("response examples with huffmann coding");
+ {
+ h2o_hpack_header_table_t header_table;
+ h2o_res_t res;
+
+ memset(&header_table, 0, sizeof(header_table));
+ header_table.hpack_capacity = 256;
+
+ memset(&res, 0, sizeof(res));
+ res.status = 302;
+ res.reason = "Found";
+ h2o_add_header(&pool, &res.headers, H2O_TOKEN_CACHE_CONTROL, NULL, H2O_STRLIT("private"));
+ h2o_add_header(&pool, &res.headers, H2O_TOKEN_DATE, NULL, H2O_STRLIT("Mon, 21 Oct 2013 20:13:21 GMT"));
+ h2o_add_header(&pool, &res.headers, H2O_TOKEN_LOCATION, NULL, H2O_STRLIT("https://www.example.com"));
+ check_flatten(&header_table, &res, H2O_STRLIT("\x08\x03\x33\x30\x32\x58\x85\xae\xc3\x77\x1a\x4b\x61\x96\xd0\x7a\xbe\x94\x10"
+ "\x54\xd4\x44\xa8\x20\x05\x95\x04\x0b\x81\x66\xe0\x82\xa6\x2d\x1b\xff\x6e\x91"
+ "\x9d\x29\xad\x17\x18\x63\xc7\x8f\x0b\x97\xc8\xe9\xae\x82\xae\x43\xd3"));
+
+ memset(&res, 0, sizeof(res));
+ res.status = 307;
+ res.reason = "Temporary Redirect";
+ h2o_add_header(&pool, &res.headers, H2O_TOKEN_CACHE_CONTROL, NULL, H2O_STRLIT("private"));
+ h2o_add_header(&pool, &res.headers, H2O_TOKEN_DATE, NULL, H2O_STRLIT("Mon, 21 Oct 2013 20:13:21 GMT"));
+ h2o_add_header(&pool, &res.headers, H2O_TOKEN_LOCATION, NULL, H2O_STRLIT("https://www.example.com"));
+ check_flatten(&header_table, &res, H2O_STRLIT("\x08\x03\x33\x30\x37\xc0\xbf\xbe"));
+#if 0
+ h2o_iovec_init(H2O_STRLIT("\x48\x03\x33\x30\x37\xc1\xc0\xbf")),
+ h2o_iovec_init(H2O_STRLIT("\x88\xc1\x61\x1d\x4d\x6f\x6e\x2c\x20\x32\x31\x20\x4f\x63\x74\x20\x32\x30\x31\x33\x20\x32\x30\x3a\x31\x33\x3a\x32\x32\x20\x47\x4d\x54\xc0\x5a\x04\x67\x7a\x69\x70\x77\x38\x66\x6f\x6f\x3d\x41\x53\x44\x4a\x4b\x48\x51\x4b\x42\x5a\x58\x4f\x51\x57\x45\x4f\x50\x49\x55\x41\x58\x51\x57\x45\x4f\x49\x55\x3b\x20\x6d\x61\x78\x2d\x61\x67\x65\x3d\x33\x36\x30\x30\x3b\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x31")));
+#endif
+ }
+
+ h2o_mem_clear_pool(&pool);
+}
+
+static void parse_and_compare_request(h2o_hpack_header_table_t *ht, const char *promise_base, size_t promise_len,
+ h2o_iovec_t expected_method, const h2o_url_scheme_t *expected_scheme,
+ h2o_iovec_t expected_authority, h2o_iovec_t expected_path, ...)
+{
+ h2o_req_t req = {NULL};
+ h2o_mem_init_pool(&req.pool);
+
+ int pseudo_header_exists_map = 0;
+ size_t content_length = SIZE_MAX;
+ const char *err_desc = NULL;
+ int r = h2o_hpack_parse_headers(&req, ht, (void *)(promise_base + 13), promise_len - 13, &pseudo_header_exists_map,
+ &content_length, NULL, &err_desc);
+ ok(r == 0);
+ ok(h2o_memis(req.input.method.base, req.input.method.len, expected_method.base, expected_method.len));
+ ok(req.input.scheme == expected_scheme);
+ ok(h2o_memis(req.input.authority.base, req.input.authority.len, expected_authority.base, expected_authority.len));
+ ok(h2o_memis(req.input.path.base, req.input.path.len, expected_path.base, expected_path.len));
+
+ va_list args;
+ va_start(args, expected_path);
+ size_t i;
+ for (i = 0; i != req.headers.size; ++i) {
+ h2o_iovec_t expected_name = va_arg(args, h2o_iovec_t);
+ if (expected_name.base == NULL)
+ break;
+ h2o_iovec_t expected_value = va_arg(args, h2o_iovec_t);
+ ok(h2o_memis(req.headers.entries[i].name->base, req.headers.entries[i].name->len, expected_name.base, expected_name.len));
+ ok(h2o_memis(req.headers.entries[i].value.base, req.headers.entries[i].value.len, expected_value.base, expected_value.len));
+ }
+ ok(i == req.headers.size);
+ va_end(args);
+
+ h2o_mem_clear_pool(&req.pool);
+}
+
+static void test_hpack_push(void)
+{
+ const static h2o_iovec_t method = {H2O_STRLIT("GET")}, authority = {H2O_STRLIT("example.com")},
+ user_agent = {H2O_STRLIT(
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:40.0) Gecko/20100101 Firefox/40.0")},
+ accept_root = {H2O_STRLIT("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")},
+ accept_images = {H2O_STRLIT("image/png,image/*;q=0.8,*/*;q=0.5")},
+ accept_language = {H2O_STRLIT("ja,en-US;q=0.7,en;q=0.3")},
+ accept_encoding = {H2O_STRLIT("gzip, deflate")}, referer = {H2O_STRLIT("https://example.com/")};
+
+ h2o_hpack_header_table_t encode_table = {NULL}, decode_table = {NULL};
+ encode_table.hpack_capacity = decode_table.hpack_capacity = 4096;
+ h2o_req_t req = {NULL};
+ h2o_mem_init_pool(&req.pool);
+ h2o_buffer_t *buf;
+ h2o_buffer_init(&buf, &h2o_socket_buffer_prototype);
+
+ /* setup first request */
+ req.input.method = method;
+ req.input.scheme = &H2O_URL_SCHEME_HTTPS;
+ req.input.authority = authority;
+ req.input.path = h2o_iovec_init(H2O_STRLIT("/"));
+ h2o_add_header(&req.pool, &req.headers, H2O_TOKEN_USER_AGENT, NULL, user_agent.base, user_agent.len);
+ h2o_add_header(&req.pool, &req.headers, H2O_TOKEN_ACCEPT, NULL, accept_root.base, accept_root.len);
+ h2o_add_header(&req.pool, &req.headers, H2O_TOKEN_ACCEPT_LANGUAGE, NULL, accept_language.base, accept_language.len);
+ h2o_add_header(&req.pool, &req.headers, H2O_TOKEN_ACCEPT_ENCODING, NULL, accept_encoding.base, accept_encoding.len);
+
+ /* serialize, deserialize, and compare */
+ h2o_hpack_flatten_request(&buf, &encode_table, 0, 16384, &req, 0);
+ parse_and_compare_request(&decode_table, buf->bytes, buf->size, method, &H2O_URL_SCHEME_HTTPS, authority,
+ h2o_iovec_init(H2O_STRLIT("/")), H2O_TOKEN_USER_AGENT->buf, user_agent, H2O_TOKEN_ACCEPT->buf,
+ accept_root, H2O_TOKEN_ACCEPT_LANGUAGE->buf, accept_language, H2O_TOKEN_ACCEPT_ENCODING->buf,
+ accept_encoding, (h2o_iovec_t){NULL});
+ h2o_buffer_consume(&buf, buf->size);
+
+ /* setup second request */
+ req.input.path = h2o_iovec_init(H2O_STRLIT("/banner.jpg"));
+ req.headers = (h2o_headers_t){NULL};
+ h2o_add_header(&req.pool, &req.headers, H2O_TOKEN_USER_AGENT, NULL, user_agent.base, user_agent.len);
+ h2o_add_header(&req.pool, &req.headers, H2O_TOKEN_ACCEPT, NULL, accept_images.base, accept_images.len);
+ h2o_add_header(&req.pool, &req.headers, H2O_TOKEN_ACCEPT_LANGUAGE, NULL, accept_language.base, accept_language.len);
+ h2o_add_header(&req.pool, &req.headers, H2O_TOKEN_ACCEPT_ENCODING, NULL, accept_encoding.base, accept_encoding.len);
+ h2o_add_header(&req.pool, &req.headers, H2O_TOKEN_REFERER, NULL, referer.base, referer.len);
+
+ /* serialize, deserialize, and compare */
+ h2o_hpack_flatten_request(&buf, &encode_table, 0, 16384, &req, 0);
+ parse_and_compare_request(
+ &decode_table, buf->bytes, buf->size, method, &H2O_URL_SCHEME_HTTPS, authority, h2o_iovec_init(H2O_STRLIT("/banner.jpg")),
+ H2O_TOKEN_USER_AGENT->buf, user_agent, H2O_TOKEN_ACCEPT->buf, accept_images, H2O_TOKEN_ACCEPT_LANGUAGE->buf,
+ accept_language, H2O_TOKEN_ACCEPT_ENCODING->buf, accept_encoding, H2O_TOKEN_REFERER->buf, referer, (h2o_iovec_t){NULL});
+ h2o_buffer_consume(&buf, buf->size);
+
+ /* setup third request (headers are the same) */
+ req.input.path = h2o_iovec_init(H2O_STRLIT("/icon.png"));
+
+ /* serialize, deserialize, and compare */
+ h2o_hpack_flatten_request(&buf, &encode_table, 0, 16384, &req, 0);
+ parse_and_compare_request(&decode_table, buf->bytes, buf->size, method, &H2O_URL_SCHEME_HTTPS, authority,
+ h2o_iovec_init(H2O_STRLIT("/icon.png")), H2O_TOKEN_USER_AGENT->buf, user_agent, H2O_TOKEN_ACCEPT->buf,
+ accept_images, H2O_TOKEN_ACCEPT_LANGUAGE->buf, accept_language, H2O_TOKEN_ACCEPT_ENCODING->buf,
+ accept_encoding, H2O_TOKEN_REFERER->buf, referer, (h2o_iovec_t){NULL});
+ h2o_buffer_consume(&buf, buf->size);
+
+ h2o_buffer_dispose(&buf);
+ h2o_mem_clear_pool(&req.pool);
+}
+
+static void test_hpack_dynamic_table(void)
+{
+ h2o_hpack_header_table_t header_table;
+ uint8_t encoded[256], *p;
+ h2o_iovec_t n, v;
+
+ memset(&header_table, 0, sizeof(header_table));
+ header_table.hpack_capacity = 4096;
+
+ p = encoded;
+ /* expected: literal header with incremental indexing (name not indexed) */
+ n = h2o_iovec_init(H2O_STRLIT("x-name"));
+ v = h2o_iovec_init(H2O_STRLIT("v1"));
+ p = encode_header(&header_table, p, &n, &v);
+ /* expected: literal header with incremental indexing (name indexed) */
+ v = h2o_iovec_init(H2O_STRLIT("v2"));
+ p = encode_header(&header_table, p, &n, &v);
+ /* expected: literal header with incremental indexing (name indexed, referring to the name associated with v2) */
+ v = h2o_iovec_init(H2O_STRLIT("v3"));
+ p = encode_header(&header_table, p, &n, &v);
+ /* expected: indexed header field */
+ v = h2o_iovec_init(H2O_STRLIT("v1"));
+ p = encode_header(&header_table, p, &n, &v);
+
+ const h2o_iovec_t expected = h2o_iovec_init(
+ H2O_STRLIT("\x40\x85" /* literal header with incremental indexing (name not indexed, 5 bytes, huffman coded) */
+ "\xf2\xb5\x43\xa4\xbf" /* "x-name" */
+ "\x02" /* value not compressed, 2 bytes */
+ "v1" /* "v1" */
+ "\x7e" /* literal header with incremental indexing (name indexed) */
+ "\x02" /* value not compressed, 2 bytes */
+ "v2" /* "v2" */
+ "\x7e" /* literal header with incremental indexing (name indexed, referring to the last entry) */
+ "\x02" /* value not compressed, 2 bytes */
+ "v3" /* "v3" */
+ "\xc0" /* indexed header field */
+ ));
+ ok(p - encoded == expected.len);
+ ok(memcmp(encoded, expected.base, expected.len) == 0);
+}
+
+void test_token_wo_hpack_id(void)
+{
+ h2o_mem_pool_t pool;
+ h2o_mem_init_pool(&pool);
+ h2o_hpack_header_table_t table = {NULL};
+ table.hpack_capacity = 4096;
+ h2o_res_t res = {0};
+ h2o_buffer_t *buf;
+ h2o_buffer_init(&buf, &h2o_socket_buffer_prototype);
+
+ res.status = 200;
+ res.reason = "OK";
+ h2o_add_header(&pool, &res.headers, H2O_TOKEN_TE, NULL, H2O_STRLIT("test"));
+
+ h2o_hpack_flatten_response(&buf, &table, 1, H2O_HTTP2_SETTINGS_DEFAULT.max_frame_size, &res, NULL, NULL, SIZE_MAX);
+ ok(h2o_memis(buf->bytes + 9, buf->size - 9, H2O_STRLIT("\x88" /* :status:200 */
+ "\x40\x02" /* literal header w. incremental indexing, raw, TE */
+ "te"
+ "\x83" /* header value, huffman */
+ "IP\x9f" /* test */)));
+ h2o_buffer_consume(&buf, buf->size);
+ h2o_hpack_flatten_response(&buf, &table, 1, H2O_HTTP2_SETTINGS_DEFAULT.max_frame_size, &res, NULL, NULL, SIZE_MAX);
+ ok(h2o_memis(buf->bytes + 9, buf->size - 9, H2O_STRLIT("\x88" /* :status:200 */
+ "\xbe" /* te: test, indexed */)));
+
+ h2o_buffer_dispose(&buf);
+ h2o_hpack_dispose_header_table(&table);
+ h2o_mem_clear_pool(&pool);
+}
+
+void test_lib__http2__hpack(void)
+{
+ subtest("hpack", test_hpack);
+ subtest("hpack-push", test_hpack_push);
+ subtest("hpack-dynamic-table", test_hpack_dynamic_table);
+ subtest("token-wo-hpack-id", test_token_wo_hpack_id);
+}
diff --git a/debian/vendor-h2o/t/00unit/lib/http2/scheduler.c b/debian/vendor-h2o/t/00unit/lib/http2/scheduler.c
new file mode 100644
index 0000000..d643cbf
--- /dev/null
+++ b/debian/vendor-h2o/t/00unit/lib/http2/scheduler.c
@@ -0,0 +1,563 @@
+/*
+ * Copyright (c) 2015 DeNA Co., Ltd.
+ *
+ * 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 <string.h>
+#include "../../test.h"
+#include "../../../../lib/http2/scheduler.c"
+
+static void test_queue(void)
+{
+ h2o_http2_scheduler_queue_t drr;
+ struct node_t {
+ h2o_http2_scheduler_queue_node_t super;
+ uint16_t weight;
+ size_t cnt;
+ } w256 = {{{NULL}}, 256}, w128 = {{{NULL}}, 128}, w32 = {{{NULL}}, 32}, w1 = {{{NULL}}, 1};
+ size_t i;
+
+ queue_init(&drr);
+ queue_set(&drr, &w256.super, 256);
+ queue_set(&drr, &w128.super, 128);
+ queue_set(&drr, &w32.super, 32);
+ queue_set(&drr, &w1.super, 1);
+
+ for (i = 0; i != (256 + 128 + 32 + 1) * 100; ++i) {
+ struct node_t *popped = (struct node_t *)queue_pop(&drr);
+ if (popped == NULL) {
+ ok(0);
+ return;
+ }
+ ++popped->cnt;
+ queue_set(&drr, &popped->super, popped->weight);
+ }
+
+ ok(w256.cnt == 25600);
+ ok(w128.cnt == 12800);
+ ok(w32.cnt == 3200);
+ ok(w1.cnt == 100);
+}
+
+typedef struct {
+ h2o_http2_scheduler_openref_t ref;
+ const char *name;
+ int still_is_active;
+ int bail_out;
+} node_t;
+
+static char output[1024];
+static size_t max_cnt;
+
+static int iterate_cb(h2o_http2_scheduler_openref_t *ref, int *still_is_active, void *_unused)
+{
+ node_t *node = (void *)ref;
+
+ if (output[0] != '\0')
+ strcat(output, ",");
+ strcat(output, node->name);
+ *still_is_active = node->still_is_active;
+
+ if (--max_cnt == 0)
+ return 1;
+ return node->bail_out;
+}
+
+static void test_round_robin(void)
+{
+ h2o_http2_scheduler_node_t root;
+ node_t nodeA = {{{NULL}}, "A", 1, 0};
+ node_t nodeB = {{{NULL}}, "B", 1, 0};
+ node_t nodeC = {{{NULL}}, "C", 1, 0};
+
+ h2o_http2_scheduler_init(&root);
+ h2o_http2_scheduler_open(&nodeA.ref, &root, 1, 0);
+ h2o_http2_scheduler_open(&nodeB.ref, &root, 1, 0);
+ h2o_http2_scheduler_open(&nodeC.ref, &root, 1, 0);
+
+ /* none are active */
+ output[0] = '\0';
+ max_cnt = 4;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "") == 0);
+
+ /* set A to active */
+ h2o_http2_scheduler_activate(&nodeA.ref);
+ output[0] = '\0';
+ max_cnt = 4;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "A,A,A,A") == 0);
+
+ /* A should change to inactive */
+ nodeA.still_is_active = 0;
+ output[0] = '\0';
+ max_cnt = 4;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "A") == 0);
+
+ /* set all to active */
+ h2o_http2_scheduler_activate(&nodeA.ref);
+ nodeA.still_is_active = 1;
+ h2o_http2_scheduler_activate(&nodeB.ref);
+ h2o_http2_scheduler_activate(&nodeC.ref);
+ output[0] = '\0';
+ max_cnt = 7;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "A,B,C,A,B,C,A") == 0);
+
+ /* change them to inactive */
+ nodeA.still_is_active = 0;
+ nodeB.still_is_active = 0;
+ nodeC.still_is_active = 0;
+ output[0] = '\0';
+ max_cnt = 4;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "B,C,A") == 0);
+
+ /* close C */
+ h2o_http2_scheduler_close(&nodeC.ref);
+ h2o_http2_scheduler_activate(&nodeA.ref);
+ h2o_http2_scheduler_activate(&nodeB.ref);
+ output[0] = '\0';
+ max_cnt = 4;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "A,B") == 0);
+
+ h2o_http2_scheduler_close(&nodeA.ref);
+ h2o_http2_scheduler_close(&nodeB.ref);
+ h2o_http2_scheduler_dispose(&root);
+}
+
+static void test_priority(void)
+{
+ h2o_http2_scheduler_node_t root;
+ node_t nodeA = {{{NULL}}, "A", 1, 0};
+ node_t nodeB = {{{NULL}}, "B", 1, 0};
+ node_t nodeC = {{{NULL}}, "C", 1, 0};
+
+ h2o_http2_scheduler_init(&root);
+ h2o_http2_scheduler_open(&nodeA.ref, &root, 32, 0);
+ h2o_http2_scheduler_activate(&nodeA.ref);
+ h2o_http2_scheduler_open(&nodeB.ref, &root, 32, 0);
+ h2o_http2_scheduler_activate(&nodeB.ref);
+ h2o_http2_scheduler_open(&nodeC.ref, &root, 12, 0);
+ h2o_http2_scheduler_activate(&nodeC.ref);
+
+ /* should only get the higher ones */
+ output[0] = '\0';
+ max_cnt = 20;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "A,B,A,B,C,A,B,A,B,A,B,C,A,B,A,B,C,A,B,A") == 0);
+
+ /* eventually disactivate A */
+ nodeA.still_is_active = 0;
+ output[0] = '\0';
+ max_cnt = 10;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "B,A,B,C,B,B,B,C,B,B") == 0);
+
+ /* should start serving C as B gets disactivated */
+ nodeB.still_is_active = 0;
+ output[0] = '\0';
+ max_cnt = 10;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "C,B,C,C,C,C,C,C,C,C") == 0);
+
+ h2o_http2_scheduler_close(&nodeA.ref);
+ h2o_http2_scheduler_close(&nodeB.ref);
+ h2o_http2_scheduler_close(&nodeC.ref);
+ h2o_http2_scheduler_dispose(&root);
+}
+
+static void test_dependency(void)
+{
+ h2o_http2_scheduler_node_t root;
+ node_t nodeA = {{{NULL}}, "A", 1, 0};
+ node_t nodeB = {{{NULL}}, "B", 1, 0};
+ node_t nodeC = {{{NULL}}, "C", 1, 0};
+ node_t nodeD = {{{NULL}}, "D", 1, 0};
+
+ /*
+ * root
+ * /|\
+ * A B C
+ * |
+ * D
+ */
+
+ h2o_http2_scheduler_init(&root);
+ h2o_http2_scheduler_open(&nodeA.ref, &root, 32, 0);
+ h2o_http2_scheduler_activate(&nodeA.ref);
+ h2o_http2_scheduler_open(&nodeB.ref, &root, 32, 0);
+ h2o_http2_scheduler_activate(&nodeB.ref);
+ h2o_http2_scheduler_open(&nodeC.ref, &root, 12, 0);
+ h2o_http2_scheduler_activate(&nodeC.ref);
+ h2o_http2_scheduler_open(&nodeD.ref, &nodeA.ref.node, 6, 0);
+ h2o_http2_scheduler_activate(&nodeD.ref);
+
+ /* should get A and B (and some C) */
+ output[0] = '\0';
+ max_cnt = 20;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "A,B,A,B,C,A,B,A,B,A,B,C,A,B,A,B,C,A,B,A") == 0);
+
+ /* eventually disactivate A, should get D,B (and some C) */
+ nodeA.still_is_active = 0;
+ output[0] = '\0';
+ max_cnt = 20;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "B,A,B,C,D,B,D,B,D,B,C,D,B,D,B,C,D,B,D,B") == 0);
+
+ /* eventually disactivate B, should get D only (and some C) */
+ nodeB.still_is_active = 0;
+ output[0] = '\0';
+ max_cnt = 20;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "D,B,C,D,D,D,C,D,D,C,D,D,D,C,D,D,C,D,D,D") == 0);
+
+ /* closing A raises D, and the priority becomes D = B > C */
+ h2o_http2_scheduler_close(&nodeA.ref);
+ h2o_http2_scheduler_activate(&nodeB.ref);
+ nodeB.still_is_active = 1;
+ output[0] = '\0';
+ max_cnt = 20;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "D,C,B,D,B,D,B,C,D,B,D,B,D,C,B,D,B,D,B,C") == 0);
+
+ h2o_http2_scheduler_close(&nodeB.ref);
+ h2o_http2_scheduler_close(&nodeC.ref);
+ h2o_http2_scheduler_close(&nodeD.ref);
+ h2o_http2_scheduler_dispose(&root);
+}
+
+static void test_exclusive(void)
+{
+ h2o_http2_scheduler_node_t scheduler;
+ node_t nodeA = {{{NULL}}, "A", 1, 0};
+ node_t nodeB = {{{NULL}}, "B", 1, 0};
+ node_t nodeC = {{{NULL}}, "C", 1, 0};
+
+ h2o_http2_scheduler_init(&scheduler);
+
+ /*
+ * root root
+ * /\ |
+ * A B => C
+ * |\
+ * A B
+ */
+
+ /* open A & B */
+ h2o_http2_scheduler_open(&nodeA.ref, &scheduler, 32, 0);
+ h2o_http2_scheduler_activate(&nodeA.ref);
+ h2o_http2_scheduler_open(&nodeB.ref, &scheduler, 32, 0);
+ h2o_http2_scheduler_activate(&nodeB.ref);
+
+ output[0] = '\0';
+ max_cnt = 5;
+ h2o_http2_scheduler_run(&scheduler, iterate_cb, NULL);
+ ok(strcmp(output, "A,B,A,B,A") == 0);
+
+ /* add C as an exclusive */
+ h2o_http2_scheduler_open(&nodeC.ref, &scheduler, 12, 1);
+
+ /* should get A & B since C is inactive */
+ output[0] = '\0';
+ max_cnt = 5;
+ h2o_http2_scheduler_run(&scheduler, iterate_cb, NULL);
+ ok(strcmp(output, "A,B,A,B,A") == 0); /* under current impl, moving the deps causes them to be ordered using _all_ref */
+
+ /* should see C once it is activated */
+ h2o_http2_scheduler_activate(&nodeC.ref);
+ output[0] = '\0';
+ max_cnt = 5;
+ h2o_http2_scheduler_run(&scheduler, iterate_cb, NULL);
+ ok(strcmp(output, "C,C,C,C,C") == 0);
+
+ /* eventually disabling C should show A and B */
+ nodeC.still_is_active = 0;
+ output[0] = '\0';
+ max_cnt = 5;
+ h2o_http2_scheduler_run(&scheduler, iterate_cb, NULL);
+ ok(strcmp(output, "C,B,A,B,A") == 0);
+
+ h2o_http2_scheduler_close(&nodeA.ref);
+ h2o_http2_scheduler_close(&nodeB.ref);
+ h2o_http2_scheduler_close(&nodeC.ref);
+ h2o_http2_scheduler_dispose(&scheduler);
+}
+
+static void test_firefox(void)
+{
+ /*
+ * firefox sends something like below
+ *
+ * PRIORITY: id:3, dependency:0, weight: 201
+ * PRIORITY: id:5, dependency:0, weight: 101
+ * PRIORITY: id:7, dependency:0, weight: 1
+ * PRIORITY: id:9, dependency:7, weight: 1
+ * PRIORITY: id:11, dependency:3, weight: 1
+ * HEADERS: id:13, dependency:11, weight: 22
+ * HEADERS: id:15, dependency:3, weight: 22
+ * HEADERS: id:17, dependency:3, weight: 22
+ */
+ h2o_http2_scheduler_node_t root;
+ node_t g1 = {{{NULL}}, "g1", 0, 0};
+ node_t g2 = {{{NULL}}, "g2", 0, 0};
+ node_t g3 = {{{NULL}}, "g3", 0, 0};
+ node_t g4 = {{{NULL}}, "g4", 0, 0};
+ node_t g5 = {{{NULL}}, "g5", 0, 0};
+ node_t r1 = {{{NULL}}, "r1", 1, 0};
+ node_t r2 = {{{NULL}}, "r2", 1, 0};
+ node_t r3 = {{{NULL}}, "r3", 1, 0};
+
+ h2o_http2_scheduler_init(&root);
+
+ /* setup the proirity groups */
+ h2o_http2_scheduler_open(&g1.ref, &root, 201, 0);
+ h2o_http2_scheduler_open(&g2.ref, &root, 101, 0);
+ h2o_http2_scheduler_open(&g3.ref, &root, 1, 0);
+ h2o_http2_scheduler_open(&g4.ref, &g3.ref.node, 1, 0);
+ h2o_http2_scheduler_open(&g5.ref, &g1.ref.node, 1, 0);
+
+ /* open r1 and set serving */
+ h2o_http2_scheduler_open(&r1.ref, &g5.ref.node, 22, 0);
+ h2o_http2_scheduler_activate(&r1.ref);
+ output[0] = '\0';
+ max_cnt = 5;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "r1,r1,r1,r1,r1") == 0);
+
+ /* open r2,r3 and serve */
+ h2o_http2_scheduler_open(&r2.ref, &g1.ref.node, 22, 0);
+ h2o_http2_scheduler_activate(&r2.ref);
+ h2o_http2_scheduler_open(&r3.ref, &g1.ref.node, 22, 0);
+ h2o_http2_scheduler_activate(&r3.ref);
+ output[0] = '\0';
+ max_cnt = 5;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "r2,r3,r2,r3,r2") == 0);
+
+ /* eventually disactive r2,r3 */
+ r2.still_is_active = 0;
+ r3.still_is_active = 0;
+ output[0] = '\0';
+ max_cnt = 5;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "r3,r2,r1,r1,r1") == 0);
+
+ /* close r2,r3 */
+ h2o_http2_scheduler_close(&r2.ref);
+ h2o_http2_scheduler_close(&r3.ref);
+ output[0] = '\0';
+ max_cnt = 5;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "r1,r1,r1,r1,r1") == 0);
+
+ h2o_http2_scheduler_close(&r1.ref);
+
+ h2o_http2_scheduler_close(&g1.ref);
+ h2o_http2_scheduler_close(&g2.ref);
+ h2o_http2_scheduler_close(&g3.ref);
+ h2o_http2_scheduler_close(&g4.ref);
+ h2o_http2_scheduler_close(&g5.ref);
+ h2o_http2_scheduler_dispose(&root);
+}
+
+static void dump_tree(h2o_http2_scheduler_node_t *node)
+{
+ if (node->_parent != NULL) {
+ node_t *n = (void *)node;
+ strcat(output, n->name);
+ sprintf(output + strlen(output), "%u", (unsigned)h2o_http2_scheduler_get_weight(&n->ref));
+ }
+
+ if (!h2o_linklist_is_empty(&node->_all_refs)) {
+ unsigned weight;
+ int found_any = 0;
+ for (weight = 256; weight >= 1; --weight) {
+ h2o_linklist_t *link;
+ for (link = node->_all_refs.next; link != &node->_all_refs; link = link->next) {
+ h2o_http2_scheduler_openref_t *ref = H2O_STRUCT_FROM_MEMBER(h2o_http2_scheduler_openref_t, _all_link, link);
+ if (ref->weight == weight) {
+ if (!found_any) {
+ found_any = 1;
+ strcat(output, "(");
+ }
+ dump_tree(&ref->node);
+ }
+ }
+ }
+ if (found_any)
+ strcat(output, ")");
+ }
+}
+
+static int test_reprioritize_exclusive;
+
+static void test_reprioritize(void)
+{
+ /* from 5.3.3 of HTTP-2 draft 16
+ * ? ? ? ?
+ * | / \ | |
+ * A D A D D
+ * / \ / / \ / \ |
+ * B C ==> F B C ==> F A OR A
+ * / \ | / \ /|\
+ * D E E B C B C F
+ * | | |
+ * F E E
+ * (intermediate) (non-exclusive) (exclusive)
+ */
+ h2o_http2_scheduler_node_t root;
+ node_t a = {{{NULL}}, "A"};
+ node_t b = {{{NULL}}, "B"};
+ node_t c = {{{NULL}}, "C"};
+ node_t d = {{{NULL}}, "D"};
+ node_t e = {{{NULL}}, "E"};
+ node_t f = {{{NULL}}, "F"};
+
+ h2o_http2_scheduler_init(&root);
+ h2o_http2_scheduler_open(&a.ref, &root, 6, 0);
+ h2o_http2_scheduler_open(&b.ref, &a.ref.node, 5, 0);
+ h2o_http2_scheduler_open(&c.ref, &a.ref.node, 4, 0);
+ h2o_http2_scheduler_open(&d.ref, &c.ref.node, 3, 0);
+ h2o_http2_scheduler_open(&e.ref, &c.ref.node, 2, 0);
+ h2o_http2_scheduler_open(&f.ref, &d.ref.node, 1, 0);
+
+ output[0] = '\0';
+ dump_tree(&root);
+ ok(strcmp(output, "(A6(B5C4(D3(F1)E2)))") == 0);
+
+ h2o_http2_scheduler_rebind(&a.ref, &d.ref.node, 1, test_reprioritize_exclusive);
+ output[0] = '\0';
+ dump_tree(&root);
+ if (!test_reprioritize_exclusive) {
+ ok(strcmp(output, "(D3(F1A1(B5C4(E2))))") == 0);
+ } else {
+ ok(strcmp(output, "(D3(A1(B5C4(E2)F1)))") == 0);
+ }
+
+ h2o_http2_scheduler_close(&a.ref);
+ h2o_http2_scheduler_close(&b.ref);
+ h2o_http2_scheduler_close(&c.ref);
+ h2o_http2_scheduler_close(&d.ref);
+ h2o_http2_scheduler_close(&e.ref);
+ h2o_http2_scheduler_close(&f.ref);
+ h2o_http2_scheduler_dispose(&root);
+}
+
+static void test_change_weight(void)
+{
+ h2o_http2_scheduler_node_t root;
+ node_t nodeA = {{{NULL}}, "A", 1, 0};
+ node_t nodeB = {{{NULL}}, "B", 1, 0};
+ node_t nodeC = {{{NULL}}, "C", 1, 0};
+
+ h2o_http2_scheduler_init(&root);
+
+ /* open them all with priority=16 */
+ h2o_http2_scheduler_open(&nodeA.ref, &root, 16, 0);
+ h2o_http2_scheduler_activate(&nodeA.ref);
+ h2o_http2_scheduler_open(&nodeB.ref, &root, 16, 0);
+ h2o_http2_scheduler_activate(&nodeB.ref);
+ h2o_http2_scheduler_open(&nodeC.ref, &root, 16, 0);
+ h2o_http2_scheduler_activate(&nodeC.ref);
+ output[0] = '\0';
+ dump_tree(&root);
+ ok(strcmp(output, "(A16B16C16)") == 0);
+
+ /* check the output */
+ output[0] = '\0';
+ max_cnt = 20;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "A,B,C,A,B,C,A,B,C,A,B,C,A,B,C,A,B,C,A,B") == 0);
+
+ /* nodeA.priority = 4 */
+ h2o_http2_scheduler_rebind(&nodeA.ref, &root, 4, 0);
+ output[0] = '\0';
+ dump_tree(&root);
+ ok(strcmp(output, "(B16C16A4)") == 0);
+ output[0] = '\0';
+ max_cnt = 20;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "C,B,C,B,C,B,C,A,B,C,B,C,B,C,B,C,A,B,C,B") == 0);
+
+ /* eventually disactivate B,C */
+ nodeB.still_is_active = 0;
+ nodeC.still_is_active = 0;
+ output[0] = '\0';
+ max_cnt = 10;
+ h2o_http2_scheduler_run(&root, iterate_cb, NULL);
+ ok(strcmp(output, "C,B,A,A,A,A,A,A,A,A") == 0);
+
+ h2o_http2_scheduler_close(&nodeA.ref);
+ h2o_http2_scheduler_close(&nodeB.ref);
+ h2o_http2_scheduler_close(&nodeC.ref);
+ h2o_http2_scheduler_dispose(&root);
+}
+
+static void test_exclusive_at_current_pos(void)
+{
+ h2o_http2_scheduler_node_t root;
+ node_t nodeA = {{{NULL}}, "A", 1, 0};
+ node_t nodeB = {{{NULL}}, "B", 1, 0};
+ node_t nodeC = {{{NULL}}, "C", 1, 0};
+
+ h2o_http2_scheduler_init(&root);
+
+ /* open them all with priority=16 */
+ h2o_http2_scheduler_open(&nodeA.ref, &root, 16, 0);
+ h2o_http2_scheduler_activate(&nodeA.ref);
+ h2o_http2_scheduler_open(&nodeB.ref, &root, 16, 0);
+ h2o_http2_scheduler_activate(&nodeB.ref);
+ h2o_http2_scheduler_open(&nodeC.ref, &root, 16, 0);
+ h2o_http2_scheduler_activate(&nodeC.ref);
+
+ output[0] = '\0';
+ dump_tree(&root);
+ ok(strcmp(output, "(A16B16C16)") == 0);
+
+ h2o_http2_scheduler_rebind(&nodeB.ref, &root, 1, 1);
+
+ output[0] = '\0';
+ dump_tree(&root);
+ ok(strcmp(output, "(B1(A16C16))") == 0);
+
+ h2o_http2_scheduler_close(&nodeA.ref);
+ h2o_http2_scheduler_close(&nodeB.ref);
+ h2o_http2_scheduler_close(&nodeC.ref);
+ h2o_http2_scheduler_dispose(&root);
+}
+
+void test_lib__http2__scheduler(void)
+{
+ subtest("drr", test_queue);
+ subtest("round-robin", test_round_robin);
+ subtest("priority", test_priority);
+ subtest("dependency", test_dependency);
+ subtest("exclusive", test_exclusive);
+ subtest("firefox", test_firefox);
+ test_reprioritize_exclusive = 0;
+ subtest("repriortize-nonexclusive", test_reprioritize);
+ test_reprioritize_exclusive = 1;
+ subtest("repriortize-exclusive", test_reprioritize);
+ subtest("change-weight", test_change_weight);
+ subtest("exclusive-at-current-pos", test_exclusive_at_current_pos);
+}