/* * Test QPACK. */ #include #include #include #include #include #include #include "lsqpack.h" #include "lsqpack-test.h" #include "lsxpack_header.h" #define ENC_BUF_SZ 200 #define HEADER_BUF_SZ 200 #define PREFIX_BUF_SZ 10 static const struct qpack_header_block_test { /* Identification: */ int qhbt_lineno; /* Input: */ unsigned qhbt_table_size; unsigned qhbt_max_risked_streams; unsigned qhbt_n_headers; struct { const char *name, *value; enum lsqpack_enc_flags flags; } qhbt_headers[10]; /* Output: */ size_t qhbt_enc_sz; unsigned char qhbt_enc_buf[ENC_BUF_SZ]; size_t qhbt_prefix_sz; unsigned char qhbt_prefix_buf[PREFIX_BUF_SZ]; size_t qhbt_header_sz; unsigned char qhbt_header_buf[HEADER_BUF_SZ]; } header_block_tests[] = { { .qhbt_lineno = __LINE__, .qhbt_table_size = 0x1000, .qhbt_max_risked_streams = 100, .qhbt_n_headers = 1, .qhbt_headers = { { ":method", "GET", 0, }, }, .qhbt_enc_sz = 0, .qhbt_prefix_sz = 2, .qhbt_prefix_buf = "\x00\x00", .qhbt_header_sz = 1, .qhbt_header_buf = { 0x80 | 0x40 | 17, }, }, { .qhbt_lineno = __LINE__, .qhbt_table_size = 0x1000, .qhbt_max_risked_streams = 100, .qhbt_n_headers = 1, .qhbt_headers = { { ":method", "method", LQEF_NEVER_INDEX, }, }, .qhbt_enc_sz = 0, .qhbt_prefix_sz = 2, .qhbt_prefix_buf = "\x00\x00", .qhbt_header_sz = 8, .qhbt_header_buf = { 0x40 | 0x20 | 0x10 | 0x0F, 0x00, 0x80 /* Huffman */ | 5 /* length */, 0xa4, 0xa9, 0x9c, 0xf2, 0x7f, }, }, { .qhbt_lineno = __LINE__, .qhbt_table_size = 0x1000, .qhbt_max_risked_streams = 0, .qhbt_n_headers = 1, .qhbt_headers = { { ":method", "method", 0, }, }, .qhbt_enc_sz = 7, .qhbt_enc_buf = { 0x80 | 0x40 /* static */ | 15 /* name index */, 0x80 /* Huffman */ | 5 /* length */, 0xa4, 0xa9, 0x9c, 0xf2, 0x7f, }, .qhbt_prefix_sz = 2, .qhbt_prefix_buf = "\x00\x00", .qhbt_header_sz = 8, .qhbt_header_buf = { 0x40 | 0x10 /* Static */ | 15, 0x00, 0x80 /* Huffman */ | 5 /* length */, 0xa4, 0xa9, 0x9c, 0xf2, 0x7f, }, }, { .qhbt_lineno = __LINE__, .qhbt_table_size = 0x1000, .qhbt_max_risked_streams = 0x1000, .qhbt_n_headers = 1, .qhbt_headers = { { ":method", "method", 0, }, }, .qhbt_enc_sz = 7, .qhbt_enc_buf = { 0x80 | 0x40 /* static */ | 15 /* name index */, 0x80 /* Huffman */ | 5 /* length */, 0xa4, 0xa9, 0x9c, 0xf2, 0x7f, }, .qhbt_prefix_sz = 2, .qhbt_prefix_buf = { 0x02, 0x80, }, .qhbt_header_sz = 1, .qhbt_header_buf = { 0x10 | 0 /* Relative dynamic ID */, }, }, { .qhbt_lineno = __LINE__, .qhbt_table_size = 0x1000, .qhbt_max_risked_streams = 0x1000, .qhbt_n_headers = 1, .qhbt_headers = { { "dude", "where is my car?", LQEF_NEVER_INDEX, }, }, .qhbt_enc_sz = 0, .qhbt_prefix_sz = 2, .qhbt_prefix_buf = "\x00\x00", .qhbt_header_sz = 17, .qhbt_header_buf = { 0x20 | 0x10 /* No index */ | 0x08 | 3, 0x92, 0xd9, 0x0b, 0x80 | 0xC, 0xf1, 0x39, 0x6c, 0x2a, 0x86, 0x42, 0x94, 0xfa, 0x50, 0x83, 0xb3, 0xfc, }, }, { .qhbt_lineno = __LINE__, .qhbt_table_size = 0x1000, .qhbt_max_risked_streams = 0, .qhbt_n_headers = 3, .qhbt_headers = { { "dude", "where is my car?", 0, }, { ":method", "GET", 0, }, /* The "aaa..." string triggers header_out_grow_buf() */ { "aaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaa", 0, }, }, .qhbt_enc_sz = 53, .qhbt_enc_buf = { 0x40 | 0x20 | 3, 0x92, 0xd9, 0x0b, 0x80 | 0xC, 0xf1, 0x39, 0x6c, 0x2a, 0x86, 0x42, 0x94, 0xfa, 0x50, 0x83, 0xb3, 0xfc, 0x40 /* Without name reference */ | 0x20 /* Huffman */ | 17 /* Length */, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xff, 0x80 /* Huffman */ | 17 /* length */, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xff, }, .qhbt_prefix_sz = 2, .qhbt_prefix_buf = "\x00\x00", .qhbt_header_sz = 55, .qhbt_header_buf = { 0x20 | 0x08 | 3, 0x92, 0xd9, 0x0b, 0x80 | 0xC, 0xf1, 0x39, 0x6c, 0x2a, 0x86, 0x42, 0x94, 0xfa, 0x50, 0x83, 0xb3, 0xfc, 0x80 | 0x40 | 17, 0x20 /* Without name reference */ | 0x08 /* Huffman */ | 7 , 0x0A /* Length */, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xff, 0x80 /* Huffman */ | 17 /* length */, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xff, }, }, { .qhbt_lineno = __LINE__, .qhbt_table_size = 0x1000, .qhbt_max_risked_streams = 1, .qhbt_n_headers = 1, .qhbt_headers = { { "dude", "where is my car?", 0, }, }, .qhbt_enc_sz = 17, .qhbt_enc_buf = { 0x40 | 0x20 | 3, 0x92, 0xd9, 0x0b, 0x80 | 0xC, 0xf1, 0x39, 0x6c, 0x2a, 0x86, 0x42, 0x94, 0xfa, 0x50, 0x83, 0xb3, 0xfc, }, .qhbt_prefix_sz = 2, .qhbt_prefix_buf = { 0x02, 0x80, }, .qhbt_header_sz = 1, .qhbt_header_buf = { 0x10 | 0 /* Relative dynamic ID */, }, }, }; struct header_buf { unsigned off; char buf[UINT16_MAX]; }; int header_set_ptr (struct lsxpack_header *hdr, struct header_buf *header_buf, const char *name, size_t name_len, const char *val, size_t val_len) { if (header_buf->off + name_len + val_len <= sizeof(header_buf->buf)) { memcpy(header_buf->buf + header_buf->off, name, name_len); memcpy(header_buf->buf + header_buf->off + name_len, val, val_len); lsxpack_header_set_offset2(hdr, header_buf->buf + header_buf->off, 0, name_len, name_len, val_len); header_buf->off += name_len + val_len; return 0; } else return -1; } static void run_header_test (const struct qpack_header_block_test *test) { unsigned char header_buf[HEADER_BUF_SZ], enc_buf[ENC_BUF_SZ], prefix_buf[PREFIX_BUF_SZ]; unsigned header_off, enc_off; size_t header_sz, enc_sz, dec_sz; struct lsqpack_enc enc; unsigned i; size_t nw; int s; enum lsqpack_enc_status enc_st; float ratio; unsigned char dec_buf[LSQPACK_LONGEST_SDTC]; struct lsxpack_header xhdr; struct header_buf hbuf; dec_sz = sizeof(dec_buf); s = lsqpack_enc_init(&enc, stderr, test->qhbt_table_size, test->qhbt_table_size, test->qhbt_max_risked_streams, LSQPACK_ENC_OPT_IX_AGGR, dec_buf, &dec_sz); assert(s == 0); s = lsqpack_enc_start_header(&enc, 0, 0); assert(s == 0); header_off = 0, enc_off = 0; for (i = 0; i < test->qhbt_n_headers; ++i) { /* Increase buffers one by one to exercise error conditions */ enc_sz = 0; header_sz = 0; while (1) { hbuf.off = 0; s = header_set_ptr(&xhdr, &hbuf, test->qhbt_headers[i].name, strlen(test->qhbt_headers[i].name), test->qhbt_headers[i].value, strlen(test->qhbt_headers[i].value)); assert(s == 0); enc_st = lsqpack_enc_encode(&enc, enc_buf + enc_off, &enc_sz, header_buf + header_off, &header_sz, &xhdr, test->qhbt_headers[i].flags); switch (enc_st) { case LQES_NOBUF_ENC: if (enc_sz < sizeof(enc_buf) - enc_off) ++enc_sz; else assert(0); break; case LQES_NOBUF_HEAD: if (header_sz < sizeof(header_buf) - header_off) ++header_sz; else assert(0); break; default: assert(enc_st == LQES_OK); goto end_encode_one_header; } } end_encode_one_header: enc_off += enc_sz; header_off += header_sz; } nw = lsqpack_enc_end_header(&enc, prefix_buf, sizeof(prefix_buf), NULL); assert(nw == test->qhbt_prefix_sz); assert(0 == memcmp(test->qhbt_prefix_buf, prefix_buf, nw)); assert(enc_off == test->qhbt_enc_sz); assert(0 == memcmp(test->qhbt_enc_buf, enc_buf, enc_off)); assert(header_off == test->qhbt_header_sz); assert(0 == memcmp(test->qhbt_header_buf, header_buf, header_off)); assert(lsqpack_enc_ratio(&enc) > 0.0); lsqpack_enc_cleanup(&enc); } struct dhte /* decoded header test ext */ { struct lsxpack_header xhdr; size_t buf_off; char buf[0x1000]; }; static void dht_unblocked (void *hblock_ctx_p) { assert(0); /* Not expecting this to be called */ } static struct lsxpack_header * dht_prepare_decode (void *hblock_ctx_p, struct lsxpack_header *xhdr, size_t space) { struct dhte *const dhte = hblock_ctx_p; size_t avail; if (space > LSXPACK_MAX_STRLEN) return NULL; if (xhdr) { assert(xhdr == &dhte->xhdr); assert(xhdr->val_len < space); avail = sizeof(dhte->buf) - xhdr->name_offset - 1 /* NUL */; if (avail < space) return NULL; xhdr->val_len = space; } else { avail = sizeof(dhte->buf) - dhte->buf_off - 1 /* NUL */; if (avail < space) return NULL; lsxpack_header_prepare_decode(&dhte->xhdr, dhte->buf, dhte->buf_off, space); } return &dhte->xhdr; } static int dht_process_header (void *hblock_ctx_p, struct lsxpack_header *xhdr) { struct dhte *const dhte = hblock_ctx_p; assert(xhdr == &dhte->xhdr); dhte->buf_off += lsxpack_header_get_dec_size(xhdr); return 0; } static void run_decoded_headers_test_ext (const struct qpack_header_block_test *test, const enum lsqpack_dec_opts opts, const size_t name_offset) { struct lsqpack_dec dec; char exp_hbuf[0x1000]; /* Large enough to hold all headers */ struct dhte dhte; size_t sz; unsigned i; int s; enum lsqpack_read_header_status rhs; const unsigned char *buf; unsigned char dec_buf[LSQPACK_LONGEST_HEADER_ACK]; size_t dec_sz; const struct lsqpack_dec_hset_if dht_if = { .dhi_unblocked = dht_unblocked, .dhi_prepare_decode = dht_prepare_decode, .dhi_process_header = dht_process_header, }; /* Construct expected headers buffer: */ sz = 0; exp_hbuf[0] = '\0'; for (i = 0; i < test->qhbt_n_headers; ++i) { strcpy(exp_hbuf + sz, test->qhbt_headers[i].name); sz += strlen(test->qhbt_headers[i].name); if (opts & LSQPACK_DEC_OPT_HTTP1X) { strcpy(exp_hbuf + sz, ": "); sz += 2; } strcpy(exp_hbuf + sz, test->qhbt_headers[i].value); sz += strlen(test->qhbt_headers[i].value); if (opts & LSQPACK_DEC_OPT_HTTP1X) { strcpy(exp_hbuf + sz, "\r\n"); sz += 2; } } assert(sz < sizeof(exp_hbuf)); /* Self-check */ /* Decode headers into dhte buffer */ dhte.buf_off = name_offset; lsqpack_dec_init(&dec, NULL, test->qhbt_table_size, test->qhbt_max_risked_streams, &dht_if, opts); if (test->qhbt_enc_sz) { s = lsqpack_dec_enc_in(&dec, test->qhbt_enc_buf, test->qhbt_enc_sz); assert(s == 0); } assert(test->qhbt_header_sz); /* Self-check */ buf = test->qhbt_prefix_buf; dec_sz = sizeof(dec_buf); rhs = lsqpack_dec_header_in(&dec, &dhte, 0, test->qhbt_prefix_sz + test->qhbt_header_sz, &buf, test->qhbt_prefix_sz, dec_buf, &dec_sz); assert(rhs == LQRHS_NEED); assert(buf == test->qhbt_prefix_buf + test->qhbt_prefix_sz); buf = test->qhbt_header_buf; dec_sz = sizeof(dec_buf); rhs = lsqpack_dec_header_read(&dec, &dhte, &buf, test->qhbt_header_sz, dec_buf, &dec_sz); assert(rhs == LQRHS_DONE); assert(buf == test->qhbt_header_buf + test->qhbt_header_sz); lsqpack_dec_cleanup(&dec); /* Now compare the buffers */ dhte.buf[dhte.buf_off] = '\0'; s = strcmp(dhte.buf + name_offset, exp_hbuf); assert(s == 0); } static void run_decoded_headers_test (const struct qpack_header_block_test *test) { const enum lsqpack_dec_opts opts[] = { 0, LSQPACK_DEC_OPT_HTTP1X, }; const size_t offs[] = { 0, 1, 20, }; unsigned i, j; for (i = 0; i < sizeof(opts) / sizeof(opts[0]); ++i) for (j = 0; j < sizeof(offs) / sizeof(offs[0]); ++j) run_decoded_headers_test_ext(test, opts[i], offs[j]); } static void run_header_cancellation_test(const struct qpack_header_block_test *test) { unsigned char header_buf[HEADER_BUF_SZ]; size_t header_sz, enc_sz; struct lsqpack_enc enc; int s; enum lsqpack_enc_status enc_st; struct lsxpack_header xhdr; struct header_buf hbuf; s = lsqpack_enc_init(&enc, stderr, 0, 0, test->qhbt_max_risked_streams, LSQPACK_ENC_OPT_IX_AGGR, NULL, NULL); assert(s == 0); s = lsqpack_enc_start_header(&enc, 0, 0); assert(s == 0); header_sz = HEADER_BUF_SZ; enc_sz = 0; hbuf.off = 0; header_set_ptr(&xhdr, &hbuf, test->qhbt_headers[0].name, strlen(test->qhbt_headers[0].name), test->qhbt_headers[0].value, strlen(test->qhbt_headers[0].value)); enc_st = lsqpack_enc_encode(&enc, NULL, &enc_sz, header_buf, &header_sz, &xhdr, 0); assert(enc_st == LQES_OK); s = lsqpack_enc_cancel_header(&enc); assert(s == 0); /* Check that we can start again after cancelling. */ s = lsqpack_enc_start_header(&enc, 0, 0); assert(s == 0); lsqpack_enc_cleanup(&enc); } static void test_enc_init (void) { struct lsqpack_enc enc; size_t dec_sz; int s; unsigned i; const unsigned char *p; uint64_t val; struct lsqpack_dec_int_state state; unsigned char dec_buf[LSQPACK_LONGEST_SDTC]; const struct { unsigned peer_max_size; /* Value provided by peer */ unsigned our_max_size; /* Value to use */ int expected_tsu; /* Expecting TSU instruction? */ } tests[] = { { 0x1000, 0x1000, 1, }, { 0x1000, 1, 1, }, { 0x100, 0x100, 1, }, { 0x1000, 0, 0, }, }; for (i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) { dec_sz = sizeof(dec_buf); s = lsqpack_enc_init(&enc, stderr, tests[i].peer_max_size, tests[i].our_max_size, 0, 0, dec_buf, &dec_sz); assert(s == 0); if (tests[i].expected_tsu) { assert(dec_sz > 0); assert((dec_buf[0] & 0xE0) == 0x20); p = dec_buf; state.resume = 0; s = lsqpack_dec_int(&p, p + dec_sz, 5, &val, &state); assert(s == 0); assert(val == tests[i].our_max_size); } else assert(dec_sz == 0); lsqpack_enc_cleanup(&enc); } } /* Test that push promise header does not use the dynamic table, nor does * it update history. */ static void test_push_promise (void) { struct lsqpack_enc enc; ssize_t nw; enum lsqpack_enc_status enc_st; int s; unsigned i; const unsigned char *p; uint64_t val; struct lsqpack_dec_int_state state; unsigned char dec_buf[LSQPACK_LONGEST_SDTC]; unsigned char header_buf[HEADER_BUF_SZ], enc_buf[ENC_BUF_SZ], prefix_buf[PREFIX_BUF_SZ]; size_t header_sz, enc_sz, dec_sz; enum lsqpack_enc_header_flags hflags; struct lsxpack_header xhdr; struct header_buf hbuf; dec_sz = sizeof(dec_buf); s = lsqpack_enc_init(&enc, stderr, 0x1000, 0x1000, 100, 0, dec_buf, &dec_sz); assert(0 == s); (void) dec_sz; /* We don't care for this test */ s = lsqpack_enc_start_header(&enc, 0, 0); assert(0 == s); enc_sz = sizeof(enc_buf); header_sz = sizeof(header_buf); hbuf.off = 0; header_set_ptr(&xhdr, &hbuf, ":method", 7, "dude!", 5); enc_st = lsqpack_enc_encode(&enc, enc_buf, &enc_sz, header_buf, &header_sz, &xhdr, 0); assert(LQES_OK == enc_st); enc_sz = sizeof(enc_buf); header_sz = sizeof(header_buf); header_set_ptr(&xhdr, &hbuf, ":method", 7, "dude!", 5); enc_st = lsqpack_enc_encode(&enc, enc_buf, &enc_sz, header_buf, &header_sz, &xhdr, 0); assert(LQES_OK == enc_st); nw = lsqpack_enc_end_header(&enc, prefix_buf, sizeof(prefix_buf), &hflags); assert(2 == nw); assert(!(prefix_buf[0] == 0 && prefix_buf[1] == 0)); /* Dynamic table used */ assert(hflags & LSQECH_REF_NEW_ENTRIES); assert(hflags & LSQECH_REF_AT_RISK); s = lsqpack_enc_start_header(&enc, 0, 0); assert(0 == s); enc_sz = sizeof(enc_buf); header_sz = sizeof(header_buf); header_set_ptr(&xhdr, &hbuf, ":method", 7, "dude!", 5); enc_st = lsqpack_enc_encode(&enc, enc_buf, &enc_sz, header_buf, &header_sz, &xhdr, LQEF_NO_HIST_UPD|LQEF_NO_DYN); assert(LQES_OK == enc_st); enc_sz = sizeof(enc_buf); header_sz = sizeof(header_buf); header_set_ptr(&xhdr, &hbuf, ":method", 7, "where is my car?", 16); enc_st = lsqpack_enc_encode(&enc, enc_buf, &enc_sz, header_buf, &header_sz, &xhdr, LQEF_NO_HIST_UPD|LQEF_NO_DYN); nw = lsqpack_enc_end_header(&enc, prefix_buf, sizeof(prefix_buf), &hflags); assert(2 == nw); assert(prefix_buf[0] == 0 && prefix_buf[1] == 0); /* Dynamic table not used */ assert(!(hflags & LSQECH_REF_NEW_ENTRIES)); /* Last check that history was not updated: */ s = lsqpack_enc_start_header(&enc, 4, 0); assert(0 == s); enc_sz = sizeof(enc_buf); header_sz = sizeof(header_buf); header_set_ptr(&xhdr, &hbuf, ":method", 7, "where is my car?", 16); enc_st = lsqpack_enc_encode(&enc, enc_buf, &enc_sz, header_buf, &header_sz, &xhdr, 0); assert(enc_sz == 0); assert(LQES_OK == enc_st); nw = lsqpack_enc_end_header(&enc, prefix_buf, sizeof(prefix_buf), &hflags); assert(2 == nw); assert(prefix_buf[0] == 0 && prefix_buf[1] == 0); /* Dynamic table not used */ assert(!(hflags & LSQECH_REF_AT_RISK)); lsqpack_enc_cleanup(&enc); } struct hblock_ctx { unsigned n_headers; int finished; struct lsxpack_header xhdr; char buf[0x10000]; }; static void unblocked (void *hblock_ctx_p) { assert(0); /* Not expecting this to be called */ } static struct lsxpack_header * prepare_decode (void *hblock_ctx_p, struct lsxpack_header *xhdr, size_t space) { struct hblock_ctx *const hctx = hblock_ctx_p; if (space > LSXPACK_MAX_STRLEN) return NULL; if (xhdr) return NULL; lsxpack_header_prepare_decode(&hctx->xhdr, hctx->buf, 0, sizeof(hctx->buf)); return &hctx->xhdr; } static int process_header (void *hblock_ctx_p, struct lsxpack_header *xhdr) { struct hblock_ctx *const hctx = hblock_ctx_p; if (xhdr) ++hctx->n_headers; else hctx->finished = 1; return 0; } static const struct lsqpack_dec_hset_if hset_if = { .dhi_unblocked = unblocked, .dhi_prepare_decode = prepare_decode, .dhi_process_header = process_header, }; static void test_discard_header (int err) { struct lsqpack_dec dec; enum lsqpack_read_header_status rhs; const unsigned char *buf; unsigned char header_block[] = "\x00\x00\xC0\x80"; struct hblock_ctx hctx = { .n_headers = 0, }; lsqpack_dec_init(&dec, NULL, 0, 0, &hset_if, LSQPACK_DEC_OPT_HTTP1X); buf = header_block; rhs = lsqpack_dec_header_in(&dec, &hctx, 0, 10, &buf, 3 + !!err, NULL, NULL); if (err) { assert(hctx.n_headers == 1); assert(hctx.finished == 0); assert(LQRHS_ERROR == rhs); } else { assert(hctx.n_headers == 1); assert(hctx.finished == 0); assert(3 == buf - header_block); assert(LQRHS_NEED == rhs); lsqpack_dec_cleanup(&dec); } } static void test_static_bounds_header_block (void) { struct lsqpack_dec dec; enum lsqpack_read_header_status rhs; const unsigned char *buf; /* Static table index 1000 */ unsigned char header_block[] = "\x00\x00\xFF\xA9\x07"; struct hblock_ctx hctx = { .n_headers = 0, }; lsqpack_dec_init(&dec, stderr, 0, 0, &hset_if, LSQPACK_DEC_OPT_HTTP1X); buf = header_block; rhs = lsqpack_dec_header_in(&dec, &hctx, 0, 10, &buf, 5, NULL, NULL); assert(hctx.n_headers == 0); assert(LQRHS_ERROR == rhs); lsqpack_dec_cleanup(&dec); } static void test_static_bounds_enc_stream (void) { struct lsqpack_dec dec; int r; /* Static table index 1000 */ unsigned char enc_stream[] = "\xFF\xA9\x07\x04" "dude"; lsqpack_dec_init(&dec, NULL, 0, 0, &hset_if, LSQPACK_DEC_OPT_HTTP1X); r = lsqpack_dec_enc_in(&dec, enc_stream, 8); assert(r == -1); lsqpack_dec_cleanup(&dec); } static void test_wonr_name_too_large_huffman (void) { struct lsqpack_dec dec; int r; /* Partial Insert Without Name Reference with Huffman-encoded name * string that is larger than 4 x capacity (0x4001) */ unsigned char enc_stream[] = "\x7F\xE2\x7F"; lsqpack_dec_init(&dec, stderr, 0x1000, 0, &hset_if, LSQPACK_DEC_OPT_HTTP1X); r = lsqpack_dec_enc_in(&dec, enc_stream, 3); assert(r == -1); lsqpack_dec_cleanup(&dec); } static void test_wonr_name_too_large_plain (void) { struct lsqpack_dec dec; int r; /* Partial Insert Without Name Reference with plain-encoded name * string that is larger than capacity (0x1001) */ unsigned char enc_stream[] = "\x5F\xE2\x1F"; lsqpack_dec_init(&dec, stderr, 0, 0, &hset_if, LSQPACK_DEC_OPT_HTTP1X); r = lsqpack_dec_enc_in(&dec, enc_stream, 3); assert(r == -1); lsqpack_dec_cleanup(&dec); } static void test_wonr_value_too_large_huffman (void) { struct lsqpack_dec dec; int r; /* Partial Insert Without Name Reference with Huffman-encoded value * string that, together with name, is larger than 4 x capacity (0x3FFF) */ unsigned char enc_stream[] = "\x42OK\xFF\x80\x7F"; lsqpack_dec_init(&dec, stderr, 0, 0, &hset_if, LSQPACK_DEC_OPT_HTTP1X); r = lsqpack_dec_enc_in(&dec, enc_stream, 6); assert(r == -1); lsqpack_dec_cleanup(&dec); } static void test_wonr_value_too_large_plain (void) { struct lsqpack_dec dec; int r; /* Partial Insert Without Name Reference with plain-encoded value * string that, together with name, is larger than capacity (0xFFF) */ unsigned char enc_stream[] = "\x42OK\x7F\x80\x1F"; lsqpack_dec_init(&dec, stderr, 0x1000, 0, &hset_if, LSQPACK_DEC_OPT_HTTP1X); r = lsqpack_dec_enc_in(&dec, enc_stream, 6); assert(r == -1); lsqpack_dec_cleanup(&dec); } static void test_winr_value_too_large_huffman (void) { struct lsqpack_dec dec; int r; /* Partial Insert With Name Reference with Huffman-encoded value * string that, together with name, is larger than 4 x capacity (0x3FE4) */ /* Refer to static entry 79: access-control-refer-headers (length 28) */ unsigned char enc_stream[] = "\xFF\x10\xFF\xE5\x7E"; lsqpack_dec_init(&dec, stderr, 0x1000, 0, &hset_if, LSQPACK_DEC_OPT_HTTP1X); r = lsqpack_dec_enc_in(&dec, enc_stream, 5); assert(r == -1); lsqpack_dec_cleanup(&dec); } static void test_winr_value_too_large_plain (void) { struct lsqpack_dec dec; int r; /* Partial Insert With Name Reference with plain-encoded value * string that, together with name, is larger than capacity (0xFE4) */ /* Refer to static entry 79: access-control-refer-headers (length 28) */ unsigned char enc_stream[] = "\xFF\x10\x7F\xE5\x1E"; lsqpack_dec_init(&dec, stderr, 0x1000, 0, &hset_if, LSQPACK_DEC_OPT_HTTP1X); r = lsqpack_dec_enc_in(&dec, enc_stream, 5); assert(r == -1); lsqpack_dec_cleanup(&dec); } /* This is an odd case, but if the first call should provide no input at all, * the decoder should return LQRHS_NEED */ static void test_dec_header_zero_in (void) { struct lsqpack_dec dec; enum lsqpack_read_header_status rhs; const unsigned char *buf = (unsigned char *) ""; struct hblock_ctx hctx = { .n_headers = 0, }; lsqpack_dec_init(&dec, stderr, 0x1000, 0, &hset_if, LSQPACK_DEC_OPT_HTTP1X); rhs = lsqpack_dec_header_in(&dec, &hctx /* hblock */, 2 /* Stream ID */, 100 /* How long the thing is */, &buf, 0 /* How many bytes are available */, NULL, 0); assert(LQRHS_NEED == rhs); lsqpack_dec_cleanup(&dec); } /* Header that's too should should return LQRHS_ERROR */ static void test_dec_header_too_short (size_t header_size) { struct lsqpack_dec dec; enum lsqpack_read_header_status rhs; const unsigned char *buf = (unsigned char *) ""; struct hblock_ctx hctx = { .n_headers = 0, }; lsqpack_dec_init(&dec, stderr, 0x1000, 0, &hset_if, LSQPACK_DEC_OPT_HTTP1X); rhs = lsqpack_dec_header_in(&dec, &hctx /* hblock */, 2 /* Stream ID */, header_size, &buf, 0 /* How many bytes are available */, NULL, 0); assert(LQRHS_ERROR == rhs); lsqpack_dec_cleanup(&dec); } static void test_enc_risked_streams_test (const char *test) { struct lsqpack_enc enc; int s; size_t sz; ssize_t ssz; long arg; enum lsqpack_enc_status es; unsigned n_seqnos = 0; struct { uint64_t stream_id; unsigned seqno; } seqnos[10], *seq_el; unsigned char buf[0x100]; unsigned char *end_cmd; int expect_failure; struct lsxpack_header xhdr; struct header_buf hbuf; const struct { const char *name; unsigned name_len; const char *value; unsigned value_len; } headers[] = { { "some", 4, "header", 6, }, { "another", 7, "header", 6, }, { "and yet another", 15, "header, duh", 11, }, }; fprintf(stderr, "BEGIN TEST %s\n", test); lsqpack_enc_preinit(&enc, stderr); hbuf.off = 0; while (1) { expect_failure = isupper(*test); switch (*test++) { case 'i': arg = strtol(test, (char**)&test, 10); sz = sizeof(buf); s = lsqpack_enc_init(&enc, stderr, 0x1000, 0x1000, (unsigned) arg, 0, buf, &sz); assert(s == 0); break; case 'r': arg = strtol(test, (char**)&test, 10); assert((unsigned) arg == enc.qpe_cur_streams_at_risk); break; case 's': /* Start header */ arg = strtol(test, (char**)&test, 10); for (seq_el = seqnos; seq_el < seqnos + n_seqnos; ++seq_el) if (seq_el->stream_id == arg) break; assert(seq_el < seqnos + sizeof(seqnos) / sizeof(seqnos[0])); if (seq_el == seqnos + n_seqnos) { ++n_seqnos; seq_el->stream_id = arg; seq_el->seqno = 0; } s = lsqpack_enc_start_header(&enc, arg, seq_el->seqno++); assert(s == 0); break; case 'c': /* En*C*ode */ arg = strtol(test, (char**)&test, 10); sz = sizeof(buf); /* We ignore the output */ header_set_ptr(&xhdr, &hbuf, headers[arg].name, headers[arg].name_len, headers[arg].value, headers[arg].value_len); es = lsqpack_enc_encode(&enc, buf, &sz, buf, &sz, &xhdr, 0); assert(LQES_OK == es); break; case 'e': /* End header */ ssz = lsqpack_enc_end_header(&enc, buf, sizeof(buf), NULL); assert(ssz > 0); break; case 'a': /* ACK header */ case 'A': arg = strtol(test, (char**)&test, 10); buf[0] = 0x80; end_cmd = lsqpack_enc_int(buf, buf + sizeof(buf), arg, 7); s = lsqpack_enc_decoder_in(&enc, buf, end_cmd - buf); if (expect_failure) assert(s < 0); else assert(s == 0); break; case 'L': /* Cancel header */ case 'l': arg = strtol(test, (char**)&test, 10); buf[0] = 0x40; end_cmd = lsqpack_enc_int(buf, buf + sizeof(buf), arg, 6); s = lsqpack_enc_decoder_in(&enc, buf, end_cmd - buf); if (expect_failure) assert(s < 0); else assert(s == 0); break; case 'N': /* Insert Count Increment */ case 'n': arg = strtol(test, (char**)&test, 10); buf[0] = 0x00; end_cmd = lsqpack_enc_int(buf, buf + sizeof(buf), arg, 6); s = lsqpack_enc_decoder_in(&enc, buf, end_cmd - buf); if (expect_failure) assert(s < 0); else assert(s == 0); break; break; case '\0': goto end; default: assert("unknown action"); goto end; } } end: lsqpack_enc_cleanup(&enc); } static void test_enc_risked_streams (void) { const char **test; const char *tests[] = { "i0r0s1c0er0", "i1r0s1c0er0s2c0er1A1r1a2r0", "i1r0s1c0er0s2c0er1s2c0c1er1a2r0a2r0", "i1r0s1c0c1er0" "s2c0er1" "s2c0c1er1" "a2r1" /* Ack first seqno, # of risked streams still 1 */ "a2r0", "i1r0s1c0c1er0" "s2c0er1" "s2c0c1er1" "l2r0" /* Cancel */ , "i1r0s1c0c1er0" "s2c0er1" "s2c0c1er1" "N3" "n1r1" "n1r0" , NULL, }; for (test = tests; *test; ++test) test_enc_risked_streams_test(*test); } int main (void) { unsigned i; for (i = 0; i < sizeof(header_block_tests) / sizeof(header_block_tests[0]); ++i) { run_header_test(&header_block_tests[i]); run_decoded_headers_test(&header_block_tests[i]); } run_header_cancellation_test(&header_block_tests[0]); test_enc_init(); test_push_promise(); test_discard_header(0); test_discard_header(1); test_static_bounds_header_block(); test_static_bounds_enc_stream(); test_wonr_name_too_large_huffman(); test_wonr_name_too_large_plain(); test_wonr_value_too_large_huffman(); test_wonr_value_too_large_plain(); test_winr_value_too_large_huffman(); test_winr_value_too_large_plain(); test_dec_header_zero_in(); test_dec_header_too_short(0); test_dec_header_too_short(1); test_enc_risked_streams(); return 0; }