litespeed-quic/test/unittests/test_send_headers.c

670 lines
20 KiB
C
Raw Normal View History

/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* test_send_headers.c -- Test what happens when lsquic_stream_send_headers()
* is called.
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <fcntl.h>
#include <limits.h>
#ifndef WIN32
#include <unistd.h>
#else
#include <getopt.h>
#endif
#include "lsquic.h"
#include "lsquic_packet_common.h"
#include "lsquic_packet_ietf.h"
#include "lsquic_alarmset.h"
#include "lsquic_packet_in.h"
#include "lsquic_conn_flow.h"
#include "lsquic_rtt.h"
#include "lsquic_sfcw.h"
#include "lsquic_varint.h"
#include "lsquic_hq.h"
#include "lsquic_hash.h"
#include "lsquic_stream.h"
#include "lsquic_types.h"
#include "lsquic_malo.h"
#include "lsquic_mm.h"
#include "lsquic_conn_public.h"
#include "lsquic_logger.h"
#include "lsquic_parse.h"
#include "lsquic_conn.h"
#include "lsquic_engine_public.h"
#include "lsquic_cubic.h"
#include "lsquic_pacer.h"
#include "lsquic_senhist.h"
#include "lsquic_bw_sampler.h"
#include "lsquic_minmax.h"
#include "lsquic_bbr.h"
#include "lsquic_send_ctl.h"
#include "lsquic_ver_neg.h"
#include "lsquic_packet_out.h"
#include "lsquic_enc_sess.h"
#include "lsqpack.h"
#include "lsquic_frab_list.h"
#include "lsquic_http1x_if.h"
#include "lsquic_qdec_hdl.h"
#include "lsquic_qenc_hdl.h"
#include "lsquic_varint.h"
#include "lsquic_hq.h"
#include "lsquic_data_in_if.h"
#include "lsquic_headers.h"
#include "lsquic_push_promise.h"
static int s_call_wantwrite_in_ctor;
static int s_wantwrite_arg;
static int s_onwrite_called;
static lsquic_stream_ctx_t *
on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
{
if (s_call_wantwrite_in_ctor)
lsquic_stream_wantwrite(stream, s_wantwrite_arg);
return NULL;
}
static void
on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
}
static void
on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *h)
{
s_onwrite_called = 1;
lsquic_stream_wantwrite(stream, 0);
}
const struct lsquic_stream_if stream_if = {
.on_new_stream = on_new_stream,
.on_write = on_write,
.on_close = on_close,
};
enum buf_packet_type
lsquic_send_ctl_determine_bpt (struct lsquic_send_ctl *ctl,
const struct lsquic_stream *stream)
{
return BPT_HIGHEST_PRIO;
}
/* This function is only here to avoid crash in the test: */
void
lsquic_engine_add_conn_to_tickable (struct lsquic_engine_public *enpub,
lsquic_conn_t *conn)
{
}
struct test_objs {
struct lsquic_engine_public eng_pub;
struct lsquic_conn lconn;
struct lsquic_conn_public conn_pub;
struct lsquic_send_ctl send_ctl;
struct lsquic_alarmset alset;
void *stream_if_ctx;
struct ver_neg ver_neg;
const struct lsquic_stream_if *
stream_if;
unsigned initial_stream_window;
enum stream_ctor_flags ctor_flags;
struct qpack_enc_hdl qeh;
struct qpack_dec_hdl qdh;
};
static int
unit_test_doesnt_write_ack (struct lsquic_conn *lconn)
{
return 0;
}
static struct network_path network_path;
static struct network_path *
get_network_path (struct lsquic_conn *lconn, const struct sockaddr *sa)
{
return &network_path;
}
static const struct conn_iface our_conn_if =
{
.ci_can_write_ack = unit_test_doesnt_write_ack,
.ci_get_path = get_network_path,
};
static void
init_test_objs (struct test_objs *tobjs, unsigned initial_conn_window,
unsigned initial_stream_window, enum stream_ctor_flags addl_ctor_flags)
{
int s;
memset(tobjs, 0, sizeof(*tobjs));
LSCONN_INITIALIZE(&tobjs->lconn);
tobjs->lconn.cn_pf = select_pf_by_ver(LSQVER_ID23);
tobjs->lconn.cn_version = LSQVER_ID23;
tobjs->lconn.cn_esf_c = &lsquic_enc_session_common_ietf_v1;
network_path.np_pack_size = IQUIC_MAX_IPv4_PACKET_SZ;
tobjs->lconn.cn_if = &our_conn_if;
lsquic_mm_init(&tobjs->eng_pub.enp_mm);
TAILQ_INIT(&tobjs->conn_pub.sending_streams);
TAILQ_INIT(&tobjs->conn_pub.read_streams);
TAILQ_INIT(&tobjs->conn_pub.write_streams);
TAILQ_INIT(&tobjs->conn_pub.service_streams);
lsquic_cfcw_init(&tobjs->conn_pub.cfcw, &tobjs->conn_pub,
initial_conn_window);
lsquic_conn_cap_init(&tobjs->conn_pub.conn_cap, initial_conn_window);
lsquic_alarmset_init(&tobjs->alset, 0);
tobjs->conn_pub.mm = &tobjs->eng_pub.enp_mm;
tobjs->conn_pub.lconn = &tobjs->lconn;
tobjs->conn_pub.enpub = &tobjs->eng_pub;
tobjs->conn_pub.send_ctl = &tobjs->send_ctl;
tobjs->conn_pub.packet_out_malo =
lsquic_malo_create(sizeof(struct lsquic_packet_out));
tobjs->conn_pub.path = &network_path;
tobjs->initial_stream_window = initial_stream_window;
lsquic_send_ctl_init(&tobjs->send_ctl, &tobjs->alset, &tobjs->eng_pub,
&tobjs->ver_neg, &tobjs->conn_pub, 0);
tobjs->stream_if = &stream_if;
tobjs->stream_if_ctx = NULL;
tobjs->ctor_flags = SCF_CALL_ON_NEW|SCF_DI_AUTOSWITCH|SCF_HTTP
|addl_ctor_flags;
if ((1 << tobjs->lconn.cn_version) & LSQUIC_IETF_VERSIONS)
{
lsquic_qeh_init(&tobjs->qeh, &tobjs->lconn);
s = lsquic_qeh_settings(&tobjs->qeh, 0, 0, 0, 0);
assert(0 == s);
tobjs->conn_pub.u.ietf.qeh = &tobjs->qeh;
tobjs->conn_pub.enpub->enp_hsi_if = lsquic_http1x_if;
s = lsquic_qdh_init(&tobjs->qdh, &tobjs->lconn, 0,
tobjs->conn_pub.enpub, 0, 0);
tobjs->conn_pub.u.ietf.qdh = &tobjs->qdh;
assert(0 == s);
}
}
static void
deinit_test_objs (struct test_objs *tobjs)
{
assert(!lsquic_malo_first(tobjs->eng_pub.enp_mm.malo.stream_frame));
lsquic_send_ctl_cleanup(&tobjs->send_ctl);
lsquic_malo_destroy(tobjs->conn_pub.packet_out_malo);
lsquic_mm_cleanup(&tobjs->eng_pub.enp_mm);
if ((1 << tobjs->lconn.cn_version) & LSQUIC_IETF_VERSIONS)
{
lsquic_qeh_cleanup(&tobjs->qeh);
lsquic_qdh_cleanup(&tobjs->qdh);
}
}
static struct lsquic_stream *
new_stream (struct test_objs *tobjs, unsigned stream_id, uint64_t send_off)
{
return lsquic_stream_new(stream_id, &tobjs->conn_pub, tobjs->stream_if,
tobjs->stream_if_ctx, tobjs->initial_stream_window, send_off,
tobjs->ctor_flags);
}
static struct test_vals {
/* What lsquic_qeh_write_headers() returns or sets */
enum qwh_status status;
size_t prefix_sz;
size_t headers_sz;
uint64_t completion_offset;
} test_vals;
enum qwh_status
lsquic_qeh_write_headers (struct qpack_enc_hdl *qeh,
lsquic_stream_id_t stream_id, unsigned seqno,
const struct lsquic_http_headers *headers, unsigned char *buf,
size_t *prefix_sz, size_t *headers_sz, uint64_t *completion_offset,
enum lsqpack_enc_header_flags *hflags)
{
memset(buf - *prefix_sz, 0xC5, *prefix_sz + *headers_sz);
*prefix_sz = test_vals.prefix_sz;
*headers_sz = test_vals.headers_sz;
*completion_offset = test_vals.completion_offset;
if (hflags)
*hflags = 0;
return test_vals.status;
}
static uint64_t s_enc_off;
uint64_t
lsquic_qeh_enc_off (struct qpack_enc_hdl *qeh)
{
return s_enc_off;
}
static void
test_flushes_and_closes (void)
{
struct test_objs tobjs;
struct lsquic_stream *stream;
ssize_t nw;
int s;
struct uncompressed_headers *uh;
void *hset;
/* For our tests purposes, we treat headers as an opaque object */
struct lsquic_http_headers *headers = (void *) 1;
init_test_objs(&tobjs, 0x1000, 0x1000, SCF_IETF);
stream = new_stream(&tobjs, 0, 0x1000);
test_vals.status = QWH_FULL;
test_vals.prefix_sz = 2;
test_vals.headers_sz = 40;
test_vals.completion_offset = 0;
s = lsquic_stream_send_headers(stream, headers, 0);
assert(0 == s);
assert(stream->sm_n_buffered == test_vals.prefix_sz + test_vals.headers_sz);
assert(0 == stream->sm_hblock_sz);
lsquic_stream_destroy(stream);
stream = new_stream(&tobjs, 4, 0x1000);
test_vals.status = QWH_PARTIAL;
test_vals.prefix_sz = 2;
test_vals.headers_sz = 40;
test_vals.completion_offset = 10;
s = lsquic_stream_send_headers(stream, headers, 0);
assert(0 == s);
assert(stream->sm_hblock_sz == test_vals.prefix_sz + test_vals.headers_sz);
assert(0 == stream->sm_n_buffered);
nw = lsquic_stream_write(stream, "hello", 5);
assert(0 == nw);
s = lsquic_stream_flush(stream);
assert(s == 0);
lsquic_stream_destroy(stream);
/* Mock server side stream cycle */
stream = new_stream(&tobjs, 8, 0x1000);
uh = calloc(1, sizeof(*uh));
*uh = (struct uncompressed_headers) {
.uh_stream_id = stream->id,
.uh_weight = 127,
.uh_hset = (void *) 12345,
};
s = lsquic_stream_uh_in(stream, uh);
assert(s == 0);
hset = lsquic_stream_get_hset(stream);
assert(hset == (void *) 12345);
s = lsquic_stream_shutdown(stream, 0);
assert(0 == s);
test_vals.status = QWH_PARTIAL;
test_vals.prefix_sz = 2;
test_vals.headers_sz = 40;
test_vals.completion_offset = 10;
assert(!(stream->sm_qflags & SMQF_WANT_WRITE)); /* Begin with them off */
s = lsquic_stream_send_headers(stream, headers, 0);
assert(0 == s);
assert(stream->sm_hblock_sz == test_vals.prefix_sz + test_vals.headers_sz);
assert(0 == stream->sm_n_buffered);
assert(stream->sm_qflags & SMQF_WANT_WRITE); /* Want write is now set */
nw = lsquic_stream_write(stream, "hello", 5);
assert(0 == nw);
s = lsquic_stream_flush(stream);
assert(s == 0);
s = lsquic_stream_close(stream);
assert(s == 0);
assert(stream->sm_hblock_sz == test_vals.prefix_sz + test_vals.headers_sz);
assert(0 == stream->sm_n_buffered);
assert(stream->sm_qflags & SMQF_WANT_WRITE); /* Still set */
s_enc_off = 10; /* Encoder is done writing */
lsquic_stream_dispatch_write_events(stream);
assert(stream->sm_qflags & SMQF_CALL_ONCLOSE);
lsquic_stream_acked(stream, QUIC_FRAME_STREAM);
lsquic_stream_call_on_close(stream);
assert(stream->sm_qflags & SMQF_FREE_STREAM);
lsquic_stream_destroy(stream);
deinit_test_objs(&tobjs);
}
static void
test_headers_wantwrite_restoration (const int want_write)
{
struct test_objs tobjs;
struct lsquic_stream *stream;
ssize_t nw;
int s;
struct uncompressed_headers *uh;
void *hset;
s_call_wantwrite_in_ctor = 1;
s_wantwrite_arg = want_write;
/* For our tests purposes, we treat headers as an opaque object */
struct lsquic_http_headers *headers = (void *) 1;
init_test_objs(&tobjs, 0x1000, 0x1000, SCF_IETF);
/* Mock server side stream cycle */
stream = new_stream(&tobjs, 4 * __LINE__, 0x1000);
uh = calloc(1, sizeof(*uh));
*uh = (struct uncompressed_headers) {
.uh_stream_id = stream->id,
.uh_weight = 127,
.uh_hset = (void *) 12345,
};
s = lsquic_stream_uh_in(stream, uh);
assert(s == 0);
hset = lsquic_stream_get_hset(stream);
assert(hset == (void *) 12345);
s = lsquic_stream_shutdown(stream, 0);
assert(0 == s);
test_vals.status = QWH_PARTIAL;
test_vals.prefix_sz = 2;
test_vals.headers_sz = 40;
test_vals.completion_offset = 10;
assert(want_write == !!(stream->sm_qflags & SMQF_WANT_WRITE));
s = lsquic_stream_send_headers(stream, headers, 0);
assert(0 == s);
assert(stream->sm_hblock_sz == test_vals.prefix_sz + test_vals.headers_sz);
assert(0 == stream->sm_n_buffered);
assert(stream->sm_qflags & SMQF_WANT_WRITE); /* Want write is now set */
nw = lsquic_stream_write(stream, "hello", 5);
assert(0 == nw);
s = lsquic_stream_flush(stream);
assert(s == 0);
s = lsquic_stream_close(stream);
assert(s == 0);
assert(stream->sm_hblock_sz == test_vals.prefix_sz + test_vals.headers_sz);
assert(0 == stream->sm_n_buffered);
assert(stream->sm_qflags & SMQF_WANT_WRITE); /* Still set */
s_enc_off = 10; /* Encoder is done writing */
lsquic_stream_dispatch_write_events(stream);
assert(stream->sm_qflags & SMQF_CALL_ONCLOSE);
lsquic_stream_acked(stream, QUIC_FRAME_STREAM);
lsquic_stream_call_on_close(stream);
assert(stream->sm_qflags & SMQF_FREE_STREAM);
lsquic_stream_destroy(stream);
stream = new_stream(&tobjs, 4 * __LINE__, 0x1000);
uh = calloc(1, sizeof(*uh));
*uh = (struct uncompressed_headers) {
.uh_stream_id = stream->id,
.uh_weight = 127,
.uh_hset = (void *) 12345,
};
s = lsquic_stream_uh_in(stream, uh);
assert(s == 0);
hset = lsquic_stream_get_hset(stream);
assert(hset == (void *) 12345);
s = lsquic_stream_shutdown(stream, 0);
assert(0 == s);
test_vals.status = QWH_PARTIAL;
test_vals.prefix_sz = 2;
test_vals.headers_sz = 40;
test_vals.completion_offset = 10;
assert(want_write == !!(stream->sm_qflags & SMQF_WANT_WRITE));
s = lsquic_stream_send_headers(stream, headers, 0);
assert(0 == s);
assert(stream->sm_hblock_sz == test_vals.prefix_sz + test_vals.headers_sz);
assert(0 == stream->sm_n_buffered);
assert(stream->sm_qflags & SMQF_WANT_WRITE); /* Want write is now set */
s_enc_off = 10; /* Encoder is done writing */
lsquic_stream_dispatch_write_events(stream);
assert(0 == stream->sm_hblock_sz); /* Wrote header */
assert(want_write == s_onwrite_called);
lsquic_stream_destroy(stream);
deinit_test_objs(&tobjs);
s_call_wantwrite_in_ctor = 0;
s_wantwrite_arg = 0;
s_onwrite_called = 0;
}
static void
test_pp_wantwrite_restoration (const int want_write)
{
struct test_objs tobjs;
struct lsquic_stream *stream;
int s;
struct uncompressed_headers *uh;
struct push_promise *promise;
void *hset;
s_call_wantwrite_in_ctor = 1;
s_wantwrite_arg = want_write;
init_test_objs(&tobjs, 0x1000, 0x1000, SCF_IETF);
/* Mock server side stream cycle */
stream = new_stream(&tobjs, 4 * __LINE__, 10);
uh = calloc(1, sizeof(*uh));
*uh = (struct uncompressed_headers) {
.uh_stream_id = stream->id,
.uh_weight = 127,
.uh_hset = (void *) 12345,
};
s = lsquic_stream_uh_in(stream, uh);
assert(s == 0);
hset = lsquic_stream_get_hset(stream);
assert(hset == (void *) 12345);
s = lsquic_stream_shutdown(stream, 0);
assert(0 == s);
promise = calloc(1, sizeof(*promise) + 20);
promise->pp_id = 0;
promise->pp_content_len = 20;
assert(want_write == !!(stream->sm_qflags & SMQF_WANT_WRITE));
s = lsquic_stream_push_promise(stream, promise);
assert(s == 0);
assert((stream->stream_flags & (STREAM_NOPUSH|STREAM_PUSHING))
== (STREAM_NOPUSH|STREAM_PUSHING));
assert(stream->sm_qflags & SMQF_WANT_WRITE); /* Want write is now set */
/* Dispatch: there should be no progress made */
lsquic_stream_dispatch_write_events(stream);
assert((stream->stream_flags & (STREAM_NOPUSH|STREAM_PUSHING))
== (STREAM_NOPUSH|STREAM_PUSHING));
assert(stream->sm_qflags & SMQF_WANT_WRITE);
assert(SLIST_FIRST(&stream->sm_promises)->pp_write_state != PPWS_DONE);
/* Now update window and dispatch again */
lsquic_stream_window_update(stream, 100);
lsquic_stream_dispatch_write_events(stream);
assert((stream->stream_flags & (STREAM_NOPUSH|STREAM_PUSHING))
== (STREAM_NOPUSH|STREAM_PUSHING));
assert(SLIST_FIRST(&stream->sm_promises)->pp_write_state == PPWS_DONE); /* Done! */
assert(want_write == s_onwrite_called); /* Restored: and on_write called */
lsquic_stream_destroy(stream);
deinit_test_objs(&tobjs);
s_call_wantwrite_in_ctor = 0;
s_wantwrite_arg = 0;
s_onwrite_called = 0;
}
/* Create a new stream frame. Each stream frame has a real packet_in to
* back it up, just like in real code. The contents of the packet do
* not matter.
*/
static stream_frame_t *
new_frame_in_ext (struct test_objs *tobjs, size_t off, size_t sz, int fin,
const void *data)
{
lsquic_packet_in_t *packet_in;
stream_frame_t *frame;
assert(sz <= 1370);
packet_in = lsquic_mm_get_packet_in(&tobjs->eng_pub.enp_mm);
if (data)
packet_in->pi_data = (void *) data;
else
{
packet_in->pi_data = lsquic_mm_get_packet_in_buf(&tobjs->eng_pub.enp_mm, 1370);
packet_in->pi_flags |= PI_OWN_DATA;
memset(packet_in->pi_data, 'A', sz);
}
/* This is not how stream frame looks in the packet: we have no
* header. In our test case it does not matter, as we only care
* about stream frame.
*/
packet_in->pi_data_sz = sz;
packet_in->pi_refcnt = 1;
frame = lsquic_malo_get(tobjs->eng_pub.enp_mm.malo.stream_frame);
memset(frame, 0, sizeof(*frame));
frame->packet_in = packet_in;
frame->data_frame.df_offset = off;
frame->data_frame.df_size = sz;
frame->data_frame.df_data = &packet_in->pi_data[0];
frame->data_frame.df_fin = fin;
return frame;
}
static stream_frame_t *
new_frame_in (struct test_objs *tobjs, size_t off, size_t sz, int fin)
{
return new_frame_in_ext(tobjs, off, sz, fin, NULL);
}
/* Test that reading from stream returns -1/EWOULDBLOCK if no headers are
* available.
*/
static void
test_read_headers (int ietf, int use_hset)
{
struct test_objs tobjs;
struct lsquic_stream *stream;
struct stream_frame *frame;
ssize_t nr;
int s;
void *hset;
unsigned char buf[1];
init_test_objs(&tobjs, 0x1000, 0x1000, ietf ? SCF_IETF : 0);
stream = new_stream(&tobjs, 0, 0x1000);
frame = new_frame_in(&tobjs, 0, 35, 1);
s = lsquic_stream_frame_in(stream, frame);
assert(s == 0);
if (use_hset)
{
hset = lsquic_stream_get_hset(stream);
assert(NULL == hset);
}
else
{
nr = lsquic_stream_read(stream, buf, sizeof(buf));
assert(-1 == nr);
/* In GQUIC mode, the error is that the headers are no available yet.
* In IETF mode, the error is that we hit EOF unexpectedly -- as headers
* are sent on the same stream in HEADERS frame.
*/
if (!ietf)
assert(EWOULDBLOCK == errno);
}
lsquic_stream_destroy(stream);
deinit_test_objs(&tobjs);
}
static void
test_read_headers_http1x (void)
{
struct test_objs tobjs;
struct lsquic_stream *stream;
struct stream_frame *frame;
int s;
const unsigned char headers_frame[5] = {
0x01, /* Headers frame */
0x03, /* Frame length */
0x00,
0x00,
0xC0 | 25 /* :status 200 */,
};
ssize_t nr;
unsigned char buf[0x100];
init_test_objs(&tobjs, 0x1000, 0x1000, SCF_IETF);
stream = new_stream(&tobjs, 0, 0x1000);
frame = new_frame_in(&tobjs, 0, sizeof(headers_frame), 1);
memcpy((unsigned char *) frame->data_frame.df_data, headers_frame,
sizeof(headers_frame));
s = lsquic_stream_frame_in(stream, frame);
assert(s == 0);
assert(stream->stream_flags & STREAM_FIN_REACHED);
s = lsquic_stream_readable(stream);
nr = lsquic_stream_read(stream, buf, sizeof(buf));
assert(nr > 0);
assert(nr == 19);
assert(0 == memcmp(buf, "HTTP/1.1 200 OK\r\n\r\n", nr));
lsquic_stream_destroy(stream);
deinit_test_objs(&tobjs);
}
int
main (int argc, char **argv)
{
int opt;
lsquic_global_init(LSQUIC_GLOBAL_SERVER);
while (-1 != (opt = getopt(argc, argv, "l:")))
{
switch (opt)
{
case 'l':
lsquic_log_to_fstream(stderr, 0);
lsquic_logger_lopt(optarg);
break;
default:
exit(1);
}
}
test_flushes_and_closes();
test_headers_wantwrite_restoration(0);
test_headers_wantwrite_restoration(1);
test_pp_wantwrite_restoration(0);
test_pp_wantwrite_restoration(1);
test_read_headers(0, 0);
test_read_headers(0, 1);
test_read_headers(1, 0);
test_read_headers(1, 1);
test_read_headers_http1x();
return 0;
}