mirror of
https://github.com/vacp2p/lsquic.git
synced 2026-08-27 12:41:06 +00:00
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.
836 lines
25 KiB
C
836 lines
25 KiB
C
/* Copyright (c) 2017 - 2026 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_adaptive_cc.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"
|
|
|
|
static int s_call_wantwrite_in_ctor;
|
|
static int s_wantwrite_arg;
|
|
static int s_onwrite_called;
|
|
static unsigned s_on_hset_in_count;
|
|
static struct lsquic_stream *s_on_hset_in_stream;
|
|
static lsquic_stream_ctx_t *s_on_hset_in_ctx;
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
static struct reset_call_ctx {
|
|
struct lsquic_stream *stream;
|
|
int how;
|
|
} s_onreset_called = { NULL, -1, };
|
|
|
|
|
|
static void
|
|
on_reset (lsquic_stream_t *stream, lsquic_stream_ctx_t *h, int how)
|
|
{
|
|
s_onreset_called = (struct reset_call_ctx) { stream, how, };
|
|
}
|
|
|
|
|
|
static void
|
|
on_hset_in (lsquic_stream_t *stream, lsquic_stream_ctx_t *h)
|
|
{
|
|
++s_on_hset_in_count;
|
|
s_on_hset_in_stream = stream;
|
|
s_on_hset_in_ctx = h;
|
|
}
|
|
|
|
|
|
const struct lsquic_stream_if stream_if = {
|
|
.on_new_stream = on_new_stream,
|
|
.on_write = on_write,
|
|
.on_close = on_close,
|
|
.on_reset = on_reset,
|
|
};
|
|
|
|
|
|
const struct lsquic_stream_if stream_if_on_hset = {
|
|
.on_new_stream = on_new_stream,
|
|
.on_write = on_write,
|
|
.on_close = on_close,
|
|
.on_reset = on_reset,
|
|
.on_hset_in = on_hset_in,
|
|
};
|
|
|
|
|
|
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 void
|
|
abort_error (struct lsquic_conn *lconn, int is_app,
|
|
unsigned error_code, const char *fmt, ...)
|
|
{
|
|
}
|
|
|
|
static void
|
|
user_stream_progress (struct lsquic_conn *lconn)
|
|
{
|
|
}
|
|
|
|
static const struct conn_iface our_conn_if =
|
|
{
|
|
.ci_can_write_ack = unit_test_doesnt_write_ack,
|
|
.ci_get_path = get_network_path,
|
|
.ci_abort_error = abort_error,
|
|
.ci_user_stream_progress = user_stream_progress,
|
|
};
|
|
|
|
|
|
static struct http1x_ctor_ctx ctor_ctx = { .is_server = 0, };
|
|
|
|
#if LSQUIC_CONN_STATS
|
|
static struct conn_stats s_conn_stats;
|
|
#endif
|
|
|
|
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_I001);
|
|
tobjs->lconn.cn_version = LSQVER_I001;
|
|
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;
|
|
#if LSQUIC_CONN_STATS
|
|
tobjs->conn_pub.conn_stats = &s_conn_stats;
|
|
#endif
|
|
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;
|
|
tobjs->conn_pub.enpub->enp_hsi_ctx = &ctor_ctx;
|
|
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);
|
|
/* OK, we did not read FIN, expect these flags: */
|
|
assert((stream->sm_qflags & (SMQF_SEND_STOP_SENDING|SMQF_WAIT_FIN_OFF)) == (SMQF_SEND_STOP_SENDING|SMQF_WAIT_FIN_OFF));
|
|
lsquic_stream_ss_frame_sent(stream);
|
|
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)); /* Not yet */
|
|
s_onreset_called = (struct reset_call_ctx) { NULL, -1, };
|
|
lsquic_stream_rst_in(stream, 0, 0);
|
|
assert(s_onreset_called.stream == NULL);
|
|
assert(s_onreset_called.how == -1);
|
|
assert(!(stream->sm_qflags & (SMQF_SEND_STOP_SENDING|SMQF_WAIT_FIN_OFF)));
|
|
assert(stream->sm_qflags & SMQF_FREE_STREAM);
|
|
lsquic_stream_destroy(stream);
|
|
|
|
deinit_test_objs(&tobjs);
|
|
}
|
|
|
|
|
|
static void
|
|
test_rejects_pending_header_overwrite (void)
|
|
{
|
|
struct test_objs tobjs;
|
|
struct lsquic_stream *stream;
|
|
int s;
|
|
unsigned hblock_sz;
|
|
unsigned char *header_block;
|
|
|
|
/* 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, 4 * __LINE__, 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_send_headers_state != SSHS_BEGIN);
|
|
assert(stream->sm_hblock_sz == test_vals.prefix_sz + test_vals.headers_sz);
|
|
assert(stream->sm_header_block);
|
|
|
|
hblock_sz = stream->sm_hblock_sz;
|
|
header_block = stream->sm_header_block;
|
|
errno = 0;
|
|
test_vals.prefix_sz = 3;
|
|
test_vals.headers_sz = 50;
|
|
s = lsquic_stream_send_headers(stream, headers, 0);
|
|
assert(-1 == s);
|
|
assert(EAGAIN == errno);
|
|
assert(stream->sm_hblock_sz == hblock_sz);
|
|
assert(stream->sm_header_block == header_block);
|
|
|
|
lsquic_stream_destroy(stream);
|
|
|
|
deinit_test_objs(&tobjs);
|
|
}
|
|
|
|
|
|
static void
|
|
test_partially_flushed_header_is_not_writeable (void)
|
|
{
|
|
struct test_objs tobjs;
|
|
struct lsquic_stream *stream;
|
|
const unsigned stream_window = 22;
|
|
unsigned hblock_sz, hblock_off;
|
|
int s;
|
|
|
|
/* For our tests purposes, we treat headers as an opaque object */
|
|
struct lsquic_http_headers *headers = (void *) 1;
|
|
|
|
init_test_objs(&tobjs, 0x1000, stream_window, SCF_IETF);
|
|
|
|
stream = new_stream(&tobjs, 4 * __LINE__, stream_window);
|
|
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(SSHS_HBLOCK_SENDING == stream->sm_send_headers_state);
|
|
assert(stream->sm_hblock_sz > 0);
|
|
assert(stream->sm_hblock_sz < test_vals.prefix_sz + test_vals.headers_sz);
|
|
assert(0 == lsquic_stream_write_avail(stream));
|
|
assert(stream->sm_qflags & SMQF_WANT_WRITE);
|
|
|
|
hblock_sz = stream->sm_hblock_sz;
|
|
hblock_off = stream->sm_hblock_off;
|
|
s_onwrite_called = 0;
|
|
lsquic_stream_dispatch_write_events(stream);
|
|
assert(0 == s_onwrite_called);
|
|
assert(stream->sm_hblock_sz == hblock_sz);
|
|
assert(stream->sm_hblock_off == hblock_off);
|
|
|
|
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);
|
|
stream->stream_flags |= STREAM_FIN_RECVD; /* Pretend we received FIN */
|
|
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;
|
|
}
|
|
|
|
|
|
/* 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_multiple_hsets_fifo (int ietf)
|
|
{
|
|
struct test_objs tobjs;
|
|
struct lsquic_stream *stream;
|
|
struct uncompressed_headers *uh;
|
|
char hsets[3];
|
|
void *hset;
|
|
unsigned i;
|
|
int s;
|
|
|
|
init_test_objs(&tobjs, 0x1000, 0x1000, ietf ? SCF_IETF : 0);
|
|
tobjs.stream_if = &stream_if_on_hset;
|
|
|
|
s_on_hset_in_count = 0;
|
|
s_on_hset_in_stream = NULL;
|
|
s_on_hset_in_ctx = NULL;
|
|
|
|
stream = new_stream(&tobjs, 4 * __LINE__, 0x1000);
|
|
|
|
for (i = 0; i < sizeof(hsets); ++i)
|
|
{
|
|
uh = calloc(1, sizeof(*uh));
|
|
*uh = (struct uncompressed_headers) {
|
|
.uh_stream_id = stream->id,
|
|
.uh_weight = 127,
|
|
.uh_hset = &hsets[i],
|
|
};
|
|
s = lsquic_stream_uh_in(stream, uh);
|
|
assert(s == 0);
|
|
assert(s_on_hset_in_count == i + 1);
|
|
assert(s_on_hset_in_stream == stream);
|
|
assert(s_on_hset_in_ctx == NULL);
|
|
}
|
|
|
|
for (i = 0; i < sizeof(hsets); ++i)
|
|
{
|
|
hset = lsquic_stream_get_hset(stream);
|
|
assert(hset == &hsets[i]);
|
|
}
|
|
hset = lsquic_stream_get_hset(stream);
|
|
assert(hset == NULL);
|
|
|
|
lsquic_stream_destroy(stream);
|
|
|
|
deinit_test_objs(&tobjs);
|
|
}
|
|
|
|
|
|
static void
|
|
test_read_headers_http1x_case (const unsigned char *field_line,
|
|
size_t field_line_sz, const char *expected)
|
|
{
|
|
struct test_objs tobjs;
|
|
struct lsquic_stream *stream;
|
|
struct stream_frame *frame;
|
|
int s;
|
|
unsigned char headers_frame[0x10];
|
|
ssize_t nr;
|
|
unsigned char buf[0x100];
|
|
|
|
assert(2 + field_line_sz < 0x40);
|
|
assert(2 + 2 + field_line_sz <= sizeof(headers_frame));
|
|
|
|
headers_frame[0] = 0x01; /* Headers frame */
|
|
headers_frame[1] = 2 + field_line_sz; /* Frame length */
|
|
headers_frame[2] = 0x00;
|
|
headers_frame[3] = 0x00;
|
|
memcpy(headers_frame + 4, field_line, field_line_sz);
|
|
|
|
init_test_objs(&tobjs, 0x1000, 0x1000, SCF_IETF);
|
|
|
|
stream = new_stream(&tobjs, 0, 0x1000);
|
|
frame = new_frame_in(&tobjs, 0, 4 + field_line_sz, 1);
|
|
memcpy((unsigned char *) frame->data_frame.df_data, headers_frame,
|
|
4 + field_line_sz);
|
|
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 == (ssize_t) strlen(expected));
|
|
assert(0 == memcmp(buf, expected, nr));
|
|
|
|
lsquic_stream_destroy(stream);
|
|
|
|
deinit_test_objs(&tobjs);
|
|
}
|
|
|
|
|
|
static void
|
|
test_read_headers_http1x (void)
|
|
{
|
|
const unsigned char status_100[] = {
|
|
0xC0 | 63, 0x00, /* :status 100 */
|
|
};
|
|
const unsigned char status_103[] = {
|
|
0xC0 | 24, /* :status 103 */
|
|
};
|
|
const unsigned char status_200[] = {
|
|
0xC0 | 25, /* :status 200 */
|
|
};
|
|
|
|
test_read_headers_http1x_case(status_100, sizeof(status_100),
|
|
"HTTP/1.1 100 Continue\r\n\r\n");
|
|
test_read_headers_http1x_case(status_103, sizeof(status_103),
|
|
"HTTP/1.1 103 Early Hints\r\n\r\n");
|
|
test_read_headers_http1x_case(status_200, sizeof(status_200),
|
|
"HTTP/1.1 200 OK\r\n\r\n");
|
|
}
|
|
|
|
|
|
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, LLTS_NONE);
|
|
lsquic_logger_lopt(optarg);
|
|
break;
|
|
default:
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
test_flushes_and_closes();
|
|
test_rejects_pending_header_overwrite();
|
|
test_partially_flushed_header_is_not_writeable();
|
|
test_headers_wantwrite_restoration(0);
|
|
test_headers_wantwrite_restoration(1);
|
|
test_read_headers(0, 0);
|
|
test_read_headers(0, 1);
|
|
test_read_headers(1, 0);
|
|
test_read_headers(1, 1);
|
|
test_multiple_hsets_fifo(0);
|
|
test_multiple_hsets_fifo(1);
|
|
test_read_headers_http1x();
|
|
|
|
return 0;
|
|
}
|