Release 2.19.9

- [FEATURE] Add lsquic_stream_pwritev().  This function allows one to
  reduce the number of system calls required to read a file from disk
  by using lsquic_stream_pwritev() together with preadv(2).
- [BUGFIX] When stream is reset, it is writeable -- let user collect
  the error.
- [BUGFIX] Calculate correct conn flow control if reading ends early.
- [BUGFIX] Remove stream from read and write queues on internal
  shutdown.  This is a regression introduced in 2.19.7.
- [BUGFIX] Swapped arguments in IETF RESET_FRAME generation.
- Turn off mini conn history when compiling with Visual Studio; this
  allows the project to compile on Windows again.
- http_client: Add -3 flag to stop reading from streams early; code
  cleanup.
- Don't use -Werror.
This commit is contained in:
Dmitri Tikhonov 2020-09-08 11:43:03 -04:00
parent 49f1f4f620
commit 2f2f436324
25 changed files with 1545 additions and 85 deletions

View File

@ -1,3 +1,20 @@
2020-09-08
- 2.19.9
- [FEATURE] Add lsquic_stream_pwritev(). This function allows one to
reduce the number of system calls required to read a file from disk
by using lsquic_stream_pwritev() together with preadv(2).
- [BUGFIX] When stream is reset, it is writeable -- let user collect
the error.
- [BUGFIX] Calculate correct conn flow control if reading ends early.
- [BUGFIX] Remove stream from read and write queues on internal
shutdown. This is a regression introduced in 2.19.7.
- [BUGFIX] Swapped arguments in IETF RESET_FRAME generation.
- Turn off mini conn history when compiling with Visual Studio; this
allows the project to compile on Windows again.
- http_client: Add -3 flag to stop reading from streams early; code
cleanup.
- Don't use -Werror.
2020-09-02
- 2.19.8
- [FEATURE] Update the timestamp extension to latest version.

View File

@ -54,7 +54,6 @@ ENDIF()
IF(CMAKE_BUILD_TYPE STREQUAL "Debug")
SET(MY_CMAKE_FLAGS "${MY_CMAKE_FLAGS} -O0 -g3")
SET(MY_CMAKE_FLAGS "${MY_CMAKE_FLAGS} -Werror")
IF(CMAKE_C_COMPILER MATCHES "clang" AND
NOT "$ENV{TRAVIS}" MATCHES "^true$" AND
NOT "$ENV{EXTRA_CFLAGS}" MATCHES "-fsanitize")

View File

@ -76,6 +76,11 @@ static int g_header_bypass;
static int s_discard_response;
/* If set to a non-zero value, abandon reading from stream early: read at
* most `s_abandon_early' bytes and then close the stream.
*/
static long s_abandon_early;
struct sample_stats
{
unsigned n;
@ -164,8 +169,6 @@ struct path_elem {
};
struct http_client_ctx {
TAILQ_HEAD(, lsquic_conn_ctx)
conn_ctxs;
const char *hostname;
const char *method;
const char *payload;
@ -294,7 +297,6 @@ http_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn)
conn_h->ch_n_reqs = MIN(client_ctx->hcc_total_n_reqs,
client_ctx->hcc_reqs_per_conn);
client_ctx->hcc_total_n_reqs -= conn_h->ch_n_reqs;
TAILQ_INSERT_TAIL(&client_ctx->conn_ctxs, conn_h, next_ch);
++conn_h->client_ctx->hcc_n_open_conns;
if (!TAILQ_EMPTY(&client_ctx->hcc_path_elems))
create_streams(client_ctx, conn_h);
@ -346,7 +348,6 @@ http_client_on_conn_closed (lsquic_conn_t *conn)
if (!(conn_h->client_ctx->hcc_flags & HCC_SEEN_FIN))
abort();
}
TAILQ_REMOVE(&conn_h->client_ctx->conn_ctxs, conn_h, next_ch);
--conn_h->client_ctx->hcc_n_open_conns;
cacos = calloc(1, sizeof(*cacos));
@ -474,9 +475,16 @@ struct lsquic_stream_ctx {
enum {
HEADERS_SENT = (1 << 0),
PROCESSED_HEADERS = 1 << 1,
ABANDON = 1 << 2, /* Abandon reading from stream after sh_stop bytes
* have been read.
*/
} sh_flags;
lsquic_time_t sh_created;
lsquic_time_t sh_ttfb;
size_t sh_stop; /* Stop after reading this many bytes if ABANDON is set */
size_t sh_nread; /* Number of bytes read from stream using one of
* lsquic_stream_read* functions.
*/
unsigned count;
struct lsquic_reader reader;
};
@ -524,6 +532,11 @@ http_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
lsquic_stream_wantwrite(stream, 1);
if (randomly_reprioritize_streams)
lsquic_stream_set_priority(stream, 1 + (random() & 0xFF));
if (s_abandon_early)
{
st_h->sh_stop = random() % (s_abandon_early + 1);
st_h->sh_flags |= ABANDON;
}
return st_h;
}
@ -634,6 +647,14 @@ http_client_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
static size_t
discard (void *ctx, const unsigned char *buf, size_t sz, int fin)
{
lsquic_stream_ctx_t *st_h = ctx;
if (st_h->sh_flags & ABANDON)
{
if (sz > st_h->sh_stop - st_h->sh_nread)
sz = st_h->sh_stop - st_h->sh_nread;
}
return sz;
}
@ -671,10 +692,14 @@ http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
st_h->sh_flags |= PROCESSED_HEADERS;
}
else if (nread = (s_discard_response
? lsquic_stream_readf(stream, discard, NULL)
: lsquic_stream_read(stream, buf, sizeof(buf))),
? lsquic_stream_readf(stream, discard, st_h)
: lsquic_stream_read(stream, buf,
st_h->sh_flags & ABANDON
? MIN(sizeof(buf), st_h->sh_nread - st_h->sh_stop)
: sizeof(buf))),
nread > 0)
{
st_h->sh_nread += (size_t) nread;
s_stat_downloaded_bytes += nread;
/* test stream_reset after some number of read bytes */
if (client_ctx->hcc_reset_after_nbytes &&
@ -713,6 +738,13 @@ http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
LSQ_DEBUG("changed stream %"PRIu64" priority from %u to %u",
lsquic_stream_id(stream), old_prio, new_prio);
}
if ((st_h->sh_flags & ABANDON) && st_h->sh_nread >= st_h->sh_stop)
{
LSQ_DEBUG("closing stream early having read %zd bytes",
st_h->sh_nread);
lsquic_stream_close(stream);
break;
}
}
else if (0 == nread)
{
@ -756,11 +788,7 @@ http_client_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
LSQ_INFO("%s called", __func__);
struct http_client_ctx *const client_ctx = st_h->client_ctx;
lsquic_conn_t *conn = lsquic_stream_conn(stream);
lsquic_conn_ctx_t *conn_h;
TAILQ_FOREACH(conn_h, &client_ctx->conn_ctxs, next_ch)
if (conn_h->conn == conn)
break;
assert(conn_h);
lsquic_conn_ctx_t *const conn_h = lsquic_conn_get_ctx(conn);
--conn_h->ch_n_reqs;
--conn_h->ch_n_cc_streams;
if (0 == conn_h->ch_n_reqs)
@ -834,6 +862,8 @@ usage (const char *prog)
" -q FILE QIF mode: issue requests from the QIF file and validate\n"
" server responses.\n"
" -e TOKEN Hexadecimal string representing resume token.\n"
" -3 MAX Close stream after reading at most MAX bytes. The actual\n"
" number of bytes read is randominzed.\n"
, prog);
}
@ -1406,7 +1436,6 @@ main (int argc, char **argv)
TAILQ_INIT(&sports);
memset(&client_ctx, 0, sizeof(client_ctx));
TAILQ_INIT(&client_ctx.hcc_path_elems);
TAILQ_INIT(&client_ctx.conn_ctxs);
client_ctx.method = "GET";
client_ctx.hcc_concurrency = 1;
client_ctx.hcc_cc_reqs_per_conn = 1;
@ -1420,6 +1449,7 @@ main (int argc, char **argv)
while (-1 != (opt = getopt(argc, argv, PROG_OPTS
"46Br:R:IKu:EP:M:n:w:H:p:0:q:e:hatT:b:d:"
"3:" /* 3 is 133+ for "e" ("e" for "early") */
#ifndef WIN32
"C:"
#endif
@ -1536,6 +1566,9 @@ main (int argc, char **argv)
http_client_if.on_sess_resume_info = http_client_on_sess_resume_info;
client_ctx.hcc_sess_resume_file_name = optarg;
break;
case '3':
s_abandon_early = strtol(optarg, NULL, 10);
break;
default:
if (0 != prog_set_opt(&prog, opt, optarg))
exit(1);

View File

@ -278,6 +278,13 @@ static const size_t IDLE_SIZE = sizeof(on_being_idle) - 1;
*/
static int s_immediate_write;
/* Use preadv(2) in conjuction with lsquic_stream_pwritev() to reduce
* number of system calls required to read from disk. The actual value
* specifies maximum write size. A negative value indicates always to use
* the remaining file size.
*/
static ssize_t s_pwritev;
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define V(v) (v), strlen(v)
@ -445,6 +452,7 @@ struct lsquic_stream_ctx {
SH_HEADERS_READ = (1 << 2),
} flags;
struct lsquic_reader reader;
int file_fd; /* Used by pwritev */
/* Fields below are used by interop callbacks: */
enum interop_handler {
@ -469,6 +477,7 @@ struct lsquic_stream_ctx {
} interop_u;
struct event *resume_resp;
size_t written;
size_t file_size; /* Used by pwritev */
};
@ -556,18 +565,35 @@ resume_response (evutil_socket_t fd, short what, void *arg)
}
static size_t
bytes_left (lsquic_stream_ctx_t *st_h)
{
if (s_pwritev)
return st_h->file_size - st_h->written;
else
return test_reader_size(st_h->reader.lsqr_ctx);
}
static ssize_t
my_preadv (void *user_data, const struct iovec *iov, int iovcnt)
{
lsquic_stream_ctx_t *const st_h = user_data;
return preadv(st_h->file_fd, iov, iovcnt, st_h->written);
}
static void
http_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
if (st_h->flags & SH_HEADERS_SENT)
{
ssize_t nw;
if (test_reader_size(st_h->reader.lsqr_ctx) > 0)
if (bytes_left(st_h) > 0)
{
if (st_h->server_ctx->delay_resp_sec
&& !(st_h->flags & SH_DELAYED)
&& st_h->written > 10000000
&& test_reader_size(st_h->reader.lsqr_ctx) > 0)
&& st_h->written > 10000000)
{
struct timeval delay = {
.tv_sec = st_h->server_ctx->delay_resp_sec, };
@ -585,7 +611,20 @@ http_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
else
LSQ_ERROR("cannot allocate event");
}
nw = lsquic_stream_writef(stream, &st_h->reader);
if (s_pwritev)
{
size_t to_write = bytes_left(st_h);
if (s_pwritev > 0 && (size_t) s_pwritev < to_write)
to_write = s_pwritev;
nw = lsquic_stream_pwritev(stream, my_preadv, st_h, to_write);
if (nw == 0)
goto use_reader;
}
else
{
use_reader:
nw = lsquic_stream_writef(stream, &st_h->reader);
}
if (nw < 0)
{
struct lsquic_conn *conn = lsquic_stream_conn(stream);
@ -601,7 +640,7 @@ http_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
exit(1);
}
}
if (test_reader_size(st_h->reader.lsqr_ctx) > 0)
if (bytes_left(st_h) > 0)
{
st_h->written += (size_t) nw;
lsquic_stream_wantwrite(stream, 1);
@ -702,11 +741,32 @@ parse_request (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h)
static void
process_request (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h)
{
st_h->reader.lsqr_read = test_reader_read;
st_h->reader.lsqr_size = test_reader_size;
st_h->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->req_path);
if (!st_h->reader.lsqr_ctx)
exit(1);
struct stat st;
if (s_pwritev)
{
st_h->file_fd = open(st_h->req_path, O_RDONLY);
if (st_h->file_fd < 0)
{
LSQ_ERROR("cannot open %s for reading: %s", st_h->req_path,
strerror(errno));
exit(1);
}
if (fstat(st_h->file_fd, &st) < 0)
{
LSQ_ERROR("fstat: %s", strerror(errno));
exit(1);
}
st_h->file_size = st.st_size;
}
else
{
st_h->reader.lsqr_read = test_reader_read;
st_h->reader.lsqr_size = test_reader_size;
st_h->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->req_path);
if (!st_h->reader.lsqr_ctx)
exit(1);
}
if (s_immediate_write)
{
@ -1369,6 +1429,24 @@ new_req (enum method method, const char *path, const char *authority)
}
static ssize_t
my_interop_preadv (void *user_data, const struct iovec *iov, int iovcnt)
{
struct gen_file_ctx *const gfc = user_data;
size_t nread, nr;
int i;
nread = 0;
for (i = 0; i < iovcnt; ++i)
{
nr = idle_read(gfc, iov[i].iov_base, iov[i].iov_len);
nread += nr;
}
return (ssize_t) nread;
}
static void
idle_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
@ -1379,16 +1457,25 @@ idle_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
struct req *req;
ssize_t nw;
struct header_buf hbuf;
struct lsquic_reader reader;
if (st_h->flags & SH_HEADERS_SENT)
{
struct lsquic_reader reader =
if (s_pwritev)
{
.lsqr_read = idle_read,
.lsqr_size = idle_size,
.lsqr_ctx = gfc,
};
nw = lsquic_stream_writef(stream, &reader);
nw = lsquic_stream_pwritev(stream, my_interop_preadv, gfc,
gfc->remain);
if (nw == 0)
goto with_reader;
}
else
{
with_reader:
reader.lsqr_read = idle_read,
reader.lsqr_size = idle_size,
reader.lsqr_ctx = gfc,
nw = lsquic_stream_writef(stream, &reader);
}
if (nw < 0)
{
LSQ_ERROR("error writing idle thoughts: %s", strerror(errno));
@ -1507,6 +1594,10 @@ usage (const char *prog)
" -p FILE Push request with this path\n"
" -w SIZE Write immediately (LSWS mode). Argument specifies maximum\n"
" size of the immediate write.\n"
" -P SIZE Use preadv(2) to read from disk and lsquic_stream_pwritev() to\n"
" write to stream. Positive SIZE indicate maximum value per\n"
" write; negative means always use remaining file size.\n"
" Incompatible with -w.\n"
" -y DELAY Delay response for this many seconds -- use for debugging\n"
, prog);
}
@ -1680,7 +1771,7 @@ main (int argc, char **argv)
prog_init(&prog, LSENG_SERVER|LSENG_HTTP, &server_ctx.sports,
&http_server_if, &server_ctx);
while (-1 != (opt = getopt(argc, argv, PROG_OPTS "y:Y:n:p:r:w:h")))
while (-1 != (opt = getopt(argc, argv, PROG_OPTS "y:Y:n:p:r:w:P:h")))
{
switch (opt) {
case 'n':
@ -1707,6 +1798,9 @@ main (int argc, char **argv)
case 'w':
s_immediate_write = atoi(optarg);
break;
case 'P':
s_pwritev = strtoull(optarg, NULL, 10);
break;
case 'y':
server_ctx.delay_resp_sec = atoi(optarg);
break;
@ -1734,6 +1828,12 @@ main (int argc, char **argv)
#endif
}
if (s_immediate_write && s_pwritev)
{
LSQ_ERROR("-w and -P are incompatible options");
exit(EXIT_FAILURE);
}
if (0 != prog_prep(&prog))
{
LSQ_ERROR("could not prep");

View File

@ -1457,6 +1457,45 @@ Writing To Streams
the write functions -- :func:`lsquic_stream_write()` and
:func:`lsquic_stream_writev()` utilize the same mechanism.
.. function:: ssize_t lsquic_stream_pwritev (struct lsquic_stream *stream, ssize_t (*preadv)(void *user_data, const struct iovec *iov, int iovcnt), void *user_data, size_t n_to_write)
:param stream: Stream to write to.
:param preadv: Pointer to a custom ``preadv(2)``-like function.
:param user_data: Data to pass to ``preadv`` function.
:param n_to_write: Number of bytes to write.
:return: Number of bytes written or -1 on error.
Write to stream using user-supplied ``preadv()`` function.
The stream allocates one or more packets and calls ``preadv()``,
which then fills the array of buffers. This is a good way to
minimize the number of ``read(2)`` system calls; the user can call
``preadv(2)`` instead.
The number of bytes available in the ``iov`` vector passed back to
the user callback may be smaller than ``n_to_write``. The expected
use pattern is to pass the number of bytes remaining in the file
and keep on calling ``preadv(2)``.
Note that, unlike other stream-writing functions above,
``lsquic_stream_pwritev()`` does *not* buffer bytes inside the
stream; it only writes to packets. That means the caller must be
prepared for this function to return 0 even inside the "on write"
stream callback. In that case, the caller should fall back to using
another write function.
It is OK for the ``preadv`` callback to write fewer bytes that
``n_to_write``. (This can happen if the underlying data source
is truncated.)
::
/*
* For example, the return value of zero can be handled as follows:
*/
nw = lsquic_stream_pwritev(stream, my_readv, some_ctx, n_to_write);
if (nw == 0)
nw = lsquic_stream_write(stream, rem_bytes_buf, rem_bytes_len);
.. function:: int lsquic_stream_flush (lsquic_stream_t *stream)
:param stream: Stream to flush.

View File

@ -26,7 +26,7 @@ author = u'LiteSpeed Technologies'
# The short X.Y version
version = u'2.19'
# The full version, including alpha/beta/rc tags
release = u'2.19.8'
release = u'2.19.9'
# -- General configuration ---------------------------------------------------

View File

@ -25,7 +25,7 @@ extern "C" {
#define LSQUIC_MAJOR_VERSION 2
#define LSQUIC_MINOR_VERSION 19
#define LSQUIC_PATCH_VERSION 8
#define LSQUIC_PATCH_VERSION 9
/**
* Engine flags:
@ -1392,6 +1392,14 @@ lsquic_stream_write (lsquic_stream_t *s, const void *buf, size_t len);
ssize_t
lsquic_stream_writev (lsquic_stream_t *s, const struct iovec *vec, int count);
/**
* Write to streams using a single call to a preadv-like function.
*/
ssize_t
lsquic_stream_pwritev (lsquic_stream_t *s,
ssize_t (*preadv)(void *user_data, const struct iovec *iov, int iovcnt),
void *user_data, size_t n_to_write);
/**
* Used as argument to @ref lsquic_stream_writef()
*/

View File

@ -86,6 +86,11 @@ struct network_path
#define NP_PEER_SA(path_) ((struct sockaddr *) (path_)->np_peer_addr)
#define NP_IS_IPv6(path_) (AF_INET6 == NP_LOCAL_SA(path_)->sa_family)
struct ack_state
{
uint32_t arr[6];
};
struct conn_iface
{
enum tick_st
@ -259,6 +264,12 @@ struct conn_iface
/* Optional method. It is called when RTO occurs. */
void
(*ci_retx_timeout) (struct lsquic_conn *);
void
(*ci_ack_snapshot) (struct lsquic_conn *, struct ack_state *);
void
(*ci_ack_rollback) (struct lsquic_conn *, struct ack_state *);
};
#define LSCONN_CCE_BITS 3

View File

@ -58,12 +58,12 @@
#include "lsquic_bw_sampler.h"
#include "lsquic_minmax.h"
#include "lsquic_bbr.h"
#include "lsquic_send_ctl.h"
#include "lsquic_set.h"
#include "lsquic_conn_flow.h"
#include "lsquic_sfcw.h"
#include "lsquic_hash.h"
#include "lsquic_conn.h"
#include "lsquic_send_ctl.h"
#include "lsquic_full_conn.h"
#include "lsquic_util.h"
#include "lsquic_qtags.h"

View File

@ -42,7 +42,6 @@
#include "lsquic_bw_sampler.h"
#include "lsquic_minmax.h"
#include "lsquic_bbr.h"
#include "lsquic_send_ctl.h"
#include "lsquic_set.h"
#include "lsquic_malo.h"
#include "lsquic_chsk_stream.h"
@ -66,6 +65,7 @@
#include "lsquic_attq.h"
#include "lsquic_conn.h"
#include "lsquic_send_ctl.h"
#include "lsquic_conn_public.h"
#include "lsquic_ver_neg.h"
#include "lsquic_mini_conn.h"
@ -1492,6 +1492,18 @@ process_padding_frame (struct full_conn *conn, lsquic_packet_in_t *packet_in,
}
static void
log_conn_flow_control (struct full_conn *conn)
{
LSQ_DEBUG("connection flow cap: wrote: %"PRIu64
"; max: %"PRIu64, conn->fc_pub.conn_cap.cc_sent,
conn->fc_pub.conn_cap.cc_max);
LSQ_DEBUG("connection flow control window: read: %"PRIu64
"; max: %"PRIu64, conn->fc_pub.cfcw.cf_max_recv_off,
conn->fc_pub.cfcw.cf_recv_off);
}
static unsigned
process_ping_frame (struct full_conn *conn, lsquic_packet_in_t *packet_in,
const unsigned char *p, size_t len)
@ -1500,6 +1512,8 @@ process_ping_frame (struct full_conn *conn, lsquic_packet_in_t *packet_in,
*/
EV_LOG_PING_FRAME_IN(LSQUIC_LOG_CONN_ID);
LSQ_DEBUG("received PING");
if (conn->fc_flags & FC_SERVER)
log_conn_flow_control(conn);
return 1;
}
@ -2763,6 +2777,8 @@ generate_ping_frame (struct full_conn *conn)
lsquic_send_ctl_incr_pack_sz(&conn->fc_send_ctl, packet_out, sz);
packet_out->po_frame_types |= 1 << QUIC_FRAME_PING;
LSQ_DEBUG("wrote PING frame");
if (!(conn->fc_flags & FC_SERVER))
log_conn_flow_control(conn);
}
@ -3271,6 +3287,53 @@ full_conn_ci_can_write_ack (struct lsquic_conn *lconn)
}
struct full_ack_state
{
enum full_conn_flags conn_flags;
enum alarm_id_bit armed_set;
unsigned n_slack_akbl;
unsigned n_stop_waiting;
};
typedef char ack_state_size[sizeof(struct full_ack_state)
<= sizeof(struct ack_state) ? 1 : - 1];
static void
full_conn_ci_ack_snapshot (struct lsquic_conn *lconn, struct ack_state *opaque)
{
struct full_conn *conn = (struct full_conn *) lconn;
struct full_ack_state *const ack_state = (struct full_ack_state *) opaque;
ack_state->conn_flags = conn->fc_flags;
ack_state->armed_set = conn->fc_alset.as_armed_set;
ack_state->n_slack_akbl = conn->fc_n_slack_akbl;
ack_state->n_stop_waiting
= lsquic_send_ctl_n_stop_waiting(&conn->fc_send_ctl);
LSQ_DEBUG("take ACK snapshot");
}
static void
full_conn_ci_ack_rollback (struct lsquic_conn *lconn, struct ack_state *opaque)
{
struct full_ack_state *const ack_state = (struct full_ack_state *) opaque;
struct full_conn *conn = (struct full_conn *) lconn;
conn->fc_flags &= ~(FC_ACK_HAD_MISS|FC_ACK_QUEUED);
conn->fc_flags |= (FC_ACK_HAD_MISS|FC_ACK_QUEUED)
& ack_state->conn_flags;
conn->fc_alset.as_armed_set &= ~ALBIT_ACK_APP;
conn->fc_alset.as_armed_set |= ALBIT_ACK_APP & ack_state->armed_set;
conn->fc_n_slack_akbl = ack_state->n_slack_akbl;
conn->fc_send_ctl.sc_n_stop_waiting = ack_state->n_stop_waiting;
LSQ_DEBUG("roll back ACK state");
}
/* This should be called before lsquic_alarmset_ring_expired() */
static void
maybe_set_noprogress_alarm (struct full_conn *conn, lsquic_time_t now)
@ -4404,6 +4467,8 @@ static const struct headers_stream_callbacks *headers_callbacks_ptr = &headers_c
static const struct conn_iface full_conn_iface = {
.ci_abort = full_conn_ci_abort,
.ci_abort_error = full_conn_ci_abort_error,
.ci_ack_rollback = full_conn_ci_ack_rollback,
.ci_ack_snapshot = full_conn_ci_ack_snapshot,
.ci_can_write_ack = full_conn_ci_can_write_ack,
.ci_cancel_pending_streams
= full_conn_ci_cancel_pending_streams,

View File

@ -1654,6 +1654,62 @@ generate_timestamp_frame (struct ietf_full_conn *conn,
}
struct ietf_ack_state
{
enum ifull_conn_flags conn_flags;
enum send_flags send_flags;
enum alarm_id_bit armed_set;
unsigned n_slack_akbl;
unsigned n_slack_all;
unsigned char unretx_thresh;
};
typedef char ack_state_size[sizeof(struct ietf_ack_state)
<= sizeof(struct ack_state) ? 1 : - 1];
static void
ietf_full_conn_ci_ack_snapshot (struct lsquic_conn *lconn,
struct ack_state *opaque)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
struct ietf_ack_state *const ack_state = (struct ietf_ack_state *) opaque;
ack_state->conn_flags = conn->ifc_flags;
ack_state->send_flags = conn->ifc_send_flags;
ack_state->armed_set = conn->ifc_alset.as_armed_set;
ack_state->n_slack_akbl = conn->ifc_n_slack_akbl[PNS_APP];
ack_state->n_slack_all = conn->ifc_n_slack_all;
ack_state->unretx_thresh= conn->ifc_ping_unretx_thresh;
LSQ_DEBUG("take ACK snapshot");
}
static void
ietf_full_conn_ci_ack_rollback (struct lsquic_conn *lconn,
struct ack_state *opaque)
{
struct ietf_ack_state *const ack_state = (struct ietf_ack_state *) opaque;
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
conn->ifc_flags &= ~(IFC_ACK_HAD_MISS|IFC_ACK_QUED_APP);
conn->ifc_flags |= (IFC_ACK_HAD_MISS|IFC_ACK_QUED_APP)
& ack_state->conn_flags;
conn->ifc_send_flags &= ~SF_SEND_PING;
conn->ifc_send_flags |= SF_SEND_PING & ack_state->send_flags;
conn->ifc_alset.as_armed_set &= ~ALBIT_ACK_APP;
conn->ifc_alset.as_armed_set |= ALBIT_ACK_APP & ack_state->armed_set;
conn->ifc_n_slack_akbl[PNS_APP] = ack_state->n_slack_akbl;
conn->ifc_n_slack_all = ack_state->n_slack_all;
conn->ifc_ping_unretx_thresh = ack_state->unretx_thresh;
LSQ_DEBUG("roll back ACK state");
}
static int
generate_ack_frame_for_pns (struct ietf_full_conn *conn,
struct lsquic_packet_out *packet_out, enum packnum_space pns,
@ -2262,7 +2318,7 @@ generate_stream_blocked_frame (struct ietf_full_conn *conn,
static int
generate_stop_sending_frame (struct ietf_full_conn *conn,
generate_stop_sending_frame_by_id (struct ietf_full_conn *conn,
lsquic_stream_id_t stream_id, enum http_error_code error_code)
{
struct lsquic_packet_out *packet_out;
@ -2301,6 +2357,21 @@ generate_stop_sending_frame (struct ietf_full_conn *conn,
}
/* Return true if generated, false otherwise */
static int
generate_stop_sending_frame (struct ietf_full_conn *conn,
struct lsquic_stream *stream)
{
if (0 == generate_stop_sending_frame_by_id(conn, stream->id, HEC_NO_ERROR))
{
lsquic_stream_ss_frame_sent(stream);
return 1;
}
else
return 0;
}
static void
generate_stop_sending_frames (struct ietf_full_conn *conn, lsquic_time_t now)
{
@ -2311,7 +2382,7 @@ generate_stop_sending_frames (struct ietf_full_conn *conn, lsquic_time_t now)
while (!STAILQ_EMPTY(&conn->ifc_stream_ids_to_ss))
{
sits = STAILQ_FIRST(&conn->ifc_stream_ids_to_ss);
if (0 == generate_stop_sending_frame(conn, sits->sits_stream_id,
if (0 == generate_stop_sending_frame_by_id(conn, sits->sits_stream_id,
sits->sits_error_code))
{
STAILQ_REMOVE_HEAD(&conn->ifc_stream_ids_to_ss, sits_next);
@ -2614,6 +2685,8 @@ process_stream_ready_to_send (struct ietf_full_conn *conn,
r &= generate_stream_blocked_frame(conn, stream);
if (stream->sm_qflags & SMQF_SEND_RST)
r &= generate_rst_stream_frame(conn, stream);
if (stream->sm_qflags & SMQF_SEND_STOP_SENDING)
r &= generate_stop_sending_frame(conn, stream);
return r;
}
@ -3994,6 +4067,18 @@ generate_connection_close_packet (struct ietf_full_conn *conn)
}
static void
log_conn_flow_control (struct ietf_full_conn *conn)
{
LSQ_DEBUG("connection flow cap: wrote: %"PRIu64
"; max: %"PRIu64, conn->ifc_pub.conn_cap.cc_sent,
conn->ifc_pub.conn_cap.cc_max);
LSQ_DEBUG("connection flow control window: read: %"PRIu64
"; max: %"PRIu64, conn->ifc_pub.cfcw.cf_max_recv_off,
conn->ifc_pub.cfcw.cf_recv_off);
}
static void
generate_ping_frame (struct ietf_full_conn *conn, lsquic_time_t unused)
{
@ -4023,6 +4108,8 @@ generate_ping_frame (struct ietf_full_conn *conn, lsquic_time_t unused)
packet_out->po_frame_types |= 1 << QUIC_FRAME_PING;
LSQ_DEBUG("wrote PING frame");
conn->ifc_send_flags &= ~SF_SEND_PING;
if (!(conn->ifc_flags & IFC_SERVER))
log_conn_flow_control(conn);
}
@ -5268,6 +5355,8 @@ process_ping_frame (struct ietf_full_conn *conn,
*/
EV_LOG_PING_FRAME_IN(LSQUIC_LOG_CONN_ID);
LSQ_DEBUG("received PING");
if (conn->ifc_flags & IFC_SERVER)
log_conn_flow_control(conn);
return 1;
}
@ -7802,6 +7891,8 @@ ietf_full_conn_ci_count_garbage (struct lsquic_conn *lconn, size_t garbage_sz)
#define IETF_FULL_CONN_FUNCS \
.ci_abort = ietf_full_conn_ci_abort, \
.ci_abort_error = ietf_full_conn_ci_abort_error, \
.ci_ack_snapshot = ietf_full_conn_ci_ack_snapshot, \
.ci_ack_rollback = ietf_full_conn_ci_ack_rollback, \
.ci_retire_cid = ietf_full_conn_ci_retire_cid, \
.ci_can_write_ack = ietf_full_conn_ci_can_write_ack, \
.ci_cancel_pending_streams = ietf_full_conn_ci_cancel_pending_streams, \

View File

@ -20,7 +20,7 @@ struct lsquic_packet_out;
struct lsquic_engine_public;
#ifndef LSQUIC_KEEP_MINICONN_HISTORY
# ifndef NDEBUG
# if !defined(NDEBUG) && !defined(_MSC_VER)
# define LSQUIC_KEEP_MINICONN_HISTORY 1
# else
# define LSQUIC_KEEP_MINICONN_HISTORY 0

View File

@ -105,6 +105,11 @@ struct parse_funcs
int
(*pf_parse_crypto_frame) (const unsigned char *buf, size_t rem_packet_sz,
struct stream_frame *);
/* Return true if STREAM frame extends to the end of the packet and thus
* does not contain a Length field (no update).
*/
int
(*pf_dec_stream_frame_size) (unsigned char *buf, size_t new_size);
int
(*pf_parse_ack_frame) (const unsigned char *buf, size_t buf_len,
struct ack_info *ack_info, uint8_t exp);

View File

@ -318,6 +318,7 @@ const struct parse_funcs lsquic_parse_funcs_gquic_Q046 =
.pf_gen_stream_frame = lsquic_gquic_be_gen_stream_frame,
.pf_calc_stream_frame_header_sz = lsquic_calc_stream_frame_header_sz_gquic,
.pf_parse_stream_frame = lsquic_gquic_be_parse_stream_frame,
.pf_dec_stream_frame_size = lsquic_gquic_be_dec_stream_frame_size,
.pf_parse_ack_frame = lsquic_gquic_be_parse_ack_frame,
.pf_gen_ack_frame = lsquic_gquic_be_gen_ack_frame,
.pf_gen_stop_waiting_frame = lsquic_gquic_be_gen_stop_waiting_frame,

View File

@ -848,6 +848,7 @@ const struct parse_funcs lsquic_parse_funcs_gquic_Q050 =
.pf_gen_stream_frame = lsquic_gquic_be_gen_stream_frame,
.pf_calc_stream_frame_header_sz = lsquic_calc_stream_frame_header_sz_gquic,
.pf_parse_stream_frame = lsquic_gquic_be_parse_stream_frame,
.pf_dec_stream_frame_size = lsquic_gquic_be_dec_stream_frame_size,
.pf_parse_ack_frame = lsquic_gquic_be_parse_ack_frame,
.pf_gen_ack_frame = lsquic_gquic_be_gen_ack_frame,
.pf_gen_stop_waiting_frame = lsquic_gquic_be_gen_stop_waiting_frame,

View File

@ -293,6 +293,26 @@ lsquic_gquic_be_gen_stream_frame (unsigned char *buf, size_t buf_len,
return p - buf;
}
int
lsquic_gquic_be_dec_stream_frame_size (unsigned char *buf, size_t new_size)
{
/* 1fdoooss */
const unsigned char type = buf[0];
if (!(type & 0x20))
return 1;
const unsigned offset_len = ((type >> 2) & 7) + 1 - !((type >> 2) & 7);
const unsigned stream_id_len = 1 + (type & 3);
uint16_t len = new_size;
#if __BYTE_ORDER == __LITTLE_ENDIAN
len = bswap_16(len);
#endif
memcpy(buf + 1 + offset_len + stream_id_len, &len, 2);
return 0;
}
/* return parsed (used) buffer length */
int
@ -1032,6 +1052,7 @@ const struct parse_funcs lsquic_parse_funcs_gquic_Q043 =
.pf_calc_stream_frame_header_sz = lsquic_calc_stream_frame_header_sz_gquic,
.pf_parse_stream_frame = lsquic_gquic_be_parse_stream_frame,
.pf_parse_ack_frame = lsquic_gquic_be_parse_ack_frame,
.pf_dec_stream_frame_size = lsquic_gquic_be_dec_stream_frame_size,
.pf_gen_ack_frame = lsquic_gquic_be_gen_ack_frame,
.pf_gen_stop_waiting_frame = lsquic_gquic_be_gen_stop_waiting_frame,
.pf_parse_stop_waiting_frame = lsquic_gquic_be_parse_stop_waiting_frame,

View File

@ -111,4 +111,7 @@ lsquic_gquic_be_gen_ack_frame (unsigned char *outbuf, size_t outbuf_sz,
void *rechist, lsquic_time_t now, int *has_missing, lsquic_packno_t *,
const uint64_t *);
int
lsquic_gquic_be_dec_stream_frame_size (unsigned char *buf, size_t new_size);
#endif

View File

@ -472,6 +472,38 @@ ietf_v1_gen_crypto_frame (unsigned char *buf, size_t buf_len,
}
static int
ietf_v1_dec_stream_frame_size (unsigned char *p, size_t new_size)
{
/* 0b00001XXX
* 0x4 OFF
* 0x2 LEN
* 0x1 FIN
*/
unsigned bits;
const char type = *p++;
if (!(type & 0x2))
return 1;
/* Stream ID */
bits = *p >> 6;
p += 1 << bits;
if (type & 0x4)
{
/* Offset */
bits = *p >> 6;
p += 1 << bits;
}
/* Write new size */
bits = *p >> 6;
vint_write(p, new_size, bits, 1 << bits);
return 0;
}
/* return parsed (used) buffer length */
static int
ietf_v1_parse_stream_frame (const unsigned char *buf, size_t rem_packet_sz,
@ -697,7 +729,7 @@ ietf_v1_rst_frame_size (lsquic_stream_id_t stream_id, uint64_t error_code,
static int
ietf_v1_gen_rst_frame (unsigned char *buf, size_t buf_len,
lsquic_stream_id_t stream_id, uint64_t error_code, uint64_t final_size)
lsquic_stream_id_t stream_id, uint64_t final_size, uint64_t error_code)
{
unsigned vbits;
unsigned char *p;
@ -2122,6 +2154,7 @@ const struct parse_funcs lsquic_parse_funcs_ietf_v1 =
.pf_gen_stream_frame = ietf_v1_gen_stream_frame,
.pf_calc_stream_frame_header_sz = ietf_v1_calc_stream_frame_header_sz,
.pf_parse_stream_frame = ietf_v1_parse_stream_frame,
.pf_dec_stream_frame_size = ietf_v1_dec_stream_frame_size,
.pf_parse_ack_frame = ietf_v1_parse_ack_frame,
.pf_gen_ack_frame = ietf_v1_gen_ack_frame,
.pf_gen_blocked_frame = ietf_v1_gen_blocked_frame,

View File

@ -30,7 +30,6 @@
#include "lsquic_bw_sampler.h"
#include "lsquic_minmax.h"
#include "lsquic_bbr.h"
#include "lsquic_send_ctl.h"
#include "lsquic_util.h"
#include "lsquic_sfcw.h"
#include "lsquic_varint.h"
@ -40,6 +39,7 @@
#include "lsquic_ver_neg.h"
#include "lsquic_ev_log.h"
#include "lsquic_conn.h"
#include "lsquic_send_ctl.h"
#include "lsquic_conn_flow.h"
#include "lsquic_conn_public.h"
#include "lsquic_cong_ctl.h"
@ -482,6 +482,9 @@ set_retx_alarm (struct lsquic_send_ctl *ctl, enum packnum_space pns,
#define SC_PACK_SIZE(ctl_) (+(ctl_)->sc_conn_pub->path->np_pack_size)
/* XXX can we optimize this by caching the value of this function? It should
* not change within one tick.
*/
static lsquic_time_t
send_ctl_transfer_time (void *ctx)
{
@ -3522,3 +3525,186 @@ lsquic_send_ctl_disable_ecn (struct lsquic_send_ctl *ctl)
TAILQ_FOREACH(packet_out, &ctl->sc_scheduled_packets, po_next)
lsquic_packet_out_set_ecn(packet_out, ECN_NOT_ECT);
}
void
lsquic_send_ctl_snapshot (struct lsquic_send_ctl *ctl,
struct send_ctl_state *ctl_state)
{
struct lsquic_conn *const lconn = ctl->sc_conn_pub->lconn;
int buffered, repace;
buffered = !lsquic_send_ctl_schedule_stream_packets_immediately(ctl);
repace = !buffered && (ctl->sc_flags & SC_PACE);
if (repace)
ctl_state->pacer = ctl->sc_pacer;
if (buffered)
{
lconn->cn_if->ci_ack_snapshot(lconn, &ctl_state->ack_state);
ctl_state->buf_counts[BPT_OTHER_PRIO]
= ctl->sc_buffered_packets[BPT_OTHER_PRIO].bpq_count;
ctl_state->buf_counts[BPT_HIGHEST_PRIO]
= ctl->sc_buffered_packets[BPT_HIGHEST_PRIO].bpq_count;
}
}
static void
send_ctl_repace (struct lsquic_send_ctl *ctl, const struct pacer *pacer,
unsigned count)
{
unsigned n;
int in_rec;
LSQ_DEBUG("repace, count: %u", count);
ctl->sc_pacer = *pacer;
in_rec = send_ctl_in_recovery(ctl);
for (n = 0; n < count; ++n)
lsquic_pacer_packet_scheduled(&ctl->sc_pacer,
ctl->sc_n_in_flight_retx + ctl->sc_n_scheduled + n, in_rec,
send_ctl_transfer_time, ctl);
}
void
lsquic_send_ctl_rollback (struct lsquic_send_ctl *ctl,
struct send_ctl_state *ctl_state, const struct iovec *last_iov,
size_t shortfall)
{
struct lsquic_conn *const lconn = ctl->sc_conn_pub->lconn;
struct lsquic_packet_out *packet_out, *next;
struct lsquic_packets_tailq *packets;
struct stream_frame stream_frame;
struct packet_out_frec_iter pofi;
enum buf_packet_type packet_type;
unsigned orig_count, new_count;
enum quic_ft_bit lost_types;
int buffered, repace, len, to_end;
unsigned short prev_frec_len;
struct frame_rec *frec;
buffered = !lsquic_send_ctl_schedule_stream_packets_immediately(ctl);
repace = !buffered && (ctl->sc_flags & SC_PACE);
if (!buffered)
{
orig_count = ctl->sc_n_scheduled;
packets = &ctl->sc_scheduled_packets;
packet_type = 0; /* Not necessary, but compiler complains */
}
else if (ctl_state->buf_counts[BPT_HIGHEST_PRIO]
< ctl->sc_buffered_packets[BPT_HIGHEST_PRIO].bpq_count)
{
packets = &ctl->sc_buffered_packets[BPT_HIGHEST_PRIO].bpq_packets;
orig_count = ctl->sc_buffered_packets[BPT_HIGHEST_PRIO].bpq_count;
packet_type = BPT_HIGHEST_PRIO;
}
else
{
packets = &ctl->sc_buffered_packets[BPT_OTHER_PRIO].bpq_packets;
orig_count = ctl->sc_buffered_packets[BPT_OTHER_PRIO].bpq_count;
packet_type = BPT_OTHER_PRIO;
}
/* Now find last packet: */
TAILQ_FOREACH(packet_out, packets, po_next)
if ((unsigned char *) last_iov->iov_base >= packet_out->po_data
&& (unsigned char *) last_iov->iov_base
< packet_out->po_data + packet_out->po_data_sz)
break;
if (!packet_out)
{
lconn->cn_if->ci_internal_error(lconn,
"rollback failed: cannot find packet");
return;
}
for (frec = lsquic_pofi_first(&pofi, packet_out); frec;
frec = lsquic_pofi_next(&pofi))
if (frec->fe_frame_type == QUIC_FRAME_STREAM
/* At the time of this writing, pwritev() generates a single STREAM
* frame per packet. To keep code future-proof, we use an extra
* check.
*/
&& (unsigned char *) last_iov->iov_base
> packet_out->po_data + frec->fe_off
&& (unsigned char *) last_iov->iov_base
< packet_out->po_data + frec->fe_off + frec->fe_len)
break;
if (!frec)
{
lconn->cn_if->ci_internal_error(lconn,
"rollback failed: cannot find frame record");
return;
}
/* Strictly less because of the STREAM frame header */
assert(last_iov->iov_len < frec->fe_len);
len = lconn->cn_pf->pf_parse_stream_frame(
packet_out->po_data + frec->fe_off, frec->fe_len, &stream_frame);
if (len < 0)
{
lconn->cn_if->ci_internal_error(lconn,
"error parsing own STREAM frame");
return;
}
if (stream_frame.data_frame.df_size > last_iov->iov_len - shortfall)
{
packet_out->po_data_sz = (unsigned char *) last_iov->iov_base
+ last_iov->iov_len - shortfall - packet_out->po_data;
prev_frec_len = frec->fe_len;
frec->fe_len = packet_out->po_data_sz - frec->fe_off;
to_end = lconn->cn_pf->pf_dec_stream_frame_size(
packet_out->po_data + frec->fe_off,
stream_frame.data_frame.df_size - (prev_frec_len - frec->fe_len));
if (to_end)
{ /* A frame that's too short may be generated when pwritev runs out
* of iovecs. In that case, we adjust it here.
*/
if (!(packet_out->po_flags & PO_STREAM_END))
LSQ_DEBUG("set stream-end flag on truncated packet");
packet_out->po_flags |= PO_STREAM_END;
}
if (!buffered)
ctl->sc_bytes_scheduled -= prev_frec_len - frec->fe_len;
}
else
assert(stream_frame.data_frame.df_size
== last_iov->iov_len - shortfall);
/* Drop any frames that follow */
for (frec = lsquic_pofi_next(&pofi); frec; frec = lsquic_pofi_next(&pofi))
frec->fe_frame_type = 0;
/* Return unused packets */
new_count = orig_count;
lost_types = 0;
for (packet_out = TAILQ_NEXT(packet_out, po_next); packet_out != NULL;
packet_out = next)
{
next = TAILQ_NEXT(packet_out, po_next);
--new_count;
lost_types |= packet_out->po_frame_types;
/* Undo lsquic_send_ctl_get_packet_for_stream() */
if (!buffered)
send_ctl_sched_remove(ctl, packet_out);
else
{
TAILQ_REMOVE(packets, packet_out, po_next);
--ctl->sc_buffered_packets[packet_type].bpq_count;
}
send_ctl_destroy_packet(ctl, packet_out);
}
if (new_count < orig_count && repace)
send_ctl_repace(ctl, &ctl_state->pacer, new_count);
if (buffered && (lost_types & QUIC_FTBIT_ACK))
lconn->cn_if->ci_ack_rollback(lconn, &ctl_state->ack_state);
}

View File

@ -412,4 +412,18 @@ lsquic_send_ctl_can_send_probe (const struct lsquic_send_ctl *,
void
lsquic_send_ctl_disable_ecn (struct lsquic_send_ctl *);
struct send_ctl_state
{
struct pacer pacer;
struct ack_state ack_state;
unsigned buf_counts[BPT_OTHER_PRIO + 1];
};
void
lsquic_send_ctl_snapshot (struct lsquic_send_ctl *, struct send_ctl_state *);
void
lsquic_send_ctl_rollback (struct lsquic_send_ctl *, struct send_ctl_state *,
const struct iovec *, size_t);
#endif

View File

@ -93,8 +93,15 @@ stream_wantread (lsquic_stream_t *stream, int is_want);
static int
stream_wantwrite (lsquic_stream_t *stream, int is_want);
enum stream_write_options
{
SWO_BUFFER = 1 << 0, /* Allow buffering in sm_buf */
};
static ssize_t
stream_write_to_packets (lsquic_stream_t *, struct lsquic_reader *, size_t);
stream_write_to_packets (lsquic_stream_t *, struct lsquic_reader *, size_t,
enum stream_write_options);
static ssize_t
save_to_buffer (lsquic_stream_t *, struct lsquic_reader *, size_t len);
@ -181,6 +188,7 @@ enum stream_history_event
SHE_EMPTY = '\0', /* Special entry. No init besides memset required */
SHE_PLUS = '+', /* Special entry: previous event occured more than once */
SHE_REACH_FIN = 'a',
SHE_EARLY_READ_STOP = 'A',
SHE_BLOCKED_OUT = 'b',
SHE_CREATED = 'C',
SHE_FRAME_IN = 'd',
@ -197,13 +205,15 @@ enum stream_history_event
SHE_ONCLOSE_CALL = 'L',
SHE_ONNEW = 'N',
SHE_SET_PRIO = 'p',
SHE_SHORT_WRITE = 'q',
SHE_USER_READ = 'r',
SHE_SHUTDOWN_READ = 'R',
SHE_RST_IN = 's',
SHE_SS_IN = 'S',
SHE_STOP_SENDIG_IN = 'S',
SHE_RST_OUT = 't',
SHE_RST_ACKED = 'T',
SHE_FLUSH = 'u',
SHE_STOP_SENDIG_OUT = 'U',
SHE_USER_WRITE_DATA = 'w',
SHE_SHUTDOWN_WRITE = 'W',
SHE_CLOSE = 'X',
@ -643,10 +653,13 @@ stream_is_finished (const lsquic_stream_t *stream)
* stream are outstanding:
*/
&& 0 == stream->n_unacked
&& 0 == (stream->sm_qflags & (
/* This checks that no packets that reference this stream will
* become outstanding:
*/
&& 0 == (stream->sm_qflags & SMQF_SEND_RST)
SMQF_SEND_RST
/* Can't finish stream until all "self" flags are unset: */
| SMQF_SELF_FLAGS))
&& ((stream->stream_flags & STREAM_FORCE_FINISH)
|| (stream->stream_flags & (STREAM_FIN_SENT |STREAM_RST_SENT)));
}
@ -822,6 +835,22 @@ lsquic_stream_readable (struct lsquic_stream *stream)
}
static int
stream_writeable (struct lsquic_stream *stream)
{
/* A stream is writeable if one of the following is true: */
return
/* - The stream is reset, by either side. In this case,
* lsquic_stream_write() will return -1 (we want the user to be
* able to collect the error).
*/
lsquic_stream_is_reset(stream)
/* - Data can be written to stream: */
|| lsquic_stream_write_avail(stream)
;
}
static size_t
stream_write_avail_no_frames (struct lsquic_stream *stream)
{
@ -1008,6 +1037,7 @@ lsquic_stream_frame_in (lsquic_stream_t *stream, stream_frame_t *frame)
{
SM_HISTORY_APPEND(stream, SHE_FIN_IN);
stream->stream_flags |= STREAM_FIN_RECVD;
stream->sm_qflags &= ~SMQF_WAIT_FIN_OFF;
stream->sm_fin_off = DF_END(frame);
maybe_finish_stream(stream);
}
@ -1127,10 +1157,22 @@ lsquic_stream_rst_in (lsquic_stream_t *stream, uint64_t offset,
lsquic_sfcw_consume_rem(&stream->fc);
drop_frames_in(stream);
drop_buffered_data(stream);
maybe_elide_stream_frames(stream);
if (!(stream->stream_flags & (STREAM_RST_SENT|STREAM_FIN_SENT))
if (!(stream->sm_bflags & SMBF_IETF))
{
drop_buffered_data(stream);
maybe_elide_stream_frames(stream);
}
if (stream->sm_qflags & SMQF_WAIT_FIN_OFF)
{
stream->sm_qflags &= ~SMQF_WAIT_FIN_OFF;
LSQ_DEBUG("final offset is now known: %"PRIu64, offset);
}
if (!(stream->stream_flags &
(STREAM_RST_SENT|STREAM_SS_SENT|STREAM_FIN_SENT))
&& !(stream->sm_bflags & SMBF_IETF)
&& !(stream->sm_qflags & SMQF_SEND_RST))
lsquic_stream_reset_ext(stream, 7 /* QUIC_RST_ACKNOWLEDGEMENT */, 0);
@ -1153,7 +1195,7 @@ lsquic_stream_stop_sending_in (struct lsquic_stream *stream,
return;
}
SM_HISTORY_APPEND(stream, SHE_SS_IN);
SM_HISTORY_APPEND(stream, SHE_STOP_SENDIG_IN);
stream->stream_flags |= STREAM_SS_RECVD;
/* Let user collect error: */
@ -1577,11 +1619,62 @@ lsquic_stream_read (lsquic_stream_t *stream, void *buf, size_t len)
}
void
lsquic_stream_ss_frame_sent (struct lsquic_stream *stream)
{
assert(stream->sm_qflags & SMQF_SEND_STOP_SENDING);
SM_HISTORY_APPEND(stream, SHE_STOP_SENDIG_OUT);
stream->sm_qflags &= ~SMQF_SEND_STOP_SENDING;
stream->stream_flags |= STREAM_SS_SENT;
if (!(stream->sm_qflags & SMQF_SENDING_FLAGS))
TAILQ_REMOVE(&stream->conn_pub->sending_streams, stream, next_send_stream);
}
static void
handle_early_read_shutdown_ietf (struct lsquic_stream *stream)
{
if (!(stream->sm_qflags & SMQF_SENDING_FLAGS))
TAILQ_INSERT_TAIL(&stream->conn_pub->sending_streams, stream,
next_send_stream);
stream->sm_qflags |= SMQF_SEND_STOP_SENDING|SMQF_WAIT_FIN_OFF;
}
static void
handle_early_read_shutdown_gquic (struct lsquic_stream *stream)
{
if (!(stream->stream_flags & STREAM_RST_SENT))
{
lsquic_stream_reset_ext(stream, 7 /* QUIC_STREAM_CANCELLED */, 0);
stream->sm_qflags |= SMQF_WAIT_FIN_OFF;
}
}
static void
handle_early_read_shutdown (struct lsquic_stream *stream)
{
if (stream->sm_bflags & SMBF_IETF)
handle_early_read_shutdown_ietf(stream);
else
handle_early_read_shutdown_gquic(stream);
}
static void
stream_shutdown_read (lsquic_stream_t *stream)
{
if (!(stream->stream_flags & STREAM_U_READ_DONE))
{
if (!(stream->stream_flags & STREAM_FIN_REACHED))
{
LSQ_DEBUG("read shut down before reading FIN. (FIN received: %d)",
!!(stream->stream_flags & STREAM_FIN_RECVD));
SM_HISTORY_APPEND(stream, SHE_EARLY_READ_STOP);
if (!(stream->stream_flags & (STREAM_FIN_RECVD|STREAM_RST_RECVD)))
handle_early_read_shutdown(stream);
}
SM_HISTORY_APPEND(stream, SHE_SHUTDOWN_READ);
stream->stream_flags |= STREAM_U_READ_DONE;
stream->sm_readable = stream_readable_discard;
@ -1625,7 +1718,9 @@ stream_shutdown_write (lsquic_stream_t *stream)
if (!(stream->sm_bflags & SMBF_CRYPTO)
&& !(stream->stream_flags & (STREAM_FIN_SENT|STREAM_RST_SENT))
&& !stream_is_incoming_unidir(stream)
&& !(stream->sm_qflags & SMQF_SEND_RST))
/* In gQUIC, receiving a RESET means "stop sending" */
&& !(!(stream->sm_qflags & SMBF_IETF)
&& (stream->stream_flags & STREAM_RST_RECVD)))
{
if ((stream->sm_bflags & SMBF_USE_HEADERS)
&& !(stream->stream_flags & STREAM_HEADERS_SENT))
@ -1705,6 +1800,8 @@ lsquic_stream_shutdown_internal (lsquic_stream_t *stream)
{
LSQ_DEBUG("internal shutdown");
stream->stream_flags |= STREAM_U_READ_DONE|STREAM_U_WRITE_DONE;
stream_wantwrite(stream, 0);
stream_wantread(stream, 0);
if (lsquic_stream_is_critical(stream))
{
LSQ_DEBUG("add flag to force-finish special stream");
@ -2054,7 +2151,7 @@ stream_dispatch_write_events_loop (lsquic_stream_t *stream)
stream->stream_flags |= STREAM_LAST_WRITE_OK;
while ((stream->sm_qflags & SMQF_WANT_WRITE)
&& (stream->stream_flags & STREAM_LAST_WRITE_OK)
&& lsquic_stream_write_avail(stream))
&& stream_writeable(stream))
{
progress = stream_progress(stream);
@ -2173,7 +2270,7 @@ lsquic_stream_dispatch_write_events (lsquic_stream_t *stream)
if (stream->sm_bflags & SMBF_RW_ONCE)
{
if ((stream->sm_qflags & SMQF_WANT_WRITE)
&& lsquic_stream_write_avail(stream))
&& stream_writeable(stream))
{
on_write = select_on_write(stream);
on_write(stream, stream->st_ctx);
@ -2229,7 +2326,7 @@ stream_flush (lsquic_stream_t *stream)
empty_reader.lsqr_size = inner_reader_empty_size;
empty_reader.lsqr_read = inner_reader_empty_read;
empty_reader.lsqr_ctx = NULL; /* pro forma */
nw = stream_write_to_packets(stream, &empty_reader, 0);
nw = stream_write_to_packets(stream, &empty_reader, 0, SWO_BUFFER);
if (nw >= 0)
{
@ -2655,6 +2752,27 @@ stream_activate_hq_frame (struct lsquic_stream *stream, uint64_t off,
}
struct hq_arr
{
unsigned char **p;
unsigned count;
unsigned max;
};
static int
save_hq_ptr (struct hq_arr *hq_arr, void *p)
{
if (hq_arr->count < hq_arr->max)
{
hq_arr->p[hq_arr->count++] = p;
return 0;
}
else
return -1;
}
static size_t
frame_hq_gen_read (void *ctx, void *begin_buf, size_t len, int *fin)
{
@ -2708,6 +2826,11 @@ frame_hq_gen_read (void *ctx, void *begin_buf, size_t len, int *fin)
if (0 == (shf->shf_flags & (SHF_FIXED_SIZE|SHF_PHANTOM)))
{
shf->shf_frame_ptr = p;
if (stream->sm_hq_arr && 0 != save_hq_ptr(stream->sm_hq_arr, p))
{
stream_hq_frame_put(stream, shf);
break;
}
memset(p, 0, frame_sz);
p += frame_sz;
}
@ -3079,7 +3202,7 @@ maybe_close_varsize_hq_frame (struct lsquic_stream *stream)
static ssize_t
stream_write_to_packets (lsquic_stream_t *stream, struct lsquic_reader *reader,
size_t thresh)
size_t thresh, enum stream_write_options swo)
{
size_t size;
ssize_t nw;
@ -3146,7 +3269,7 @@ stream_write_to_packets (lsquic_stream_t *stream, struct lsquic_reader *reader,
if (use_framing && seen_ok)
maybe_close_varsize_hq_frame(stream);
if (thresh)
if (thresh && (swo & SWO_BUFFER))
{
assert(size < thresh);
assert(size >= stream->sm_n_buffered);
@ -3159,7 +3282,8 @@ stream_write_to_packets (lsquic_stream_t *stream, struct lsquic_reader *reader,
fg_ctx.fgc_nread_from_reader += nw; /* Make this cleaner? */
}
}
else
#ifndef NDEBUG
else if (swo & SWO_BUFFER)
{
/* We count flushed data towards both stream and connection limits,
* so we should have been able to packetize all of it:
@ -3167,6 +3291,7 @@ stream_write_to_packets (lsquic_stream_t *stream, struct lsquic_reader *reader,
assert(0 == stream->sm_n_buffered);
assert(size == 0);
}
#endif
maybe_mark_as_blocked(stream);
@ -3333,7 +3458,8 @@ save_to_buffer (lsquic_stream_t *stream, struct lsquic_reader *reader,
static ssize_t
stream_write (lsquic_stream_t *stream, struct lsquic_reader *reader)
stream_write (lsquic_stream_t *stream, struct lsquic_reader *reader,
enum stream_write_options swo)
{
const struct stream_hq_frame *shf;
size_t thresh, len, frames, total_len, n_allowed, nwritten;
@ -3354,6 +3480,8 @@ stream_write (lsquic_stream_t *stream, struct lsquic_reader *reader)
n_allowed = stream_get_n_allowed(stream);
if (total_len <= n_allowed && total_len < thresh)
{
if (!(swo & SWO_BUFFER))
return 0;
nwritten = 0;
do
{
@ -3370,7 +3498,7 @@ stream_write (lsquic_stream_t *stream, struct lsquic_reader *reader)
return nwritten;
}
else
return stream_write_to_packets(stream, reader, thresh);
return stream_write_to_packets(stream, reader, thresh, swo);
}
@ -3450,7 +3578,7 @@ lsquic_stream_writev (lsquic_stream_t *stream, const struct iovec *iov,
.lsqr_ctx = &iro,
};
return stream_write(stream, &reader);
return stream_write(stream, &reader, SWO_BUFFER);
}
@ -3459,7 +3587,221 @@ lsquic_stream_writef (lsquic_stream_t *stream, struct lsquic_reader *reader)
{
COMMON_WRITE_CHECKS();
SM_HISTORY_APPEND(stream, SHE_USER_WRITE_DATA);
return stream_write(stream, reader);
return stream_write(stream, reader, SWO_BUFFER);
}
/* Configuration for lsquic_stream_pwritev: */
#ifndef LSQUIC_PWRITEV_DEF_IOVECS
#define LSQUIC_PWRITEV_DEF_IOVECS 16
#endif
/* This is an overkill, this limit should only be reached during testing: */
#ifndef LSQUIC_PWRITEV_DEF_FRAMES
#define LSQUIC_PWRITEV_DEF_FRAMES (LSQUIC_PWRITEV_DEF_IOVECS * 2)
#endif
#ifdef NDEBUG
#define PWRITEV_IOVECS LSQUIC_PWRITEV_DEF_IOVECS
#define PWRITEV_FRAMES LSQUIC_PWRITEV_DEF_FRAMES
#else
static unsigned
PWRITEV_IOVECS = LSQUIC_PWRITEV_DEF_IOVECS,
PWRITEV_FRAMES = LSQUIC_PWRITEV_DEF_FRAMES;
void
lsquic_stream_set_pwritev_params (unsigned iovecs, unsigned frames)
{
PWRITEV_IOVECS = iovecs;
PWRITEV_FRAMES = frames;
}
#endif
struct pwritev_ctx
{
struct iovec *iov;
const struct hq_arr *hq_arr;
size_t total_bytes;
size_t n_to_write;
unsigned n_iovecs, max_iovecs;
};
static size_t
pwritev_size (void *lsqr_ctx)
{
struct pwritev_ctx *const ctx = lsqr_ctx;
if (ctx->n_iovecs < ctx->max_iovecs
&& ctx->hq_arr->count < ctx->hq_arr->max)
return ctx->n_to_write - ctx->total_bytes;
else
return 0;
}
static size_t
pwritev_read (void *lsqr_ctx, void *buf, size_t count)
{
struct pwritev_ctx *const ctx = lsqr_ctx;
assert(ctx->n_iovecs < ctx->max_iovecs);
ctx->iov[ctx->n_iovecs].iov_base = buf;
ctx->iov[ctx->n_iovecs].iov_len = count;
++ctx->n_iovecs;
ctx->total_bytes += count;
return count;
}
/* pwritev works as follows: allocate packets via lsquic_stream_writef() call
* and record pointers and sizes into an iovec array. Then issue a single call
* to user-supplied preadv() to populate all packets in one shot.
*
* Unwinding state changes due to a short write is by far the most complicated
* part of the machinery that follows. We optimize the normal path: it should
* be cheap to be prepared for the unwinding; unwinding itself can be more
* expensive, as we do not expect it to happen often.
*/
ssize_t
lsquic_stream_pwritev (struct lsquic_stream *stream,
ssize_t (*preadv)(void *user_data, const struct iovec *iov, int iovcnt),
void *user_data, size_t n_to_write)
{
struct lsquic_send_ctl *const ctl = stream->conn_pub->send_ctl;
struct iovec iovecs[PWRITEV_IOVECS], *last_iov;
unsigned char *hq_frames[PWRITEV_FRAMES];
struct pwritev_ctx ctx;
struct lsquic_reader reader;
struct send_ctl_state ctl_state;
struct hq_arr hq_arr;
ssize_t nw;
size_t n_allocated, sum;
#ifndef NDEBUG
const unsigned short n_buffered = stream->sm_n_buffered;
#endif
COMMON_WRITE_CHECKS();
SM_HISTORY_APPEND(stream, SHE_USER_WRITE_DATA);
lsquic_send_ctl_snapshot(ctl, &ctl_state);
ctx.total_bytes = 0;
ctx.n_to_write = n_to_write;
ctx.n_iovecs = 0;
ctx.max_iovecs = sizeof(iovecs) / sizeof(iovecs[0]);
ctx.iov = iovecs;
ctx.hq_arr = &hq_arr;
hq_arr.p = hq_frames;
hq_arr.count = 0;
hq_arr.max = sizeof(hq_frames) / sizeof(hq_frames[0]);
stream->sm_hq_arr = &hq_arr;
reader.lsqr_ctx = &ctx;
reader.lsqr_size = pwritev_size;
reader.lsqr_read = pwritev_read;
nw = stream_write(stream, &reader, 0);
LSQ_DEBUG("pwritev: stream_write returned %zd, n_iovecs: %d", nw,
ctx.n_iovecs);
if (nw > 0)
{
/* Amount of buffered data shouldn't have increased */
assert(n_buffered >= stream->sm_n_buffered);
n_allocated = (size_t) nw;
nw = preadv(user_data, ctx.iov, ctx.n_iovecs);
LSQ_DEBUG("pwritev: preadv returned %zd", nw);
if (nw >= 0 && (size_t) nw < n_allocated)
goto unwind_short_write;
}
cleanup:
stream->sm_hq_arr = NULL;
return nw;
unwind_short_write:
/* What follows is not the most efficient process. The emphasis here is
* on being simple instead. We expect short writes to be rare, so being
* slower than possible is a good tradeoff for being correct.
*/
LSQ_DEBUG("short write occurred, unwind");
SM_HISTORY_APPEND(stream, SHE_SHORT_WRITE);
/* First, adjust connection cap and stream offsets, and HTTP/3 framing,
* if necessary.
*/
if ((stream->sm_bflags & (SMBF_USE_HEADERS|SMBF_IETF))
== (SMBF_USE_HEADERS|SMBF_IETF))
{
size_t shortfall, payload_sz, decr;
unsigned char *p;
unsigned bits;
assert(hq_arr.count > 0);
shortfall = n_allocated - (size_t) nw;
do
{
const unsigned count = hq_arr.count;
(void) count;
p = hq_frames[--hq_arr.count];
assert(p[0] == HQFT_DATA);
assert(!(p[1] & 0x80)); /* Only one- and two-byte frame sizes */
if (p[1] & 0x40)
{
payload_sz = (p[1] & 0x3F) << 8;
payload_sz |= p[2];
}
else
payload_sz = p[1];
if (payload_sz > shortfall)
{
bits = p[1] >> 6;
vint_write(p + 1, payload_sz - shortfall, bits, 1 << bits);
decr = shortfall;
if (stream->sm_bflags & SMBF_CONN_LIMITED)
stream->conn_pub->conn_cap.cc_sent -= decr;
stream->sm_payload -= decr;
stream->tosend_off -= decr;
shortfall = 0;
}
else
{
decr = payload_sz + 2 + (p[1] >> 6);
if (stream->sm_bflags & SMBF_CONN_LIMITED)
stream->conn_pub->conn_cap.cc_sent -= decr;
stream->sm_payload -= payload_sz;
stream->tosend_off -= decr;
shortfall -= payload_sz;
}
}
while (hq_arr.count);
assert(shortfall == 0);
}
else
{
const size_t shortfall = n_allocated - (size_t) nw;
if (stream->sm_bflags & SMBF_CONN_LIMITED)
stream->conn_pub->conn_cap.cc_sent -= shortfall;
stream->sm_payload -= shortfall;
stream->tosend_off -= shortfall;
}
/* Find last iovec: */
sum = 0;
for (last_iov = iovecs; last_iov
< iovecs + sizeof(iovecs)/sizeof(iovecs[0]); ++last_iov)
{
sum += last_iov->iov_len;
if ((last_iov == iovecs || (size_t) nw > sum - last_iov->iov_len)
&& (size_t) nw <= sum)
break;
}
assert(last_iov < iovecs + sizeof(iovecs)/sizeof(iovecs[0]));
lsquic_send_ctl_rollback(ctl, &ctl_state, last_iov, sum - nw);
goto cleanup;
}
@ -3478,7 +3820,7 @@ stream_write_buf (struct lsquic_stream *stream, const void *buf, size_t sz)
.lsqr_size = inner_reader_iovec_size,
.lsqr_ctx = &iro,
};
return stream_write(stream, &reader);
return stream_write(stream, &reader, SWO_BUFFER);
}
@ -4747,7 +5089,7 @@ on_write_pp_wrapper (struct lsquic_stream *stream, lsquic_stream_ctx_t *h)
promise = SLIST_FIRST(&stream->sm_promises);
init_pp_reader(promise, &pp_reader);
nw = stream_write(stream, &pp_reader);
nw = stream_write(stream, &pp_reader, SWO_BUFFER);
if (nw > 0)
{
LSQ_DEBUG("wrote %zd bytes more of push promise (%s)",
@ -4801,7 +5143,7 @@ lsquic_stream_push_promise (struct lsquic_stream *stream,
stream->stream_flags |= STREAM_PUSHING;
init_pp_reader(promise, &pp_reader);
nw = stream_write(stream, &pp_reader);
nw = stream_write(stream, &pp_reader, SWO_BUFFER);
if (nw > 0)
{
SLIST_INSERT_HEAD(&stream->sm_promises, promise, pp_next);

View File

@ -22,11 +22,7 @@ TAILQ_HEAD(lsquic_streams_tailq, lsquic_stream);
#ifndef LSQUIC_KEEP_STREAM_HISTORY
# ifdef NDEBUG
# define LSQUIC_KEEP_STREAM_HISTORY 0
# else
# define LSQUIC_KEEP_STREAM_HISTORY 1
# endif
# define LSQUIC_KEEP_STREAM_HISTORY 1
#endif
@ -149,11 +145,13 @@ enum stream_q_flags
* connections's sending_streams queue. Note that writing STREAM
* frames is done separately.
*/
#define SMQF_SENDING_FLAGS (SMQF_SEND_WUF|SMQF_SEND_RST|SMQF_SEND_BLOCKED)
#define SMQF_SENDING_FLAGS (SMQF_SEND_WUF|SMQF_SEND_RST|SMQF_SEND_BLOCKED\
|SMQF_SEND_STOP_SENDING)
/* sending_streams: */
SMQF_SEND_WUF = 1 << 3, /* WUF: Window Update Frame */
SMQF_SEND_BLOCKED = 1 << 4,
SMQF_SEND_RST = 1 << 5, /* Error: want to send RST_STREAM */
SMQF_SEND_STOP_SENDING = 1 << 10,
/* The equivalent of WINDOW_UPDATE frame for streams in IETF QUIC is
* the MAX_STREAM_DATA frame. Define an alias for use in the IETF
@ -167,6 +165,10 @@ enum stream_q_flags
SMQF_ABORT_CONN = 1 << 8, /* Unrecoverable error occurred */
SMQF_QPACK_DEC = 1 << 9, /* QPACK decoder handler is holding a reference to this stream */
/* The stream can reference itself, preventing its own destruction: */
#define SMQF_SELF_FLAGS SMQF_WAIT_FIN_OFF
SMQF_WAIT_FIN_OFF = 1 << 11, /* Waiting for final offset: FIN or RST */
};
@ -209,7 +211,7 @@ enum stream_flags {
STREAM_PUSHING = 1 << 18,
STREAM_NOPUSH = 1 << 19, /* Disallow further push promises */
STREAM_GOAWAY_IN = 1 << 20, /* Incoming GOAWAY has been processed */
STREAM_UNUSED21 = 1 << 21, /* Unused */
STREAM_SS_SENT = 1 << 21, /* STOP_SENDING sent */
STREAM_RST_ACKED = 1 << 22, /* Packet containing RST has been acked */
STREAM_BLOCKED_SENT = 1 << 23, /* Stays set once a STREAM_BLOCKED frame is sent */
STREAM_RST_READ = 1 << 24, /* User code collected the error */
@ -266,6 +268,9 @@ struct lsquic_stream
struct hq_filter sm_hq_filter;
/* Optional tap for pwritev undo */
struct hq_arr *sm_hq_arr;
/* We can safely use sm_hq_filter */
#define sm_uni_type_state sm_hq_filter.hqfi_vint2_state.vr2s_varint_state
@ -602,4 +607,12 @@ lsquic_stream_verify_len (struct lsquic_stream *, unsigned long long);
#define lsquic_stream_is_blocked(stream_) ((stream_)->blocked_off && \
(stream_)->blocked_off == (stream_)->max_send_off)
void
lsquic_stream_ss_frame_sent (struct lsquic_stream *);
#ifndef NDEBUG
void
lsquic_stream_set_pwritev_params (unsigned iovecs, unsigned frames);
#endif
#endif

View File

@ -59,6 +59,9 @@
#include "lsquic_hq.h"
#include "lsquic_data_in_if.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
static const struct parse_funcs *g_pf = select_pf_by_ver(LSQVER_ID27);
struct test_ctl_settings
@ -270,11 +273,24 @@ struct test_objs {
struct lsquic_hset_if hsi_if;
};
static int s_ack_written;
static void
write_ack (struct lsquic_conn *conn, struct lsquic_packet_out *packet_out)
{
/* We don't need to generate full-blown ACK, as logic in
* lsquic_send_ctl_rollback() only looks at po_frame_types.
*/
packet_out->po_frame_types |= QUIC_FRAME_ACK;
s_ack_written = 1;
}
static int s_can_write_ack;
static int
unit_test_doesnt_write_ack (struct lsquic_conn *lconn)
can_write_ack (struct lsquic_conn *lconn)
{
return 0;
return s_can_write_ack;
}
@ -286,11 +302,31 @@ get_network_path (struct lsquic_conn *lconn, const struct sockaddr *sa)
return &network_path;
}
static enum {
SNAPSHOT_STATE_NONE = 0,
SNAPSHOT_STATE_TAKEN = 1 << 0,
SNAPSHOT_STATE_ROLLED_BACK = 1 << 1,
} s_snapshot_state;
static void
ack_snapshot (struct lsquic_conn *lconn, struct ack_state *ack_state)
{
s_snapshot_state |= SNAPSHOT_STATE_TAKEN;
}
static void
ack_rollback (struct lsquic_conn *lconn, struct ack_state *ack_state)
{
s_snapshot_state |= SNAPSHOT_STATE_ROLLED_BACK;
}
static const struct conn_iface our_conn_if =
{
.ci_can_write_ack = unit_test_doesnt_write_ack,
.ci_can_write_ack = can_write_ack,
.ci_write_ack = write_ack,
.ci_get_path = get_network_path,
.ci_ack_snapshot = ack_snapshot,
.ci_ack_rollback = ack_rollback,
};
@ -662,7 +698,7 @@ main_test_hq_framing (void)
* file. This allows afl-fuzz explore the code paths.
*/
void
fuzz_guided_testing (const char *input)
fuzz_guided_hq_framing_testing (const char *input)
{
/* Range */ /* Bytes from file */
unsigned short packet_sz; /* [200, 0x3FFF] */ /* 2 */
@ -728,6 +764,391 @@ fuzz_guided_testing (const char *input)
}
struct pwritev_stream_ctx
{
int limit; /* Test limit */
size_t avail;
const unsigned char *input;
size_t input_sz;
ssize_t nw; /* Number of bytes written */
};
static ssize_t
my_preadv (void *user_data, const struct iovec *iov, int iovcnt)
{
struct pwritev_stream_ctx *const pw_ctx = user_data;
const unsigned char *p;
size_t ntoread, tocopy;
int i;
ntoread = 0;
for (i = 0; i < iovcnt; ++i)
ntoread += iov[i].iov_len;
if (pw_ctx->limit < 0)
{
if ((size_t) -pw_ctx->limit < ntoread)
ntoread -= (size_t) -pw_ctx->limit;
}
else if ((size_t) pw_ctx->limit < ntoread)
ntoread = (size_t) pw_ctx->limit;
assert(ntoread <= pw_ctx->input_sz); /* Self-check */
p = pw_ctx->input;
for (i = 0; i < iovcnt; ++i)
{
tocopy = MIN(iov[i].iov_len, ntoread - (p - pw_ctx->input));
memcpy(iov[i].iov_base, p, tocopy);
p += tocopy;
if (ntoread == (size_t) (p - pw_ctx->input))
break;
}
assert(ntoread == (size_t) (p - pw_ctx->input));
return (ssize_t) (p - pw_ctx->input);
}
static void
pwritev_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *ctx)
{
struct pwritev_stream_ctx *const pw_ctx = (void *) ctx;
ssize_t nw;
nw = lsquic_stream_pwritev(stream, my_preadv, pw_ctx, pw_ctx->input_sz);
pw_ctx->nw = nw;
lsquic_stream_wantwrite(stream, 0);
}
static const struct lsquic_stream_if pwritev_stream_if = {
.on_new_stream = packetization_on_new_stream,
.on_close = packetization_on_close,
.on_write = pwritev_on_write,
};
static void
test_pwritev (enum lsquic_version version, int http, int sched_immed,
int limit, unsigned short packet_sz, size_t prologue_sz,
unsigned n_packets)
{
struct test_objs tobjs;
struct lsquic_stream *stream;
size_t nw;
int fin, s;
unsigned char *buf_in, *buf_out;
/* Some values that are large enough: */
const size_t buf_in_sz = MAX(n_packets * packet_sz, 0x1000),
buf_out_sz = (float) buf_in_sz * 1.1;
const int ietf = (1 << version) & LSQUIC_IETF_VERSIONS;
const enum stream_ctor_flags ietf_flags = ietf ? SCF_IETF : 0;
s_snapshot_state = 0;
s_ack_written = 0;
/* We'll write headers first after which stream will switch to using
* data-framing writer. This is simply so that we don't have to
* expose more stream things only for testing.
*/
struct lsxpack_header header = { XHDR(":method", "GET") };
struct lsquic_http_headers headers = { 1, &header, };
buf_in = malloc(buf_in_sz);
buf_out = malloc(buf_out_sz);
assert(buf_in && buf_out);
struct pwritev_stream_ctx pwritev_stream_ctx =
{
.input = buf_in + prologue_sz,
.input_sz = buf_in_sz - prologue_sz,
.limit = limit,
};
init_buf(buf_in, buf_in_sz);
init_test_ctl_settings(&g_ctl_settings);
g_ctl_settings.tcs_schedule_stream_packets_immediately = sched_immed;
stream_ctor_flags |= ietf_flags;
init_test_objs(&tobjs, buf_out_sz, buf_out_sz, packet_sz);
tobjs.lconn.cn_version = version;
tobjs.lconn.cn_esf_c = select_esf_common_by_ver(version);
tobjs.stream_if_ctx = &pwritev_stream_ctx;
tobjs.ctor_flags |= (http ? SCF_HTTP : 0)|ietf_flags;
if (sched_immed)
{
g_ctl_settings.tcs_can_send = n_packets;
tobjs.stream_if = &pwritev_stream_if;
}
else
{
lsquic_send_ctl_set_max_bpq_count(n_packets);
g_ctl_settings.tcs_can_send = INT_MAX;
g_ctl_settings.tcs_bp_type = BPT_OTHER_PRIO;
/* Need this for on_new_stream() callback not to mess with
* the context, otherwise this is not used.
*/
tobjs.stream_if = &pwritev_stream_if;
}
stream = new_stream(&tobjs, 0, buf_out_sz);
if (http)
{
if (ietf)
{
s = lsquic_stream_send_headers(stream, &headers, 0);
assert(0 == s);
}
else
/* Here we fake it in order not to have to set up frame writer. */
stream->stream_flags |= STREAM_HEADERS_SENT;
}
if (prologue_sz)
{
ssize_t written = lsquic_stream_write(stream, buf_in, prologue_sz);
assert(written > 0 && (size_t) written == prologue_sz);
}
if (sched_immed)
{
lsquic_stream_dispatch_write_events(stream);
assert(!(s_snapshot_state & SNAPSHOT_STATE_TAKEN));
// lsquic_stream_flush(stream);
}
else
{
pwritev_on_write(stream, (void *) &pwritev_stream_ctx);
assert(s_snapshot_state & SNAPSHOT_STATE_TAKEN);
if (n_packets > 0
&& s_ack_written
&& tobjs.send_ctl.sc_buffered_packets[BPT_OTHER_PRIO].bpq_count == 0)
assert(s_snapshot_state & SNAPSHOT_STATE_ROLLED_BACK);
g_ctl_settings.tcs_schedule_stream_packets_immediately = 1;
lsquic_send_ctl_schedule_buffered(&tobjs.send_ctl, BPT_OTHER_PRIO);
g_ctl_settings.tcs_schedule_stream_packets_immediately = 0;
lsquic_send_ctl_set_max_bpq_count(10);
}
assert(pwritev_stream_ctx.nw >= 0);
/* Verify written data: */
nw = read_from_scheduled_packets(&tobjs.send_ctl, 0, buf_out, buf_out_sz,
0, &fin, 1);
assert(nw <= buf_in_sz);
if (ietf && http)
{ /* Remove framing and verify contents */
const unsigned char *src;
unsigned char *dst;
uint64_t sz;
unsigned frame_type;
int s;
src = buf_out;
dst = buf_out;
while (src < buf_out + nw)
{
frame_type = *src++;
s = vint_read(src, buf_out + buf_out_sz, &sz);
assert(s > 0);
/* In some rare circumstances it is possible to produce zero-length
* DATA frames:
*
* assert(sz > 0);
*/
assert(sz < (1 << 14));
src += s;
if (src == buf_out + s + 1)
{
/* Ignore headers */
assert(frame_type == HQFT_HEADERS);
src += sz;
}
else
{
assert(frame_type == HQFT_DATA);
if (src + sz > buf_out + nw) /* Chopped DATA frame (last) */
sz = buf_out + nw - src;
memmove(dst, src, sz);
dst += sz;
src += sz;
}
}
assert(nw <= buf_in_sz);
if (n_packets && pwritev_stream_ctx.nw)
{
assert((size_t) pwritev_stream_ctx.nw + prologue_sz == (uintptr_t) dst - (uintptr_t) buf_out);
assert(0 == memcmp(buf_in, buf_out, (uintptr_t) dst - (uintptr_t) buf_out));
}
else
assert((uintptr_t) dst - (uintptr_t) buf_out == 0
|| (uintptr_t) dst - (uintptr_t) buf_out == prologue_sz);
}
else
{
assert(nw <= buf_in_sz);
assert(nw <= buf_out_sz);
if (n_packets && pwritev_stream_ctx.nw)
{
assert((size_t) pwritev_stream_ctx.nw + prologue_sz == nw);
assert(0 == memcmp(buf_in, buf_out, (size_t) nw));
}
else
assert(nw == 0 || nw == prologue_sz);
}
lsquic_stream_destroy(stream);
deinit_test_objs(&tobjs);
free(buf_in);
free(buf_out);
stream_ctor_flags &= ~ietf_flags;
}
static void
main_test_pwritev (void)
{
const int limits[] = { INT_MAX, -1, -2, -3, -7, -10, -50, -100, -201, -211,
-1000, -2003, -3000, -4000, -17803, -20000, 16 * 1024, 16 * 1024 - 1,
16 * 1024 - 2, 8000, 273, 65, 63, 10, 5, 1, 0, };
unsigned n_packets;
const unsigned short packet_sz[] = { 1252, 1370, 0x1000, 0xFF00, };
const size_t prologues[] = { 0, 17, 238, };
unsigned i, j, k;
enum lsquic_version version;
int http, sched_immed;
const struct { unsigned iovecs, frames; } combos[] =
{
{ 32, 16, },
{ 16, 16, },
{ 16, 8, },
{ 3, 7, },
{ 7, 3, },
{ 100, 100, },
}, *combo = combos;
s_can_write_ack = 1;
run_test:
for (version = 0; version < N_LSQVER; ++version)
if ((1 << version) & LSQUIC_SUPPORTED_VERSIONS)
for (http = 0; http < 2; ++http)
for (sched_immed = 0; sched_immed <= 1; ++sched_immed)
for (i = 0; i < sizeof(limits) / sizeof(limits[i]); ++i)
for (j = 0; j < sizeof(packet_sz) / sizeof(packet_sz[0]);
++j)
for (k = 0; k < sizeof(prologues) / sizeof(prologues[0]); ++k)
for (n_packets = 1; n_packets < 21; ++n_packets)
test_pwritev(version, http, sched_immed,
limits[i], packet_sz[j], prologues[k], n_packets);
if (combo < combos + sizeof(combos) / sizeof(combos[0]))
{
lsquic_stream_set_pwritev_params(combo->iovecs, combo->frames);
++combo;
goto run_test;
}
s_can_write_ack = 0;
}
/* Instead of the not-very-random testing done in main_test_pwritev(),
* the fuzz-guided testing initializes parameters based on the fuzz input
* file. This allows afl-fuzz explore the code paths.
*/
void
fuzz_guided_pwritev_testing (const char *input)
{
/* Range */ /* Bytes from file */
unsigned short packet_sz; /* [1200, 0xFF00] */ /* 2 */
int limit; /* [INT_MIN, INT_MAX] */ /* 2 */
unsigned n_packets; /* [0, 255] */ /* 1 */
unsigned n_iovecs; /* [0, 255] */ /* 1 */
unsigned n_frames; /* [0, 255] */ /* 1 */
size_t prologue_sz; /* [0, 170] */ /* 1 */
enum lsquic_version version;/* [0,7] */ /* 1 */
int sched_immed; /* 0 or 1 */ /* 1 (same byte) */
int http; /* 0 or 1 */ /* 1 (same byte) */
/* TOTAL: 9 bytes */
FILE *f;
size_t nread;
union {
uint16_t tmp;
int16_t itmp;
} u;
unsigned char buf[10];
f = fopen(input, "rb");
if (!f)
{
assert(0);
return;
}
nread = fread(buf, 1, sizeof(buf), f);
if (nread != 9)
goto cleanup;
memcpy(&u.tmp, &buf[0], 2);
if (u.tmp < 1200)
u.tmp = 1200;
else if (u.tmp > 0xFF00)
u.tmp = 0xFF00;
packet_sz = u.tmp;
memcpy(&u.itmp, &buf[2], 2);
if (u.itmp < SHRT_MIN / 2)
limit = INT_MIN;
else if (u.itmp < SHRT_MIN / 4)
limit = 0;
else if (u.itmp > SHRT_MAX / 2)
limit = INT_MAX;
else if (u.itmp > SHRT_MAX / 2)
limit = 0;
else
limit = u.itmp;
n_packets = buf[4];
n_iovecs = buf[5];
n_frames = buf[6];
prologue_sz = buf[7];
if (prologue_sz > 170)
prologue_sz = 170;
switch (buf[8] & 7)
{
case 0: version = LSQVER_043; break;
case 1: version = LSQVER_046; break;
case 2: version = LSQVER_050; break;
case 3: version = LSQVER_ID27; break;
case 4: version = LSQVER_ID28; break;
default:
case 5: version = LSQVER_ID29; break;
}
sched_immed = !!(buf[8] & 0x08);
http = !!(buf[8] & 0x10);
lsquic_stream_set_pwritev_params(n_iovecs, n_frames);
test_pwritev(version, http, sched_immed, limit, packet_sz, prologue_sz,
n_packets);
cleanup:
(void) fclose(f);
}
static void
test_frame_header_split (unsigned n_packets, unsigned extra_sz,
int add_one_more)
@ -1170,18 +1591,22 @@ test_reading_zero_size_data_frame_scenario3 (void)
int
main (int argc, char **argv)
{
const char *fuzz_input = NULL;
const char *fuzz_hq_framing_input = NULL;
const char *fuzz_pwritev_input = NULL;
int opt, add_one_more;
unsigned n_packets, extra_sz;
lsquic_global_init(LSQUIC_GLOBAL_SERVER);
while (-1 != (opt = getopt(argc, argv, "f:l:")))
while (-1 != (opt = getopt(argc, argv, "f:p:l:")))
{
switch (opt)
{
case 'f':
fuzz_input = optarg;
fuzz_hq_framing_input = optarg;
break;
case 'p':
fuzz_pwritev_input = optarg;
break;
case 'l':
lsquic_log_to_fstream(stderr, 0);
@ -1194,10 +1619,14 @@ main (int argc, char **argv)
init_test_ctl_settings(&g_ctl_settings);
if (fuzz_input)
fuzz_guided_testing(fuzz_input);
if (fuzz_hq_framing_input)
fuzz_guided_hq_framing_testing(fuzz_hq_framing_input);
else if (fuzz_pwritev_input)
fuzz_guided_pwritev_testing(fuzz_pwritev_input);
else
{
main_test_pwritev();
return 0;
main_test_hq_framing();
for (n_packets = 1; n_packets <= 2; ++n_packets)
for (extra_sz = 0; extra_sz <= 2; ++extra_sz)

View File

@ -340,6 +340,9 @@ test_flushes_and_closes (void)
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 */
@ -348,6 +351,9 @@ test_flushes_and_closes (void)
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 */
lsquic_stream_rst_in(stream, 0, 0);
assert(!(stream->sm_qflags & (SMQF_SEND_STOP_SENDING|SMQF_WAIT_FIN_OFF)));
assert(stream->sm_qflags & SMQF_FREE_STREAM);
lsquic_stream_destroy(stream);
@ -386,6 +392,7 @@ test_headers_wantwrite_restoration (const int want_write)
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;

View File

@ -745,7 +745,7 @@ test_rem_FIN_loc_FIN (struct test_objs *tobjs)
/* Server: we read data and close the read side before reading FIN, which
* DOES NOT result in stream being reset.
* results in stream being reset.
*/
static void
test_rem_data_loc_close_and_rst_in (struct test_objs *tobjs)
@ -765,6 +765,13 @@ test_rem_data_loc_close_and_rst_in (struct test_objs *tobjs)
s = lsquic_stream_shutdown(stream, 0);
assert(0 == s);
/* Early read shutdown results in different frames on different QUIC
* transports:
*/
if (stream->sm_bflags & SMBF_IETF)
assert(stream->sm_qflags & SMQF_SEND_STOP_SENDING);
else
assert(stream->sm_qflags & SMQF_SEND_RST);
assert(TAILQ_EMPTY(&tobjs->conn_pub.service_streams));
assert(!((stream->sm_qflags & (SMQF_SERVICE_FLAGS)) == SMQF_CALL_ONCLOSE));
@ -776,6 +783,7 @@ test_rem_data_loc_close_and_rst_in (struct test_objs *tobjs)
assert(0 == s);
assert(1 == lsquic_send_ctl_n_scheduled(&tobjs->send_ctl)); /* Shutdown performs a flush */
assert(stream->n_unacked == 1);
assert(!TAILQ_EMPTY(&tobjs->conn_pub.service_streams));
assert((stream->sm_qflags & (SMQF_SERVICE_FLAGS)) == SMQF_CALL_ONCLOSE);
@ -783,7 +791,17 @@ test_rem_data_loc_close_and_rst_in (struct test_objs *tobjs)
s = lsquic_stream_rst_in(stream, 100, 1);
assert(0 == s);
assert(stream->sm_qflags & SMQF_FREE_STREAM);
assert(!(stream->sm_qflags & SMQF_FREE_STREAM)); /* Not yet */
assert(stream->sm_qflags & SMQF_CALL_ONCLOSE);
lsquic_stream_rst_frame_sent(stream);
stream->n_unacked++; /* RESET frame take a reference */
assert(!(stream->sm_qflags & SMQF_FREE_STREAM)); /* Not yet,
because: */ assert(stream->n_unacked == 2);
lsquic_stream_acked(stream, QUIC_FRAME_STREAM);
lsquic_stream_acked(stream, QUIC_FRAME_RST_STREAM);
assert(stream->sm_qflags & SMQF_FREE_STREAM); /* OK, now */
lsquic_stream_destroy(stream);
/* This simply checks that the stream got removed from the queue: */
@ -795,8 +813,8 @@ test_rem_data_loc_close_and_rst_in (struct test_objs *tobjs)
/* Server: we read data and close the read side before reading FIN. No
* FIN or RST arrive from peer. This should still place the stream on
* the "streams to be freed" list.
* FIN or RST arrive from peer. This should schedule RST_STREAM to be
* sent (this is gQUIC) and add "wait for known FIN" flag.
*/
static void
test_rem_data_loc_close (struct test_objs *tobjs)
@ -833,7 +851,16 @@ test_rem_data_loc_close (struct test_objs *tobjs)
assert(!(stream->sm_qflags & SMQF_FREE_STREAM));
lsquic_stream_acked(stream, QUIC_FRAME_STREAM);
assert(stream->sm_qflags & SMQF_FREE_STREAM);
lsquic_stream_rst_frame_sent(stream);
stream->n_unacked++; /* RESET frame take a reference */
assert(!(stream->sm_qflags & SMQF_FREE_STREAM)); /* No */
lsquic_stream_acked(stream, QUIC_FRAME_RST_STREAM);
assert(!(stream->sm_qflags & SMQF_FREE_STREAM)); /* Still no */
/* Stream will linger until we have the offset: */
assert(stream->sm_qflags & SMQF_WAIT_FIN_OFF);
lsquic_stream_destroy(stream);
/* This simply checks that the stream got removed from the queue: */
@ -918,6 +945,13 @@ test_loc_FIN_rem_RST (struct test_objs *tobjs)
ack_packet(&tobjs->send_ctl, 1);
ack_packet(&tobjs->send_ctl, 2);
#if 0
/* OK, here we pretend that we sent a RESET and it was acked */
assert(stream->sm_qflags & SMQF_SEND_RST);
stream->sm_qflags |= SMQF_SEND_RST;
stream->stream_flags
#endif
assert(!TAILQ_EMPTY(&tobjs->conn_pub.service_streams));
assert((stream->sm_qflags & (SMQF_SERVICE_FLAGS)) == (SMQF_CALL_ONCLOSE|SMQF_FREE_STREAM));
@ -1044,7 +1078,7 @@ test_loc_RST_rem_FIN (struct test_objs *tobjs)
assert(!TAILQ_EMPTY(&tobjs->conn_pub.sending_streams));
assert((stream->sm_qflags & SMQF_SENDING_FLAGS) == SMQF_SEND_RST);
sss = lsquic_stream_sending_state(stream);
assert(SSS_SEND == sss); /* Reset hasn't been packetized yet */
assert(SSS_DATA_SENT == sss); /* FIN was packetized */
s = lsquic_stream_frame_in(stream, new_frame_in(tobjs, 0, 90, 1));
assert(s == 0);
@ -1061,9 +1095,12 @@ test_loc_RST_rem_FIN (struct test_objs *tobjs)
sss = lsquic_stream_sending_state(stream);
assert(SSS_RESET_RECVD == sss);
lsquic_stream_call_on_close(stream);
assert(TAILQ_EMPTY(&tobjs->conn_pub.sending_streams));
lsquic_stream_call_on_close(stream);
assert(TAILQ_EMPTY(&tobjs->conn_pub.service_streams)); /* Not acked yet */
lsquic_stream_acked(stream, QUIC_FRAME_STREAM);
assert(!TAILQ_EMPTY(&tobjs->conn_pub.service_streams));
assert((stream->sm_qflags & SMQF_SERVICE_FLAGS) == SMQF_FREE_STREAM);
@ -1317,7 +1354,12 @@ test_data_flush_on_close (struct test_objs *tobjs)
assert(0 == lsquic_send_ctl_n_scheduled(&tobjs->send_ctl));
lsquic_stream_close(stream);
assert(1 == lsquic_send_ctl_n_scheduled(&tobjs->send_ctl));
/* Nothing is scheduled because STREAM frames are elided */
assert(0 == lsquic_send_ctl_n_scheduled(&tobjs->send_ctl));
assert(stream->sm_qflags & SMQF_SEND_RST);
assert(!(stream->sm_qflags & SMQF_FREE_STREAM));
assert(stream->sm_qflags & SMQF_WAIT_FIN_OFF);
/* We take connection cap hit after stream is flushed: */
assert(0x4000 - 100 == lsquic_conn_cap_avail(cap)); /* Conn cap hit */