Release 2.22.0

- [FEATURE] Extensible HTTP Priorities (HTTP/3 only).
- [FEATURE] Add conn context to packet-out memory interface (PR #175).
- [BUGFIX] gQUIC proof generation: allocate buffer big enough for
  signature (issue #173).
- [BUGFIX] Make library thread-safe: drop use of global variables
  (issue #133, issue #167).
- [BUGFIX] Deactivate only *recent* HQ frame, not any HQ frame.
- [BUGFIX] gQUIC server: associate compressed cert with SSL_CTX,
  instead of keeping them in a separate hash, potentially leading
  to mismatches.
- [BUGFIX] Stream data discard infinite loop: break on FIN.
- cmake: add install target via -DCMAKE_INSTALL_PREFIX (PR #171).
- Support randomized packet number to begin a connection.
- Mini and full IETF connection size optimization.
- http_client: specify HTTP priorities based on stream conditions.
This commit is contained in:
Dmitri Tikhonov 2020-10-07 09:41:26 -04:00
parent cb1e8c1022
commit fbc6cc0413
55 changed files with 6557 additions and 391 deletions

View File

@ -1,3 +1,21 @@
2020-10-07
- 2.22.0
- [FEATURE] Extensible HTTP Priorities (HTTP/3 only).
- [FEATURE] Add conn context to packet-out memory interface (PR #175).
- [BUGFIX] gQUIC proof generation: allocate buffer big enough for
signature (issue #173).
- [BUGFIX] Make library thread-safe: drop use of global variables
(issue #133, issue #167).
- [BUGFIX] Deactivate only *recent* HQ frame, not any HQ frame.
- [BUGFIX] gQUIC server: associate compressed cert with SSL_CTX,
instead of keeping them in a separate hash, potentially leading
to mismatches.
- [BUGFIX] Stream data discard infinite loop: break on FIN.
- cmake: add install target via -DCMAKE_INSTALL_PREFIX (PR #171).
- Support randomized packet number to begin a connection.
- Mini and full IETF connection size optimization.
- http_client: specify HTTP priorities based on stream conditions.
2020-09-29
- 2.21.0
- [FEATURE] QUIC and HTTP/3 Internet Draft 31 support.

View File

@ -50,7 +50,7 @@ You may need to install pre-requisites like zlib and libevent.
2. Use specific BoringSSL version
```
git checkout b117a3a0b7bd11fe6ebd503ec6b45d6b910b41a1
git checkout 251b5169fd44345f455438312ec4e18ae07fd58c
```
3. Compile the library

View File

@ -161,6 +161,24 @@ strndup(const char *s, size_t n)
}
#endif
/* When more than `nread' bytes are read from stream `stream_id', apply
* priority in `ehp'.
*/
struct priority_spec
{
enum {
PRIORITY_SPEC_ACTIVE = 1 << 0,
} flags;
lsquic_stream_id_t stream_id;
size_t nread;
struct lsquic_ext_http_prio ehp;
};
static struct priority_spec *s_priority_specs;
static unsigned s_n_prio_specs;
static void
maybe_perform_priority_actions (struct lsquic_stream *, lsquic_stream_ctx_t *);
struct lsquic_conn_ctx;
struct path_elem {
@ -531,7 +549,20 @@ http_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
LSQ_INFO("created new stream, path: %s", st_h->path);
lsquic_stream_wantwrite(stream, 1);
if (randomly_reprioritize_streams)
lsquic_stream_set_priority(stream, 1 + (random() & 0xFF));
{
if ((1 << lsquic_conn_quic_version(lsquic_stream_conn(stream)))
& LSQUIC_IETF_VERSIONS)
lsquic_stream_set_http_prio(stream,
&(struct lsquic_ext_http_prio){
.urgency = random() & 7,
.incremental = random() & 1,
}
);
else
lsquic_stream_set_priority(stream, 1 + (random() & 0xFF));
}
if (s_priority_specs)
maybe_perform_priority_actions(stream, st_h);
if (s_abandon_early)
{
st_h->sh_stop = random() % (s_abandon_early + 1);
@ -547,25 +578,37 @@ send_headers (lsquic_stream_ctx_t *st_h)
{
const char *hostname = st_h->client_ctx->hostname;
struct header_buf hbuf;
unsigned h_idx = 0;
if (!hostname)
hostname = st_h->client_ctx->prog->prog_hostname;
hbuf.off = 0;
struct lsxpack_header headers_arr[7];
struct lsxpack_header headers_arr[9];
#define V(v) (v), strlen(v)
header_set_ptr(&headers_arr[0], &hbuf, V(":method"), V(st_h->client_ctx->method));
header_set_ptr(&headers_arr[1], &hbuf, V(":scheme"), V("https"));
header_set_ptr(&headers_arr[2], &hbuf, V(":path"), V(st_h->path));
header_set_ptr(&headers_arr[3], &hbuf, V(":authority"), V(hostname));
header_set_ptr(&headers_arr[4], &hbuf, V("user-agent"), V(st_h->client_ctx->prog->prog_settings.es_ua));
/* The following headers only gets sent if there is request payload: */
header_set_ptr(&headers_arr[5], &hbuf, V("content-type"), V("application/octet-stream"));
header_set_ptr(&headers_arr[6], &hbuf, V("content-length"), V( st_h->client_ctx->payload_size));
header_set_ptr(&headers_arr[h_idx++], &hbuf, V(":method"), V(st_h->client_ctx->method));
header_set_ptr(&headers_arr[h_idx++], &hbuf, V(":scheme"), V("https"));
header_set_ptr(&headers_arr[h_idx++], &hbuf, V(":path"), V(st_h->path));
header_set_ptr(&headers_arr[h_idx++], &hbuf, V(":authority"), V(hostname));
header_set_ptr(&headers_arr[h_idx++], &hbuf, V("user-agent"), V(st_h->client_ctx->prog->prog_settings.es_ua));
if (randomly_reprioritize_streams)
{
char pfv[10];
sprintf(pfv, "u=%ld", random() & 7);
header_set_ptr(&headers_arr[h_idx++], &hbuf, V("priority"), V(pfv));
if (random() & 1)
sprintf(pfv, "i");
else
sprintf(pfv, "i=?0");
header_set_ptr(&headers_arr[h_idx++], &hbuf, V("priority"), V(pfv));
}
if (st_h->client_ctx->payload)
{
header_set_ptr(&headers_arr[h_idx++], &hbuf, V("content-type"), V("application/octet-stream"));
header_set_ptr(&headers_arr[h_idx++], &hbuf, V("content-length"), V( st_h->client_ctx->payload_size));
}
lsquic_http_headers_t headers = {
.count = sizeof(headers_arr) / sizeof(headers_arr[0]),
.count = h_idx,
.headers = headers_arr,
};
if (!st_h->client_ctx->payload)
headers.count -= 2;
if (0 != lsquic_stream_send_headers(st_h->stream, &headers,
st_h->client_ctx->payload == NULL))
{
@ -659,6 +702,40 @@ discard (void *ctx, const unsigned char *buf, size_t sz, int fin)
}
static void
maybe_perform_priority_actions (struct lsquic_stream *stream,
lsquic_stream_ctx_t *st_h)
{
const lsquic_stream_id_t stream_id = lsquic_stream_id(stream);
struct priority_spec *spec;
unsigned n_active;
int s;
n_active = 0;
for (spec = s_priority_specs; spec < s_priority_specs + s_n_prio_specs;
++spec)
{
if ((spec->flags & PRIORITY_SPEC_ACTIVE)
&& spec->stream_id == stream_id
&& st_h->sh_nread >= spec->nread)
{
s = lsquic_stream_set_http_prio(stream, &spec->ehp);
if (s != 0)
{
LSQ_ERROR("could not apply priorities to stream %"PRIu64,
stream_id);
exit(1);
}
spec->flags &= ~PRIORITY_SPEC_ACTIVE;
}
n_active += !!(spec->flags & PRIORITY_SPEC_ACTIVE);
}
if (n_active == 0)
s_priority_specs = NULL;
}
static void
http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
@ -728,16 +805,36 @@ http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
fwrite(buf, 1, nread, stdout);
if (randomly_reprioritize_streams && (st_h->count++ & 0x3F) == 0)
{
old_prio = lsquic_stream_priority(stream);
new_prio = 1 + (random() & 0xFF);
if ((1 << lsquic_conn_quic_version(lsquic_stream_conn(stream)))
& LSQUIC_IETF_VERSIONS)
{
struct lsquic_ext_http_prio ehp;
if (0 == lsquic_stream_get_http_prio(stream, &ehp))
{
ehp.urgency = 7 & (ehp.urgency + 1);
ehp.incremental = !ehp.incremental;
#ifndef NDEBUG
const int s =
const int s =
#endif
lsquic_stream_set_priority(stream, new_prio);
assert(s == 0);
LSQ_DEBUG("changed stream %"PRIu64" priority from %u to %u",
lsquic_stream_set_http_prio(stream, &ehp);
assert(s == 0);
}
}
else
{
old_prio = lsquic_stream_priority(stream);
new_prio = 1 + (random() & 0xFF);
#ifndef NDEBUG
const int s =
#endif
lsquic_stream_set_priority(stream, new_prio);
assert(s == 0);
LSQ_DEBUG("changed stream %"PRIu64" priority from %u to %u",
lsquic_stream_id(stream), old_prio, new_prio);
}
}
if (s_priority_specs)
maybe_perform_priority_actions(stream, st_h);
if ((st_h->sh_flags & ABANDON) && st_h->sh_nread >= st_h->sh_stop)
{
LSQ_DEBUG("closing stream early having read %zd bytes",
@ -864,6 +961,9 @@ usage (const char *prog)
" -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"
" -9 SPEC Priority specification. May be specified several times.\n"
" SPEC takes the form stream_id:nread:UI, where U is\n"
" urgency and I is incremental. Matched \\d+:\\d+:[0-7][01]\n"
, prog);
}
@ -1432,6 +1532,7 @@ main (int argc, char **argv)
struct sport_head sports;
struct prog prog;
const char *token = NULL;
struct priority_spec *priority_specs = NULL;
TAILQ_INIT(&sports);
memset(&client_ctx, 0, sizeof(client_ctx));
@ -1450,6 +1551,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") */
"9:" /* 9 sort of looks like P... */
#ifndef WIN32
"C:"
#endif
@ -1486,6 +1588,7 @@ main (int argc, char **argv)
case 'E': /* E: randomly reprioritize str<E>ams. Now, that's
* pretty random. :)
*/
srand((uintptr_t) argv);
randomly_reprioritize_streams = 1;
break;
case 'n':
@ -1569,6 +1672,45 @@ main (int argc, char **argv)
case '3':
s_abandon_early = strtol(optarg, NULL, 10);
break;
case '9':
{
/* Parse priority spec and tack it onto the end of the array */
lsquic_stream_id_t stream_id;
size_t nread;
struct lsquic_ext_http_prio ehp;
struct priority_spec *new_specs;
stream_id = strtoull(optarg, &optarg, 10);
if (*optarg != ':')
exit(1);
++optarg;
nread = strtoull(optarg, &optarg, 10);
if (*optarg != ':')
exit(1);
++optarg;
if (!(*optarg >= '0' && *optarg <= '7'))
exit(1);
ehp.urgency = *optarg++ - '0';
if (!(*optarg >= '0' && *optarg <= '1'))
exit(1);
ehp.incremental = *optarg++ - '0';
++s_n_prio_specs;
new_specs = realloc(priority_specs,
sizeof(priority_specs[0]) * s_n_prio_specs);
if (!new_specs)
{
perror("malloc");
exit(1);
}
priority_specs = new_specs;
priority_specs[s_n_prio_specs - 1] = (struct priority_spec) {
.flags = PRIORITY_SPEC_ACTIVE,
.stream_id = stream_id,
.nread = nread,
.ehp = ehp,
};
s_priority_specs = priority_specs;
break;
}
default:
if (0 != prog_set_opt(&prog, opt, optarg))
exit(1);
@ -1660,5 +1802,6 @@ main (int argc, char **argv)
if (client_ctx.qif_fh)
(void) fclose(client_ctx.qif_fh);
free(priority_specs);
exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE);
}

View File

@ -41,8 +41,8 @@
#include <event2/event.h>
#include "test_common.h"
#include "lsquic.h"
#include "test_common.h"
#include "prog.h"
#include "lsxpack_header.h"
@ -1916,6 +1916,11 @@ set_engine_option (struct lsquic_engine_settings *settings,
settings->es_scid_iss_rate = atoi(val);
return 0;
}
if (0 == strncmp(name, "ext_http_prio", 13))
{
settings->es_ext_http_prio = atoi(val);
return 0;
}
break;
case 14:
if (0 == strncmp(name, "max_streams_in", 14))
@ -2043,8 +2048,8 @@ pba_init (struct packout_buf_allocator *pba, unsigned max)
void *
pba_allocate (void *packout_buf_allocator, void *peer_ctx, void *conn_ctx, unsigned short size,
char is_ipv6)
pba_allocate (void *packout_buf_allocator, void *peer_ctx,
lsquic_conn_ctx_t *conn_ctx, unsigned short size, char is_ipv6)
{
struct packout_buf_allocator *const pba = packout_buf_allocator;
struct packout_buf *pb;

View File

@ -115,7 +115,7 @@ void
pba_init (struct packout_buf_allocator *, unsigned max);
void *
pba_allocate (void *packout_buf_allocator, void*, void *conn_ctx, unsigned short, char);
pba_allocate (void *packout_buf_allocator, void*, lsquic_conn_ctx_t *, unsigned short, char);
void
pba_release (void *packout_buf_allocator, void *, void *obj, char);

View File

@ -842,6 +842,13 @@ settings structure:
Default value is :macro:`LSQUIC_DF_OPTIMISTIC_NAT`
.. member:: int es_ext_http_prio
If set to true, Extensible HTTP Priorities are enabled. This
is HTTP/3-only setting.
Default value is :macro:`LSQUIC_DF_EXT_HTTP_PRIO`
To initialize the settings structure to library defaults, use the following
convenience function:
@ -1066,6 +1073,10 @@ out of date. Please check your :file:`lsquic.h` for actual values.*
Assume optimistic NAT by default.
.. macro:: LSQUIC_DF_EXT_HTTP_PRIO
Turn on Extensible HTTP Priorities by default.
Receiving Packets
-----------------
@ -2162,7 +2173,7 @@ The following log modules are defined:
- *hcsi-reader*: Reader of the HTTP/3 control stream.
- *hcso-writer*: Writer of the HTTP/3 control stream.
- *headers*: HEADERS stream (Google QUIC).
- *hsk-adapter*:
- *hsk-adapter*:
- *http1x*: Header conversion to HTTP/1.x.
- *logger*: Logger.
- *mini-conn*: Mini connection.
@ -2184,6 +2195,72 @@ The following log modules are defined:
- *tokgen*: Token generation and validation.
- *trapa*: Transport parameter processing.
.. _extensible-http-priorities:
Extensible HTTP Priorities
--------------------------
lsquic supports the
`Extensible HTTP Priorities Extension <https://tools.ietf.org/html/draft-ietf-httpbis-priority>`_.
It is enabled by default when HTTP/3 is used. The "urgency" and "incremental"
parameters are included into a dedicated type:
.. type:: struct lsquic_ext_http_prio
.. member:: unsigned char urgency
This value's range is [0, 7], where 0 is the highest and 7 is
the lowest urgency.
.. member:: signed char incremental
This is a boolean value. The valid range is [0, 1].
Some useful macros are also available:
.. macro:: LSQUIC_MAX_HTTP_URGENCY
The maximum value of the "urgency" parameter is 7.
.. macro:: LSQUIC_DEF_HTTP_URGENCY
The default value of the "urgency" parameter is 3.
.. macro:: LSQUIC_DEF_HTTP_INCREMENTAL
The default value of the "incremental" parameter is 0.
There are two functions to
manage a stream's priority:
.. function:: int lsquic_stream_get_http_prio (lsquic_stream_t \*stream, struct lsquic_ext_http_prio \*ehp)
Get a stream's priority information.
:param stream: The stream whose priority informaion we want.
:param ehp: Structure that is to be populated with the stream's
priority information.
:return: Returns zero on success of a negative value on failure.
A failure occurs if this is not an HTTP/3 stream or if
Extensible HTTP Priorities have not been enabled.
See :member:`lsquic_engine_settings.es_ext_http_prio`.
.. function:: int lsquic_stream_set_http_prio (lsquic_stream_t \*stream, const struct lsquic_ext_http_prio \*ehp)
Set a stream's priority information.
:param stream: The stream whose priority we want to set.
:param ehp: Structure containing the stream's new priority information.
:return: Returns zero on success of a negative value on failure.
A failure occurs if some internal error occured or if this
is not an HTTP/3 stream or if Extensible HTTP Priorities
haven't been enabled.
See :member:`lsquic_engine_settings.es_ext_http_prio`.
.. _apiref-datagrams:
Datagrams

View File

@ -24,9 +24,9 @@ copyright = u'2020, LiteSpeed Technologies'
author = u'LiteSpeed Technologies'
# The short X.Y version
version = u'2.21'
version = u'2.22'
# The full version, including alpha/beta/rc tags
release = u'2.21.0'
release = u'2.22.0'
# -- General configuration ---------------------------------------------------

View File

@ -37,6 +37,7 @@ LSQUIC supports nearly all QUIC and HTTP/3 features, including
- TLS Key updates
- Extensions:
- :ref:`extensible-http-priorities`
- :ref:`apiref-datagrams`
- Loss bits extension (allowing network observer to locate source of packet loss)
- Timestamps extension (allowing for one-way delay calculation, improving performance of some congestion controllers)

View File

@ -24,7 +24,7 @@ extern "C" {
#endif
#define LSQUIC_MAJOR_VERSION 2
#define LSQUIC_MINOR_VERSION 21
#define LSQUIC_MINOR_VERSION 22
#define LSQUIC_PATCH_VERSION 0
/**
@ -371,6 +371,9 @@ typedef struct ssl_ctx_st * (*lsquic_lookup_cert_f)(
/** Assume optimistic NAT by default. */
#define LSQUIC_DF_OPTIMISTIC_NAT 1
/** Turn on Extensible HTTP Priorities by default. */
#define LSQUIC_DF_EXT_HTTP_PRIO 1
/** By default, incoming packet size is not limited. */
#define LSQUIC_DF_MAX_UDP_PAYLOAD_SIZE_RX 0
@ -939,6 +942,14 @@ struct lsquic_engine_settings {
* Default value is @ref LSQUIC_DF_OPTIMISTIC_NAT.
*/
int es_optimistic_nat;
/**
* If set to true, Extensible HTTP Priorities are enabled. This
* is HTTP/3-only setting.
*
* Default value is @ref LSQUIC_DF_EXT_HTTP_PRIO
*/
int es_ext_http_prio;
};
/* Initialize `settings' to default values */
@ -1637,6 +1648,42 @@ unsigned lsquic_stream_priority (const lsquic_stream_t *s);
*/
int lsquic_stream_set_priority (lsquic_stream_t *s, unsigned priority);
/*
* Definitions for Extensible HTTP Priorities:
* https://tools.ietf.org/html/draft-ietf-httpbis-priority-01
*/
/* This is maximum *value* -- but it's the lowest *priority* */
#define LSQUIC_MAX_HTTP_URGENCY 7
#define LSQUIC_DEF_HTTP_URGENCY 3
#define LSQUIC_DEF_HTTP_INCREMENTAL 0
struct lsquic_ext_http_prio
{
unsigned char urgency;
signed char incremental;
};
/**
* Get Extensible HTTP Priorities associated with the stream.
*
* Returns zero on success of a negative value on failure. A failure occurs
* if this is not an HTTP/3 stream or if Extensible HTTP Priorities haven't
* been enabled. See @ref es_ext_http_prio.
*/
int
lsquic_stream_get_http_prio (lsquic_stream_t *, struct lsquic_ext_http_prio *);
/**
* Set Extensible HTTP Priorities of the stream.
*
* Returns zero on success of a negative value on failure. A failure occurs
* if some internal error occured or if this is not an HTTP/3 stream or if
* Extensible HTTP Priorities haven't been enabled. See @ref es_ext_http_prio.
*/
int
lsquic_stream_set_http_prio (lsquic_stream_t *,
const struct lsquic_ext_http_prio *);
/**
* Get a pointer to the connection object. Use it with lsquic_conn_*
* functions.

View File

@ -35,7 +35,9 @@ SET(lsquic_STAT_SRCS
lsquic_hcso_writer.c
lsquic_headers_stream.c
lsquic_hkdf.c
lsquic_hpi.c
lsquic_hspack_valid.c
lsquic_http.c
lsquic_http1x_if.c
lsquic_logger.c
lsquic_malo.c
@ -75,6 +77,7 @@ SET(lsquic_STAT_SRCS
lsquic_stream.c
lsquic_tokgen.c
lsquic_trans_params.c
lsquic_trechist.c
lsquic_util.c
lsquic_varint.c
lsquic_version.c
@ -103,6 +106,7 @@ ADD_CUSTOM_COMMAND(
DEPENDS ./gen-verstrs.pl ${CMAKE_CURRENT_SOURCE_DIR}/../../include/lsquic.h
)
SET(lsquic_STAT_SRCS ${lsquic_STAT_SRCS} lsquic_versions_to_string.c)
SET(lsquic_STAT_SRCS ${lsquic_STAT_SRCS} ls-sfparser.c)
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DXXH_HEADER_NAME=\\\"lsquic_xxhash.h\\\"")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DLSQPACK_ENC_LOGGER_HEADER=\\\"lsquic_qpack_enc_logger.h\\\"")

View File

@ -5,85 +5,91 @@ AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src -I$(top_srcdir)/src/ls
liblsquic_a_METASOURCES = AUTO
liblsquic_a_SOURCES = ls-qpack/lsqpack.c \
lsquic_adaptive_cc.c \
lsquic_alarmset.c \
lsquic_arr.c \
lsquic_attq.c \
lsquic_bbr.c \
lsquic_bw_sampler.c \
lsquic_cfcw.c \
lsquic_chsk_stream.c \
lsquic_conn.c \
lsquic_crand.c \
lsquic_crt_compress.c \
lsquic_crypto.c \
lsquic_cubic.c \
lsquic_di_error.c \
lsquic_di_hash.c \
lsquic_di_nocopy.c \
lsquic_enc_sess_common.c \
lsquic_enc_sess_ietf.c \
lsquic_eng_hist.c \
lsquic_engine.c \
lsquic_ev_log.c \
lsquic_frab_list.c \
lsquic_frame_common.c \
lsquic_frame_reader.c \
lsquic_frame_writer.c \
lsquic_full_conn.c \
lsquic_full_conn_ietf.c \
lsquic_global.c \
lsquic_handshake.c \
lsquic_hash.c \
lsquic_hcsi_reader.c \
lsquic_hcso_writer.c \
lsquic_headers_stream.c \
lsquic_hkdf.c \
lsquic_hspack_valid.c \
lsquic_http1x_if.c \
lsquic_logger.c \
lsquic_malo.c \
lsquic_min_heap.c \
lsquic_mini_conn.c \
lsquic_mini_conn_ietf.c \
lsquic_minmax.c \
lsquic_mm.c \
lsquic_pacer.c \
lsquic_packet_common.c \
lsquic_packet_gquic.c \
lsquic_packet_in.c \
lsquic_packet_out.c \
lsquic_packet_resize.c \
lsquic_packints.c \
lsquic_parse_Q046.c \
lsquic_parse_Q050.c \
lsquic_parse_common.c \
lsquic_parse_gquic_be.c \
lsquic_parse_gquic_common.c \
lsquic_parse_ietf_v1.c \
lsquic_parse_iquic_common.c \
lsquic_pr_queue.c \
lsquic_purga.c \
lsquic_qdec_hdl.c \
lsquic_qenc_hdl.c \
lsquic_qlog.c \
lsquic_rtt.c \
lsquic_send_ctl.c \
lsquic_senhist.c \
lsquic_set.c \
lsquic_sfcw.c \
lsquic_shsk_stream.c \
lsquic_spi.c \
lsquic_stock_shi.c \
lsquic_str.c \
lsquic_stream.c \
lsquic_tokgen.c \
lsquic_trans_params.c \
lsquic_util.c \
lsquic_varint.c \
lsquic_version.c \
lsquic_versions_to_string.c
liblsquic_a_SOURCES = \
ls-qpack/lsqpack.c \
lsquic_adaptive_cc.c \
lsquic_alarmset.c \
lsquic_arr.c \
lsquic_attq.c \
lsquic_bbr.c \
lsquic_bw_sampler.c \
lsquic_cfcw.c \
lsquic_chsk_stream.c \
lsquic_conn.c \
lsquic_crand.c \
lsquic_crt_compress.c \
lsquic_crypto.c \
lsquic_cubic.c \
lsquic_di_error.c \
lsquic_di_hash.c \
lsquic_di_nocopy.c \
lsquic_enc_sess_common.c \
lsquic_enc_sess_ietf.c \
lsquic_eng_hist.c \
lsquic_engine.c \
lsquic_ev_log.c \
lsquic_frab_list.c \
lsquic_frame_common.c \
lsquic_frame_reader.c \
lsquic_frame_writer.c \
lsquic_full_conn.c \
lsquic_full_conn_ietf.c \
lsquic_global.c \
lsquic_handshake.c \
lsquic_hash.c \
lsquic_hcsi_reader.c \
lsquic_hcso_writer.c \
lsquic_headers_stream.c \
lsquic_hkdf.c \
lsquic_hpi.c \
lsquic_hspack_valid.c \
lsquic_http.c \
lsquic_http1x_if.c \
lsquic_logger.c \
lsquic_malo.c \
lsquic_min_heap.c \
lsquic_mini_conn.c \
lsquic_mini_conn_ietf.c \
lsquic_minmax.c \
lsquic_mm.c \
lsquic_pacer.c \
lsquic_packet_common.c \
lsquic_packet_gquic.c \
lsquic_packet_in.c \
lsquic_packet_out.c \
lsquic_packet_resize.c \
lsquic_parse_Q046.c \
lsquic_parse_Q050.c \
lsquic_parse_common.c \
lsquic_parse_gquic_be.c \
lsquic_parse_gquic_common.c \
lsquic_parse_ietf_v1.c \
lsquic_parse_iquic_common.c \
lsquic_pr_queue.c \
lsquic_purga.c \
lsquic_qdec_hdl.c \
lsquic_qenc_hdl.c \
lsquic_qlog.c \
lsquic_rechist.c \
lsquic_rtt.c \
lsquic_send_ctl.c \
lsquic_senhist.c \
lsquic_set.c \
lsquic_sfcw.c \
lsquic_shsk_stream.c \
lsquic_spi.c \
lsquic_stock_shi.c \
lsquic_str.c \
lsquic_stream.c \
lsquic_tokgen.c \
lsquic_trans_params.c \
lsquic_trechist.c \
lsquic_util.c \
lsquic_varint.c \
lsquic_version.c \
lsquic_xxhash.c \
lsquic_versions_to_string.c \
ls-sfparser.c
lsquic_versions_to_string.c: gen-verstrs.pl
$(top_srcdir)/src/liblsquic/gen-verstrs.pl $(top_srcdir)/include/lsquic.h lsquic_versions_to_string.c

3173
src/liblsquic/ls-sfparser.c Normal file

File diff suppressed because it is too large Load Diff

129
src/liblsquic/ls-sfparser.h Normal file
View File

@ -0,0 +1,129 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
MIT License
Copyright (c) 2020 LiteSpeed Technologies Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/* The LiteSpeed Structured Fields Parser parses structured fields decribed in
* https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-19
*
* It provides a simple streaming interface which allows the user to process
* structured fields in any manner.
*/
#ifndef LS_SFPARSER_H
#define LS_SFPARSER_H 1
enum ls_sf_dt
{ /* LS SF DT: LiteSpeed Structured Field Data Type */
LS_SF_DT_INTEGER,
LS_SF_DT_DECIMAL,
/* Name only applies to dictionary names. They may repeat: the
* parser does not drop duplicates.
*/
LS_SF_DT_NAME,
/* Parameter name may apply to any applicable preceding Item or
* Inner List.
*/
LS_SF_DT_PARAM_NAME,
/* The returned string does not include enclosing double quotes. */
LS_SF_DT_STRING,
LS_SF_DT_TOKEN,
/* The byte sequence is not base64-decoded; it is up to the caller
* to do so. The returned string does not include the enclosing
* colons.
*/
LS_SF_DT_BYTESEQ,
/* Note that true boolean values are serialized *without* the values.
* The parser makes one up and passes a pointer to its internal buffer.
*/
LS_SF_DT_BOOLEAN,
/* The Inner List has a beginning and an end. The returned strings
* are opening and closing parentheses.
*/
LS_SF_DT_INNER_LIST_BEGIN,
LS_SF_DT_INNER_LIST_END,
};
enum ls_sf_tlt
{ /* LS SF TLT: LiteSpeed Structured Field Top-Level Type */
LS_SF_TLT_DICTIONARY,
LS_SF_TLT_LIST,
LS_SF_TLT_ITEM,
};
/* Return 0 if parsed correctly, -1 on error, -2 if ran out of memory. */
int
ls_sf_parse (
/* Expected type of top-level input. This tells the parser how to
* parse the input.
*/
enum ls_sf_tlt,
/* Input; does not have to be NUL-terminated: */
const char *input, size_t input_sz,
/* Callback function to call each time a token is parsed. A non-zero
* return value indicates that parsing should stop.
*/
int (*callback)(
/* The first argument to the callback is user-specified additional
* data.
*/
void *user_data,
/* The second argument is the data type. */
enum ls_sf_dt,
/* The third and fourth arguments are NUL-terminated string and
* its length, respectively. The string can be modified, because
* the parser makes a copy.
*/
char *str, size_t len,
/* Offset to the token in the input buffer. In the special case
* of an implicit boolean value, this value is negative: this is
* because this value is not present in the input buffer.
*/
int off),
/* Additional data to pass to the callback: */
void *user_data,
/* Allocate memory from this memory buffer. If set to NULL, regular
* system memory allocator will be used.
*/
char *mem_buf, size_t mem_buf_sz);
/* Convenience array with type names. */
extern const char *const ls_sf_dt2str[];
#endif

View File

@ -205,14 +205,14 @@ lsquic_conn_push_stream (struct lsquic_conn *lconn, void *hset,
lsquic_conn_ctx_t *
lsquic_conn_get_ctx (const struct lsquic_conn *lconn)
{
return lconn->conn_ctx;
return lconn->cn_conn_ctx;
}
void
lsquic_conn_set_ctx (struct lsquic_conn *lconn, lsquic_conn_ctx_t *ctx)
{
lconn->conn_ctx = ctx;
lconn->cn_conn_ctx = ctx;
}

View File

@ -312,7 +312,6 @@ struct conn_cid_elem
struct lsquic_conn
{
void *cn_enc_session;
lsquic_conn_ctx_t *conn_ctx;
const struct enc_session_funcs_common
*cn_esf_c;
union {
@ -334,6 +333,7 @@ struct lsquic_conn
lsquic_time_t cn_last_sent;
lsquic_time_t cn_last_ticked;
struct conn_cid_elem *cn_cces; /* At least one is available */
lsquic_conn_ctx_t *cn_conn_ctx;
enum lsquic_conn_flags cn_flags;
enum lsquic_version cn_version:8;
unsigned char cn_cces_mask; /* Those that are set */

View File

@ -43,6 +43,7 @@ struct lsquic_conn_public {
struct {
struct qpack_enc_hdl *qeh;
struct qpack_dec_hdl *qdh;
struct hcso_writer *hcso;
struct lsquic_hash *promises;
} ietf;
} u;

View File

@ -438,10 +438,10 @@ void serialize_cert_entries(uint8_t* out, int *out_len, cert_entry_t *entries,
int
lsquic_get_certs_count(lsquic_str_t *compressed_crt_buf)
lsquic_get_certs_count (const struct compressed_cert *ccert)
{
char *in = lsquic_str_buf(compressed_crt_buf);
char *in_end = in + lsquic_str_len(compressed_crt_buf);
const unsigned char *in = ccert->buf;
const unsigned char *in_end = in + ccert->len;
size_t idx = 0;
uint8_t type_byte;
@ -578,14 +578,11 @@ static int parse_entries(const unsigned char **in_out, const unsigned char *cons
}
/* return 0 for OK */
int
struct compressed_cert *
lsquic_compress_certs (lsquic_str_t **certs, size_t certs_count,
lsquic_str_t *client_common_set_hashes,
lsquic_str_t *client_cached_cert_hashes,
lsquic_str_t *result)
lsquic_str_t *client_cached_cert_hashes)
{
int rv;
size_t i;
size_t uncompressed_size = 0, compressed_size = 0 ;
z_stream z;
@ -595,10 +592,12 @@ lsquic_compress_certs (lsquic_str_t **certs, size_t certs_count,
uint8_t* out;
uint32_t tmp_size_32;
cert_entry_t *entries;
struct compressed_cert *ccert;
ccert = NULL;
entries = malloc(sizeof(cert_entry_t) * certs_count);
if (!entries)
return -1;
return NULL;
dict = lsquic_str_new(NULL, 0);
if (!dict)
@ -631,16 +630,18 @@ lsquic_compress_certs (lsquic_str_t **certs, size_t certs_count,
entries_size = get_entries_size(entries, certs_count);
result_length = entries_size + (uncompressed_size > 0 ? 4 : 0) +
compressed_size;
lsquic_str_prealloc(result, result_length);
ccert = malloc(sizeof(*ccert) + result_length);
if (!ccert)
goto err;
ccert->refcnt = 0;
out = (unsigned char *)lsquic_str_buf(result);
out = ccert->buf;
serialize_cert_entries(out, &out_len, entries, certs_count);
out += entries_size;
if (uncompressed_size == 0)
{
lsquic_str_setlen(result, entries_size);
rv = 0;
ccert->len = entries_size;
goto cleanup;
}
@ -671,9 +672,7 @@ lsquic_compress_certs (lsquic_str_t **certs, size_t certs_count,
if (Z_STREAM_END != deflate(&z, Z_FINISH))
goto err;
rv = 0;
result_length -= z.avail_out;
lsquic_str_setlen(result, result_length);
ccert->len = result_length - z.avail_out;
cleanup:
free(entries);
@ -681,10 +680,12 @@ lsquic_compress_certs (lsquic_str_t **certs, size_t certs_count,
lsquic_str_delete(dict);
if (uncompressed_size)
deflateEnd(&z);
return rv;
return ccert;
err:
rv = -1;
if (ccert)
free(ccert);
ccert = NULL;
goto cleanup;
}

View File

@ -42,12 +42,22 @@ struct lsquic_str * lsquic_get_common_certs_hash();
int lsquic_get_common_cert(uint64_t hash, uint32_t index, struct lsquic_str *buf);
int lsquic_compress_certs(struct lsquic_str **certs, size_t certs_count,
struct lsquic_str *client_common_set_hashes,
struct lsquic_str *client_cached_cert_hashes,
struct lsquic_str *result);
struct compressed_cert
{
size_t len;
unsigned refcnt;
unsigned char buf[0]; /* len bytes */
};
/* Returns newly allocated structure or NULL on error */
struct compressed_cert *
lsquic_compress_certs(struct lsquic_str **certs, size_t certs_count,
struct lsquic_str *client_common_set_hashes,
struct lsquic_str *client_cached_cert_hashes);
int
lsquic_get_certs_count (const struct compressed_cert *);
int lsquic_get_certs_count(struct lsquic_str *compressed_crt_buf);
int lsquic_decompress_certs(const unsigned char *in, const unsigned char *in_end,
struct lsquic_str *cached_certs, size_t cached_certs_count,
struct lsquic_str **out_certs,

View File

@ -240,6 +240,9 @@ struct enc_sess_iquic
ESI_RSCID = 1 << 14,
ESI_ISCID = 1 << 15,
ESI_RETRY = 1 << 16, /* Connection was retried */
ESI_MAX_PACKNO_INIT = 1 << 17,
ESI_MAX_PACKNO_HSK = ESI_MAX_PACKNO_INIT << PNS_HSK,
ESI_MAX_PACKNO_APP = ESI_MAX_PACKNO_INIT << PNS_APP,
} esi_flags;
enum enc_level esi_last_w;
unsigned esi_trasec_sz;
@ -475,7 +478,17 @@ strip_hp (struct enc_sess_iquic *enc_sess,
shift += 8;
}
pns = lsquic_enclev2pns[hp->hp_enc_level];
return decode_packno(enc_sess->esi_max_packno[pns], packno, shift);
if (enc_sess->esi_flags & (ESI_MAX_PACKNO_INIT << pns))
{
LSQ_DEBUG("pre-decode packno: %"PRIu64, packno);
return decode_packno(enc_sess->esi_max_packno[pns], packno, shift);
}
else
{
LSQ_DEBUG("first packet in %s, packno: %"PRIu64, lsquic_pns2str[pns],
packno);
return packno;
}
}
@ -1976,7 +1989,7 @@ iquic_esf_encrypt_packet (enc_session_t *enc_session_p,
dst_sz = lconn->cn_pf->pf_packout_size(lconn, packet_out);
ipv6 = NP_IS_IPv6(packet_out->po_path);
dst = enpub->enp_pmi->pmi_allocate(enpub->enp_pmi_ctx,
packet_out->po_path->np_peer_ctx, lconn->conn_ctx, dst_sz, ipv6);
packet_out->po_path->np_peer_ctx, lconn->cn_conn_ctx, dst_sz, ipv6);
if (!dst)
{
LSQ_DEBUG("could not allocate memory for outgoing packet of size %zd",
@ -2307,8 +2320,10 @@ iquic_esf_decrypt_packet (enc_session_t *enc_session_p,
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "decrypted packet %"PRIu64,
packet_in->pi_packno);
pns = lsquic_enclev2pns[enc_level];
if (packet_in->pi_packno > enc_sess->esi_max_packno[pns])
if (packet_in->pi_packno > enc_sess->esi_max_packno[pns]
|| !(enc_sess->esi_flags & (ESI_MAX_PACKNO_INIT << pns)))
enc_sess->esi_max_packno[pns] = packet_in->pi_packno;
enc_sess->esi_flags |= ESI_MAX_PACKNO_INIT << pns;
if (is_valid_packno(pair->ykp_thresh)
&& packet_in->pi_packno > pair->ykp_thresh)
pair->ykp_thresh = packet_in->pi_packno;

View File

@ -76,6 +76,7 @@
#include "lsquic_version.h"
#include "lsquic_pr_queue.h"
#include "lsquic_mini_conn.h"
#include "lsquic_trechist.h"
#include "lsquic_mini_conn_ietf.h"
#include "lsquic_stock_shi.h"
#include "lsquic_purga.h"
@ -364,6 +365,7 @@ lsquic_engine_init_settings (struct lsquic_engine_settings *settings,
settings->es_cc_algo = LSQUIC_DF_CC_ALGO;
settings->es_cc_rtt_thresh = LSQUIC_DF_CC_RTT_THRESH;
settings->es_optimistic_nat = LSQUIC_DF_OPTIMISTIC_NAT;
settings->es_ext_http_prio = LSQUIC_DF_EXT_HTTP_PRIO;
}
@ -1969,8 +1971,8 @@ copy_packet (struct lsquic_engine *engine, struct lsquic_conn *conn,
}
packet_out->po_enc_data = engine->pub.enp_pmi->pmi_allocate(
engine->pub.enp_pmi_ctx, packet_out->po_path->np_peer_ctx, conn->conn_ctx,
packet_out->po_data_sz, ipv6);
engine->pub.enp_pmi_ctx, packet_out->po_path->np_peer_ctx,
conn->cn_conn_ctx, packet_out->po_data_sz, ipv6);
if (!packet_out->po_enc_data)
{
LSQ_DEBUG("could not allocate memory for outgoing unencrypted packet "
@ -2477,7 +2479,7 @@ send_packets_out (struct lsquic_engine *engine,
batch->outs [n].peer_ctx = packet_out->po_path->np_peer_ctx;
batch->outs [n].local_sa = NP_LOCAL_SA(packet_out->po_path);
batch->outs [n].dest_sa = NP_PEER_SA(packet_out->po_path);
batch->outs [n].conn_ctx = conn->conn_ctx;
batch->outs [n].conn_ctx = conn->cn_conn_ctx;
batch->conns [n] = conn;
}
*packet = packet_out;

View File

@ -797,7 +797,7 @@ full_conn_ci_client_call_on_new (struct lsquic_conn *lconn)
{
struct full_conn *const conn = (struct full_conn *) lconn;
assert(conn->fc_flags & FC_CREATED_OK);
lconn->conn_ctx = conn->fc_stream_ifs[STREAM_IF_STD].stream_if
lconn->cn_conn_ctx = conn->fc_stream_ifs[STREAM_IF_STD].stream_if
->on_new_conn(conn->fc_stream_ifs[STREAM_IF_STD].stream_if_ctx, lconn);
}
@ -971,7 +971,7 @@ lsquic_gquic_full_conn_server_new (struct lsquic_engine_public *enpub,
lsquic_send_ctl_turn_nstp_on(&conn->fc_send_ctl);
}
LSQ_DEBUG("Calling on_new_conn callback");
lconn_full->conn_ctx = enpub->enp_stream_if->on_new_conn(
lconn_full->cn_conn_ctx = enpub->enp_stream_if->on_new_conn(
enpub->enp_stream_if_ctx, &conn->fc_conn);
/* Now that user code knows about this connection, process incoming
* packets, if any.
@ -2866,7 +2866,7 @@ process_streams_ready_to_send (struct full_conn *conn)
lsquic_spi_init(&spi, TAILQ_FIRST(&conn->fc_pub.sending_streams),
TAILQ_LAST(&conn->fc_pub.sending_streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_send_stream),
&conn->fc_conn, "send", NULL, NULL);
&conn->fc_pub, "send", NULL, NULL);
for (stream = lsquic_spi_first(&spi); stream;
stream = lsquic_spi_next(&spi))
@ -3054,7 +3054,7 @@ process_streams_read_events (struct full_conn *conn)
lsquic_spi_init(&spi, TAILQ_FIRST(&conn->fc_pub.read_streams),
TAILQ_LAST(&conn->fc_pub.read_streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_read_stream),
&conn->fc_conn, "read", NULL, NULL);
&conn->fc_pub, "read", NULL, NULL);
needs_service = 0;
for (stream = lsquic_spi_first(&spi); stream;
@ -3081,7 +3081,7 @@ process_streams_read_events (struct full_conn *conn)
lsquic_spi_init(&spi, TAILQ_FIRST(&conn->fc_pub.read_streams),
TAILQ_LAST(&conn->fc_pub.read_streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_read_stream),
&conn->fc_conn, "read-new",
&conn->fc_pub, "read-new",
filter_out_old_streams, &fctx);
for (stream = lsquic_spi_first(&spi); stream;
stream = lsquic_spi_next(&spi))
@ -3113,7 +3113,7 @@ process_streams_write_events (struct full_conn *conn, int high_prio)
lsquic_spi_init(&spi, TAILQ_FIRST(&conn->fc_pub.write_streams),
TAILQ_LAST(&conn->fc_pub.write_streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_write_stream),
&conn->fc_conn,
&conn->fc_pub,
high_prio ? "write-high" : "write-low", NULL, NULL);
if (high_prio)

View File

@ -64,14 +64,18 @@
#include "lsquic_http1x_if.h"
#include "lsquic_qenc_hdl.h"
#include "lsquic_qdec_hdl.h"
#include "lsquic_trechist.h"
#include "lsquic_mini_conn_ietf.h"
#include "lsquic_tokgen.h"
#include "lsquic_full_conn.h"
#include "lsquic_spi.h"
#include "lsquic_min_heap.h"
#include "lsquic_hpi.h"
#include "lsquic_ietf.h"
#include "lsquic_push_promise.h"
#include "lsquic_headers.h"
#include "lsquic_crand.h"
#include "ls-sfparser.h"
#define LSQUIC_LOGGER_MODULE LSQLM_CONN
#define LSQUIC_LOG_CONN_ID ietf_full_conn_ci_get_log_cid(&conn->ifc_conn)
@ -338,6 +342,53 @@ struct inc_ack_stats /* Incoming ACK stats */
};
union prio_iter
{
struct stream_prio_iter spi;
struct http_prio_iter hpi;
};
struct prio_iter_if
{
void (*pii_init) (void *, struct lsquic_stream *first,
struct lsquic_stream *last, uintptr_t next_ptr_offset,
struct lsquic_conn_public *, const char *name,
int (*filter)(void *filter_ctx, struct lsquic_stream *),
void *filter_ctx);
struct lsquic_stream * (*pii_first) (void *);
struct lsquic_stream * (*pii_next) (void *);
void (*pii_drop_non_high) (void *);
void (*pii_drop_high) (void *);
void (*pii_cleanup) (void *);
};
static const struct prio_iter_if orig_prio_iter_if = {
lsquic_spi_init,
lsquic_spi_first,
lsquic_spi_next,
lsquic_spi_drop_non_high,
lsquic_spi_drop_high,
lsquic_spi_cleanup,
};
static const struct prio_iter_if ext_prio_iter_if = {
lsquic_hpi_init,
lsquic_hpi_first,
lsquic_hpi_next,
lsquic_hpi_drop_non_high,
lsquic_hpi_drop_high,
lsquic_hpi_cleanup,
};
struct ietf_full_conn
{
struct lsquic_conn ifc_conn;
@ -371,6 +422,7 @@ struct ietf_full_conn
const struct lsquic_stream_if
*ifc_stream_if;
void *ifc_stream_ctx;
const struct prio_iter_if *ifc_pii;
char *ifc_errmsg;
struct lsquic_engine_public
*ifc_enpub;
@ -454,6 +506,7 @@ struct ietf_full_conn
} ifc_u;
lsquic_time_t ifc_idle_to;
lsquic_time_t ifc_ping_period;
struct lsquic_hash *ifc_bpus;
uint64_t ifc_last_max_data_off_sent;
unsigned short ifc_min_dg_sz,
ifc_max_dg_sz;
@ -994,7 +1047,11 @@ create_bidi_stream_out (struct ietf_full_conn *conn)
if (conn->ifc_enpub->enp_settings.es_rw_once)
flags |= SCF_DISP_RW_ONCE;
if (conn->ifc_flags & IFC_HTTP)
{
flags |= SCF_HTTP;
if (conn->ifc_pii == &ext_prio_iter_if)
flags |= SCF_HTTP_PRIO;
}
stream_id = generate_stream_id(conn, SD_BIDI);
stream = lsquic_stream_new(stream_id, &conn->ifc_pub,
@ -1190,6 +1247,7 @@ ietf_full_conn_init (struct ietf_full_conn *conn,
return -1;
conn->ifc_pub.u.ietf.qeh = &conn->ifc_qeh;
conn->ifc_pub.u.ietf.qdh = &conn->ifc_qdh;
conn->ifc_pub.u.ietf.hcso = &conn->ifc_hcso;
conn->ifc_peer_hq_settings.header_table_size = HQ_DF_QPACK_MAX_TABLE_CAPACITY;
conn->ifc_peer_hq_settings.qpack_blocked_streams = HQ_DF_QPACK_BLOCKED_STREAMS;
@ -1209,6 +1267,10 @@ ietf_full_conn_init (struct ietf_full_conn *conn,
conn->ifc_max_retx_since_last_ack = MAX_RETR_PACKETS_SINCE_LAST_ACK;
if (conn->ifc_settings->es_noprogress_timeout)
conn->ifc_mflags |= MF_NOPROG_TIMEOUT;
if (conn->ifc_settings->es_ext_http_prio)
conn->ifc_pii = &ext_prio_iter_if;
else
conn->ifc_pii = &orig_prio_iter_if;
return 0;
}
@ -1368,9 +1430,9 @@ lsquic_ietf_full_conn_server_new (struct lsquic_engine_public *enpub,
int have_outgoing_ack;
lsquic_packno_t next_packno;
lsquic_time_t now;
packno_set_t set;
enum packnum_space pns;
unsigned i;
struct ietf_mini_rechist mini_rechist;
conn = calloc(1, sizeof(*conn));
if (!conn)
@ -1472,14 +1534,14 @@ lsquic_ietf_full_conn_server_new (struct lsquic_engine_public *enpub,
conn->ifc_send_ctl.sc_cur_packno = imc->imc_next_packno - 1;
lsquic_send_ctl_begin_optack_detection(&conn->ifc_send_ctl);
for (pns = 0; pns < N_PNS; ++pns)
for (pns = 0; pns < IMICO_N_PNS; ++pns)
{
for (set = imc->imc_recvd_packnos[pns], i = 0;
set && i < MAX_PACKETS; set &= ~(1ULL << i), ++i)
if (set & (1ULL << i))
(void) lsquic_rechist_received(&conn->ifc_rechist[pns], i, 0);
if (i)
conn->ifc_rechist[pns].rh_largest_acked_received
lsquic_imico_rechist_init(&mini_rechist, imc, pns);
if (0 != lsquic_rechist_copy_ranges(&conn->ifc_rechist[pns],
&mini_rechist, lsquic_imico_rechist_first,
lsquic_imico_rechist_next))
goto err2;
conn->ifc_rechist[pns].rh_largest_acked_received
= imc->imc_largest_recvd[pns];
}
@ -1551,7 +1613,7 @@ lsquic_ietf_full_conn_server_new (struct lsquic_engine_public *enpub,
conn->ifc_last_live_update = now;
LSQ_DEBUG("Calling on_new_conn callback");
conn->ifc_conn.conn_ctx = conn->ifc_enpub->enp_stream_if->on_new_conn(
conn->ifc_conn.cn_conn_ctx = conn->ifc_enpub->enp_stream_if->on_new_conn(
conn->ifc_enpub->enp_stream_if_ctx, &conn->ifc_conn);
if (0 != handshake_ok(&conn->ifc_conn))
@ -2708,19 +2770,21 @@ static void
process_streams_ready_to_send (struct ietf_full_conn *conn)
{
struct lsquic_stream *stream;
struct stream_prio_iter spi;
union prio_iter pi;
assert(!TAILQ_EMPTY(&conn->ifc_pub.sending_streams));
lsquic_spi_init(&spi, TAILQ_FIRST(&conn->ifc_pub.sending_streams),
conn->ifc_pii->pii_init(&pi, TAILQ_FIRST(&conn->ifc_pub.sending_streams),
TAILQ_LAST(&conn->ifc_pub.sending_streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_send_stream),
&conn->ifc_conn, "send", NULL, NULL);
&conn->ifc_pub, "send", NULL, NULL);
for (stream = lsquic_spi_first(&spi); stream;
stream = lsquic_spi_next(&spi))
for (stream = conn->ifc_pii->pii_first(&pi); stream;
stream = conn->ifc_pii->pii_next(&pi))
if (!process_stream_ready_to_send(conn, stream))
break;
conn->ifc_pii->pii_cleanup(&pi);
}
@ -2760,7 +2824,7 @@ ietf_full_conn_ci_client_call_on_new (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
assert(conn->ifc_flags & IFC_CREATED_OK);
lconn->conn_ctx = conn->ifc_enpub->enp_stream_if->on_new_conn(
lconn->cn_conn_ctx = conn->ifc_enpub->enp_stream_if->on_new_conn(
conn->ifc_enpub->enp_stream_if_ctx, lconn);
}
@ -2976,6 +3040,13 @@ ietf_full_conn_ci_destroy (struct lsquic_conn *lconn)
}
for (i = 0; i < N_SITS; ++i)
lsquic_set64_cleanup(&conn->ifc_closed_stream_ids[i]);
if (conn->ifc_bpus)
{
for (el = lsquic_hash_first(conn->ifc_bpus); el;
el = lsquic_hash_next(conn->ifc_bpus))
free(lsquic_hashelem_getdata(el));
lsquic_hash_destroy(conn->ifc_bpus);
}
lsquic_hash_destroy(conn->ifc_pub.all_streams);
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "full connection destroyed");
free(conn->ifc_errmsg);
@ -3950,7 +4021,7 @@ process_streams_read_events (struct ietf_full_conn *conn)
struct lsquic_stream *stream;
int iters;
enum stream_q_flags q_flags, needs_service;
struct stream_prio_iter spi;
union prio_iter pi;
static const char *const labels[2] = { "read-0", "read-1", };
if (TAILQ_EMPTY(&conn->ifc_pub.read_streams))
@ -3960,19 +4031,20 @@ process_streams_read_events (struct ietf_full_conn *conn)
iters = 0;
do
{
lsquic_spi_init(&spi, TAILQ_FIRST(&conn->ifc_pub.read_streams),
conn->ifc_pii->pii_init(&pi, TAILQ_FIRST(&conn->ifc_pub.read_streams),
TAILQ_LAST(&conn->ifc_pub.read_streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_read_stream),
&conn->ifc_conn, labels[iters], NULL, NULL);
&conn->ifc_pub, labels[iters], NULL, NULL);
needs_service = 0;
for (stream = lsquic_spi_first(&spi); stream;
stream = lsquic_spi_next(&spi))
for (stream = conn->ifc_pii->pii_first(&pi); stream;
stream = conn->ifc_pii->pii_next(&pi))
{
q_flags = stream->sm_qflags & SMQF_SERVICE_FLAGS;
lsquic_stream_dispatch_read_events(stream);
needs_service |= q_flags ^ (stream->sm_qflags & SMQF_SERVICE_FLAGS);
}
conn->ifc_pii->pii_cleanup(&pi);
if (needs_service)
service_streams(conn);
@ -4045,23 +4117,25 @@ static void
process_streams_write_events (struct ietf_full_conn *conn, int high_prio)
{
struct lsquic_stream *stream;
struct stream_prio_iter spi;
union prio_iter pi;
lsquic_spi_init(&spi, TAILQ_FIRST(&conn->ifc_pub.write_streams),
conn->ifc_pii->pii_init(&pi, TAILQ_FIRST(&conn->ifc_pub.write_streams),
TAILQ_LAST(&conn->ifc_pub.write_streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_write_stream),
&conn->ifc_conn,
&conn->ifc_pub,
high_prio ? "write-high" : "write-low", NULL, NULL);
if (high_prio)
lsquic_spi_drop_non_high(&spi);
conn->ifc_pii->pii_drop_non_high(&pi);
else
lsquic_spi_drop_high(&spi);
conn->ifc_pii->pii_drop_high(&pi);
for (stream = lsquic_spi_first(&spi); stream && write_is_possible(conn);
stream = lsquic_spi_next(&spi))
for (stream = conn->ifc_pii->pii_first(&pi);
stream && write_is_possible(conn);
stream = conn->ifc_pii->pii_next(&pi))
if (stream->sm_qflags & SMQF_WRITE_Q_FLAGS)
lsquic_stream_dispatch_write_events(stream);
conn->ifc_pii->pii_cleanup(&pi);
maybe_conn_flush_special_streams(conn);
}
@ -4844,12 +4918,22 @@ maybe_schedule_ss_for_stream (struct ietf_full_conn *conn,
}
struct buffered_priority_update
{
struct lsquic_hash_elem hash_el;
lsquic_stream_id_t stream_id;
struct lsquic_ext_http_prio ehp;
};
/* This function is called to create incoming streams */
static struct lsquic_stream *
new_stream (struct ietf_full_conn *conn, lsquic_stream_id_t stream_id,
enum stream_ctor_flags flags)
{
const struct lsquic_stream_if *iface;
struct buffered_priority_update *bpu;
struct lsquic_hash_elem *el;
void *stream_ctx;
struct lsquic_stream *stream;
unsigned initial_window;
@ -4876,7 +4960,11 @@ new_stream (struct ietf_full_conn *conn, lsquic_stream_id_t stream_id,
if (conn->ifc_enpub->enp_settings.es_rw_once)
flags |= SCF_DISP_RW_ONCE;
if (conn->ifc_flags & IFC_HTTP)
{
flags |= SCF_HTTP;
if (conn->ifc_pii == &ext_prio_iter_if)
flags |= SCF_HTTP_PRIO;
}
}
if (((stream_id >> SD_SHIFT) & 1) == SD_UNI)
@ -4891,6 +4979,20 @@ new_stream (struct ietf_full_conn *conn, lsquic_stream_id_t stream_id,
conn->ifc_cfg.max_stream_send, flags);
if (stream)
{
if (conn->ifc_bpus)
{
el = lsquic_hash_find(conn->ifc_bpus, &stream->id,
sizeof(stream->id));
if (el)
{
LSQ_DEBUG("apply buffered PRIORITY_UPDATE to stream %"PRIu64,
stream->id);
lsquic_hash_erase(conn->ifc_bpus, el);
bpu = lsquic_hashelem_getdata(el);
(void) lsquic_stream_set_http_prio(stream, &bpu->ehp);
free(bpu);
}
}
if (lsquic_hash_insert(conn->ifc_pub.all_streams, &stream->id,
sizeof(stream->id), stream, &stream->sm_hash_el))
{
@ -8522,6 +8624,174 @@ on_goaway_server (void *ctx, uint64_t max_push_id)
}
static void
on_priority_update_client (void *ctx, enum hq_frame_type frame_type,
uint64_t id, const char *pfv, size_t pfv_sz)
{
struct ietf_full_conn *const conn = ctx;
if (conn->ifc_pii == &ext_prio_iter_if)
ABORT_QUIETLY(1, HEC_FRAME_UNEXPECTED, "Frame type %u is not "
"expected to be sent by the server", (unsigned) frame_type);
/* else ignore */
}
/* This should not happen often, so do not bother to optimize memory. */
static int
buffer_priority_update (struct ietf_full_conn *conn,
lsquic_stream_id_t stream_id, const struct lsquic_ext_http_prio *ehp)
{
struct buffered_priority_update *bpu;
struct lsquic_hash_elem *el;
if (!conn->ifc_bpus)
{
conn->ifc_bpus = lsquic_hash_create();
if (!conn->ifc_bpus)
{
ABORT_ERROR("cannot allocate BPUs hash");
return -1;
}
goto insert_new;
}
el = lsquic_hash_find(conn->ifc_bpus, &stream_id, sizeof(stream_id));
if (el)
{
bpu = lsquic_hashelem_getdata(el);
bpu->ehp = *ehp;
return 0;
}
insert_new:
bpu = malloc(sizeof(*bpu));
if (!bpu)
{
ABORT_ERROR("cannot allocate BPU");
return -1;
}
bpu->hash_el.qhe_flags = 0;
bpu->stream_id = stream_id;
bpu->ehp = *ehp;
if (!lsquic_hash_insert(conn->ifc_bpus, &bpu->stream_id,
sizeof(bpu->stream_id), bpu, &bpu->hash_el))
{
free(bpu);
ABORT_ERROR("cannot insert BPU");
return -1;
}
return 0;
}
static void
on_priority_update_server (void *ctx, enum hq_frame_type frame_type,
uint64_t id, const char *pfv, size_t pfv_sz)
{
struct ietf_full_conn *const conn = ctx;
struct lsquic_hash_elem *el;
struct push_promise *promise;
struct lsquic_stream *stream;
enum stream_id_type sit;
struct lsquic_ext_http_prio ehp;
if (conn->ifc_pii != &ext_prio_iter_if)
{
LSQ_DEBUG("Ignore PRIORITY_UPDATE frame");
return;
}
if (frame_type == HQFT_PRIORITY_UPDATE_STREAM)
{
sit = id & SIT_MASK;
if (sit != SIT_BIDI_CLIENT)
{
ABORT_QUIETLY(1, HEC_ID_ERROR, "PRIORITY_UPDATE for non-request "
"stream");
return;
}
if (id >= conn->ifc_max_allowed_stream_id[sit])
{
ABORT_QUIETLY(1, HEC_ID_ERROR, "PRIORITY_UPDATE for non-existing "
"stream %"PRIu64" exceeds allowed max of %"PRIu64,
id, conn->ifc_max_allowed_stream_id[sit]);
return;
}
stream = find_stream_by_id(conn, id);
if (!stream && conn_is_stream_closed(conn, id))
{
LSQ_DEBUG("stream %"PRIu64" closed, ignore PRIORITY_UPDATE", id);
return;
}
}
else
{
if (id >= conn->ifc_u.ser.ifser_next_push_id)
{
ABORT_QUIETLY(1, HEC_ID_ERROR, "received PRIORITY_UPDATE with "
"ID=%"PRIu64", which is greater than the maximum Push ID "
"ever generated by this connection", id);
return;
}
el = lsquic_hash_find(conn->ifc_pub.u.ietf.promises, &id, sizeof(id));
if (!el)
{
LSQ_DEBUG("push promise %"PRIu64" not found, ignore "
"PRIORITY_UPDATE", id);
return;
}
promise = lsquic_hashelem_getdata(el);
stream = promise->pp_pushed_stream;
assert(stream);
}
ehp = (struct lsquic_ext_http_prio) {
.urgency = LSQUIC_DEF_HTTP_URGENCY,
.incremental = LSQUIC_DEF_HTTP_INCREMENTAL,
};
if (pfv_sz)
{
switch (lsquic_http_parse_pfv(pfv, pfv_sz, NULL, &ehp,
(char *) conn->ifc_pub.mm->acki,
sizeof(*conn->ifc_pub.mm->acki)))
{
case 0:
LSQ_DEBUG("Parsed PFV `%.*s' correctly", (int) pfv_sz, pfv);
break;
case -2: /* Out of memory, ignore */
LSQ_INFO("Ignore PFV `%.*s': out of memory", (int) pfv_sz, pfv);
return;
default:
LSQ_INFO("connection error due to invalid PFV `%.*s'",
(int) pfv_sz, pfv);
/* From the draft (between versions 1 and 2):
" Failure to parse the Priority Field Value MUST be treated
" as a connection error of type FRAME_ENCODING_ERROR.
*/
ABORT_QUIETLY(1, HEC_FRAME_ERROR, "cannot parse Priority Field "
"Value in PRIORITY_UPDATE frame");
return;
}
}
else
{ /* Empty PFV means "use defaults" */ }
if (stream)
(void) lsquic_stream_set_http_prio(stream, &ehp);
else
{
assert(frame_type == HQFT_PRIORITY_UPDATE_STREAM);
if (0 == buffer_priority_update(conn, id, &ehp))
LSQ_INFO("buffered priority update for stream %"PRIu64"; "
"urgency: %hhu, incremental: %hhd", id, ehp.urgency,
ehp.incremental);
}
}
static void
on_unexpected_frame (void *ctx, uint64_t frame_type)
{
@ -8539,6 +8809,7 @@ static const struct hcsi_callbacks hcsi_callbacks_server_27 =
.on_setting = on_setting,
.on_goaway = on_goaway_server_27,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_server,
};
static const struct hcsi_callbacks hcsi_callbacks_client_27 =
@ -8549,6 +8820,7 @@ static const struct hcsi_callbacks hcsi_callbacks_client_27 =
.on_setting = on_setting,
.on_goaway = on_goaway_client_28 /* sic */,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_client,
};
@ -8560,6 +8832,7 @@ static const struct hcsi_callbacks hcsi_callbacks_server_28 =
.on_setting = on_setting,
.on_goaway = on_goaway_server /* sic */,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_server,
};
static const struct hcsi_callbacks hcsi_callbacks_client_28 =
@ -8570,6 +8843,7 @@ static const struct hcsi_callbacks hcsi_callbacks_client_28 =
.on_setting = on_setting,
.on_goaway = on_goaway_client_28,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_client,
};
@ -8581,6 +8855,7 @@ static const struct hcsi_callbacks hcsi_callbacks_server_29 =
.on_setting = on_setting,
.on_goaway = on_goaway_server,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_server,
};
static const struct hcsi_callbacks hcsi_callbacks_client_29 =
@ -8591,6 +8866,7 @@ static const struct hcsi_callbacks hcsi_callbacks_client_29 =
.on_setting = on_setting,
.on_goaway = on_goaway_client,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_client,
};

View File

@ -76,6 +76,8 @@ static struct conn_cid_elem dummy_cce;
static const struct lsquic_conn dummy_lsquic_conn = { .cn_cces = &dummy_cce, };
static const struct lsquic_conn *const lconn = &dummy_lsquic_conn;
static int s_ccrt_idx;
static const int s_log_seal_and_open;
static char s_str[0x1000];
@ -167,7 +169,7 @@ typedef struct hs_ctx_st
struct lsquic_str prof;
struct lsquic_str csct;
struct lsquic_str crt; /* compressed certs buffer */
struct compressed_cert *ccert;
struct lsquic_str scfg_pubs; /* Need to copy PUBS, as KEXS comes after it */
} hs_ctx_t;
@ -314,9 +316,6 @@ static void s_free_cert_hash_item(cert_item_t *item);
static cert_item_t* insert_cert(struct lsquic_engine_public *,
const unsigned char *key, size_t key_sz, const struct lsquic_str *crt);
static compress_cert_hash_item_t* find_compress_certs(struct lsquic_engine_public *, struct lsquic_str *domain);
static compress_cert_hash_item_t *make_compress_cert_hash_item(struct lsquic_str *domain, struct lsquic_str *crts_compress_buf);
#ifdef NDEBUG
static
enum hsk_failure_reason
@ -338,6 +337,8 @@ static uint64_t get_tag_value_i64(unsigned char *, int);
static void determine_keys(struct lsquic_enc_session *enc_session);
static void put_compressed_cert (struct compressed_cert *);
#if LSQUIC_KEEP_ENC_SESS_HISTORY
static void
@ -354,10 +355,26 @@ eshist_append (struct lsquic_enc_session *enc_session,
# define ESHIST_APPEND(sess, event) do { } while (0)
#endif
static void
free_compressed_cert (void *parent, void *ptr, CRYPTO_EX_DATA *ad,
int index, long argl, void *argp)
{
put_compressed_cert(ptr);
}
static int
lsquic_handshake_init(int flags)
{
lsquic_crypto_init();
if (flags & LSQUIC_GLOBAL_SERVER)
{
s_ccrt_idx = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
free_compressed_cert);
if (s_ccrt_idx < 0)
return -1;
}
return lsquic_crt_init();
}
@ -526,53 +543,6 @@ insert_cert (struct lsquic_engine_public *enpub, const unsigned char *key,
}
/* server */
static compress_cert_hash_item_t *
find_compress_certs (struct lsquic_engine_public *enpub, lsquic_str_t *domain)
{
struct lsquic_hash_elem *el;
if (!enpub->enp_compressed_server_certs)
return NULL;
el = lsquic_hash_find(enpub->enp_compressed_server_certs,
lsquic_str_cstr(domain), lsquic_str_len(domain));
if (el == NULL)
return NULL;
return lsquic_hashelem_getdata(el);
}
/* server */
static compress_cert_hash_item_t *
make_compress_cert_hash_item(lsquic_str_t *domain, lsquic_str_t *crts_compress_buf)
{
compress_cert_hash_item_t *item = calloc(1, sizeof(*item));
item->crts_compress_buf = lsquic_str_new(NULL, 0);
item->domain = lsquic_str_new(NULL, 0);
lsquic_str_copy(item->domain, domain);
lsquic_str_copy(item->crts_compress_buf, crts_compress_buf);
return item;
}
/* server */
static int
insert_compress_certs (struct lsquic_engine_public *enpub,
compress_cert_hash_item_t *item)
{
if (lsquic_hash_insert(enpub->enp_compressed_server_certs,
lsquic_str_cstr(item->domain),
lsquic_str_len(item->domain), item, &item->hash_el) == NULL)
{
return -1;
}
else
return 0;
}
enum rtt_deserialize_return_type
{
RTT_DESERIALIZE_OK = 0,
@ -928,6 +898,35 @@ lsquic_enc_session_reset_cid (enc_session_t *enc_session_p,
}
static void
put_compressed_cert (struct compressed_cert *ccert)
{
if (ccert)
{
assert(ccert->refcnt > 0);
--ccert->refcnt;
if (0 == ccert->refcnt)
free(ccert);
}
}
static struct compressed_cert *
new_compressed_cert (const unsigned char *buf, size_t len)
{
struct compressed_cert *ccert;
ccert = malloc(sizeof(*ccert) + len);
if (ccert)
{
ccert->refcnt = 1;
ccert->len = len;
memcpy(ccert->buf, buf, len);
}
return ccert;
}
static void
lsquic_enc_session_destroy (enc_session_t *enc_session_p)
{
@ -947,7 +946,8 @@ lsquic_enc_session_destroy (enc_session_t *enc_session_p)
lsquic_str_d(&hs_ctx->sno);
lsquic_str_d(&hs_ctx->prof);
lsquic_str_d(&hs_ctx->csct);
lsquic_str_d(&hs_ctx->crt);
put_compressed_cert(hs_ctx->ccert);
hs_ctx->ccert = NULL;
lsquic_str_d(&hs_ctx->uaid);
lsquic_str_d(&hs_ctx->scfg_pubs);
lsquic_str_d(&enc_session->chlo);
@ -1075,7 +1075,8 @@ static int parse_hs_data (struct lsquic_enc_session *enc_session, uint32_t tag,
break;
case QTAG_CRT:
lsquic_str_setto(&hs_ctx->crt, val, len);
put_compressed_cert(hs_ctx->ccert);
hs_ctx->ccert = new_compressed_cert(val, len);
break;
case QTAG_PUBS:
@ -1884,7 +1885,7 @@ get_valid_scfg (const struct lsquic_enc_session *enc_session,
static int
generate_crt (struct lsquic_enc_session *enc_session, int common_case)
generate_crt (struct lsquic_enc_session *enc_session)
{
int i, n, len, crt_num, rv = -1;
lsquic_str_t **crts;
@ -1893,6 +1894,7 @@ generate_crt (struct lsquic_enc_session *enc_session, int common_case)
STACK_OF(X509) *pXchain;
SSL_CTX *const ctx = enc_session->ssl_ctx;
hs_ctx_t *const hs_ctx = &enc_session->hs_ctx;
struct compressed_cert *ccert;
SSL_CTX_get0_chain_certs(ctx, &pXchain);
n = sk_X509_num(pXchain);
@ -1918,17 +1920,22 @@ generate_crt (struct lsquic_enc_session *enc_session, int common_case)
OPENSSL_free(out);
}
if (0 != lsquic_compress_certs(crts, crt_num, &hs_ctx->ccs, &hs_ctx->ccrt,
&hs_ctx->crt))
ccert = lsquic_compress_certs(crts, crt_num, &hs_ctx->ccs, &hs_ctx->ccrt);
if (!ccert)
goto cleanup;
if (common_case)
if (SSL_CTX_set_ex_data(ctx, s_ccrt_idx, ccert))
++ccert->refcnt;
else
{
if (0 != insert_compress_certs(enc_session->enpub,
make_compress_cert_hash_item(&hs_ctx->sni, &hs_ctx->crt)))
goto cleanup;
free(ccert);
ccert = NULL;
goto cleanup;
}
++ccert->refcnt;
hs_ctx->ccert = ccert;
/* We got here, set rv to 0: success */
rv = 0;
@ -1946,6 +1953,11 @@ static int
gen_rej1_data (struct lsquic_enc_session *enc_session, uint8_t *data,
size_t max_len, const struct sockaddr *ip, time_t t)
{
#ifndef WIN32
# define ERR(e_) do { return (e_); } while (0)
#else
# define ERR(e_) do { len = (e_); goto end; } while (0)
#endif
int len;
EVP_PKEY * rsa_priv_key;
SSL_CTX *ctx = enc_session->ssl_ctx;
@ -1954,10 +1966,6 @@ gen_rej1_data (struct lsquic_enc_session *enc_session, uint8_t *data,
hs_ctx_t *const hs_ctx = &enc_session->hs_ctx;
int scfg_len = enc_session->server_config->lsc_scfg->info.scfg_len;
uint8_t *scfg_data = enc_session->server_config->lsc_scfg->scfg;
char prof_buf[512];
size_t prof_len = 512;
compress_cert_hash_item_t* compress_certs_item;
int common_case;
size_t msg_len;
struct message_writer mw;
@ -1965,33 +1973,46 @@ gen_rej1_data (struct lsquic_enc_session *enc_session, uint8_t *data,
if (!rsa_priv_key)
return -1;
lsquic_str_d(&hs_ctx->crt);
size_t prof_len = (size_t) EVP_PKEY_size(rsa_priv_key);
#ifndef WIN32
char prof_buf[prof_len];
#else
prof_buf = _malloca(prof_len);
if (!prof_buf)
return -1;
#endif
/**
* Only cache hs_ctx->ccs is the hardcoded common certs and hs_ctx->ccrt is empty case
* This is the most common case
*/
common_case = lsquic_str_len(&hs_ctx->ccrt) == 0
&& lsquic_str_bcmp(&hs_ctx->ccs, lsquic_get_common_certs_hash()) == 0;
if (common_case)
compress_certs_item = find_compress_certs(enc_session->enpub, &hs_ctx->sni);
else
compress_certs_item = NULL;
if (compress_certs_item)
if (hs_ctx->ccert)
{
lsquic_str_d(&hs_ctx->crt);
lsquic_str_copy(&hs_ctx->crt, compress_certs_item->crts_compress_buf);
put_compressed_cert(hs_ctx->ccert);
hs_ctx->ccert = NULL;
}
hs_ctx->ccert = SSL_CTX_get_ex_data(ctx, s_ccrt_idx);
if (hs_ctx->ccert)
{
++hs_ctx->ccert->refcnt;
LSQ_DEBUG("use cached compressed cert");
}
else if (0 == generate_crt(enc_session))
LSQ_DEBUG("generated compressed cert");
else
generate_crt(enc_session, common_case);
{
LSQ_INFO("cannot could not generate compressed cert for");
ERR(-1);
}
LSQ_DEBUG("gQUIC rej1 data");
LSQ_DEBUG("gQUIC NOT enabled");
lsquic_gen_prof((const uint8_t *)lsquic_str_cstr(&enc_session->chlo),
const int s = lsquic_gen_prof((const uint8_t *)lsquic_str_cstr(&enc_session->chlo),
(size_t)lsquic_str_len(&enc_session->chlo),
scfg_data, scfg_len,
rsa_priv_key, (uint8_t *)prof_buf, &prof_len);
if (s != 0)
{
LSQ_INFO("could not generate server proof, code %d", s);
ERR(-1);
}
lsquic_str_setto(&hs_ctx->prof, prof_buf, prof_len);
@ -2008,10 +2029,11 @@ gen_rej1_data (struct lsquic_enc_session *enc_session, uint8_t *data,
MSG_LEN_ADD(msg_len, SNO_LENGTH);
MSG_LEN_ADD(msg_len, sizeof(settings->es_sttl));
MSG_LEN_ADD(msg_len, lsquic_str_len(&hs_ctx->prof));
MSG_LEN_ADD(msg_len, lsquic_str_len(&hs_ctx->crt));
if (hs_ctx->ccert)
MSG_LEN_ADD(msg_len, hs_ctx->ccert->len);
if (MSG_LEN_VAL(msg_len) > max_len)
return -1;
ERR(-1);
memcpy(enc_session->priv_key, enc_session->server_config->lsc_scfg->info.priv_key, 32);
@ -2040,13 +2062,19 @@ gen_rej1_data (struct lsquic_enc_session *enc_session, uint8_t *data,
MW_WRITE_BUFFER(&mw, QTAG_RREJ, &hs_ctx->rrej, sizeof(hs_ctx->rrej));
MW_WRITE_BUFFER(&mw, QTAG_STTL, &settings->es_sttl,
sizeof(settings->es_sttl));
MW_WRITE_LS_STR(&mw, QTAG_CRT, &hs_ctx->crt);
if (hs_ctx->ccert)
MW_WRITE_BUFFER(&mw, QTAG_CRT, hs_ctx->ccert->buf, hs_ctx->ccert->len);
MW_END(&mw);
assert(data + max_len >= MW_P(&mw));
len = MW_P(&mw) - data;
LSQ_DEBUG("gen_rej1_data called, return len %d.", len);
#ifdef WIN32
end:
_freea(prof_buf);
#endif
return len;
#undef ERR
}
@ -2316,10 +2344,11 @@ static int handle_chlo_reply_verify_prof(struct lsquic_enc_session *enc_session,
lsquic_str_t *cached_certs,
int cached_certs_count)
{
const unsigned char *const in =
(const unsigned char *) lsquic_str_buf(&enc_session->hs_ctx.crt);
const unsigned char *const in_end =
in + lsquic_str_len(&enc_session->hs_ctx.crt);
const unsigned char *dummy = (unsigned char *) "";
const unsigned char *const in = enc_session->hs_ctx.ccert
? enc_session->hs_ctx.ccert->buf : dummy;
const unsigned char *const in_end = enc_session->hs_ctx.ccert
? in + enc_session->hs_ctx.ccert->len : 0;
EVP_PKEY *pub_key;
int ret;
size_t i;
@ -2749,9 +2778,9 @@ lsquic_enc_session_handle_chlo_reply (enc_session_t *enc_session_p,
goto end;
}
if (lsquic_str_len(&enc_session->hs_ctx.crt) > 0)
if (enc_session->hs_ctx.ccert)
{
out_certs_count = lsquic_get_certs_count(&enc_session->hs_ctx.crt);
out_certs_count = lsquic_get_certs_count(enc_session->hs_ctx.ccert);
if (out_certs_count > 0)
{
out_certs = malloc(out_certs_count * sizeof(lsquic_str_t *));
@ -3559,7 +3588,9 @@ lsquic_enc_session_mem_used (enc_session_t *enc_session_p)
size += lsquic_str_len(&enc_session->hs_ctx.sno);
size += lsquic_str_len(&enc_session->hs_ctx.prof);
size += lsquic_str_len(&enc_session->hs_ctx.csct);
size += lsquic_str_len(&enc_session->hs_ctx.crt);
if (enc_session->hs_ctx.ccert)
size += enc_session->hs_ctx.ccert->len
+ sizeof(*enc_session->hs_ctx.ccert);
if (enc_session->info)
{
@ -3738,7 +3769,8 @@ gquic_encrypt_packet (enc_session_t *enc_session_p,
return ENCPA_BADCRYPT; /* To cause connection to close */
ipv6 = NP_IS_IPv6(packet_out->po_path);
buf = enpub->enp_pmi->pmi_allocate(enpub->enp_pmi_ctx,
packet_out->po_path->np_peer_ctx, lconn->conn_ctx, bufsz, ipv6);
packet_out->po_path->np_peer_ctx, lconn->cn_conn_ctx,
bufsz, ipv6);
if (!buf)
{
LSQ_DEBUG("could not allocate memory for outgoing packet of size %zd",
@ -3936,7 +3968,8 @@ gquic2_esf_encrypt_packet (enc_session_t *enc_session_p,
dst_sz = lconn->cn_pf->pf_packout_size(lconn, packet_out);
ipv6 = NP_IS_IPv6(packet_out->po_path);
dst = enpub->enp_pmi->pmi_allocate(enpub->enp_pmi_ctx,
packet_out->po_path->np_peer_ctx, lconn->conn_ctx, dst_sz, ipv6);
packet_out->po_path->np_peer_ctx, lconn->cn_conn_ctx,
dst_sz, ipv6);
if (!dst)
{
LSQ_DEBUG("could not allocate memory for outgoing packet of size %zd",

View File

@ -46,6 +46,7 @@ lsquic_hcsi_reader_feed (struct hcsi_reader *reader, const void *buf,
uint64_t len;
int s;
continue_reading:
while (p < end)
{
switch (reader->hr_state)
@ -83,6 +84,10 @@ lsquic_hcsi_reader_feed (struct hcsi_reader *reader, const void *buf,
case HQFT_MAX_PUSH_ID:
reader->hr_state = HR_READ_VARINT;
break;
case HQFT_PRIORITY_UPDATE_PUSH:
case HQFT_PRIORITY_UPDATE_STREAM:
reader->hr_state = HR_READ_VARINT;
break;
case HQFT_DATA:
case HQFT_HEADERS:
case HQFT_PUSH_PROMISE:
@ -111,13 +116,20 @@ lsquic_hcsi_reader_feed (struct hcsi_reader *reader, const void *buf,
reader->hr_nread += p - orig_p;
if (0 == s)
{
if (reader->hr_nread != reader->hr_frame_length)
switch (reader->hr_frame_type)
{
reader->hr_conn->cn_if->ci_abort_error(reader->hr_conn, 1,
HEC_FRAME_ERROR,
"Frame length does not match actual payload length");
reader->hr_state = HR_ERROR;
return -1;
case HQFT_GOAWAY:
case HQFT_CANCEL_PUSH:
case HQFT_MAX_PUSH_ID:
if (reader->hr_nread != reader->hr_frame_length)
{
reader->hr_conn->cn_if->ci_abort_error(reader->hr_conn, 1,
HEC_FRAME_ERROR,
"Frame length does not match actual payload length");
reader->hr_state = HR_ERROR;
return -1;
}
break;
}
switch (reader->hr_frame_type)
{
@ -133,6 +145,35 @@ lsquic_hcsi_reader_feed (struct hcsi_reader *reader, const void *buf,
reader->hr_cb->on_max_push_id(reader->hr_ctx,
reader->hr_u.vint_state.val);
break;
case HQFT_PRIORITY_UPDATE_PUSH:
case HQFT_PRIORITY_UPDATE_STREAM:
len = reader->hr_frame_length - reader->hr_nread;
if (len <= (uintptr_t) (end - p))
{
reader->hr_cb->on_priority_update(reader->hr_ctx,
reader->hr_frame_type, reader->hr_u.vint_state.val,
(char *) p, len);
p += len;
}
else if (len <= sizeof(reader->hr_u.prio_state.buf))
{
reader->hr_frame_length = len;
reader->hr_nread = 0;
reader->hr_state = HR_READ_PRIORITY_UPDATE;
goto continue_reading;
}
else
{
p += len;
/* 16 bytes is more than enough for a PRIORITY_UPDATE
* frame, anything larger than that is unreasonable.
*/
if (reader->hr_frame_length
> sizeof(reader->hr_u.prio_state.buf))
LSQ_INFO("skip PRIORITY_UPDATE frame that's too "
"long (%"PRIu64" bytes)", len);
}
break;
default:
assert(0);
}
@ -179,6 +220,20 @@ lsquic_hcsi_reader_feed (struct hcsi_reader *reader, const void *buf,
else
reader->hr_state = HR_READ_SETTING_BEGIN;
break;
case HR_READ_PRIORITY_UPDATE:
len = MIN((uintptr_t) (end - p),
reader->hr_frame_length - reader->hr_nread);
memcpy(reader->hr_u.prio_state.buf + reader->hr_nread, p, len);
reader->hr_nread += len;
p += len;
if (reader->hr_frame_length == reader->hr_nread)
{
reader->hr_cb->on_priority_update(reader->hr_ctx,
reader->hr_frame_type, reader->hr_u.vint_state.val,
reader->hr_u.prio_state.buf, reader->hr_frame_length);
reader->hr_state = HR_READ_FRAME_BEGIN;
}
break;
default:
assert(0);
/* fall-through */

View File

@ -18,6 +18,8 @@ struct hcsi_callbacks
void (*on_setting)(void *ctx, uint64_t setting_id, uint64_t value);
void (*on_goaway)(void *ctx, uint64_t stream_id);
void (*on_unexpected_frame)(void *ctx, uint64_t frame_type);
void (*on_priority_update)(void *ctx, enum hq_frame_type, uint64_t id,
const char *, size_t);
};
@ -29,6 +31,7 @@ struct hcsi_reader
HR_SKIPPING,
HR_READ_SETTING_BEGIN,
HR_READ_SETTING_CONTINUE,
HR_READ_PRIORITY_UPDATE,
HR_READ_VARINT,
HR_READ_VARINT_CONTINUE,
HR_ERROR,
@ -40,10 +43,19 @@ struct hcsi_reader
{
struct varint_read_state vint_state;
struct varint_read2_state vint2_state;
struct {
/* We just need the offset to rest of prio_state to read Priority
* Field Value.
*/
struct varint_read_state UNUSED;
char buf[
sizeof(struct varint_read2_state)
- sizeof(struct varint_read_state)];
} prio_state;
} hr_u;
const struct hcsi_callbacks *hr_cb;
void *hr_ctx;
unsigned hr_nread; /* Used for PRIORITY and SETTINGS frames */
unsigned hr_nread; /* Used for PRIORITY_UPDATE and SETTINGS frames */
};

View File

@ -213,6 +213,8 @@ hqft2str (enum hq_frame_type type)
case HQFT_MAX_PUSH_ID: return "MAX_PUSH_ID";
case HQFT_CANCEL_PUSH: return "CANCEL_PUSH";
case HQFT_GOAWAY: return "GOAWAY";
case HQFT_PRIORITY_UPDATE_PUSH:return "PRIORITY_UPDATE (push)";
case HQFT_PRIORITY_UPDATE_STREAM:return "PRIORITY_UPDATE (stream)";
default: return "<unknown>";
}
}
@ -275,6 +277,60 @@ lsquic_hcso_write_cancel_push (struct hcso_writer *writer, uint64_t push_id)
}
int
lsquic_hcso_write_priority_update (struct hcso_writer *writer,
enum hq_frame_type type, uint64_t stream_or_push_id,
const struct lsquic_ext_http_prio *ehp)
{
unsigned char *p, *len;
unsigned bits;
int was_empty;
unsigned char buf[8 /* Frame type */ + /* Frame size */ 1 + 8 /* Value */
+ 5 /* PFV: "u=.,i" or "u=." */];
p = buf;
bits = vint_val2bits(type);
vint_write(p, type, bits, 1 << bits);
p += 1 << bits;
bits = vint_val2bits(stream_or_push_id);
len = p;
++p;
vint_write(p, stream_or_push_id, bits, 1 << bits);
p += 1 << bits;
if (!(ehp->urgency == LSQUIC_DEF_HTTP_URGENCY
&& ehp->incremental == LSQUIC_DEF_HTTP_INCREMENTAL))
{
*p++ = 'u';
*p++ = '=';
*p++ = '0' + ehp->urgency;
if (ehp->incremental)
{
*p++ = ',';
*p++ = 'i';
}
}
*len = p - len - 1;
was_empty = lsquic_frab_list_empty(&writer->how_fral);
if (0 != lsquic_frab_list_write(&writer->how_fral, buf, p - buf))
{
LSQ_INFO("cannot write %s frame to frab list", hqft2str(type));
return -1;
}
if (was_empty)
lsquic_stream_wantwrite(writer->how_stream, 1);
LSQ_DEBUG("generated %u-byte %s frame", (unsigned) (p - buf),
hqft2str(type));
return 0;
}
#ifndef NDEBUG
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static size_t

View File

@ -7,6 +7,7 @@
#define LSQUIC_HCSO_WRITER_H 1
struct lsquic_engine_settings;
struct lsquic_ext_http_prio;
struct lsquic_stream;
struct hcso_writer
@ -34,6 +35,11 @@ lsquic_hcso_write_max_push_id (struct hcso_writer *, uint64_t max_push_id);
int
lsquic_hcso_write_cancel_push (struct hcso_writer *, uint64_t push_id);
int
lsquic_hcso_write_priority_update (struct hcso_writer *,
enum hq_frame_type, uint64_t stream_or_push_id,
const struct lsquic_ext_http_prio *);
extern const struct lsquic_stream_if *const lsquic_hcso_writer_if;
#endif

394
src/liblsquic/lsquic_hpi.c Normal file
View File

@ -0,0 +1,394 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* lsquic_hpi.c - implementation of (Extensible) HTTP Priority Iterator.
*/
#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <sys/types.h>
#ifdef WIN32
#include <vc_compat.h>
#endif
#include "lsquic.h"
#include "lsquic_types.h"
#include "lsquic_int_types.h"
#include "lsquic_sfcw.h"
#include "lsquic_varint.h"
#include "lsquic_hq.h"
#include "lsquic_hash.h"
#include "lsquic_stream.h"
#include "lsquic_conn_flow.h"
#include "lsquic_rtt.h"
#include "lsquic_conn_public.h"
#include "lsquic_min_heap.h"
#include "lsquic_mm.h"
#include "lsquic_hpi.h"
#define LSQUIC_LOGGER_MODULE LSQLM_HPI
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(iter->hpi_conn_pub->lconn)
#include "lsquic_logger.h"
#define HPI_DEBUG(fmt, ...) LSQ_DEBUG("%s: " fmt, iter->hpi_name, __VA_ARGS__)
#define NEXT_STREAM(stream, off) \
(* (struct lsquic_stream **) ((unsigned char *) (stream) + (off)))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static void
add_stream_to_hpi (struct http_prio_iter *iter,
struct lsquic_stream *new_stream)
{
unsigned prio, incr;
if (lsquic_stream_is_critical(new_stream))
{
prio = 0;
incr = 1; /* Place in incremental bucket: these do not need to be
* ordered by stream ID.
*/
}
else
{
prio = 1 + MIN(new_stream->sm_priority, LSQUIC_MAX_HTTP_URGENCY);
incr = !!(new_stream->sm_bflags & SMBF_INCREMENTAL);
}
if (!(iter->hpi_set[incr] & (1u << prio)))
{
iter->hpi_set[incr] |= 1u << prio;
if (0 == incr)
iter->hpi_counts[prio] = 0;
TAILQ_INIT(&iter->hpi_streams[incr][prio]);
}
if (0 == incr)
++iter->hpi_counts[prio];
TAILQ_INSERT_TAIL(&iter->hpi_streams[incr][prio],
new_stream, next_prio_stream);
}
void
lsquic_hpi_init (void *iter_p, struct lsquic_stream *first,
struct lsquic_stream *last, uintptr_t next_ptr_offset,
struct lsquic_conn_public *conn_pub, const char *name,
int (*filter)(void *filter_ctx, struct lsquic_stream *),
void *filter_ctx)
{
struct http_prio_iter *const iter = iter_p;
struct lsquic_stream *stream;
unsigned count;
iter->hpi_conn_pub = conn_pub;
iter->hpi_name = name ? name : "UNSET";
iter->hpi_flags = 0;
iter->hpi_heaped = 0;
iter->hpi_set[0] = 0;
iter->hpi_set[1] = 0;
memset(&iter->hpi_min_heap, 0, sizeof(iter->hpi_min_heap));
stream = first;
count = 0;
if (filter)
while (1)
{
if (filter(filter_ctx, stream))
{
add_stream_to_hpi(iter, stream);
++count;
}
if (stream == last)
break;
stream = NEXT_STREAM(stream, next_ptr_offset);
}
else
while (1)
{
add_stream_to_hpi(iter, stream);
++count;
if (stream == last)
break;
stream = NEXT_STREAM(stream, next_ptr_offset);
}
if (count > 2)
HPI_DEBUG("initialized; # elems: %u; sets: [ %08X, %08X ]",
count, iter->hpi_set[0], iter->hpi_set[1]);
}
/* Number of trailing zeroes */
static const unsigned char ntz[] = {
9, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0,
1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0,
2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0,
1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0,
3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0,
1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0,
2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0,
1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0,
1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0,
2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0,
1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0,
3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0,
1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0,
2, 0, 1, 0, 8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0,
1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0,
1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0,
2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0,
1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0,
3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0,
1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0,
2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0,
1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0,
1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0,
2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0,
1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0,
3, 0, 1, 0, 2, 0, 1, 0,
};
/* Sets prio_ and incr_ */
#define calc_next_prio_and_incr(iter_, prio_, incr_) do { \
prio_ = ntz[iter_->hpi_set[0]]; \
if (prio_ <= ntz[iter_->hpi_set[1]]) \
incr_ = 0; \
else \
{ \
prio_ = ntz[iter_->hpi_set[1]]; \
incr_ = 1; \
} \
} while (0)
struct lsquic_stream *
lsquic_hpi_first (void *iter_p)
{
struct http_prio_iter *const iter = iter_p;
assert(!(iter->hpi_set[0] & ~((1 << N_HPI_PRIORITIES) - 1)));
assert(!(iter->hpi_set[1] & ~((1 << N_HPI_PRIORITIES) - 1)));
return lsquic_hpi_next(iter);
}
static struct lsquic_stream *
next_incr (struct http_prio_iter *iter, unsigned prio)
{
struct lsquic_stream *stream;
stream = TAILQ_FIRST(&iter->hpi_streams[1][prio]);
TAILQ_REMOVE(&iter->hpi_streams[1][prio], stream, next_prio_stream);
if (TAILQ_EMPTY(&iter->hpi_streams[1][prio]))
iter->hpi_set[1] &= ~(1u << prio);
return stream;
}
static void
free_heap_elems (struct http_prio_iter *iter)
{
if (0 == (iter->hpi_flags & (HPI_MH_4K|HPI_MH_MALLOC)))
/* Expected condition: nothing to do */ ;
else if (iter->hpi_flags & HPI_MH_4K)
{
lsquic_mm_put_4k(iter->hpi_conn_pub->mm, iter->hpi_min_heap.mh_elems);
iter->hpi_flags &= ~HPI_MH_4K;
}
else
{
assert(iter->hpi_flags & HPI_MH_MALLOC);
iter->hpi_flags &= ~HPI_MH_MALLOC;
free(iter->hpi_min_heap.mh_elems);
}
iter->hpi_min_heap.mh_elems = NULL;
}
#ifndef NDEBUG
static int lsquic_hpi_heap_test = (
LSQUIC_HPI_HEAP_TEST_STACK_OK | LSQUIC_HPI_HEAP_TEST_4K_OK);
void
lsquic_hpi_set_heap_test (int val)
{
lsquic_hpi_heap_test = val;
}
#endif
static int
heap_nonincr_bucket (struct http_prio_iter *iter, unsigned prio)
{
struct lsquic_stream *stream;
size_t need;
if (iter->hpi_counts[prio] <= sizeof(iter->hpi_min_heap_els)
/ sizeof(iter->hpi_min_heap_els[0])
#ifndef NDEBUG
&& (lsquic_hpi_heap_test & LSQUIC_HPI_HEAP_TEST_STACK_OK)
#endif
)
iter->hpi_min_heap.mh_elems = iter->hpi_min_heap_els;
else if (need = iter->hpi_counts[prio] * sizeof(struct min_heap_elem),
need <= 0x1000
#ifndef NDEBUG
&& (lsquic_hpi_heap_test & LSQUIC_HPI_HEAP_TEST_4K_OK)
#endif
)
{
iter->hpi_min_heap.mh_elems = lsquic_mm_get_4k(iter->hpi_conn_pub->mm);
if (!iter->hpi_min_heap.mh_elems)
return -1;
iter->hpi_flags |= HPI_MH_4K;
}
else
{
iter->hpi_min_heap.mh_elems = malloc(need);
if (!iter->hpi_min_heap.mh_elems)
return -1;
iter->hpi_flags |= HPI_MH_MALLOC;
}
iter->hpi_min_heap.mh_nalloc = iter->hpi_counts[prio];
TAILQ_FOREACH(stream, &iter->hpi_streams[0][prio], next_prio_stream)
lsquic_mh_insert(&iter->hpi_min_heap, stream, stream->id);
iter->hpi_heaped |= 1u << prio;
return 0;
}
static struct lsquic_stream *
next_nonincr (struct http_prio_iter *iter, unsigned prio)
{
struct lsquic_stream *stream;
if (iter->hpi_heaped & (1u << prio))
{
pop_stream:
stream = lsquic_mh_pop(&iter->hpi_min_heap);
if (lsquic_mh_count(&iter->hpi_min_heap) == 0)
{
free_heap_elems(iter);
iter->hpi_set[0] &= ~(1u << prio);
}
}
else if (iter->hpi_counts[prio] > 1)
{
if (0 == heap_nonincr_bucket(iter, prio))
goto pop_stream;
/* Handle memory allocation failure by abandoning attempts to order
* the streams:
*/
iter->hpi_counts[prio] = 1;
goto first_stream;
}
else
{
first_stream:
stream = TAILQ_FIRST(&iter->hpi_streams[0][prio]);
TAILQ_REMOVE(&iter->hpi_streams[0][prio], stream, next_prio_stream);
if (TAILQ_EMPTY(&iter->hpi_streams[0][prio]))
iter->hpi_set[0] &= ~(1u << prio);
}
return stream;
}
struct lsquic_stream *
lsquic_hpi_next (void *iter_p)
{
struct http_prio_iter *const iter = iter_p;
struct lsquic_stream *stream;
unsigned prio, incr;
calc_next_prio_and_incr(iter, prio, incr);
if (prio >= N_HPI_PRIORITIES)
return NULL;
if (incr)
stream = next_incr(iter, prio);
else
stream = next_nonincr(iter, prio);
if (LSQ_LOG_ENABLED(LSQ_LOG_DEBUG))
HPI_DEBUG("%s: return stream %"PRIu64", incr: %u, priority %u",
__func__, stream->id, incr, prio);
return stream;
}
#if __GNUC__
# define popcount __builtin_popcount
#else
static int
popcount (unsigned v)
{
int count;
unsigned i;
for (i = 0, count = 0; i < sizeof(v) * 8; ++i)
if (v & (1 << i))
++count;
return count;
}
#endif
static void
hpi_drop_high_or_non_high (void *iter_p, int drop_high)
{
struct http_prio_iter *const iter = iter_p;
unsigned prio, incr;
/* Nothing to drop if there is only one bucket */
if (popcount(iter->hpi_set[0]) + popcount(iter->hpi_set[1]) < 2)
return;
calc_next_prio_and_incr(iter, prio, incr);
if (drop_high)
iter->hpi_set[incr] &= ~(1u << prio);
else
{
iter->hpi_set[incr] = 1u << prio;
iter->hpi_set[!incr] = 0;
}
}
void
lsquic_hpi_drop_high (void *iter_p)
{
hpi_drop_high_or_non_high(iter_p, 1);
}
void
lsquic_hpi_drop_non_high (void *iter_p)
{
hpi_drop_high_or_non_high(iter_p, 0);
}
void
lsquic_hpi_cleanup (void *iter_p)
{
struct http_prio_iter *const iter = iter_p;
if (iter->hpi_min_heap.mh_elems)
free_heap_elems(iter);
}

View File

@ -0,0 +1,75 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* lsquic_hpi.h - HPI: (Extensible) HTTP Priority Iterator
*
* https://tools.ietf.org/html/draft-ietf-httpbis-priority-01
*
* Changing a stream's priority when the stream is in the iterator
* does not change the stream's position in the iterator.
*/
#ifndef LSQUIC_HPI
#define LSQUIC_HPI 1
struct lsquic_conn_public;
/* We add 1 to the urgency when we place them on hpi_streams. Critical
* streams get the highest-priority slot zero.
*/
#define N_HPI_PRIORITIES (2 + LSQUIC_MAX_HTTP_URGENCY)
struct http_prio_iter
{
const char *hpi_name; /* Used for logging */
struct lsquic_conn_public *hpi_conn_pub;
enum {
HPI_MH_4K = 1 << 0,
HPI_MH_MALLOC = 1 << 1,
} hpi_flags;
unsigned hpi_set[2]; /* Bitmask */
unsigned hpi_counts[N_HPI_PRIORITIES]; /* For non-incr only */
unsigned hpi_heaped; /* Bitmask */
struct lsquic_streams_tailq hpi_streams[2][N_HPI_PRIORITIES];
struct min_heap hpi_min_heap;
/* We do this because http_prio_iter is used in a union with
* stream_prio_iter, which is over 4KB on the stack. Since we
* are already allocating this memory on the stack, we might as well
* use it.
*/
struct min_heap_elem hpi_min_heap_els[236];
};
void
lsquic_hpi_init (void *, struct lsquic_stream *first,
struct lsquic_stream *last, uintptr_t next_ptr_offset,
struct lsquic_conn_public *, const char *name,
int (*filter)(void *filter_ctx, struct lsquic_stream *),
void *filter_ctx);
struct lsquic_stream *
lsquic_hpi_first (void *);
struct lsquic_stream *
lsquic_hpi_next (void *);
void
lsquic_hpi_drop_non_high (void *);
void
lsquic_hpi_drop_high (void *);
void
lsquic_hpi_cleanup (void *);
#ifndef NDEBUG
#define LSQUIC_HPI_HEAP_TEST_STACK_OK (1 << 0)
#define LSQUIC_HPI_HEAP_TEST_4K_OK (1 << 1)
#if LSQUIC_TEST
void
lsquic_hpi_set_heap_test (int val);
#endif
#endif
#endif

View File

@ -6,6 +6,8 @@
#ifndef LSQUIC_HQ_H
#define LSQUIC_HQ_H 1
struct lsquic_ext_http_prio;
/* [draft-ietf-quic-http-27] Section 11.2.1 */
enum hq_frame_type
{
@ -16,6 +18,14 @@ enum hq_frame_type
HQFT_PUSH_PROMISE = 5,
HQFT_GOAWAY = 7,
HQFT_MAX_PUSH_ID = 0xD,
/* These made me expand shf_frame_type to 4 bytes from 1. If at some
* point we have to support a frame that is wider than 4 byte, it will
* be time to bite the bullet and use our own enum for these types
* (just like we do for transport parameters). A simpler alternative
* would be to drop the enum and use #define's, but it would stink...
*/
HQFT_PRIORITY_UPDATE_STREAM= 0xF0700,
HQFT_PRIORITY_UPDATE_PUSH = 0xF0701,
/* This frame is made up and its type is never written to stream.
* Nevertheless, just to be on the safe side, give it a value as
* described in [draft-ietf-quic-http-20] Section 4.2.10.
@ -76,4 +86,17 @@ enum http_error_code
};
enum ppc_flags
{
PPC_URG_NAME = 1 << 0,
PPC_INC_NAME = 1 << 1,
PPC_URG_SET = 1 << 2, /* 'urgency' is set */
PPC_INC_SET = 1 << 3, /* 'incremental' is set */
};
int
lsquic_http_parse_pfv (const char *, size_t, enum ppc_flags * /* optional */,
struct lsquic_ext_http_prio *, char *str, size_t);
#endif

View File

@ -0,0 +1,77 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/* Various HTTP-related functions. */
#include <stddef.h>
#include <stdlib.h>
#ifdef WIN32
#include <vc_compat.h>
#endif
#include "ls-sfparser.h"
#include "lsquic.h"
#include "lsquic_hq.h"
struct parse_pfv_ctx
{
enum ppc_flags ppc_flags;
struct lsquic_ext_http_prio *ppc_ehp;
};
static int
parse_pfv (void *user_data, enum ls_sf_dt type, char *str, size_t len, int off)
{
struct parse_pfv_ctx *const pfv_ctx = user_data;
unsigned urgency;
if (type == LS_SF_DT_NAME)
{
if (1 == len)
switch (str[0])
{
case 'u': pfv_ctx->ppc_flags |= PPC_URG_NAME; return 0;
case 'i': pfv_ctx->ppc_flags |= PPC_INC_NAME; return 0;
}
}
else if (pfv_ctx->ppc_flags & PPC_URG_NAME)
{
if (type == LS_SF_DT_INTEGER)
{
urgency = atoi(str);
if (urgency <= LSQUIC_MAX_HTTP_URGENCY)
{
pfv_ctx->ppc_ehp->urgency = urgency;
pfv_ctx->ppc_flags |= PPC_URG_SET;
}
}
}
else if (pfv_ctx->ppc_flags & PPC_INC_NAME)
{
if (type == LS_SF_DT_BOOLEAN)
{
pfv_ctx->ppc_ehp->incremental = str[0] - '0';
pfv_ctx->ppc_flags |= PPC_INC_SET;
}
}
pfv_ctx->ppc_flags &= ~(PPC_INC_NAME|PPC_URG_NAME);
return 0;
}
int
lsquic_http_parse_pfv (const char *pfv, size_t pfv_sz,
enum ppc_flags *flags, struct lsquic_ext_http_prio *ehp,
char *scratch_buf, size_t scratch_sz)
{
int ret;
struct parse_pfv_ctx pfv_ctx = { .ppc_flags = flags ? *flags : 0,
.ppc_ehp = ehp, };
ret = ls_sf_parse(LS_SF_TLT_DICTIONARY, pfv, pfv_sz, parse_pfv, &pfv_ctx,
scratch_buf, scratch_sz);
if (flags)
*flags = pfv_ctx.ppc_flags;
return ret;
}

View File

@ -79,6 +79,7 @@ enum lsq_log_level lsq_log_levels[N_LSQUIC_LOGGER_MODULES] = {
[LSQLM_TOKGEN] = LSQ_LOG_WARN,
[LSQLM_ENG_HIST] = LSQ_LOG_WARN,
[LSQLM_SPI] = LSQ_LOG_WARN,
[LSQLM_HPI] = LSQ_LOG_WARN,
[LSQLM_DI] = LSQ_LOG_WARN,
[LSQLM_PRQ] = LSQ_LOG_WARN,
[LSQLM_PACER] = LSQ_LOG_WARN,
@ -122,6 +123,7 @@ const char *const lsqlm_to_str[N_LSQUIC_LOGGER_MODULES] = {
[LSQLM_TOKGEN] = "tokgen",
[LSQLM_ENG_HIST] = "eng-hist",
[LSQLM_SPI] = "spi",
[LSQLM_HPI] = "hpi",
[LSQLM_DI] = "di",
[LSQLM_PRQ] = "prq",
[LSQLM_PACER] = "pacer",

View File

@ -70,6 +70,7 @@ enum lsquic_logger_module {
LSQLM_TOKGEN,
LSQLM_ENG_HIST,
LSQLM_SPI,
LSQLM_HPI,
LSQLM_DI,
LSQLM_PRQ,
LSQLM_PACER,

View File

@ -44,14 +44,14 @@ heapify_min_heap (struct min_heap *heap, unsigned i)
void
lsquic_mh_insert (struct min_heap *heap, struct lsquic_conn *conn, uint64_t val)
lsquic_mh_insert (struct min_heap *heap, void *item, uint64_t val)
{
struct min_heap_elem el;
unsigned i;
assert(heap->mh_nelem < heap->mh_nalloc);
heap->mh_elems[ heap->mh_nelem ].mhe_conn = conn;
heap->mh_elems[ heap->mh_nelem ].mhe_item = item;
heap->mh_elems[ heap->mh_nelem ].mhe_val = val;
++heap->mh_nelem;
@ -67,15 +67,15 @@ lsquic_mh_insert (struct min_heap *heap, struct lsquic_conn *conn, uint64_t val)
}
struct lsquic_conn *
void *
lsquic_mh_pop (struct min_heap *heap)
{
struct lsquic_conn *conn;
void *item;
if (heap->mh_nelem == 0)
return NULL;
conn = heap->mh_elems[0].mhe_conn;
item = heap->mh_elems[0].mhe_item;
--heap->mh_nelem;
if (heap->mh_nelem > 0)
{
@ -83,5 +83,5 @@ lsquic_mh_pop (struct min_heap *heap)
heapify_min_heap(heap, 0);
}
return conn;
return item;
}

View File

@ -1,16 +1,15 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* lsquic_min_heap.h -- Min-heap for connections
* lsquic_min_heap.h -- Min-heap for pointers
*/
#ifndef LSQUIC_MIN_HEAP_H
#define LSQUIC_MIN_HEAP_H 1
struct lsquic_conn;
struct min_heap_elem
{
struct lsquic_conn *mhe_conn;
void *mhe_item;
uint64_t mhe_val;
};
@ -24,12 +23,12 @@ struct min_heap
void
lsquic_mh_insert (struct min_heap *, struct lsquic_conn *conn, uint64_t val);
lsquic_mh_insert (struct min_heap *, void *item, uint64_t val);
struct lsquic_conn *
void *
lsquic_mh_pop (struct min_heap *);
#define lsquic_mh_peek(heap) ((heap)->mh_elems[0].mhe_conn)
#define lsquic_mh_peek(heap) ((heap)->mh_elems[0].mhe_item)
#define lsquic_mh_count(heap) (+(heap)->mh_nelem)

View File

@ -27,6 +27,7 @@
#include "lsquic_rtt.h"
#include "lsquic_util.h"
#include "lsquic_enc_sess.h"
#include "lsquic_trechist.h"
#include "lsquic_mini_conn_ietf.h"
#include "lsquic_ev_log.h"
#include "lsquic_trans_params.h"
@ -34,6 +35,7 @@
#include "lsquic_packet_ietf.h"
#include "lsquic_attq.h"
#include "lsquic_alarmset.h"
#include "lsquic_crand.h"
#define LSQUIC_LOGGER_MODULE LSQLM_MINI_CONN
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(&conn->imc_conn)
@ -469,6 +471,7 @@ lsquic_mini_conn_ietf_new (struct lsquic_engine_public *enpub,
enc_session_t *enc_sess;
enum enc_level i;
const struct enc_session_funcs_iquic *esfi;
unsigned char rand_nybble;
if (!is_first_packet_ok(packet_in, udp_payload_size))
return NULL;
@ -513,6 +516,23 @@ lsquic_mini_conn_ietf_new (struct lsquic_engine_public *enpub,
conn->imc_stream_ps[i] = &conn->imc_streams[i];
}
rand_nybble = lsquic_crand_get_nybble(enpub->enp_crand);
if (rand_nybble == 0)
{
/* Use trechist for about one out of every sixteen connections so
* that the code does not grow stale.
*/
LSQ_DEBUG("using trechist");
conn->imc_flags |= IMC_TRECHIST;
conn->imc_recvd_packnos.trechist.hist_elems
= malloc(TRECHIST_SIZE * IMICO_N_PNS);
if (!conn->imc_recvd_packnos.trechist.hist_elems)
{
LSQ_WARN("cannot allocate trechist elems");
return NULL;
}
}
esfi = select_esf_iquic_by_ver(version);
enc_sess = esfi->esfi_create_server(enpub, &conn->imc_conn,
&packet_in->pi_dcid, conn->imc_stream_ps, &crypto_stream_if,
@ -581,6 +601,8 @@ ietf_mini_conn_ci_destroy (struct lsquic_conn *lconn)
if (lconn->cn_enc_session)
lconn->cn_esf.i->esfi_destroy(lconn->cn_enc_session);
LSQ_DEBUG("ietf_mini_conn_ci_destroyed");
if (conn->imc_flags & IMC_TRECHIST)
free(conn->imc_recvd_packnos.trechist.hist_elems);
lsquic_malo_put(conn);
}
@ -1253,6 +1275,96 @@ imico_maybe_validate_by_dcid (struct ietf_mini_conn *conn,
}
static int
imico_received_packet_is_dup (struct ietf_mini_conn *conn,
enum packnum_space pns, lsquic_packno_t packno)
{
if (conn->imc_flags & IMC_TRECHIST)
return lsquic_trechist_contains(
conn->imc_recvd_packnos.trechist.hist_masks[pns],
conn->imc_recvd_packnos.trechist.hist_elems
+ TRECHIST_MAX_RANGES * pns, packno);
else
return !!(conn->imc_recvd_packnos.bitmasks[pns] & (1ULL << packno));
}
static int
imico_packno_is_largest (struct ietf_mini_conn *conn,
enum packnum_space pns, lsquic_packno_t packno)
{
if (conn->imc_flags & IMC_TRECHIST)
return 0 == conn->imc_recvd_packnos.trechist.hist_masks[pns]
|| packno > lsquic_trechist_max(
conn->imc_recvd_packnos.trechist.hist_masks[pns],
conn->imc_recvd_packnos.trechist.hist_elems
+ TRECHIST_MAX_RANGES * pns);
else
return 0 == conn->imc_recvd_packnos.bitmasks[pns]
|| packno > highest_bit_set(conn->imc_recvd_packnos.bitmasks[pns]);
}
static void
imico_record_recvd_packno (struct ietf_mini_conn *conn,
enum packnum_space pns, lsquic_packno_t packno)
{
if (conn->imc_flags & IMC_TRECHIST)
{
if (0 != lsquic_trechist_insert(
&conn->imc_recvd_packnos.trechist.hist_masks[pns],
conn->imc_recvd_packnos.trechist.hist_elems
+ TRECHIST_MAX_RANGES * pns, packno))
{
LSQ_INFO("too many ranges for trechist to hold or range too wide");
conn->imc_flags |= IMC_ERROR;
}
}
else
conn->imc_recvd_packnos.bitmasks[pns] |= 1ULL << packno;
}
static int
imico_switch_to_trechist (struct ietf_mini_conn *conn)
{
uint32_t masks[IMICO_N_PNS];
enum packnum_space pns;
struct trechist_elem *elems;
struct ietf_mini_rechist iter;
elems = malloc(TRECHIST_SIZE * N_PNS);
if (!elems)
{
LSQ_WARN("cannot allocate trechist elems");
return -1;
}
for (pns = 0; pns < IMICO_N_PNS; ++pns)
if (conn->imc_recvd_packnos.bitmasks[pns])
{
lsquic_imico_rechist_init(&iter, conn, pns);
if (0 != lsquic_trechist_copy_ranges(&masks[pns],
elems + TRECHIST_MAX_RANGES * pns, &iter,
lsquic_imico_rechist_first,
lsquic_imico_rechist_next))
{
LSQ_WARN("cannot copy ranges from bitmask to trechist");
free(elems);
return -1;
}
}
else
masks[pns] = 0;
memcpy(conn->imc_recvd_packnos.trechist.hist_masks, masks, sizeof(masks));
conn->imc_recvd_packnos.trechist.hist_elems = elems;
conn->imc_flags |= IMC_TRECHIST;
LSQ_DEBUG("switched to trechist");
return 0;
}
/* Only a single packet is supported */
static void
ietf_mini_conn_ci_packet_in (struct lsquic_conn *lconn,
@ -1318,7 +1430,14 @@ ietf_mini_conn_ci_packet_in (struct lsquic_conn *lconn,
if (pns == PNS_HSK && !(conn->imc_flags & IMC_IGNORE_INIT))
ignore_init(conn);
if (conn->imc_recvd_packnos[pns] & (1ULL << packet_in->pi_packno))
if (packet_in->pi_packno > MAX_PACKETS
&& !(conn->imc_flags & IMC_TRECHIST))
{
if (0 != imico_switch_to_trechist(conn))
return;
}
if (imico_received_packet_is_dup(conn, pns, packet_in->pi_packno))
{
LSQ_DEBUG("duplicate packet %"PRIu64, packet_in->pi_packno);
return;
@ -1328,10 +1447,9 @@ ietf_mini_conn_ci_packet_in (struct lsquic_conn *lconn,
* error, the connection is terminated and recording this packet number
* is helpful when it is printed along with other diagnostics in dtor.
*/
if (0 == conn->imc_recvd_packnos[pns] ||
packet_in->pi_packno > highest_bit_set(conn->imc_recvd_packnos[pns]))
if (imico_packno_is_largest(conn, pns, packet_in->pi_packno))
conn->imc_largest_recvd[pns] = packet_in->pi_received;
conn->imc_recvd_packnos[pns] |= 1ULL << packet_in->pi_packno;
imico_record_recvd_packno(conn, pns, packet_in->pi_packno);
if (0 != imico_parse_regular_packet(conn, packet_in))
{
@ -1483,24 +1601,21 @@ imico_have_packets_to_send (struct ietf_mini_conn *conn, lsquic_time_t now)
}
struct ietf_mini_rechist
{
const struct ietf_mini_conn *conn;
packno_set_t cur_set;
struct lsquic_packno_range range; /* We return a pointer to this */
int cur_idx;
enum packnum_space pns;
};
static void
imico_rechist_init (struct ietf_mini_rechist *rechist,
void
lsquic_imico_rechist_init (struct ietf_mini_rechist *rechist,
const struct ietf_mini_conn *conn, enum packnum_space pns)
{
rechist->conn = conn;
rechist->pns = pns;
rechist->cur_set = 0;
rechist->cur_idx = 0;
rechist->conn = conn;
rechist->pns = pns;
if (conn->imc_flags & IMC_TRECHIST)
lsquic_trechist_iter(&rechist->u.trechist_iter,
conn->imc_recvd_packnos.trechist.hist_masks[pns],
conn->imc_recvd_packnos.trechist.hist_elems + TRECHIST_MAX_RANGES * pns);
else
{
rechist->u.bitmask.cur_set = 0;
rechist->u.bitmask.cur_idx = 0;
}
}
@ -1513,51 +1628,70 @@ imico_rechist_largest_recv (void *rechist_ctx)
static const struct lsquic_packno_range *
imico_rechist_next (void *rechist_ctx)
imico_bitmask_rechist_next (struct ietf_mini_rechist *rechist)
{
struct ietf_mini_rechist *rechist = rechist_ctx;
const struct ietf_mini_conn *conn = rechist->conn;
packno_set_t packnos;
int i;
packnos = rechist->cur_set;
packnos = rechist->u.bitmask.cur_set;
if (0 == packnos)
return NULL;
/* There may be a faster way to do this, but for now, we just want
* correctness.
*/
for (i = rechist->cur_idx; i >= 0; --i)
for (i = rechist->u.bitmask.cur_idx; i >= 0; --i)
if (packnos & (1ULL << i))
{
rechist->range.low = i;
rechist->range.high = i;
rechist->u.bitmask.range.low = i;
rechist->u.bitmask.range.high = i;
break;
}
assert(i >= 0); /* We must have hit at least one bit */
--i;
for ( ; i >= 0 && (packnos & (1ULL << i)); --i)
rechist->range.low = i;
rechist->u.bitmask.range.low = i;
if (i >= 0)
{
rechist->cur_set = packnos & ((1ULL << i) - 1);
rechist->cur_idx = i;
rechist->u.bitmask.cur_set = packnos & ((1ULL << i) - 1);
rechist->u.bitmask.cur_idx = i;
}
else
rechist->cur_set = 0;
rechist->u.bitmask.cur_set = 0;
LSQ_DEBUG("%s: return [%"PRIu64", %"PRIu64"]", __func__,
rechist->range.low, rechist->range.high);
return &rechist->range;
rechist->u.bitmask.range.low, rechist->u.bitmask.range.high);
return &rechist->u.bitmask.range;
}
static const struct lsquic_packno_range *
imico_rechist_first (void *rechist_ctx)
const struct lsquic_packno_range *
lsquic_imico_rechist_next (void *rechist_ctx)
{
struct ietf_mini_rechist *rechist = rechist_ctx;
rechist->cur_set = rechist->conn->imc_recvd_packnos[ rechist->pns ];
rechist->cur_idx = highest_bit_set(rechist->cur_set);
return imico_rechist_next(rechist_ctx);
if (rechist->conn->imc_flags & IMC_TRECHIST)
return lsquic_trechist_next(&rechist->u.trechist_iter);
else
return imico_bitmask_rechist_next(rechist);
}
const struct lsquic_packno_range *
lsquic_imico_rechist_first (void *rechist_ctx)
{
struct ietf_mini_rechist *rechist = rechist_ctx;
if (rechist->conn->imc_flags & IMC_TRECHIST)
return lsquic_trechist_first(&rechist->u.trechist_iter);
else
{
rechist->u.bitmask.cur_set
= rechist->conn->imc_recvd_packnos.bitmasks[ rechist->pns ];
rechist->u.bitmask.cur_idx
= highest_bit_set(rechist->u.bitmask.cur_set);
return lsquic_imico_rechist_next(rechist_ctx);
}
}
@ -1598,11 +1732,11 @@ imico_generate_ack (struct ietf_mini_conn *conn, enum packnum_space pns,
return -1;
/* Generate ACK frame */
imico_rechist_init(&rechist, conn, pns);
lsquic_imico_rechist_init(&rechist, conn, pns);
len = conn->imc_conn.cn_pf->pf_gen_ack_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), imico_rechist_first,
imico_rechist_next, imico_rechist_largest_recv, &rechist,
lsquic_packet_out_avail(packet_out), lsquic_imico_rechist_first,
lsquic_imico_rechist_next, imico_rechist_largest_recv, &rechist,
now, &not_used_has_missing, &packet_out->po_ack2ed, ecn_counts);
if (len < 0)
{
@ -1625,7 +1759,7 @@ imico_generate_acks (struct ietf_mini_conn *conn, lsquic_time_t now)
{
enum packnum_space pns;
for (pns = PNS_INIT; pns < N_PNS; ++pns)
for (pns = PNS_INIT; pns < IMICO_N_PNS; ++pns)
if (conn->imc_flags & (IMC_QUEUED_ACK_INIT << pns)
&& !(pns == PNS_INIT && (conn->imc_flags & IMC_IGNORE_INIT)))
if (0 != imico_generate_ack(conn, pns, now))
@ -1814,8 +1948,7 @@ ietf_mini_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now)
}
if (conn->imc_flags &
(IMC_QUEUED_ACK_INIT|IMC_QUEUED_ACK_HSK|IMC_QUEUED_ACK_APP))
if (conn->imc_flags & (IMC_QUEUED_ACK_INIT|IMC_QUEUED_ACK_HSK))
{
if (0 != imico_generate_acks(conn, now))
{

View File

@ -27,6 +27,13 @@ struct mini_crypto_stream
typedef uint64_t packno_set_t;
#define MAX_PACKETS ((sizeof(packno_set_t) * 8) - 1)
/* We do not handle packets in the App packet number space in the mini
* connection. They are all buffered to be handled later when the
* connection is promoted. This means we do not have to have data
* structures to track the App PNS.
*/
#define IMICO_N_PNS (N_PNS - 1)
struct ietf_mini_conn
{
struct lsquic_conn imc_conn;
@ -37,7 +44,7 @@ struct ietf_mini_conn
IMC_ENC_SESS_INITED = 1 << 0,
IMC_QUEUED_ACK_INIT = 1 << 1,
IMC_QUEUED_ACK_HSK = IMC_QUEUED_ACK_INIT << PNS_HSK,
IMC_QUEUED_ACK_APP = IMC_QUEUED_ACK_INIT << PNS_APP,
IMC_UNUSED3 = 1 << 3,
IMC_ERROR = 1 << 4,
IMC_HSK_OK = 1 << 5,
IMC_HSK_FAILED = 1 << 6,
@ -58,6 +65,7 @@ struct ietf_mini_conn
IMC_PARSE_FAILED = 1 << 20,
IMC_PATH_CHANGED = 1 << 21,
IMC_HSK_DONE_SENT = 1 << 22,
IMC_TRECHIST = 1 << 23,
} imc_flags;
struct mini_crypto_stream imc_streams[N_ENC_LEVS];
void *imc_stream_ps[N_ENC_LEVS];
@ -69,9 +77,15 @@ struct ietf_mini_conn
TAILQ_HEAD(, lsquic_packet_out) imc_packets_out;
TAILQ_HEAD(, stream_frame) imc_crypto_frames;
packno_set_t imc_sent_packnos;
packno_set_t imc_recvd_packnos[N_PNS];
packno_set_t imc_acked_packnos[N_PNS];
lsquic_time_t imc_largest_recvd[N_PNS];
union {
packno_set_t bitmasks[IMICO_N_PNS];
struct {
struct trechist_elem *hist_elems;
trechist_mask_t hist_masks[IMICO_N_PNS];
} trechist;
} imc_recvd_packnos;
packno_set_t imc_acked_packnos[IMICO_N_PNS];
lsquic_time_t imc_largest_recvd[IMICO_N_PNS];
struct lsquic_rtt_stats imc_rtt_stats;
unsigned imc_error_code;
unsigned imc_bytes_in;
@ -90,8 +104,8 @@ struct ietf_mini_conn
*/
uint8_t imc_ecn_packnos;
uint8_t imc_ack_exp;
uint8_t imc_ecn_counts_in[N_PNS][4];
uint8_t imc_ecn_counts_out[N_PNS][4];
uint8_t imc_ecn_counts_in[IMICO_N_PNS][4];
uint8_t imc_ecn_counts_out[IMICO_N_PNS][4];
uint8_t imc_incoming_ecn;
uint8_t imc_tls_alert;
#define IMICO_MAX_DELAYED_PACKETS_UNVALIDATED 1u
@ -121,4 +135,29 @@ lsquic_mini_conn_ietf_new (struct lsquic_engine_public *,
int
lsquic_mini_conn_ietf_ecn_ok (const struct ietf_mini_conn *);
struct ietf_mini_rechist
{
const struct ietf_mini_conn *conn;
enum packnum_space pns;
union {
struct {
packno_set_t cur_set;
struct lsquic_packno_range range; /* We return a pointer to this */
int cur_idx;
} bitmask;
struct trechist_iter trechist_iter;
} u;
};
void
lsquic_imico_rechist_init (struct ietf_mini_rechist *rechist,
const struct ietf_mini_conn *conn, enum packnum_space pns);
const struct lsquic_packno_range *
lsquic_imico_rechist_first (void *rechist_ctx);
const struct lsquic_packno_range *
lsquic_imico_rechist_next (void *rechist_ctx);
#endif

View File

@ -22,6 +22,7 @@
#include "lsquic_packet_common.h"
#include "lsquic_mini_conn.h"
#include "lsquic_enc_sess.h"
#include "lsquic_trechist.h"
#include "lsquic_mini_conn_ietf.h"
#include "lsquic_packet_gquic.h"
#include "lsquic_packet_in.h"

View File

@ -27,6 +27,11 @@
#include "lsquic_engine_public.h"
#include "lsquic_headers.h"
#include "lsquic_conn.h"
#include "lsquic_conn_flow.h"
#include "lsquic_rtt.h"
#include "lsquic_conn_public.h"
#include "lsquic_hq.h"
#include "lsquic_parse.h"
#define LSQUIC_LOGGER_MODULE LSQLM_QDEC_HDL
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(qdh->qdh_conn)
@ -39,6 +44,8 @@ struct header_ctx
{
void *hset;
struct qpack_dec_hdl *qdh;
enum ppc_flags ppc_flags;
struct lsquic_ext_http_prio ehp;
};
@ -471,6 +478,14 @@ is_content_length (const struct lsxpack_header *xhdr)
}
static int
is_priority (const struct lsxpack_header *xhdr)
{
return xhdr->name_len == 8
&& 0 == memcmp(lsxpack_header_get_name(xhdr), "priority", 8);
}
static struct lsxpack_header *
qdh_prepare_decode (void *stream_p, struct lsxpack_header *xhdr, size_t space)
{
@ -499,6 +514,16 @@ qdh_process_header (void *stream_p, struct lsxpack_header *xhdr)
if (cl.has > 0)
(void) lsquic_stream_verify_len(stream, cl.value);
}
else if ((stream->sm_bflags & (SMBF_HTTP_PRIO|SMBF_HPRIO_SET))
== SMBF_HTTP_PRIO
&& is_priority(xhdr))
{
u->ctx.ppc_flags &= ~(PPC_INC_NAME|PPC_URG_NAME);
(void) lsquic_http_parse_pfv(lsxpack_header_get_value(xhdr),
xhdr->val_len, &u->ctx.ppc_flags, &u->ctx.ehp,
(char *) stream->conn_pub->mm->acki,
sizeof(*stream->conn_pub->mm->acki));
}
return qdh->qdh_enpub->enp_hsi_if->hsi_process_header(u->ctx.hset, xhdr);
}
@ -525,6 +550,15 @@ qdh_header_read_results (struct qpack_dec_hdl *qdh,
{
if (!lsquic_stream_header_is_trailer(stream))
{
if (stream->sm_hblock_ctx->ctx.ppc_flags
& (PPC_INC_SET|PPC_URG_SET))
{
assert(stream->sm_bflags & SMBF_HTTP_PRIO);
LSQ_DEBUG("Apply Priority from headers to stream %"PRIu64,
stream->id);
(void) lsquic_stream_set_http_prio(stream,
&stream->sm_hblock_ctx->ctx.ehp);
}
hset = stream->sm_hblock_ctx->ctx.hset;
uh = &stream->sm_hblock_ctx->uh;
stream->sm_hblock_ctx = NULL;
@ -621,6 +655,11 @@ lsquic_qdh_header_in_begin (struct qpack_dec_hdl *qdh,
u->ctx.hset = hset;
u->ctx.qdh = qdh;
u->ctx.ppc_flags = 0;
u->ctx.ehp = (struct lsquic_ext_http_prio) {
.urgency = LSQUIC_DEF_HTTP_URGENCY,
.incremental = LSQUIC_DEF_HTTP_INCREMENTAL,
};
stream->sm_hblock_ctx = u;
dec_buf_sz = sizeof(dec_buf);

View File

@ -434,3 +434,34 @@ lsquic_rechist_peek (struct lsquic_rechist *rechist)
else
return NULL;
}
int
lsquic_rechist_copy_ranges (struct lsquic_rechist *rechist, void *src_rechist,
const struct lsquic_packno_range * (*first) (void *),
const struct lsquic_packno_range * (*next) (void *))
{
const struct lsquic_packno_range *range;
struct rechist_elem *el;
unsigned *next_idx;
int idx;
/* This function only works if rechist contains no elements */
assert(rechist->rh_n_used == 0);
next_idx = &rechist->rh_head;
for (range = first(src_rechist); range; range = next(src_rechist))
{
idx = rechist_alloc_elem(rechist);
if (idx < 0)
return -1;
el = &rechist->rh_elems[idx];
el->re_low = range->low;
el->re_count = range->high - range->low + 1;
el->re_next = UINT_MAX;
*next_idx = idx;
next_idx = &el->re_next;
}
return 0;
}

View File

@ -84,4 +84,9 @@ lsquic_rechist_peek (struct lsquic_rechist *);
#define lsquic_rechist_is_empty(rechist_) ((rechist_)->rh_n_used == 0)
int
lsquic_rechist_copy_ranges (struct lsquic_rechist *, void *rechist_ctx,
const struct lsquic_packno_range * (*first) (void *),
const struct lsquic_packno_range * (*next) (void *));
#endif

View File

@ -305,16 +305,23 @@ first_packno (const struct lsquic_send_ctl *ctl)
}
/*
* [draft-ietf-quic-transport-12], Section 4.4.1:
*
* " The first Initial packet that is sent by a client contains a packet
* " number of 0. All subsequent packets contain a packet number that is
* " incremented by at least one, see (Section 4.8).
*/
static void
send_ctl_pick_initial_packno (struct lsquic_send_ctl *ctl)
{
#ifndef NDEBUG
lsquic_packno_t packno;
const char *s;
if (!(ctl->sc_conn_pub->lconn->cn_flags & LSCONN_SERVER)
&& (s = getenv("LSQUIC_STARTING_PACKNO"), s != NULL))
{
packno = (lsquic_packno_t) strtoull(s, NULL, 10);
LSQ_DEBUG("starting sending packet numbers starting with %"PRIu64
" based on environment variable", packno);
ctl->sc_cur_packno = packno - 1;
}
else
#endif
ctl->sc_cur_packno = first_packno(ctl) - 1;
}
@ -352,6 +359,13 @@ lsquic_send_ctl_init (lsquic_send_ctl_t *ctl, struct lsquic_alarmset *alset,
lsquic_alarmset_init_alarm(alset, AL_RETX_HSK, retx_alarm_rings, ctl);
lsquic_alarmset_init_alarm(alset, AL_RETX_APP, retx_alarm_rings, ctl);
lsquic_senhist_init(&ctl->sc_senhist, ctl->sc_flags & SC_IETF);
#ifndef NDEBUG
/* TODO: the logic to select the "previously sent" packno should not be
* duplicated here and in lsquic_senhist_init()...
*/
if (!(ctl->sc_conn_pub->lconn->cn_flags & LSCONN_SERVER))
ctl->sc_senhist.sh_last_sent = ctl->sc_cur_packno;
#endif
switch (enpub->enp_settings.es_cc_algo)
{
case 1:
@ -1093,6 +1107,7 @@ lsquic_send_ctl_got_ack (lsquic_send_ctl_t *ctl,
unsigned ecn_total_acked, ecn_ce_cnt, one_rtt_cnt;
pns = acki->pns;
ctl->sc_flags |= SC_ACK_RECV_INIT << pns;
packet_out = TAILQ_FIRST(&ctl->sc_unacked_packets[pns]);
#if __GNUC__
__builtin_prefetch(packet_out);
@ -1704,6 +1719,8 @@ lsquic_send_ctl_do_sanity_check (const struct lsquic_send_ctl *ctl)
assert(count == ctl->sc_n_scheduled);
assert(bytes == ctl->sc_bytes_scheduled);
}
#endif
@ -2073,7 +2090,7 @@ lsquic_send_ctl_new_packet_out (lsquic_send_ctl_t *ctl, unsigned need_at_least,
lsquic_packet_out_t *packet_out;
enum packno_bits bits;
bits = lsquic_send_ctl_packno_bits(ctl);
bits = lsquic_send_ctl_packno_bits(ctl, pns);
packet_out = send_ctl_allocate_packet(ctl, bits, need_at_least, pns, path);
if (!packet_out)
return NULL;
@ -2395,6 +2412,7 @@ send_ctl_log_packet_q (const lsquic_send_ctl_t *ctl, const char *prefix,
free(buf);
}
#define LOG_PACKET_Q(prefix, queue) do { \
if (LSQ_LOG_ENABLED(LSQ_LOG_DEBUG)) \
send_ctl_log_packet_q(ctl, queue, prefix); \
@ -2772,13 +2790,22 @@ lsquic_send_ctl_calc_packno_bits (lsquic_send_ctl_t *ctl)
enum packno_bits
lsquic_send_ctl_packno_bits (lsquic_send_ctl_t *ctl)
lsquic_send_ctl_packno_bits (struct lsquic_send_ctl *ctl,
enum packnum_space pns)
{
if (lsquic_send_ctl_schedule_stream_packets_immediately(ctl))
if ((ctl->sc_flags & (SC_ACK_RECV_INIT << pns))
&& lsquic_send_ctl_schedule_stream_packets_immediately(ctl))
return lsquic_send_ctl_calc_packno_bits(ctl);
else
else if (ctl->sc_flags & (SC_ACK_RECV_INIT << pns))
return lsquic_send_ctl_guess_packno_bits(ctl);
else
/* From [draft-ietf-quic-transport-31] Section 17.1:
*
" Prior to receiving an acknowledgement for a packet number space, the
" full packet number MUST be included; it is not to be truncated as
" described below.
*/
return vint_val2bits(ctl->sc_cur_packno + 1);
}

View File

@ -50,6 +50,9 @@ enum send_ctl_flags {
SC_CIDLEN = 1 << 16, /* sc_cidlen is set */
SC_POISON = 1 << 17, /* poisoned packet exists */
SC_CLEANUP_BBR = 1 << 18,
SC_ACK_RECV_INIT= 1 << 19,
SC_ACK_RECV_HSK = SC_ACK_RECV_INIT << PNS_HSK,
SC_ACK_RECV_APP = SC_ACK_RECV_INIT << PNS_APP,
};
typedef struct lsquic_send_ctl {
@ -290,7 +293,7 @@ lsquic_send_ctl_drop_scheduled (lsquic_send_ctl_t *);
: 0 )
enum packno_bits
lsquic_send_ctl_packno_bits (lsquic_send_ctl_t *);
lsquic_send_ctl_packno_bits (struct lsquic_send_ctl *, enum packnum_space);
int
lsquic_send_ctl_schedule_buffered (lsquic_send_ctl_t *, enum buf_packet_type);

View File

@ -21,6 +21,9 @@
#include "lsquic_hq.h"
#include "lsquic_hash.h"
#include "lsquic_stream.h"
#include "lsquic_conn_flow.h"
#include "lsquic_rtt.h"
#include "lsquic_conn_public.h"
#include "lsquic_spi.h"
#define LSQUIC_LOGGER_MODULE LSQLM_SPI
@ -51,17 +54,18 @@ add_stream_to_spi (struct stream_prio_iter *iter, lsquic_stream_t *stream)
void
lsquic_spi_init (struct stream_prio_iter *iter, struct lsquic_stream *first,
lsquic_spi_init (void *iter_p, struct lsquic_stream *first,
struct lsquic_stream *last, uintptr_t next_ptr_offset,
const struct lsquic_conn *conn,
struct lsquic_conn_public *conn_pub,
const char *name,
int (*filter)(void *filter_ctx, struct lsquic_stream *),
void *filter_ctx)
{
struct stream_prio_iter *const iter = iter_p;
struct lsquic_stream *stream;
unsigned count;
iter->spi_conn = conn;
iter->spi_conn = conn_pub->lconn;
iter->spi_name = name ? name : "UNSET";
iter->spi_set[0] = 0;
iter->spi_set[1] = 0;
@ -194,8 +198,9 @@ find_and_set_next_priority (struct stream_prio_iter *iter)
lsquic_stream_t *
lsquic_spi_first (struct stream_prio_iter *iter)
lsquic_spi_first (void *iter_p)
{
struct stream_prio_iter *const iter = iter_p;
lsquic_stream_t *stream;
unsigned set, bit;
@ -222,8 +227,9 @@ lsquic_spi_first (struct stream_prio_iter *iter)
lsquic_stream_t *
lsquic_spi_next (struct stream_prio_iter *iter)
lsquic_spi_next (void *iter_p)
{
struct stream_prio_iter *const iter = iter_p;
lsquic_stream_t *stream;
stream = iter->spi_next_stream;
@ -303,8 +309,9 @@ spi_has_more_than_one_queue (const struct stream_prio_iter *iter)
static void
spi_drop_high_or_non_high (struct stream_prio_iter *iter, int drop_high)
spi_drop_high_or_non_high (void *iter_p, int drop_high)
{
struct stream_prio_iter *const iter = iter_p;
uint64_t new_set[ sizeof(iter->spi_set) / sizeof(iter->spi_set[0]) ];
unsigned bit, set, n;
@ -336,14 +343,22 @@ spi_drop_high_or_non_high (struct stream_prio_iter *iter, int drop_high)
void
lsquic_spi_drop_high (struct stream_prio_iter *iter)
lsquic_spi_drop_high (void *iter_p)
{
struct stream_prio_iter *const iter = iter_p;
spi_drop_high_or_non_high(iter, 1);
}
void
lsquic_spi_drop_non_high (struct stream_prio_iter *iter)
lsquic_spi_drop_non_high (void *iter_p)
{
struct stream_prio_iter *const iter = iter_p;
spi_drop_high_or_non_high(iter, 0);
}
void
lsquic_spi_cleanup (void *iter_p)
{
}

View File

@ -2,20 +2,13 @@
/*
* lsquic_spi.h - SPI: Stream Priority Iterator
*
* SPI purposefully does not support switching stream priorities while
* iterator is active, because this puts iteration termination outside
* of our control. One can imagine (admittedly theoretical) scenario
* in which the user keeps on switching stream priorities around and
* causing an infinite loop.
* Changing a stream's priority when the stream is in the iterator
* does not change the stream's position in the iterator.
*/
#ifndef LSQUIC_SPI
#define LSQUIC_SPI 1
#include <stdint.h>
enum stream_q_flags;
struct stream_prio_iter
{
@ -30,26 +23,26 @@ struct stream_prio_iter
void
lsquic_spi_init (struct stream_prio_iter *, struct lsquic_stream *first,
lsquic_spi_init (void *, struct lsquic_stream *first,
struct lsquic_stream *last, uintptr_t next_ptr_offset,
const struct lsquic_conn *,
struct lsquic_conn_public *,
const char *name,
int (*filter)(void *filter_ctx, struct lsquic_stream *),
void *filter_ctx);
struct lsquic_stream *
lsquic_spi_first (struct stream_prio_iter *);
lsquic_spi_first (void *);
struct lsquic_stream *
lsquic_spi_next (struct stream_prio_iter *);
lsquic_spi_next (void *);
void
lsquic_spi_exhaust_on (struct stream_prio_iter *);
lsquic_spi_drop_non_high (void *);
void
lsquic_spi_drop_non_high (struct stream_prio_iter *);
lsquic_spi_drop_high (void *);
void
lsquic_spi_drop_high (struct stream_prio_iter *);
lsquic_spi_cleanup (void *);
#endif

View File

@ -74,6 +74,7 @@
#include "lsquic_byteswap.h"
#include "lsquic_ietf.h"
#include "lsquic_push_promise.h"
#include "lsquic_hcso_writer.h"
#define LSQUIC_LOGGER_MODULE LSQLM_STREAM
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(stream->conn_pub->lconn)
@ -439,7 +440,11 @@ lsquic_stream_new (lsquic_stream_id_t id,
}
else
stream->sm_readable = stream_readable_non_http;
lsquic_stream_set_priority_internal(stream,
if ((ctor_flags & (SCF_HTTP|SCF_HTTP_PRIO))
== (SCF_HTTP|SCF_HTTP_PRIO))
lsquic_stream_set_priority_internal(stream, LSQUIC_DEF_HTTP_URGENCY);
else
lsquic_stream_set_priority_internal(stream,
LSQUIC_STREAM_DEFAULT_PRIO);
stream->sm_write_to_packet = stream_write_to_packet_std;
stream->sm_frame_header_sz = stream_stream_frame_header_sz;
@ -807,14 +812,18 @@ stream_readable_discard (struct lsquic_stream *stream)
{
struct data_frame *data_frame;
uint64_t toread;
int fin;
while ((data_frame = stream->data_in->di_if->di_get_frame(
stream->data_in, stream->read_offset)))
{
fin = data_frame->df_fin;
toread = data_frame->df_size - data_frame->df_read_off;
stream->read_offset += toread;
data_frame->df_read_off = data_frame->df_size;
stream->data_in->di_if->di_frame_done(stream->data_in, data_frame);
if (fin)
break;
}
(void) maybe_switch_data_in(stream);
@ -2417,7 +2426,7 @@ lsquic_stream_flush_threshold (const struct lsquic_stream *stream,
size_t packet_header_sz, stream_header_sz, tag_len;
size_t threshold;
bits = lsquic_send_ctl_packno_bits(stream->conn_pub->send_ctl);
bits = lsquic_send_ctl_packno_bits(stream->conn_pub->send_ctl, PNS_APP);
flags = bits << POBIT_SHIFT;
if (!(stream->conn_pub->lconn->cn_flags & LSCONN_TCID0))
flags |= PO_CONN_ID;
@ -2802,13 +2811,17 @@ frame_hq_gen_read (void *ctx, void *begin_buf, size_t len, int *fin)
struct stream_hq_frame *shf;
size_t nw, frame_sz, avail, rem;
unsigned bits;
int new;
while (p < end)
{
shf = find_cur_hq_frame(stream);
if (shf)
{
new = 0;
LSQ_DEBUG("found current HQ frame of type 0x%X at offset %"PRIu64,
shf->shf_frame_type, shf->shf_off);
}
else
{
rem = frame_std_gen_size(ctx);
@ -2819,7 +2832,10 @@ frame_hq_gen_read (void *ctx, void *begin_buf, size_t len, int *fin)
shf = stream_activate_hq_frame(stream,
stream->sm_payload, HQFT_DATA, 0, rem);
if (shf)
{
new = 1;
goto insert;
}
else
{
/* TODO: abort connection? Handle failure somehow */
@ -2836,7 +2852,8 @@ frame_hq_gen_read (void *ctx, void *begin_buf, size_t len, int *fin)
frame_sz = stream_hq_frame_size(shf);
if (frame_sz > (uintptr_t) (end - p))
{
stream_hq_frame_put(stream, shf);
if (new)
stream_hq_frame_put(stream, shf);
break;
}
LSQ_DEBUG("insert %zu-byte HQ frame of type 0x%X at payload "
@ -4337,7 +4354,10 @@ lsquic_stream_uh_in (lsquic_stream_t *stream, struct uncompressed_headers *uh)
unsigned
lsquic_stream_priority (const lsquic_stream_t *stream)
{
return 256 - stream->sm_priority;
if (stream->sm_bflags & SMBF_HTTP_PRIO)
return stream->sm_priority;
else
return 256 - stream->sm_priority;
}
@ -4349,9 +4369,20 @@ lsquic_stream_set_priority_internal (lsquic_stream_t *stream, unsigned priority)
*/
if (lsquic_stream_is_critical(stream))
return -1;
if (priority < 1 || priority > 256)
return -1;
stream->sm_priority = 256 - priority;
if (stream->sm_bflags & SMBF_HTTP_PRIO)
{
if (priority > LSQUIC_MAX_HTTP_URGENCY)
return -1;
stream->sm_priority = priority;
}
else
{
if (priority < 1 || priority > 256)
return -1;
stream->sm_priority = 256 - priority;
}
lsquic_send_ctl_invalidate_bpt_cache(stream->conn_pub->send_ctl);
LSQ_DEBUG("set priority to %u", priority);
SM_HISTORY_APPEND(stream, SHE_SET_PRIO);
@ -4379,10 +4410,17 @@ maybe_send_priority_gquic (struct lsquic_stream *stream, unsigned priority)
static int
send_priority_ietf (struct lsquic_stream *stream, unsigned priority)
send_priority_ietf (struct lsquic_stream *stream)
{
LSQ_WARN("%s: TODO", __func__); /* TODO */
return -1;
struct lsquic_ext_http_prio ehp;
if (0 == lsquic_stream_get_http_prio(stream, &ehp)
&& 0 == lsquic_hcso_write_priority_update(
stream->conn_pub->u.ietf.hcso,
HQFT_PRIORITY_UPDATE_STREAM, stream->id, &ehp))
return 0;
else
return -1;
}
@ -4392,7 +4430,12 @@ lsquic_stream_set_priority (lsquic_stream_t *stream, unsigned priority)
if (0 == lsquic_stream_set_priority_internal(stream, priority))
{
if (stream->sm_bflags & SMBF_IETF)
return send_priority_ietf(stream, priority);
{
if (stream->sm_bflags & SMBF_HTTP_PRIO)
return send_priority_ietf(stream);
else
return 0;
}
else
return maybe_send_priority_gquic(stream, priority);
}
@ -4559,6 +4602,18 @@ update_type_hist_and_check (const struct lsquic_stream *stream,
case 9: /* HTTP/2 CONTINUATION */
/* [draft-ietf-quic-http-30], Section 7.2.8 */
return -1;
case HQFT_PRIORITY_UPDATE_STREAM:
case HQFT_PRIORITY_UPDATE_PUSH:
if (stream->sm_bflags & SMBF_HTTP_PRIO)
/* If we know about Extensible HTTP Priorities, we should check
* that they do not arrive on any but the control stream:
*/
return -1;
else
/* On the other hand, if we do not support Priorities, treat it
* as an unknown frame:
*/
return 0;
default:
/* Ignore unknown frames */
return 0;
@ -5239,3 +5294,47 @@ lsquic_stream_verify_len (struct lsquic_stream *stream,
else
return -1;
}
int
lsquic_stream_get_http_prio (struct lsquic_stream *stream,
struct lsquic_ext_http_prio *ehp)
{
if (stream->sm_bflags & SMBF_HTTP_PRIO)
{
ehp->urgency = MIN(stream->sm_priority, LSQUIC_MAX_HTTP_URGENCY);
ehp->incremental = !!(stream->sm_bflags & SMBF_INCREMENTAL);
return 0;
}
else
return -1;
}
int
lsquic_stream_set_http_prio (struct lsquic_stream *stream,
const struct lsquic_ext_http_prio *ehp)
{
if (stream->sm_bflags & SMBF_HTTP_PRIO)
{
if (ehp->urgency > LSQUIC_MAX_HTTP_URGENCY)
{
LSQ_INFO("%s: invalid urgency: %hhu", __func__, ehp->urgency);
return -1;
}
stream->sm_priority = ehp->urgency;
if (ehp->incremental)
stream->sm_bflags |= SMBF_INCREMENTAL;
else
stream->sm_bflags &= ~SMBF_INCREMENTAL;
stream->sm_bflags |= SMBF_HPRIO_SET;
LSQ_DEBUG("set urgency to %hhu, incremental to %hhd", ehp->urgency,
ehp->incremental);
if (!(stream->sm_bflags & SMBF_SERVER))
return send_priority_ietf(stream);
else
return 0;
}
else
return -1;
}

View File

@ -81,7 +81,7 @@ struct stream_hq_frame
} shf_u;
#define shf_frame_ptr shf_u.frame_ptr
#define shf_frame_size shf_u.frame_size
enum hq_frame_type shf_frame_type:8;
enum hq_frame_type shf_frame_type;
enum shf_flags {
SHF_TWO_BYTES = 1 << 0, /* Use two byte to encode frame length */
SHF_FIXED_SIZE = 1 << 1, /* Payload size guaranteed */
@ -185,7 +185,10 @@ enum stream_b_flags
SMBF_CONN_LIMITED = 1 << 7,
SMBF_HEADERS = 1 << 8, /* Headers stream */
SMBF_VERIFY_CL = 1 << 9, /* Verify content-length (stored in sm_cont_len) */
#define N_SMBF_FLAGS 10
SMBF_HTTP_PRIO = 1 <<10, /* Extensible HTTP Priorities are used */
SMBF_INCREMENTAL = 1 <<11, /* Value of the "incremental" HTTP Priority parameter */
SMBF_HPRIO_SET = 1 <<12, /* Extensible HTTP Priorities have been set once */
#define N_SMBF_FLAGS 13
};
@ -343,6 +346,9 @@ struct lsquic_stream
unsigned short sm_n_buffered; /* Amount of data in sm_buf */
unsigned short sm_n_allocated; /* Size of sm_buf */
/* If SMBF_HTTP_PRIO is set, the priority is used to represent the
* Extensible Priority urgency, which is in the range [0, 7].
*/
unsigned char sm_priority; /* 0: high; 255: low */
unsigned char sm_enc_level;
enum {
@ -381,6 +387,7 @@ enum stream_ctor_flags
SCF_HTTP = SMBF_USE_HEADERS,
SCF_CRYPTO = SMBF_CRYPTO,
SCF_HEADERS = SMBF_HEADERS,
SCF_HTTP_PRIO = SMBF_HTTP_PRIO,
};

View File

@ -0,0 +1,236 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
#include <assert.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include "lsquic_int_types.h"
#include "lsquic_trechist.h"
static unsigned
find_free_slot (uint32_t slots)
{
#if __GNUC__
return __builtin_ctz(~slots);
#else
unsigned n;
slots =~ slots;
n = 0;
if (0 == (slots & ((1ULL << 16) - 1))) { n += 16; slots >>= 16; }
if (0 == (slots & ((1ULL << 8) - 1))) { n += 8; slots >>= 8; }
if (0 == (slots & ((1ULL << 4) - 1))) { n += 4; slots >>= 4; }
if (0 == (slots & ((1ULL << 2) - 1))) { n += 2; slots >>= 2; }
if (0 == (slots & ((1ULL << 1) - 1))) { n += 1; slots >>= 1; }
return n;
#endif
}
/* Returns 0 on success, 1 if dup, -1 if out of elements */
int
lsquic_trechist_insert (trechist_mask_t *mask, struct trechist_elem *elems,
uint32_t packno)
{
struct trechist_elem *el, *prev;
unsigned idx;
if (*mask == 0)
{
elems[0].te_low = packno;
elems[0].te_count = 1;
elems[0].te_next = 0;
*mask |= 1;
return 0;
}
el = elems;
prev = NULL;
while (1)
{
if (packno > TE_HIGH(el) + 1)
goto insert_before;
if (packno == el->te_low - 1)
{
if (el->te_count == UCHAR_MAX)
return -1;
--el->te_low;
++el->te_count;
if (el->te_next && el->te_low == TE_HIGH(&elems[el->te_next]) + 1)
{
*mask &= ~(1u << el->te_next);
el->te_count += elems[el->te_next].te_count;
el->te_low = elems[el->te_next].te_low;
el->te_next = elems[el->te_next].te_next;
}
return 0;
}
if (packno == TE_HIGH(el) + 1)
{
if (el->te_count == UCHAR_MAX)
return -1;
++el->te_count;
return 0;
}
if (packno >= el->te_low && packno <= TE_HIGH(el))
return 1; /* Dup */
if (!el->te_next)
break; /* insert tail */
prev = el;
el = &elems[el->te_next];
}
if (*mask == ((1u << TRECHIST_MAX_RANGES) - 1))
return -1;
idx = find_free_slot(*mask);
elems[idx].te_low = packno;
elems[idx].te_count = 1;
elems[idx].te_next = 0;
*mask |= 1u << idx;;
el->te_next = idx;
return 0;
insert_before:
if (*mask == ((1u << TRECHIST_MAX_RANGES) - 1))
return -1;
idx = find_free_slot(*mask);
*mask |= 1u << idx;;
if (el == elems)
{
elems[idx] = *el;
elems[0].te_low = packno;
elems[0].te_count = 1;
elems[0].te_next = idx;
}
else
{
assert(prev);
elems[idx].te_low = packno;
elems[idx].te_count = 1;
elems[idx].te_next = prev->te_next;
prev->te_next = idx;
}
return 0;
}
void
lsquic_trechist_iter (struct trechist_iter *iter, trechist_mask_t mask,
const struct trechist_elem *elems)
{
iter->mask = mask;
iter->elems = elems;
}
const struct lsquic_packno_range *
lsquic_trechist_first (void *iter_p)
{
struct trechist_iter *const iter = iter_p;
if (iter->mask == 0)
return NULL;
iter->next = iter->elems[0].te_next;
iter->range.low = iter->elems[0].te_low;
iter->range.high = TE_HIGH(&iter->elems[0]);
return &iter->range;
}
const struct lsquic_packno_range *
lsquic_trechist_next (void *iter_p)
{
struct trechist_iter *const iter = iter_p;
if (iter->next == 0)
return NULL;
iter->range.low = iter->elems[iter->next].te_low;
iter->range.high = TE_HIGH(&iter->elems[iter->next]);
iter->next = iter->elems[iter->next].te_next;
return &iter->range;
}
int
lsquic_trechist_copy_ranges (trechist_mask_t *mask,
struct trechist_elem *elems, void *src_rechist,
const struct lsquic_packno_range * (*first) (void *),
const struct lsquic_packno_range * (*next) (void *))
{
const struct lsquic_packno_range *range;
struct trechist_elem *el;
unsigned i;
for (el = NULL, i = 0, range = first(src_rechist);
i < TRECHIST_MAX_RANGES && range;
range = next(src_rechist), ++i)
{
/* This should never happen: */
assert(range->high - range->low + 1 <= UINT_MAX);
el = &elems[i];
el->te_low = range->low;
el->te_count = range->high - range->low + 1;
el->te_next = i + 1;
}
if (!range && el)
{
el->te_next = 0;
*mask = (1u << i) - 1;
return 0;
}
else if (!el)
{
*mask = 0;
return 0; /* Must have been an empty */
}
else
return -1;
}
int
lsquic_trechist_contains (trechist_mask_t mask,
const struct trechist_elem *elems, uint32_t packno)
{
const struct trechist_elem *el;
if (mask == 0)
return 0;
el = &elems[0];
while (1)
{
if (packno > TE_HIGH(el))
return 0;
if (packno >= el->te_low)
return 1;
if (el->te_next)
el = &elems[el->te_next];
else
break;
}
return 0;
}
uint32_t
lsquic_trechist_max (trechist_mask_t mask, const struct trechist_elem *elems)
{
if (mask)
{
assert(mask & 1);
return TE_HIGH(&elems[0]);
}
else
return 0;
}

View File

@ -0,0 +1,74 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* Tiny receive history. It is used in IETF mini connection, where we want
* to use as little memory as possible. This data structure is an array of
* packet ranges. Each packet range is six bytes. This is possible because
* initial packets must not be wider than four bytes.
*
* Another limitation of this history is that it never shrinks. (Although
* technically is is possible to implement.
*/
#ifndef LSQUIC_TRECHIST
#define LSQUIC_TRECHIST 1
struct lsquic_packno_range;
/* This value could be as large as 32, which is how many bits wide
* trechist_mask_t is. The other limit on the number of ranges is
* UCHAR_MAX, which is how many different values can fit into te_next.
*/
#define TRECHIST_MAX_RANGES 16
struct trechist_elem
{
uint32_t te_low;
unsigned char te_count;
unsigned char te_next; /* 0 means no next element */
};
#define TE_HIGH(te_) ((te_)->te_low + (te_)->te_count - 1)
#define TRECHIST_SIZE (TRECHIST_MAX_RANGES * sizeof(struct trechist_elem))
/* There are two parts to this: the array of trechist_elem's and the bitmask
* that tracks which elements are used. The smallest range must always be at
* offset zero.
*/
typedef uint32_t trechist_mask_t;
int
lsquic_trechist_insert (trechist_mask_t *, struct trechist_elem *, uint32_t);
struct trechist_iter {
struct lsquic_packno_range range;
const struct trechist_elem *elems;
trechist_mask_t mask;
unsigned char next;
};
void
lsquic_trechist_iter (struct trechist_iter *iter, trechist_mask_t mask,
const struct trechist_elem *);
/* Don't modify history while iterating */
const struct lsquic_packno_range *
lsquic_trechist_first (void *iter);
const struct lsquic_packno_range *
lsquic_trechist_next (void *iter);
int
lsquic_trechist_copy_ranges (trechist_mask_t *mask /* This gets overwritten */,
struct trechist_elem *elems, void *src_rechist,
const struct lsquic_packno_range * (*first) (void *),
const struct lsquic_packno_range * (*next) (void *));
int
lsquic_trechist_contains (trechist_mask_t mask,
const struct trechist_elem *elems, uint32_t packno);
uint32_t
lsquic_trechist_max (trechist_mask_t mask, const struct trechist_elem *elems);
#endif

View File

@ -46,6 +46,7 @@ SET(TESTS
goaway_gquic_be
h3_framing
hkdf
hpi
lsquic_hash
packet_out
packet_resize
@ -129,3 +130,6 @@ ADD_TEST(minmax test_minmax)
ADD_EXECUTABLE(test_rechist test_rechist.c ../src/liblsquic/lsquic_rechist.c)
ADD_TEST(rechist test_rechist)
ADD_EXECUTABLE(test_trechist test_trechist.c ../src/liblsquic/lsquic_trechist.c)
ADD_TEST(trechist test_trechist)

View File

@ -1151,6 +1151,17 @@ fuzz_guided_pwritev_testing (const char *input)
}
static unsigned
count_hq_frames (const struct lsquic_stream *stream)
{
const struct stream_hq_frame *shf;
unsigned n_frames = 0;
STAILQ_FOREACH(shf, &stream->sm_hq_frames, shf_next)
++n_frames;
return n_frames;
}
static void
test_frame_header_split (unsigned n_packets, unsigned extra_sz,
int add_one_more)
@ -1162,6 +1173,7 @@ test_frame_header_split (unsigned n_packets, unsigned extra_sz,
unsigned char *buf_in, *buf_out;
const unsigned wsize = 70;
const size_t buf_in_sz = wsize, buf_out_sz = 0x500000;
unsigned n_frames;
struct lsxpack_header header = { XHDR(":method", "GET") };
struct lsquic_http_headers headers = { 1, &header, };
@ -1210,7 +1222,12 @@ test_frame_header_split (unsigned n_packets, unsigned extra_sz,
const ssize_t w = lsquic_stream_write(stream, buf_in, buf_in_sz);
assert(w >= 0 && (size_t) w == buf_in_sz);
n_frames = count_hq_frames(stream);
assert(n_frames == 1 + (w > 0));
lsquic_stream_flush(stream);
n_frames = count_hq_frames(stream);
assert(n_frames == !!stream->sm_n_buffered);
if (add_one_more)
{

View File

@ -18,6 +18,11 @@ struct test
{
int lineno;
enum {
TEST_NO_FLAGS = 0,
TEST_NUL_OUT_LEST_FULL = 1 << 0,
} flags;
unsigned char input[0x100];
size_t input_sz;
@ -30,6 +35,7 @@ static const struct test tests[] =
{
{
__LINE__,
TEST_NO_FLAGS,
{
0x03,
0x04,
@ -42,6 +48,7 @@ static const struct test tests[] =
{
__LINE__,
TEST_NO_FLAGS,
{
HQFT_MAX_PUSH_ID,
0x02,
@ -54,6 +61,7 @@ static const struct test tests[] =
{
__LINE__,
TEST_NO_FLAGS,
{
HQFT_SETTINGS,
0x00,
@ -65,6 +73,7 @@ static const struct test tests[] =
{ /* Frame contents do not match frame length */
__LINE__,
TEST_NO_FLAGS,
{
HQFT_MAX_PUSH_ID,
0x03,
@ -77,6 +86,7 @@ static const struct test tests[] =
{
__LINE__,
TEST_NO_FLAGS,
{
HQFT_SETTINGS,
13,
@ -93,6 +103,58 @@ static const struct test tests[] =
,
},
{
__LINE__,
TEST_NO_FLAGS,
{
0x80, 0x0F, 0x07, 0x00, /* HQFT_PRIORITY_UPDATE_STREAM */
7,
0x52, 0x34,
0x41, 0x42, 0x43, 0x44, 0x45, /* ABCDE */
HQFT_MAX_PUSH_ID,
0x02,
0x41, 0x23,
},
16,
0,
"on_priority_update: stream=0x1234, [ABCDE]\n"
"on_max_push_id: 291\n"
,
},
{
__LINE__,
TEST_NO_FLAGS,
{
0x80, 0x0F, 0x07, 0x01, /* HQFT_PRIORITY_UPDATE_PUSH */
6,
0x08,
0x50, 0x51, 0x52, 0x53, 0x54, /* PQRST */
},
11,
0,
"on_priority_update: push=0x8, [PQRST]\n"
,
},
{
__LINE__,
TEST_NUL_OUT_LEST_FULL,
{
0x80, 0x0F, 0x07, 0x01, /* HQFT_PRIORITY_UPDATE_PUSH */
21,
0x08,
0x50, 0x51, 0x52, 0x53, 0x54, /* PQRST */
0x50, 0x51, 0x52, 0x53, 0x54, /* PQRST */
0x50, 0x51, 0x52, 0x53, 0x54, /* PQRST */
0x50, 0x51, 0x52, 0x53, 0x54, /* PQRST */
},
26,
0,
"on_priority_update: push=0x8, [PQRSTPQRSTPQRSTPQRST]\n"
,
},
};
@ -132,6 +194,24 @@ on_unexpected_frame (void *ctx, uint64_t frame_type)
fprintf(ctx, "%s: %"PRIu64"\n", __func__, frame_type);
}
static void
on_priority_update (void *ctx, enum hq_frame_type frame_type,
/* PFV: Priority Field Value */
uint64_t id, const char *pfv, size_t pfv_sz)
{
const char *type;
switch (frame_type)
{
case HQFT_PRIORITY_UPDATE_STREAM: type = "stream"; break;
case HQFT_PRIORITY_UPDATE_PUSH: type = "push"; break;
default: assert(0); return;
}
fprintf(ctx, "%s: %s=0x%"PRIX64", [%.*s]\n", __func__, type, id,
(int) pfv_sz, pfv);
}
static const struct hcsi_callbacks callbacks =
{
.on_cancel_push = on_cancel_push,
@ -140,6 +220,7 @@ static const struct hcsi_callbacks callbacks =
.on_setting = on_setting,
.on_goaway = on_goaway,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update,
};
@ -167,7 +248,7 @@ run_test (const struct test *test)
struct lsquic_conn lconn = LSCONN_INITIALIZER_CIDLEN(lconn, 0);
lconn.cn_if = &conn_iface;
for (read_sz = 1; read_sz < test->input_sz; ++read_sz)
for (read_sz = 1; read_sz <= test->input_sz; ++read_sz)
{
out_f = open_memstream(&output, &out_sz);
lsquic_hcsi_reader_init(&reader, &lconn, &callbacks, out_f);
@ -188,7 +269,11 @@ run_test (const struct test *test)
assert(s == test->retval);
fclose(out_f);
assert(0 == strcmp(test->output, output));
if (test->retval == 0 && read_sz < test->input_sz
&& (test->flags & TEST_NUL_OUT_LEST_FULL))
assert(0 == strcmp(output, ""));
else
assert(0 == strcmp(test->output, output));
free(output);
}
}
@ -204,7 +289,7 @@ main (void)
memset(&coalesced_test, 0, sizeof(coalesced_test));
for (test = tests; test < tests + sizeof(tests) / sizeof(tests[0]); ++test)
if (test->retval == 0)
if (test->retval == 0 && !(test->flags & TEST_NUL_OUT_LEST_FULL))
{
memcpy(coalesced_test.input + coalesced_test.input_sz,
test->input, test->input_sz);

351
tests/test_hpi.c Normal file
View File

@ -0,0 +1,351 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include "lsquic.h"
#include "lsquic_int_types.h"
#include "lsquic_packet_common.h"
#include "lsquic_packet_in.h"
#include "lsquic_conn_flow.h"
#include "lsquic_sfcw.h"
#include "lsquic_varint.h"
#include "lsquic_hq.h"
#include "lsquic_hash.h"
#include "lsquic_conn.h"
#include "lsquic_stream.h"
#include "lsquic_types.h"
#include "lsquic_rtt.h"
#include "lsquic_conn_flow.h"
#include "lsquic_conn_public.h"
#include "lsquic_mm.h"
#include "lsquic_min_heap.h"
#include "lsquic_hpi.h"
#include "lsquic_logger.h"
/*
* DSL:
*
* S\d+:\d+:\d+ Create stream and insert it into list. The three numbers
* are stream ID, priority, and incremental boolean flag.
* Priority can be negative, which indicates a critical
* stream. Otherwise, the priorirty should be in the range
* [0, MAX_HTTP_PRIORITY]. The incremental flag is ignored
* for critical streams.
*
* F\d+ Use filter identified by the number. For "F" to take
* effect, it should be called before "I".
*
* I Initialize the iterator.
*
* D[hH] Call drop high (h) or drop non-high (H).
*
* N\d+ Call "next" and verify that the stream ID is this number.
* If "next" returns NULL, the test fails.
*
*/
static const struct test_spec {
int lineno;
const char *prog;
} test_specs[] = {
{ __LINE__,
"S0:3:0;"
"I;"
"N0;"
},
{ __LINE__,
/* Insert non-incremental streams with same priority, check that
* they come back out in the order of stream IDs.
*/
"S1:3:0;" "S0:3:0;" "S2:3:0;"
"I;"
"N0;" "N1;" "N2;"
},
{ __LINE__,
/* Insert incremental streams with same priority, check that they
* come back out in the same order.
*/
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"I;"
"N1;" "N0;" "N2;"
},
{ __LINE__,
/* Insert incremental streams with same priority, filter out out odd
* IDs, check that they come back out in the same order and without
* the odd stream ID"
*/
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"F;"
"I;"
"N0;" "N2;"
},
{ __LINE__,
/* Insert incremental and non-incremental streams with same priority.
* Check that non-incrementals are returned first.
*/
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"S6:3:0;" "S10:3:0;" "S3:3:0;"
"I;"
"N3;N6;N10;"
"N1;N0;N2;"
},
{ __LINE__,
/* Drop high with same priority: nothing should be dropped */
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"I;"
"Dh;"
"N1;" "N0;" "N2;"
},
{ __LINE__,
/* Drop non-high with same priority: nothing should be dropped */
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"I;"
"DH;"
"N1;" "N0;" "N2;"
},
{ __LINE__,
/* Drop high with same priority: drop non-incrementals */
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"S6:3:0;" "S10:3:0;" "S3:3:0;"
"I;"
"Dh;"
"N1;" "N0;" "N2;"
},
{ __LINE__,
/* Drop non-high with same priority: drop incrementals */
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"S6:3:0;" "S10:3:0;" "S3:3:0;"
"I;"
"DH;"
"N3;N6;N10;"
},
{ __LINE__,
/* Insert streams with different priorities */
"S1:1:1;" "S2:2:1;" "S3:3:1;"
"S6:6:0;" "S5:5:0;" "S4:4:0;"
"I;"
"N1;N2;N3;N4;N5;N6;"
},
{ __LINE__,
/* Insert regular and critical streams */
"S1:1:1;" "S2:2:1;" "S3333:-1:1;"
"S6:6:0;" "S2222:-1:0;" "S4:4:0;"
"I;"
"N3333;N2222;N1;N2;N4;N6;"
},
{ __LINE__,
/* Insert regular and critical streams; drop high */
"S1:1:1;" "S2:2:1;" "S3333:-1:1;"
"S6:6:0;" "S2222:-1:0;" "S4:4:0;"
"I;"
"Dh;"
"N1;N2;N4;N6;"
},
{ __LINE__,
/* Insert regular and critical streams; drop non-high */
"S1:1:1;" "S2:2:1;" "S3333:-1:1;"
"S6:6:0;" "S2222:-1:0;" "S4:4:0;"
"I;"
"DH;"
"N3333;N2222;"
},
{ __LINE__,
/* Insert streams with different priorities, non-incremental,
* several per bucket.
*/
"S1:1:0;" "S4:2:0;" "S3:2:0;"
"S6:1:0;" "S5:1:0;" "S2:2:0;"
"I;"
"N1;N5;N6;N2;N3;N4;"
},
};
/* Sharing the same HPI tests safety of reusing the same iterator object
* (no need to deinitialize it).
*/
static struct http_prio_iter hpi;
static struct lsquic_conn lconn = LSCONN_INITIALIZER_CIDLEN(lconn, 0);
static struct lsquic_conn_public conn_pub = { .lconn = &lconn, };
static struct lsquic_stream *
new_stream (lsquic_stream_id_t stream_id, int priority, int incr)
{
struct lsquic_stream *stream = calloc(1, sizeof(*stream));
stream->id = stream_id;
if (priority >= 0)
{
stream->sm_priority = priority;
if (incr)
stream->sm_bflags |= SMBF_INCREMENTAL;
}
else
stream->sm_bflags |= SMBF_CRITICAL;
/* Critical streams are never incremental */
return stream;
}
#define MAGIC 0x12312312U
struct my_filter_ctx
{
unsigned magic;
};
static int
filter_out_odd_stream_ids (void *ctx, struct lsquic_stream *stream)
{
struct my_filter_ctx *fctx = ctx;
assert(fctx->magic == MAGIC);
return 0 == (stream->id & 1);
}
static int (*const filters[])(void *, struct lsquic_stream *) = {
filter_out_odd_stream_ids,
};
/* Just pick one (as long as it's not next_prio_stream) */
#define next_field next_write_stream
static void
run_test (const struct test_spec *spec)
{
struct lsquic_streams_tailq streams;
struct lsquic_stream *stream;
lsquic_stream_id_t stream_id;
long incr, priority, tmp;
const char *pc;
char cmd;
char name[20];
struct my_filter_ctx filter_ctx = { MAGIC };
int (*filter_cb)(void *, struct lsquic_stream *) = NULL;
int first_called = 0;
struct lsquic_mm mm;
lsquic_mm_init(&mm);
conn_pub.mm = &mm;
TAILQ_INIT(&streams);
snprintf(name, sizeof(name), "line-%d", spec->lineno);
for (pc = spec->prog; *pc; ++pc)
{
cmd = *pc++;
switch (cmd)
{
case 'S':
stream_id = strtol(pc, (char **) &pc, 10);
assert(':' == *pc);
priority = strtol(pc + 1, (char **) &pc, 10);
assert(':' == *pc);
incr = strtol(pc + 1, (char **) &pc, 10);
stream = new_stream(stream_id, priority, incr);
TAILQ_INSERT_TAIL(&streams, stream, next_field);
break;
case 'I':
lsquic_hpi_init(&hpi, TAILQ_FIRST(&streams),
TAILQ_LAST(&streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_field),
&conn_pub, name, filter_cb, &filter_ctx);
break;
case 'N':
stream_id = strtol(pc, (char **) &pc, 10);
if (first_called)
stream = lsquic_hpi_next(&hpi);
else
{
stream = lsquic_hpi_first(&hpi);
first_called = 1;
}
assert(stream);
assert(stream->id == stream_id);
break;
case 'F':
tmp = strtol(pc, (char **) &pc, 10);
assert(tmp >= 0
&& (size_t) tmp < sizeof(filters) / sizeof(filters[0]));
filter_cb = filters[tmp];
break;
case 'D':
switch (*pc++)
{
case 'h':
lsquic_hpi_drop_high(&hpi);
break;
case 'H':
lsquic_hpi_drop_non_high(&hpi);
break;
default:
assert(0);
break;
}
break;
default:
assert(0);
}
assert(*pc == ';');
}
lsquic_hpi_cleanup(&hpi);
while (stream = TAILQ_FIRST(&streams), stream != NULL)
{
TAILQ_REMOVE(&streams, stream, next_field);
free(stream);
}
lsquic_mm_cleanup(&mm);
}
int
main (int argc, char **argv)
{
unsigned n;
lsquic_log_to_fstream(stderr, LLTS_NONE);
lsq_log_levels[LSQLM_HPI] = LSQ_LOG_DEBUG;
for (n = 0; n < sizeof(test_specs) / sizeof(test_specs[0]); ++n)
run_test(&test_specs[n]);
lsquic_hpi_set_heap_test(LSQUIC_HPI_HEAP_TEST_STACK_OK);
for (n = 0; n < sizeof(test_specs) / sizeof(test_specs[0]); ++n)
run_test(&test_specs[n]);
lsquic_hpi_set_heap_test(LSQUIC_HPI_HEAP_TEST_4K_OK);
for (n = 0; n < sizeof(test_specs) / sizeof(test_specs[0]); ++n)
run_test(&test_specs[n]);
lsquic_hpi_set_heap_test(0);
for (n = 0; n < sizeof(test_specs) / sizeof(test_specs[0]); ++n)
run_test(&test_specs[n]);
return 0;
}

View File

@ -22,6 +22,8 @@
#include "lsquic_conn.h"
#include "lsquic_stream.h"
#include "lsquic_types.h"
#include "lsquic_rtt.h"
#include "lsquic_conn_public.h"
#include "lsquic_spi.h"
#include "lsquic_logger.h"
@ -33,6 +35,8 @@ static struct stream_prio_iter spi;
static struct lsquic_conn lconn = LSCONN_INITIALIZER_CIDLEN(lconn, 0);
static struct lsquic_conn_public conn_pub = { .lconn = &lconn, };
static lsquic_stream_t *
new_stream (unsigned priority)
@ -73,7 +77,7 @@ test_same_priority (unsigned priority)
lsquic_spi_init(&spi, TAILQ_FIRST(&streams),
TAILQ_LAST(&streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_write_stream),
&lconn, __func__, NULL, NULL);
&conn_pub, __func__, NULL, NULL);
stream = lsquic_spi_first(&spi);
assert(stream == stream_arr[0]);
@ -89,7 +93,7 @@ test_same_priority (unsigned priority)
/* Test reinitialization: */
lsquic_spi_init(&spi, stream_arr[0], stream_arr[1],
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_write_stream),
&lconn, __func__, NULL, NULL);
&conn_pub, __func__, NULL, NULL);
stream = lsquic_spi_first(&spi);
assert(stream == stream_arr[0]);
stream = lsquic_spi_next(&spi);
@ -121,7 +125,7 @@ test_different_priorities (int *priority)
lsquic_spi_init(&spi, TAILQ_FIRST(&streams),
TAILQ_LAST(&streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_send_stream),
&lconn, __func__, NULL, NULL);
&conn_pub, __func__, NULL, NULL);
for (prev_prio = -1, count = 0, stream = lsquic_spi_first(&spi); stream;
stream = lsquic_spi_next(&spi), ++count)
{
@ -214,7 +218,7 @@ test_drop (const struct drop_test *test)
lsquic_spi_init(&spi, TAILQ_FIRST(&streams),
TAILQ_LAST(&streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_write_stream),
&lconn, __func__, NULL, NULL);
&conn_pub, __func__, NULL, NULL);
if (drop_high)
lsquic_spi_drop_high(&spi);
@ -275,7 +279,7 @@ test_different_priorities_filter_odd (int *priority)
lsquic_spi_init(&spi, TAILQ_FIRST(&streams),
TAILQ_LAST(&streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_send_stream),
&lconn, __func__, filter_out_odd_priorities, &my_filter_ctx);
&conn_pub, __func__, filter_out_odd_priorities, &my_filter_ctx);
for (prev_prio = -1, count = 0, stream = lsquic_spi_first(&spi); stream;
stream = lsquic_spi_next(&spi), ++count)

362
tests/test_trechist.c Normal file
View File

@ -0,0 +1,362 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/* Tests based on rechist tests */
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#include "vc_compat.h"
#endif
#include "lsquic_int_types.h"
#include "lsquic_trechist.h"
static void
test_clone (trechist_mask_t src_mask, struct trechist_elem *src_elems)
{
trechist_mask_t hist_mask;
struct trechist_elem *hist_elems;
const struct lsquic_packno_range *ranges[2];
struct trechist_iter iters[2];
int s;
hist_elems = malloc(sizeof(hist_elems[0]) * TRECHIST_MAX_RANGES);
lsquic_trechist_iter(&iters[0], src_mask, src_elems);
s = lsquic_trechist_copy_ranges(&hist_mask, hist_elems, &iters[0],
lsquic_trechist_first, lsquic_trechist_next);
assert(s == 0);
lsquic_trechist_iter(&iters[0], src_mask, src_elems);
lsquic_trechist_iter(&iters[1], hist_mask, hist_elems);
for (ranges[0] = lsquic_trechist_first(&iters[0]),
ranges[1] = lsquic_trechist_first(&iters[1]);
ranges[0] && ranges[1];
ranges[0] = lsquic_trechist_next(&iters[0]),
ranges[1] = lsquic_trechist_next(&iters[1]))
{
assert(ranges[0]->low == ranges[1]->low);
assert(ranges[0]->high == ranges[1]->high);
}
assert(!ranges[0] && !ranges[1]);
free(hist_elems);
}
static void
test4 (void)
{
trechist_mask_t hist_mask;
struct trechist_elem *hist_elems;
const struct lsquic_packno_range *range;
struct trechist_iter iter;
lsquic_packno_t packno;
hist_elems = malloc(sizeof(hist_elems[0]) * TRECHIST_MAX_RANGES);
hist_mask = 0;
test_clone(hist_mask, hist_elems);
for (packno = 11917; packno <= 11941; ++packno)
lsquic_trechist_insert(&hist_mask, hist_elems, packno);
for (packno = 11946; packno <= 11994; ++packno)
lsquic_trechist_insert(&hist_mask, hist_elems, packno);
test_clone(hist_mask, hist_elems);
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(range);
assert(range->high == 11994);
assert(range->low == 11946);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_trechist_next(&iter);
assert(!range);
lsquic_trechist_insert(&hist_mask, hist_elems, 11995);
lsquic_trechist_insert(&hist_mask, hist_elems, 11996);
test_clone(hist_mask, hist_elems);
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(range);
assert(range->high == 11996);
assert(range->low == 11946);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_trechist_next(&iter);
assert(!range);
test_clone(hist_mask, hist_elems);
lsquic_trechist_insert(&hist_mask, hist_elems, 11912);
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(range);
assert(range->high == 11996);
assert(range->low == 11946);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11912);
assert(range->low == 11912);
range = lsquic_trechist_next(&iter);
assert(!range);
for (packno = 12169; packno <= 12193; ++packno)
lsquic_trechist_insert(&hist_mask, hist_elems, packno);
test_clone(hist_mask, hist_elems);
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(range);
assert(range->high == 12193);
assert(range->low == 12169);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11996);
assert(range->low == 11946);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11912);
assert(range->low == 11912);
range = lsquic_trechist_next(&iter);
assert(!range);
test_clone(hist_mask, hist_elems);
free(hist_elems);
}
static void
rechist2str (trechist_mask_t hist_mask, const struct trechist_elem *hist_elems,
char *buf, size_t bufsz)
{
const struct lsquic_packno_range *range;
struct trechist_iter iter;
size_t off;
int n;
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
for (off = 0, range = lsquic_trechist_first(&iter);
range && off < bufsz;
off += n, range = lsquic_trechist_next(&iter))
{
n = snprintf(buf + off, bufsz - off, "[%"PRIu64"-%"PRIu64"]",
range->high, range->low);
if (n < 0 || (size_t) n >= bufsz - off)
break;
}
}
static void
test5 (void)
{
trechist_mask_t hist_mask;
struct trechist_elem *hist_elems;
char buf[100];
hist_elems = malloc(sizeof(hist_elems[0]) * TRECHIST_MAX_RANGES);
hist_mask = 0;
lsquic_trechist_insert(&hist_mask, hist_elems, 1);
/* Packet 2 omitted because it could not be decrypted */
lsquic_trechist_insert(&hist_mask, hist_elems, 3);
lsquic_trechist_insert(&hist_mask, hist_elems, 12);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][3-3][1-1]"));
lsquic_trechist_insert(&hist_mask, hist_elems, 4);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][4-3][1-1]"));
lsquic_trechist_insert(&hist_mask, hist_elems, 10);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-10][4-3][1-1]"));
lsquic_trechist_insert(&hist_mask, hist_elems, 6);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-10][6-6][4-3][1-1]"));
lsquic_trechist_insert(&hist_mask, hist_elems, 7);
lsquic_trechist_insert(&hist_mask, hist_elems, 8);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-10][8-6][4-3][1-1]"));
test_clone(hist_mask, hist_elems);
assert(!(lsquic_trechist_contains(hist_mask, hist_elems, 0)));
assert(!(lsquic_trechist_contains(hist_mask, hist_elems, 9)));
assert(!(lsquic_trechist_contains(hist_mask, hist_elems, 20)));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 4));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 1));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 7));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 8));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 6));
lsquic_trechist_insert(&hist_mask, hist_elems, 9);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-6][4-3][1-1]"));
test_clone(hist_mask, hist_elems);
lsquic_trechist_insert(&hist_mask, hist_elems, 5);
lsquic_trechist_insert(&hist_mask, hist_elems, 11);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-3][1-1]"));
free(hist_elems);
}
static void
basic_test (void)
{
trechist_mask_t hist_mask;
struct trechist_elem *hist_elems;
const struct lsquic_packno_range *range;
struct trechist_iter iter;
unsigned i;
int s;
hist_elems = malloc(sizeof(hist_elems[0]) * TRECHIST_MAX_RANGES);
hist_mask = 0;
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(!range);
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1);
assert(("inserting packet number one is successful", 0 == s));
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1);
assert(("inserting packet number one again results in duplicate error",
s == 1));
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(("first range returned correctly", range));
assert(("first range low value checks out", range->low == 1));
assert(("first range high value checks out", range->high == 1));
range = lsquic_trechist_next(&iter);
assert(!range);
assert(("second range does not exist", !range));
for (i = 3; i <= 5; ++i)
{
s = lsquic_trechist_insert(&hist_mask, hist_elems, i);
assert(("inserting packet", s == 0));
}
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(("first range returned correctly", range));
assert(("first range low value checks out", range->low == 3));
assert(("first range high value checks out", range->high == 5));
assert(!(lsquic_trechist_contains(hist_mask, hist_elems, 7)));
assert(!(lsquic_trechist_contains(hist_mask, hist_elems, 2)));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 4));
range = lsquic_trechist_next(&iter);
assert(("second range returned correctly", range));
assert(("second range low value checks out", range->low == 1));
assert(("second range high value checks out", range->high == 1));
range = lsquic_trechist_next(&iter);
assert(("third range does not exist", !range));
assert(5 == lsquic_trechist_max(hist_mask, hist_elems));
s = lsquic_trechist_insert(&hist_mask, hist_elems, 10);
assert(("inserting packet", s == 0));
assert(10 == lsquic_trechist_max(hist_mask, hist_elems));
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(("first range returned correctly", range));
assert(("first range low value checks out", range->low == 10));
assert(("first range high value checks out", range->high == 10));
test_clone(hist_mask, hist_elems);
s = lsquic_trechist_insert(&hist_mask, hist_elems, 8);
assert(("inserting packet", s == 0));
s = lsquic_trechist_insert(&hist_mask, hist_elems, 9);
assert(("inserting packet", s == 0));
/* Check merge */
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(("first range returned correctly", range));
assert(("first range low value checks out", range->low == 8));
assert(("first range high value checks out", range->high == 10));
free(hist_elems);
}
static void
test_limits (void)
{
trechist_mask_t hist_mask;
struct trechist_elem *hist_elems;
unsigned i;
int s;
hist_elems = malloc(sizeof(hist_elems[0]) * TRECHIST_MAX_RANGES);
hist_mask = 0;
for (i = 1; i <= UCHAR_MAX; ++i)
{
s = lsquic_trechist_insert(&hist_mask, hist_elems, i);
assert(s == 0);
}
s = lsquic_trechist_insert(&hist_mask, hist_elems, i);
assert(s == -1); /* Overflow */
for (i = 0; i < TRECHIST_MAX_RANGES - 1; ++i)
{
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1000 + 2 * i);
assert(s == 0);
}
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1000 + 2 * i);
assert(s == -1); /* Out of ranges */
free(hist_elems);
}
int
main (void)
{
basic_test();
test4();
test5();
test_limits();
return 0;
}