Files
lsquic/tests/test_frame_reader.c
Dmitri Tikhonov b7bb00e675 Remove unused push promise support (#642)
Remove the HTTP/2 and HTTP/3 push promise implementation while keeping the 
public APIs in place as disabled stubs.  Server push is no longer used,
so this drops the associated state, frame generation, parsing callbacks,
and stream bookkeeping.  Update tests and documentation to cover the
disabled behavior and the remaining HTTP/3 framing paths.
2026-05-03 22:55:05 -04:00

1185 lines
39 KiB
C

/* Copyright (c) 2017 - 2026 LiteSpeed Technologies Inc. See LICENSE. */
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
#include <unistd.h>
#else
#include <getopt.h>
#endif
#include <sys/queue.h>
#include "lsquic.h"
#include "lsquic_frame_common.h"
#include "lshpack.h"
#include "lsquic_mm.h"
#include "lsquic_int_types.h"
#include "lsquic_conn_flow.h"
#include "lsquic_sfcw.h"
#include "lsquic_rtt.h"
#include "lsquic_hash.h"
#include "lsquic_conn.h"
#include "lsquic_varint.h"
#include "lsquic_hq.h"
#include "lsquic_stream.h"
#include "lsquic_conn_public.h"
#include "lsquic_logger.h"
#if LSQUIC_CONN_STATS
#include "lsquic_int_types.h"
#include "lsquic_conn.h"
#endif
#include "lsquic_frame_reader.h"
#include "lsquic_headers.h"
#include "lsquic_http1x_if.h"
struct callback_value /* What callback returns */
{
enum {
CV_HEADERS,
CV_SETTINGS,
CV_PRIORITY,
CV_ERROR,
} type;
unsigned stream_off; /* Checked only if not zero */
union {
struct headers {
uint32_t stream_id;
uint32_t oth_stream_id;
unsigned short weight;
signed char exclusive;
unsigned char flags;
unsigned size;
unsigned off;
char buf[0x100];
} headers;
struct {
uint16_t id;
uint32_t value;
} setting;
struct cv_error {
enum frame_reader_error code;
lsquic_stream_id_t stream_id;
} error;
struct cv_priority {
lsquic_stream_id_t stream_id;
int exclusive;
lsquic_stream_id_t dep_stream_id;
unsigned weight;
} priority;
} u;
};
void
compare_headers (const struct headers *got_h, const struct headers *exp_h)
{
assert(got_h->stream_id == exp_h->stream_id);
assert(got_h->oth_stream_id == exp_h->oth_stream_id);
assert(got_h->weight == exp_h->weight);
assert(got_h->exclusive == exp_h->exclusive);
assert(got_h->size == exp_h->size);
assert(strlen(got_h->buf) == got_h->size);
assert(got_h->off == exp_h->off);
assert(got_h->flags == exp_h->flags);
assert(0 == memcmp(got_h->buf, exp_h->buf, got_h->size));
}
void
compare_priorities (const struct cv_priority *got_prio,
const struct cv_priority *exp_prio)
{
assert(got_prio->stream_id == exp_prio->stream_id);
assert(got_prio->exclusive == exp_prio->exclusive);
assert(got_prio->dep_stream_id == exp_prio->dep_stream_id);
assert(got_prio->weight == exp_prio->weight);
}
void
compare_errors (const struct cv_error *got_err,
const struct cv_error *exp_err)
{
assert(got_err->code == exp_err->code);
assert(got_err->stream_id == exp_err->stream_id);
}
static void
compare_cb_vals (const struct callback_value *got,
const struct callback_value *exp)
{
assert(got->type == exp->type);
if (exp->stream_off)
assert(exp->stream_off == got->stream_off);
switch (got->type)
{
case CV_HEADERS:
compare_headers(&got->u.headers, &exp->u.headers);
break;
case CV_ERROR:
compare_errors(&got->u.error, &exp->u.error);
break;
case CV_PRIORITY:
compare_priorities(&got->u.priority, &exp->u.priority);
break;
case CV_SETTINGS:
/* TODO */
break;
}
}
static struct {
size_t in_sz;
size_t in_off;
size_t in_max_req_sz;
size_t in_max_sz;
unsigned char in_buf[0x1000];
} input;
static struct cb_ctx {
unsigned n_cb_vals;
struct callback_value cb_vals[10];
} g_cb_ctx;
static void
reset_cb_ctx (struct cb_ctx *cb_ctx)
{
cb_ctx->n_cb_vals = 0;
memset(&cb_ctx->cb_vals, 0xA5, sizeof(cb_ctx->cb_vals));
}
static void
copy_uh_to_headers (const struct uncompressed_headers *uh, struct headers *h)
{
const struct http1x_headers *h1h = uh->uh_hset;
h->flags = uh->uh_flags;
h->weight = uh->uh_weight;
h->stream_id = uh->uh_stream_id;
h->exclusive = uh->uh_exclusive;
h->oth_stream_id = uh->uh_oth_stream_id;
h->size = h1h->h1h_size;
h->off = h1h->h1h_off;
memcpy(h->buf, h1h->h1h_buf, h->size);
h->buf[h->size] = '\0';
}
static void
on_incoming_headers (void *ctx, struct uncompressed_headers *uh)
{
struct cb_ctx *cb_ctx = ctx;
assert(cb_ctx == &g_cb_ctx);
unsigned i = cb_ctx->n_cb_vals++;
assert(i < sizeof(cb_ctx->cb_vals) / sizeof(cb_ctx->cb_vals[0]));
cb_ctx->cb_vals[i].type = CV_HEADERS;
cb_ctx->cb_vals[i].stream_off = input.in_off;
copy_uh_to_headers(uh, &cb_ctx->cb_vals[i].u.headers);
assert(uh->uh_flags & UH_H1H);
lsquic_http1x_if->hsi_discard_header_set(uh->uh_hset);
free(uh);
}
static void
on_error (void *ctx, lsquic_stream_id_t stream_id, enum frame_reader_error error)
{
struct cb_ctx *cb_ctx = ctx;
assert(cb_ctx == &g_cb_ctx);
unsigned i = cb_ctx->n_cb_vals++;
assert(i < sizeof(cb_ctx->cb_vals) / sizeof(cb_ctx->cb_vals[0]));
cb_ctx->cb_vals[i].type = CV_ERROR;
cb_ctx->cb_vals[i].u.error.stream_id = stream_id;
cb_ctx->cb_vals[i].u.error.code = error;
cb_ctx->cb_vals[i].stream_off = input.in_off;
}
static void
on_settings (void *ctx, uint16_t id, uint32_t value)
{
struct cb_ctx *cb_ctx = ctx;
assert(cb_ctx == &g_cb_ctx);
unsigned i = cb_ctx->n_cb_vals++;
assert(i < sizeof(cb_ctx->cb_vals) / sizeof(cb_ctx->cb_vals[0]));
cb_ctx->cb_vals[i].type = CV_SETTINGS;
cb_ctx->cb_vals[i].u.setting.id = id;
cb_ctx->cb_vals[i].u.setting.value = value;
cb_ctx->cb_vals[i].stream_off = input.in_off;
}
static void
on_priority (void *ctx, lsquic_stream_id_t stream_id, int exclusive,
lsquic_stream_id_t dep_stream_id, unsigned weight)
{
struct cb_ctx *cb_ctx = ctx;
assert(cb_ctx == &g_cb_ctx);
unsigned i = cb_ctx->n_cb_vals++;
assert(i < sizeof(cb_ctx->cb_vals) / sizeof(cb_ctx->cb_vals[0]));
cb_ctx->cb_vals[i].type = CV_PRIORITY;
cb_ctx->cb_vals[i].u.priority.stream_id = stream_id;
cb_ctx->cb_vals[i].u.priority.exclusive = exclusive;
cb_ctx->cb_vals[i].u.priority.dep_stream_id = dep_stream_id;
cb_ctx->cb_vals[i].u.priority.weight = weight;
cb_ctx->cb_vals[i].stream_off = input.in_off;
}
static const struct frame_reader_callbacks frame_callbacks = {
.frc_on_headers = on_incoming_headers,
.frc_on_settings = on_settings,
.frc_on_priority = on_priority,
.frc_on_error = on_error,
};
static ssize_t
read_from_stream (struct lsquic_stream *stream, void *buf, size_t sz)
{
if (sz > input.in_max_req_sz)
input.in_max_req_sz = sz;
if (input.in_sz - input.in_off < sz)
sz = input.in_sz - input.in_off;
if (sz > input.in_max_sz)
sz = input.in_max_sz;
memcpy(buf, input.in_buf + input.in_off, sz);
input.in_off += sz;
return sz;
}
struct frame_reader_test {
unsigned frt_lineno;
/* Input */
enum frame_reader_flags frt_fr_flags;
unsigned char frt_buf[0x100];
unsigned short frt_bufsz;
unsigned frt_max_headers_sz;
/* Output */
unsigned short frt_in_off;
int frt_err; /* True if expecting error */
unsigned frt_n_cb_vals;
struct callback_value frt_cb_vals[10];
};
#define HEADERS(str) .buf = (str), .size = sizeof(str) - 1
static const struct frame_reader_test tests[] = {
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x04,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS,
0x80| /* <----- This bit must be ignored */
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
0x48, 0x82, 0x64, 0x02,
},
.frt_bufsz = 13,
.frt_n_cb_vals = 1,
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0,
.weight = 0,
.exclusive = -1,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x16,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS|HFHF_PADDED,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Padding length */0x11,
/* Block fragment: */
0x48, 0x82, 0x64, 0x02,
/* Padding: */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF,
},
.frt_bufsz = 9 + 1 + 4 + 17,
.frt_n_cb_vals = 1,
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0,
.weight = 0,
.exclusive = -1,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x1B,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS|HFHF_PADDED|HFHF_PRIORITY|
HFHF_END_STREAM,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Padding length */0x11,
/* Exclusive: */ 0x80|
/* Dep Stream Id: */
0x00, 0x00, 0x12, 0x34,
/* Weight: */ 0xFF,
/* Block fragment: */
0x48, 0x82, 0x64, 0x02,
/* Padding: */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF,
/* Length: */ 0x00, 0x00, 0x05,
/* Type: */ HTTP_FRAME_PRIORITY,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x00, 0x39,
/* Dep Stream Id: */0x80, 0x00, 0x00, 0x19,
/* Weight: */ 0x77,
},
.frt_bufsz = 9 + 1 + 5 + 4 + 17
+ 9 + 5,
.frt_n_cb_vals = 2,
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 0xFF + 1,
.exclusive = 1,
.off = 0,
.flags = UH_FIN | UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
},
},
{
.type = CV_PRIORITY,
.u.priority = {
.stream_id = 0x39,
.exclusive = 1,
.dep_stream_id = 0x19,
.weight = 0x77 + 1,
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x09,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS|HFHF_PRIORITY,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Exclusive: */ 0x00|
/* Dep Stream Id: */
0x00, 0x00, 0x12, 0x34,
/* Weight: */ 0x00,
/* Block fragment: */
0x48, 0x82, 0x64, 0x02,
},
.frt_bufsz = 9 + 5 + 4,
.frt_n_cb_vals = 1,
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 1,
.exclusive = 0,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x0E,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS|HFHF_PRIORITY,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Exclusive: */ 0x00|
/* Dep Stream Id: */
0x00, 0x00, 0x12, 0x34,
/* Weight: */ 0x00,
/* Block fragment: */
0x48, 0x82, 0x64, 0x02,
0x60, 0x03, 0x61, 0x3d, 0x62,
},
.frt_bufsz = 9 + 5 + 4 + 5,
.frt_n_cb_vals = 1,
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 1,
.exclusive = 0,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n"
"Cookie: a=b\r\n\r\n"),
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x18,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS|HFHF_PRIORITY,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Exclusive: */ 0x00|
/* Dep Stream Id: */
0x00, 0x00, 0x12, 0x34,
/* Weight: */ 0x00,
/* Block fragment: */
0x48, 0x82, 0x64, 0x02,
0x60, 0x03, 0x61, 0x3d, 0x62,
0x60, 0x03, 0x63, 0x3d, 0x64,
0x60, 0x03, 0x65, 0x3d, 0x66,
},
.frt_bufsz = 9 + 5 + 4 + 15,
.frt_n_cb_vals = 1,
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 1,
.exclusive = 0,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n"
"Cookie: a=b; c=d; e=f\r\n\r\n"),
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = FRF_SERVER,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x16,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS|HFHF_PRIORITY,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Exclusive: */ 0x00|
/* Dep Stream Id: */
0x00, 0x00, 0x12, 0x34,
/* Weight: */ 0x00,
/* Block fragment: */
0x82, 0x84, 0x86, 0x41, 0x8c, 0xf1, 0xe3, 0xc2,
0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4,
0xff,
/* Length: */ 0x00, 0x00, 0xEE,
/* Type: */ HTTP_FRAME_CONTINUATION,
/* Flags: */ HFHF_END_HEADERS,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
'W', 'H', 'A', 'T', 'E', 'V', 'E', 'R',
},
.frt_bufsz = 9 + 5 + 17
+ 9 + 0 + 8,
.frt_err = 1,
.frt_in_off = 9 + 5 + 17 + 9,
.frt_n_cb_vals = 1,
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 1,
.exclusive = 0,
.off = 0,
.flags = UH_H1H,
HEADERS("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"),
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = FRF_SERVER,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x16,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS|HFHF_PRIORITY,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Exclusive: */ 0x00|
/* Dep Stream Id: */
0x00, 0x00, 0x12, 0x34,
/* Weight: */ 0x00,
/* Block fragment: */
0x82, 0x84, 0x86, 0x41, 0x8c, 0xf1, 0xe3, 0xc2,
0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4,
0xff,
/* Length: */ 0x00, 0x00, 0xEE,
/* Type: */ HTTP_FRAME_CONTINUATION,
/* Flags: */ HFHF_END_HEADERS,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
'W', 'H', 'A', 'T', 'E', 'V', 'E', 'R',
},
.frt_bufsz = 9 + 5 + 17
+ 9 + 0 + 8,
.frt_err = 1,
.frt_in_off = 9 + 5 + 17 + 9,
.frt_n_cb_vals = 1,
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 1,
.exclusive = 0,
.off = 0,
.flags = UH_H1H,
HEADERS("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"),
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = FRF_SERVER,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x16,
/* Type: */ 0x01,
/* Flags: */ HFHF_PRIORITY,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Exclusive: */ 0x00|
/* Dep Stream Id: */
0x00, 0x00, 0x12, 0x34,
/* Weight: */ 0x00,
/* Block fragment: */
0x82, 0x84, 0x86, 0x41, 0x8c, 0xf1, 0xe3, 0xc2,
0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4,
0xff,
/* Length: */ 0x00, 0x00, 0xEE,
/* Type: */ HTTP_FRAME_CONTINUATION,
/* Flags: */ HFHF_END_HEADERS,
/* Stream Id: */ 0x00, 0xFF, 0x30, 0x39, /* Stream ID does not match */
/* Block fragment: */
'W', 'H', 'A', 'T', 'E', 'V', 'E', 'R',
},
.frt_bufsz = 9 + 5 + 17
+ 9 + 0 + 8,
.frt_err = 1,
.frt_in_off = 9 + 5 + 17 + 9,
.frt_n_cb_vals = 0,
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = FRF_SERVER,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0xEE,
/* Type: */ HTTP_FRAME_CONTINUATION,
/* Flags: */ HFHF_END_HEADERS,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
'W', 'H', 'A', 'T', 'E', 'V', 'E', 'R',
},
.frt_bufsz = 9 + 0 + 8,
.frt_err = 1,
.frt_in_off = 9,
.frt_n_cb_vals = 0,
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = FRF_SERVER,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x10,
/* Type: */ 0x01,
/* Flags: */ 0x00, /* Note absence of HFHF_END_HEADERS */
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment:
* perl hpack.pl :method GET :path / host www.example.com
*/
0x82, 0x84, 0x66, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5,
0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
/* Length: */ 0x00, 0x00, 0x08,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
'W', 'H', 'A', 'T', 'E', 'V', 'E', 'R',
},
.frt_bufsz = 9 + 0 + 16
+ 9 + 0 + 8,
.frt_in_off = 9 + 16 + 9,
.frt_err = 1,
.frt_n_cb_vals = 1,
.frt_cb_vals = {
{
.type = CV_ERROR,
.u.error = {
.stream_id = 0x3039,
.code = FR_ERR_EXPECTED_CONTIN,
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = FRF_SERVER,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x10,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment:
* perl hpack.pl :method GET :path / host www.example.com
*/
0x82, 0x84, 0x66, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5,
0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
/* Length: */ 0x00, 0x00, 0x1A,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS|HFHF_PRIORITY,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Exclusive: */ 0x00|
/* Dep Stream Id: */
0x00, 0x00, 0x12, 0x34,
/* Weight: */ 0x00,
/* Block fragment:
* perl hpack.pl :method GET :path / :scheme http Host www.example.com
*/
0x82, 0x84, 0x86, 0x40, 0x83, 0xc6, 0x74, 0x27,
0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b,
0xa0, 0xab, 0x90, 0xf4, 0xff,
/* Length: */ 0x00, 0x00, 0x11,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
0x82, 0x84, 0x86, 0x41, 0x8c, 0xf1, 0xe3, 0xc2,
0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4,
0xff,
},
.frt_bufsz = 9 + 0 + 16
+ 9 + 5 + 21
+ 9 + 0 + 17,
.frt_n_cb_vals = 3,
.frt_cb_vals = {
{
.type = CV_ERROR,
.u.error = {
.stream_id = 12345,
.code = FR_ERR_BAD_HEADER,
},
},
{
.type = CV_ERROR,
.u.error = {
.stream_id = 12345,
.code = FR_ERR_BAD_HEADER,
},
},
{
.type = CV_HEADERS,
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0,
.weight = 0,
.exclusive = -1,
.off = 0,
.flags = UH_H1H,
HEADERS("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"),
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x15,
/* Type: */ HTTP_FRAME_PUSH_PROMISE,
/* Flags: */ HFHF_END_HEADERS,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Dep stream Id: */0x00, 0x12, 0x34, 0x56,
/* Block fragment: */
0x82, 0x84, 0x86, 0x41, 0x8c, 0xf1, 0xe3, 0xc2,
0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4,
0xff,
},
.frt_bufsz = 9 + 0 + 0x15,
.frt_in_off = 9,
.frt_err = 1,
.frt_n_cb_vals = 1,
.frt_cb_vals = {
{
.type = CV_ERROR,
.u.error = {
.stream_id = 12345,
.code = FR_ERR_UNEXPECTED_PUSH,
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x02,
/* Type: */ HTTP_FRAME_HEADERS,
/* Flags: */ 0x00,
0x80| /* <----- This bit must be ignored */
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
0x48, 0x82,
/* Length: */ 0x00, 0x00, 0x02,
/* Type: */ HTTP_FRAME_CONTINUATION,
/* Flags: */ HFHF_END_HEADERS,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
0x64, 0x02,
},
.frt_bufsz = 9 + 2 + 9 + 2,
.frt_n_cb_vals = 1,
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0,
.weight = 0,
.exclusive = -1,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
},
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x00,
/* Type: */ HTTP_FRAME_SETTINGS,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
},
.frt_bufsz = 9,
.frt_n_cb_vals = 1,
.frt_err = 1,
.frt_cb_vals = {
{
.type = CV_ERROR,
.u.error.code = FR_ERR_INVALID_FRAME_SIZE,
.u.error.stream_id = 12345,
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x07,
/* Type: */ HTTP_FRAME_SETTINGS,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
},
.frt_bufsz = 9 + 7,
.frt_n_cb_vals = 1,
.frt_err = 1,
.frt_in_off = 9,
.frt_cb_vals = {
{
.type = CV_ERROR,
.u.error.code = FR_ERR_INVALID_FRAME_SIZE,
.u.error.stream_id = 12345,
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x06,
/* Type: */ HTTP_FRAME_SETTINGS,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
},
.frt_bufsz = 9 + 6,
.frt_n_cb_vals = 1,
.frt_err = 1,
.frt_in_off = 9,
.frt_cb_vals = {
{
.type = CV_ERROR,
.u.error.code = FR_ERR_NONZERO_STREAM_ID,
.u.error.stream_id = 12345,
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x0C,
/* Type: */ HTTP_FRAME_SETTINGS,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x00, 0x00,
0x00, SETTINGS_INITIAL_WINDOW_SIZE,
0x01, 0x02, 0x03, 0x04,
0x00, SETTINGS_HEADER_TABLE_SIZE,
0x02, 0x03, 0x04, 0x05,
},
.frt_bufsz = 9 + 12,
.frt_n_cb_vals = 2,
.frt_cb_vals = {
{
.type = CV_SETTINGS,
.u.setting.id = SETTINGS_INITIAL_WINDOW_SIZE,
.u.setting.value = 0x01020304,
},
{
.type = CV_SETTINGS,
.u.setting.id = SETTINGS_HEADER_TABLE_SIZE,
.u.setting.value = 0x02030405,
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x09,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS|HFHF_PRIORITY,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Exclusive: */ 0x00|
/* Dep Stream Id: */
0x00, 0x00, 0x12, 0x34,
/* Weight: */ 0x00,
/* Block fragment: */
0x48, 0x82, 0x64, 0x02,
/* Length: */ 0x00, 0x00, 0x06,
/* Type: */ HTTP_FRAME_SETTINGS,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x00, 0x00,
0x00, SETTINGS_INITIAL_WINDOW_SIZE,
0x01, 0x02, 0x03, 0x04,
},
.frt_bufsz = 9 + 5 + 4 + 9 + 6,
.frt_max_headers_sz = 10,
.frt_n_cb_vals = 2,
.frt_cb_vals = {
{
.type = CV_ERROR,
.stream_off = 9 + 5 + 4,
.u.error.code = FR_ERR_BAD_HEADER,
.u.error.stream_id = 12345,
},
{
.type = CV_SETTINGS,
.u.setting.id = SETTINGS_INITIAL_WINDOW_SIZE,
.u.setting.value = 0x01020304,
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x11,
/* Type: */ 0x01,
/* Flags: */ HFHF_END_HEADERS,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
/* 0x11 bytes of no consequence: they are not
* parsed.
*/
000, 001, 002, 003, 004, 005, 006, 007,
010, 011, 012, 013, 014, 015, 016, 017,
020,
/* Length: */ 0x00, 0x00, 0x06,
/* Type: */ HTTP_FRAME_SETTINGS,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x00, 0x00,
0x00, SETTINGS_INITIAL_WINDOW_SIZE,
0x01, 0x02, 0x03, 0x04,
},
.frt_bufsz = 9 + 0 + 0x11 + 9 + 6,
.frt_max_headers_sz = 0x10,
.frt_n_cb_vals = 2,
.frt_cb_vals = {
{
.type = CV_ERROR,
.stream_off = 9,
.u.error.code = FR_ERR_BAD_HEADER,
.u.error.stream_id = 12345,
},
{
.type = CV_SETTINGS,
.u.setting.id = SETTINGS_INITIAL_WINDOW_SIZE,
.u.setting.value = 0x01020304,
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x10,
/* Type: */ 0x01,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
/* 0x10 bytes of no consequence: they are not
* parsed.
*/
000, 001, 002, 003, 004, 005, 006, 007,
010, 011, 012, 013, 014, 015, 016, 017,
/* Length: */ 0x00, 0x00, 0x10,
/* Type: */ HTTP_FRAME_CONTINUATION,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
000, 001, 002, 003, 004, 005, 006, 007,
010, 011, 012, 013, 014, 015, 016, 017,
/* Length: */ 0x00, 0x00, 0x10,
/* Type: */ HTTP_FRAME_CONTINUATION,
/* Flags: */ HFHF_END_HEADERS,
/* Stream Id: */ 0x00, 0x00, 0x30, 0x39,
/* Block fragment: */
000, 001, 002, 003, 004, 005, 006, 007,
010, 011, 012, 013, 014, 015, 016, 017,
/* Length: */ 0x00, 0x00, 0x06,
/* Type: */ HTTP_FRAME_SETTINGS,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x00, 0x00,
0x00, SETTINGS_INITIAL_WINDOW_SIZE,
0x01, 0x02, 0x03, 0x04,
},
.frt_bufsz = 9 + 0 + 0x10 + 9 + 0 + 0x10 + 9 + 0 + 0x10 + 9 + 6,
.frt_max_headers_sz = 0x19,
.frt_n_cb_vals = 2,
.frt_cb_vals = {
{
.type = CV_ERROR,
.stream_off = 9 + 0 + 0x10 + 9,
.u.error.code = FR_ERR_BAD_HEADER,
.u.error.stream_id = 12345,
},
{
.type = CV_SETTINGS,
.u.setting.id = SETTINGS_INITIAL_WINDOW_SIZE,
.u.setting.value = 0x01020304,
},
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00,
0x04, /* <-- wrong payload size */
/* Type: */ HTTP_FRAME_PRIORITY,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x00, 0x39,
/* Dep Stream Id: */0x80, 0x00, 0x00, 0x19,
/* Weight: */ 0x77,
},
.frt_bufsz = 9 + 5,
.frt_n_cb_vals = 1,
.frt_err = 1,
.frt_in_off = 9,
.frt_cb_vals = {
{
.type = CV_ERROR,
.stream_off = 9,
.u.error.code = FR_ERR_INVALID_FRAME_SIZE,
.u.error.stream_id = 0x39,
}
},
},
{ .frt_lineno = __LINE__,
.frt_fr_flags = 0,
.frt_buf = {
/* Length: */ 0x00, 0x00, 0x05,
/* Type: */ HTTP_FRAME_PRIORITY,
/* Flags: */ 0x00,
/* Stream Id: */ 0x00, 0x00, 0x00, 0x00, /* Invalid stream ID */
/* Dep Stream Id: */0x80, 0x00, 0x00, 0x19,
/* Weight: */ 0x77,
},
.frt_bufsz = 9 + 5,
.frt_n_cb_vals = 1,
.frt_err = 1,
.frt_in_off = 9,
.frt_cb_vals = {
{
.type = CV_ERROR,
.stream_off = 9,
.u.error.code = FR_ERR_ZERO_STREAM_ID,
.u.error.stream_id = 0x00,
}
},
},
{
.frt_bufsz = 0,
},
};
static struct lsquic_stream *
my_get_stream_by_id (struct lsquic_conn *conn, lsquic_stream_id_t stream_id)
{
return (void *) my_get_stream_by_id;
}
static void
test_one_frt (const struct frame_reader_test *frt)
{
struct lsquic_frame_reader *fr;
unsigned short exp_off;
struct lshpack_dec hdec;
struct lsquic_mm mm;
struct lsquic_conn lconn;
struct lsquic_conn_public conn_pub;
struct lsquic_stream stream;
int s;
struct conn_iface my_conn_if;
#if LSQUIC_CONN_STATS
struct conn_stats conn_stats;
memset(&conn_stats, 0, sizeof(conn_stats));
#endif
memset(&stream, 0, sizeof(stream));
memset(&lconn, 0, sizeof(lconn));
memset(&conn_pub, 0, sizeof(conn_pub));
memset(&my_conn_if, 0, sizeof(my_conn_if));
my_conn_if.ci_get_stream_by_id = my_get_stream_by_id;
lconn.cn_if = &my_conn_if;
stream.conn_pub = &conn_pub;
conn_pub.lconn = &lconn;
lsquic_mm_init(&mm);
top:
lshpack_dec_init(&hdec);
memset(&input, 0, sizeof(input));
memcpy(input.in_buf, frt->frt_buf, frt->frt_bufsz);
input.in_sz = frt->frt_bufsz;
do
{
reset_cb_ctx(&g_cb_ctx);
input.in_off = 0;
++input.in_max_sz;
fr = lsquic_frame_reader_new(frt->frt_fr_flags, frt->frt_max_headers_sz,
&mm, &stream, read_from_stream, &hdec, &frame_callbacks, &g_cb_ctx,
#if LSQUIC_CONN_STATS
&conn_stats,
#endif
lsquic_http1x_if, NULL);
do
{
s = lsquic_frame_reader_read(fr);
if (s != 0)
break;
}
while (input.in_off < input.in_sz);
assert(frt->frt_err || 0 == s);
if (my_conn_if.ci_get_stream_by_id)
{
assert(g_cb_ctx.n_cb_vals == frt->frt_n_cb_vals);
unsigned i;
for (i = 0; i < g_cb_ctx.n_cb_vals; ++i)
compare_cb_vals(&g_cb_ctx.cb_vals[i], &frt->frt_cb_vals[i]);
}
exp_off = frt->frt_in_off;
if (!exp_off)
exp_off = frt->frt_bufsz;
assert(input.in_off == exp_off);
lsquic_frame_reader_destroy(fr);
}
while (input.in_max_sz < input.in_max_req_sz);
lshpack_dec_cleanup(&hdec);
if (!(frt->frt_fr_flags & FRF_SERVER) && my_conn_if.ci_get_stream_by_id)
{
/* Do it again, but this time test header block skip logic */
my_conn_if.ci_get_stream_by_id = NULL;
goto top;
}
lsquic_mm_cleanup(&mm);
}
int
main (int argc, char **argv)
{
int opt;
while (-1 != (opt = getopt(argc, argv, "l:")))
{
switch (opt)
{
case 'l':
lsquic_log_to_fstream(stderr, LLTS_NONE);
lsquic_logger_lopt(optarg);
break;
default:
exit(1);
}
}
const struct frame_reader_test *frt;
for (frt = tests; frt->frt_bufsz > 0; ++frt)
test_one_frt(frt);
return 0;
}