From b8fa61956714b2a0329e819d33928f012f13db5a Mon Sep 17 00:00:00 2001 From: Dmitri Tikhonov Date: Wed, 29 Jul 2020 11:33:52 -0400 Subject: [PATCH] Release 2.19.0 - [FEATURE] DPLPMTUD support. IETF connections now search for the maximum packet size, improving throughput. - [DEBUG] Record event in stream history when on_close() is called in dtor. --- CHANGELOG | 7 + EXAMPLES.txt | 9 +- bin/prog.c | 4 +- bin/test_common.c | 55 +- docs/apiref.rst | 45 +- docs/conf.py | 4 +- include/lsquic.h | 65 +- src/liblsquic/CMakeLists.txt | 1 + src/liblsquic/lsquic_alarmset.c | 1 + src/liblsquic/lsquic_alarmset.h | 2 + src/liblsquic/lsquic_conn.c | 6 +- src/liblsquic/lsquic_conn.h | 14 +- src/liblsquic/lsquic_engine.c | 62 +- src/liblsquic/lsquic_engine_public.h | 1 + src/liblsquic/lsquic_full_conn.c | 21 +- src/liblsquic/lsquic_full_conn.h | 2 +- src/liblsquic/lsquic_full_conn_ietf.c | 481 +++++++- src/liblsquic/lsquic_logger.c | 2 + src/liblsquic/lsquic_logger.h | 1 + src/liblsquic/lsquic_mini_conn.c | 4 +- src/liblsquic/lsquic_mini_conn_ietf.c | 18 +- src/liblsquic/lsquic_mm.c | 8 +- src/liblsquic/lsquic_mm.h | 2 +- src/liblsquic/lsquic_pacer.c | 10 + src/liblsquic/lsquic_pacer.h | 4 + src/liblsquic/lsquic_packet_gquic.h | 3 + src/liblsquic/lsquic_packet_out.c | 644 +++------- src/liblsquic/lsquic_packet_out.h | 98 +- src/liblsquic/lsquic_packet_resize.c | 258 ++++ src/liblsquic/lsquic_packet_resize.h | 53 + src/liblsquic/lsquic_parse.h | 19 +- src/liblsquic/lsquic_parse_Q046.c | 3 +- src/liblsquic/lsquic_parse_Q050.c | 7 +- src/liblsquic/lsquic_parse_gquic_be.c | 3 +- src/liblsquic/lsquic_parse_ietf.h | 4 +- src/liblsquic/lsquic_parse_ietf_v1.c | 14 +- src/liblsquic/lsquic_send_ctl.c | 557 +++++++-- src/liblsquic/lsquic_send_ctl.h | 12 + src/liblsquic/lsquic_stream.c | 15 +- tests/CMakeLists.txt | 1 + tests/test_crypto_gen.c | 8 +- tests/test_elision.c | 52 +- tests/test_h3_framing.c | 30 +- tests/test_packet_out.c | 64 +- tests/test_packet_resize.c | 1640 +++++++++++++++++++++++++ tests/test_stream.c | 169 ++- 46 files changed, 3629 insertions(+), 854 deletions(-) create mode 100644 src/liblsquic/lsquic_packet_resize.c create mode 100644 src/liblsquic/lsquic_packet_resize.h create mode 100644 tests/test_packet_resize.c diff --git a/CHANGELOG b/CHANGELOG index 14dbe7c..e2a7b0a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +2020-07-29 + - 2.19.0 + - [FEATURE] DPLPMTUD support. IETF connections now search for the + maximum packet size, improving throughput. + - [DEBUG] Record event in stream history when on_close() is called + in dtor. + 2020-07-22 - 2.18.2 - [BUGFIX] Send prediction: lone path challenges do not get squeezed out diff --git a/EXAMPLES.txt b/EXAMPLES.txt index 2ed8288..c9cb260 100644 --- a/EXAMPLES.txt +++ b/EXAMPLES.txt @@ -105,9 +105,8 @@ Compilation - Build both the http_client and http_server test programs. Running Instructions - - On the server side, define the environment variable - LSQUIC_CN_PACK_SIZE and set it to the intended packet size. - Valid sizes are up to 65507 (IPv4). + - Specify maximum packet size using -o base_plpmtu=$number. + Valid sizes are up to 65535. - Use the -W flag for http_client and http_server for the ability to send packets of large size. - On the client side, use the -z flag to specify the maximum size @@ -115,9 +114,9 @@ Running Instructions Example Usage ./http_client -p /file-1M -H www.litespeedtech.com -s 192.168.0.85:5443 - -o version=FF000014 -z 65507 -W + -o version=FF000014 ./http_server -c www.litespeedtech.com,certschain,privkey - -s 0.0.0.0:5443 -W + -s 0.0.0.0:5443 -W -o base_plpmtu=65535 Additional Notes Since this feature does not have MTU discovery enabled at the time of diff --git a/bin/prog.c b/bin/prog.c index b2cc044..c42cb29 100644 --- a/bin/prog.c +++ b/bin/prog.c @@ -140,8 +140,8 @@ prog_print_common_options (const struct prog *prog, FILE *out) #if LSQUIC_DONTFRAG_SUPPORTED " -D Do not set `do not fragment' flag on outgoing UDP packets\n" #endif -" -z BYTES Maximum size of outgoing UDP packets. The default is 1370\n" -" bytes for IPv4 socket and 1350 bytes for IPv6 socket\n" +" -z BYTES Maximum size of outgoing UDP packets (client only).\n" +" Overrides -o base_plpmtu.\n" " -L LEVEL Log level for all modules. Possible values are `debug',\n" " `info', `notice', `warn', `error', `alert', `emerg',\n" " and `crit'.\n" diff --git a/bin/test_common.c b/bin/test_common.c index 6d94dbc..7949b50 100644 --- a/bin/test_common.c +++ b/bin/test_common.c @@ -1816,6 +1816,11 @@ set_engine_option (struct lsquic_engine_settings *settings, settings->es_scid_len = atoi(val); return 0; } + if (0 == strncmp(name, "dplpmtud", 8)) + { + settings->es_dplpmtud = atoi(val); + return 0; + } break; case 9: if (0 == strncmp(name, "send_prst", 9)) @@ -1835,6 +1840,11 @@ set_engine_option (struct lsquic_engine_settings *settings, settings->es_timestamps = atoi(val); return 0; } + if (0 == strncmp(name, "max_plpmtu", 10)) + { + settings->es_max_plpmtu = atoi(val); + return 0; + } break; case 11: if (0 == strncmp(name, "ping_period", 11)) @@ -1842,6 +1852,11 @@ set_engine_option (struct lsquic_engine_settings *settings, settings->es_ping_period = atoi(val); return 0; } + if (0 == strncmp(name, "base_plpmtu", 11)) + { + settings->es_base_plpmtu = atoi(val); + return 0; + } break; case 12: if (0 == strncmp(name, "idle_conn_to", 12)) @@ -1975,7 +1990,7 @@ set_engine_option (struct lsquic_engine_settings *settings, } break; case 23: - if (0 == strncmp(name, "max_udp_payload_size_rx", 18)) + if (0 == strncmp(name, "max_udp_payload_size_rx", 23)) { settings->es_max_udp_payload_size_rx = atoi(val); return 0; @@ -2008,7 +2023,9 @@ set_engine_option (struct lsquic_engine_settings *settings, } -#define MAX_PACKOUT_BUF_SZ 1370 +/* So that largest allocation in PBA fits in 4KB */ +#define PBA_SIZE_MAX 0x1000 +#define PBA_SIZE_THRESH (PBA_SIZE_MAX - sizeof(uintptr_t)) struct packout_buf { @@ -2032,12 +2049,6 @@ pba_allocate (void *packout_buf_allocator, void *peer_ctx, unsigned short size, struct packout_buf_allocator *const pba = packout_buf_allocator; struct packout_buf *pb; - if (size > MAX_PACKOUT_BUF_SZ) - { - fprintf(stderr, "packout buf size too large: %hu", size); - abort(); - } - if (pba->max && pba->n_out >= pba->max) { LSQ_DEBUG("# outstanding packout bufs reached the limit of %u, " @@ -2047,16 +2058,24 @@ pba_allocate (void *packout_buf_allocator, void *peer_ctx, unsigned short size, #if LSQUIC_USE_POOLS pb = SLIST_FIRST(&pba->free_packout_bufs); - if (pb) + if (pb && size <= PBA_SIZE_THRESH) SLIST_REMOVE_HEAD(&pba->free_packout_bufs, next_free_pb); + else if (size <= PBA_SIZE_THRESH) + pb = malloc(PBA_SIZE_MAX); else + pb = malloc(sizeof(uintptr_t) + size); +#else + pb = malloc(sizeof(uintptr_t) + size); #endif - pb = malloc(MAX_PACKOUT_BUF_SZ); if (pb) + { + * (uintptr_t *) pb = size; ++pba->n_out; - - return pb; + return (uintptr_t *) pb + 1; + } + else + return NULL; } @@ -2064,12 +2083,16 @@ void pba_release (void *packout_buf_allocator, void *peer_ctx, void *obj, char ipv6) { struct packout_buf_allocator *const pba = packout_buf_allocator; + obj = (uintptr_t *) obj - 1; #if LSQUIC_USE_POOLS - struct packout_buf *const pb = obj; - SLIST_INSERT_HEAD(&pba->free_packout_bufs, pb, next_free_pb); -#else - free(obj); + if (* (uintptr_t *) obj <= PBA_SIZE_THRESH) + { + struct packout_buf *const pb = obj; + SLIST_INSERT_HEAD(&pba->free_packout_bufs, pb, next_free_pb); + } + else #endif + free(obj); --pba->n_out; } diff --git a/docs/apiref.rst b/docs/apiref.rst index 53f2869..3d09b37 100644 --- a/docs/apiref.rst +++ b/docs/apiref.rst @@ -739,6 +739,29 @@ settings structure: Default value is :macro:`LSQUIC_DF_MAX_UDP_PAYLOAD_SIZE_RX` + .. member:: int es_dplpmtud + + If set to true value, enable DPLPMTUD -- Datagram Packetization + Layer Path MTU Discovery. + + Default value is :macro:`LSQUIC_DF_DPLPMTUD` + + .. member:: unsigned short es_base_plpmtu + + PLPMTU size expected to work for most paths. + + If set to zero, this value is calculated based on QUIC and IP versions. + + Default value is :macro:`LSQUIC_DF_BASE_PLPMTU` + + .. member:: unsigned short es_max_plpmtu + + Largest PLPMTU size the engine will try. + + If set to zero, picking this value is left to the engine. + + Default value is :macro:`LSQUIC_DF_MAX_PLPMTU` + .. member:: unsigned es_noprogress_timeout No progress timeout. @@ -938,6 +961,18 @@ out of date. Please check your :file:`lsquic.h` for actual values.* By default, incoming packet size is not limited. +.. macro:: LSQUIC_DF_DPLPMTUD + + By default, DPLPMTUD is enabled + +.. macro:: LSQUIC_DF_BASE_PLPMTU + + By default, this value is left up to the engine. + +.. macro:: LSQUIC_DF_MAX_PLPMTU + + By default, this value is left up to the engine. + .. macro:: LSQUIC_DF_NOPROGRESS_TIMEOUT_SERVER By default, drop no-progress connections after 60 seconds on the server. @@ -1170,7 +1205,7 @@ callback. In client mode, a new connection is created by -.. function:: lsquic_conn_t * lsquic_engine_connect (lsquic_engine_t *engine, enum lsquic_version version, const struct sockaddr *local_sa, const struct sockaddr *peer_sa, void *peer_ctx, lsquic_conn_ctx_t *conn_ctx, const char *sni, unsigned short max_udp_payload_size, const unsigned char *sess_resume, size_t sess_resume_len, const unsigned char *token, size_t token_sz) +.. function:: lsquic_conn_t * lsquic_engine_connect (lsquic_engine_t *engine, enum lsquic_version version, const struct sockaddr *local_sa, const struct sockaddr *peer_sa, void *peer_ctx, lsquic_conn_ctx_t *conn_ctx, const char *sni, unsigned short base_plpmtu, const unsigned char *sess_resume, size_t sess_resume_len, const unsigned char *token, size_t token_sz) :param engine: Engine to use. @@ -1203,10 +1238,12 @@ In client mode, a new connection is created by The SNI is required for Google QUIC connections; it is optional for IETF QUIC and may be set to NULL. - :param max_udp_payload_size: + :param base_plpmtu: - Maximum packet size. If set to zero, it is inferred based on `peer_sa` - and `version`. + Base PLPMTU. If set to zero, it is selected based on the + engine settings (see + :member:`lsquic_engine_settings.es_base_plpmtu`), + QUIC version, and IP version. :param sess_resume: diff --git a/docs/conf.py b/docs/conf.py index d01fd6f..f82e1e0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,9 +24,9 @@ copyright = u'2020, LiteSpeed Technologies' author = u'LiteSpeed Technologies' # The short X.Y version -version = u'2.18' +version = u'2.19' # The full version, including alpha/beta/rc tags -release = u'2.18.2' +release = u'2.19.0' # -- General configuration --------------------------------------------------- diff --git a/include/lsquic.h b/include/lsquic.h index 854fa8b..36133e4 100644 --- a/include/lsquic.h +++ b/include/lsquic.h @@ -24,8 +24,8 @@ extern "C" { #endif #define LSQUIC_MAJOR_VERSION 2 -#define LSQUIC_MINOR_VERSION 18 -#define LSQUIC_PATCH_VERSION 2 +#define LSQUIC_MINOR_VERSION 19 +#define LSQUIC_PATCH_VERSION 0 /** * Engine flags: @@ -350,12 +350,24 @@ typedef struct ssl_ctx_st * (*lsquic_lookup_cert_f)( */ #define LSQUIC_DF_GREASE_QUIC_BIT 1 +/** By default, DPLPMTUD is enabled */ +#define LSQUIC_DF_DPLPMTUD 1 + +/** By default, this value is left up to the engine. */ +#define LSQUIC_DF_BASE_PLPMTU 0 + +/** By default, this value is left up to the engine. */ +#define LSQUIC_DF_MAX_PLPMTU 0 + /** By default, drop no-progress connections after 60 seconds on the server */ #define LSQUIC_DF_NOPROGRESS_TIMEOUT_SERVER 60 /** By default, do not use no-progress timeout on the client */ #define LSQUIC_DF_NOPROGRESS_TIMEOUT_CLIENT 0 +/** By default, we use the minimum timer of 1000 milliseconds */ +#define LSQUIC_DF_MTU_PROBE_TIMER 1000 + struct lsquic_engine_settings { /** * This is a bit mask wherein each bit corresponds to a value in @@ -818,6 +830,49 @@ struct lsquic_engine_settings { * Default value is @ref LSQUIC_DF_GREASE_QUIC_BIT */ int es_grease_quic_bit; + + /** + * If set to true value, enable DPLPMTUD -- Datagram Packetization + * Layer Path MTU Discovery. + * + * Default value is @ref LSQUIC_DF_DPLPMTUD + */ + int es_dplpmtud; + + /** + * PLPMTU size expected to work for most paths. + * + * If set to zero, this value is calculated based on QUIC and IP versions. + * + * Default value is @ref LSQUIC_DF_BASE_PLPMTU. + */ + unsigned short es_base_plpmtu; + + /** + * Largest PLPMTU size the engine will try. + * + * If set to zero, picking this value is left to the engine. + * + * Default value is @ref LSQUIC_DF_MAX_PLPMTU. + */ + unsigned short es_max_plpmtu; + + /** + * This value specifies how long the DPLPMTUD probe timer is, in + * milliseconds. [draft-ietf-tsvwg-datagram-plpmtud-17] says: + * + " PROBE_TIMER: The PROBE_TIMER is configured to expire after a period + " longer than the maximum time to receive an acknowledgment to a + " probe packet. This value MUST NOT be smaller than 1 second, and + " SHOULD be larger than 15 seconds. Guidance on selection of the + " timer value are provided in section 3.1.1 of the UDP Usage + " Guidelines [RFC8085]. + * + * If set to zero, the default is used. + * + * Default value is @ref LSQUIC_DF_MTU_PROBE_TIMER. + */ + unsigned es_mtu_probe_timer; }; /* Initialize `settings' to default values */ @@ -1146,15 +1201,15 @@ lsquic_engine_new (unsigned lsquic_engine_flags, * To let the engine specify QUIC version, use N_LSQVER. If session resumption * information is supplied, version is picked from there instead. * - * If `max_udp_payload_size' is set to zero, it is inferred based on `peer_sa': - * 1350 for IPv6 and 1370 for IPv4. + * If `base_plpmtu' is set to zero, it is selected based on the + * engine settings, QUIC version, and IP version. */ lsquic_conn_t * lsquic_engine_connect (lsquic_engine_t *, enum lsquic_version, const struct sockaddr *local_sa, const struct sockaddr *peer_sa, void *peer_ctx, lsquic_conn_ctx_t *conn_ctx, - const char *hostname, unsigned short max_udp_payload_size, + const char *hostname, unsigned short base_plpmtu, const unsigned char *sess_resume, size_t sess_resume_len, /** Resumption token: optional */ const unsigned char *token, size_t token_sz); diff --git a/src/liblsquic/CMakeLists.txt b/src/liblsquic/CMakeLists.txt index ff5c638..dd53951 100644 --- a/src/liblsquic/CMakeLists.txt +++ b/src/liblsquic/CMakeLists.txt @@ -48,6 +48,7 @@ SET(lsquic_STAT_SRCS 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 diff --git a/src/liblsquic/lsquic_alarmset.c b/src/liblsquic/lsquic_alarmset.c index c602510..e17776d 100644 --- a/src/liblsquic/lsquic_alarmset.c +++ b/src/liblsquic/lsquic_alarmset.c @@ -47,6 +47,7 @@ const char *const lsquic_alid2str[] = [AL_PATH_CHAL_1] = "PATH_CHAL_1", [AL_SESS_TICKET] = "SESS_TICKET", [AL_BLOCKED_KA] = "BLOCKED_KA", + [AL_MTU_PROBE] = "MTU_PROBE", }; diff --git a/src/liblsquic/lsquic_alarmset.h b/src/liblsquic/lsquic_alarmset.h index 6ed7dbb..9c047e0 100644 --- a/src/liblsquic/lsquic_alarmset.h +++ b/src/liblsquic/lsquic_alarmset.h @@ -26,6 +26,7 @@ enum alarm_id { AL_RETX_HSK = AL_RETX_INIT + PNS_HSK, AL_RETX_APP = AL_RETX_INIT + PNS_APP, AL_PING, + AL_MTU_PROBE, AL_IDLE, AL_ACK_APP, AL_RET_CIDS, @@ -57,6 +58,7 @@ enum alarm_id_bit { ALBIT_PATH_CHAL_3 = 1 << AL_PATH_CHAL_3, ALBIT_SESS_TICKET = 1 << AL_SESS_TICKET, ALBIT_BLOCKED_KA = 1 << AL_BLOCKED_KA, + ALBIT_MTU_PROBE = 1 << AL_MTU_PROBE, }; diff --git a/src/liblsquic/lsquic_conn.c b/src/liblsquic/lsquic_conn.c index ab74111..dd42a83 100644 --- a/src/liblsquic/lsquic_conn.c +++ b/src/liblsquic/lsquic_conn.c @@ -65,10 +65,10 @@ int lsquic_conn_copy_and_release_pi_data (const lsquic_conn_t *conn, struct lsquic_engine_public *enpub, lsquic_packet_in_t *packet_in) { + unsigned char *copy; + assert(!(packet_in->pi_flags & PI_OWN_DATA)); - /* The size should be guarded in lsquic_engine_packet_in(): */ - assert(packet_in->pi_data_sz <= GQUIC_MAX_PACKET_SZ); - unsigned char *const copy = lsquic_mm_get_packet_in_buf(&enpub->enp_mm, 1370); + copy = lsquic_mm_get_packet_in_buf(&enpub->enp_mm, packet_in->pi_data_sz); if (!copy) { LSQ_WARN("cannot allocate memory to copy incoming packet data"); diff --git a/src/liblsquic/lsquic_conn.h b/src/liblsquic/lsquic_conn.h index 76971aa..3bc0c1d 100644 --- a/src/liblsquic/lsquic_conn.h +++ b/src/liblsquic/lsquic_conn.h @@ -79,7 +79,7 @@ struct network_path void *np_peer_ctx; lsquic_cid_t np_dcid; unsigned short np_pack_size; - unsigned char np_path_id; /* Used for logging */ + unsigned char np_path_id; }; #define NP_LOCAL_SA(path_) (&(path_)->np_local_addr_u.sockaddr) @@ -111,6 +111,9 @@ struct conn_iface void (*ci_packet_not_sent) (struct lsquic_conn *, struct lsquic_packet_out *); + void + (*ci_packet_too_large) (struct lsquic_conn *, struct lsquic_packet_out *); + void (*ci_hsk_done) (struct lsquic_conn *, enum lsquic_hsk_status); @@ -247,6 +250,15 @@ struct conn_iface /* Optional method. Only used by IETF connections */ void (*ci_count_garbage) (struct lsquic_conn *, size_t); + + /* Optional method. Must be implemented if connection sends MTU probes */ + void + (*ci_mtu_probe_acked) (struct lsquic_conn *, + const struct lsquic_packet_out *); + + /* Optional method. It is called when RTO occurs. */ + void + (*ci_retx_timeout) (struct lsquic_conn *); }; #define LSCONN_CCE_BITS 3 diff --git a/src/liblsquic/lsquic_engine.c b/src/liblsquic/lsquic_engine.c index 0270dcb..ab0e995 100644 --- a/src/liblsquic/lsquic_engine.c +++ b/src/liblsquic/lsquic_engine.c @@ -357,6 +357,8 @@ lsquic_engine_init_settings (struct lsquic_engine_settings *settings, settings->es_delayed_acks = LSQUIC_DF_DELAYED_ACKS; settings->es_timestamps = LSQUIC_DF_TIMESTAMPS; settings->es_grease_quic_bit = LSQUIC_DF_GREASE_QUIC_BIT; + settings->es_mtu_probe_timer = LSQUIC_DF_MTU_PROBE_TIMER; + settings->es_dplpmtud = LSQUIC_DF_DPLPMTUD; } @@ -440,6 +442,14 @@ lsquic_engine_check_settings (const struct lsquic_engine_settings *settings, return -1; } + if (settings->es_mtu_probe_timer && settings->es_mtu_probe_timer < 1000) + { + if (err_buf) + snprintf(err_buf, err_buf_sz, "mtu probe timer is too small: " + "%u ms", settings->es_mtu_probe_timer); + return -1; + } + return 0; } @@ -612,6 +622,10 @@ lsquic_engine_new (unsigned flags, if (engine->pub.enp_settings.es_noprogress_timeout) engine->pub.enp_noprog_timeout = engine->pub.enp_settings.es_noprogress_timeout * 1000000; + engine->pub.enp_mtu_probe_timer = 1000 + * (engine->pub.enp_settings.es_mtu_probe_timer + ? engine->pub.enp_settings.es_mtu_probe_timer + : LSQUIC_DF_MTU_PROBE_TIMER); if (flags & ENG_SERVER) { engine->pr_queue = lsquic_prq_create( @@ -1580,7 +1594,7 @@ lsquic_engine_connect (lsquic_engine_t *engine, enum lsquic_version version, const struct sockaddr *local_sa, const struct sockaddr *peer_sa, void *peer_ctx, lsquic_conn_ctx_t *conn_ctx, - const char *hostname, unsigned short max_packet_size, + const char *hostname, unsigned short base_plpmtu, const unsigned char *sess_resume, size_t sess_resume_len, const unsigned char *token, size_t token_sz) { @@ -1627,11 +1641,11 @@ lsquic_engine_connect (lsquic_engine_t *engine, enum lsquic_version version, versions = 1u << version; if (versions & LSQUIC_IETF_VERSIONS) conn = lsquic_ietf_full_conn_client_new(&engine->pub, versions, - flags, hostname, max_packet_size, + flags, hostname, base_plpmtu, is_ipv4, sess_resume, sess_resume_len, token, token_sz); else conn = lsquic_gquic_full_conn_client_new(&engine->pub, versions, - flags, hostname, max_packet_size, is_ipv4, + flags, hostname, base_plpmtu, is_ipv4, sess_resume, sess_resume_len); if (!conn) goto err; @@ -2181,7 +2195,7 @@ send_batch (lsquic_engine_t *engine, const struct send_batch_ctx *sb_ctx, { int n_sent, i, e_val; lsquic_time_t now; - unsigned off; + unsigned off, skip; size_t count; CONST_BATCH struct out_batch *const batch = sb_ctx->batch; struct lsquic_packet_out *CONST_BATCH *packet_out, *CONST_BATCH *end; @@ -2191,9 +2205,11 @@ send_batch (lsquic_engine_t *engine, const struct send_batch_ctx *sb_ctx, if (engine->flags & ENG_LOSE_PACKETS) lose_matching_packets(engine, batch, n_to_send); #endif + skip = 0; + restart_batch: /* Set sent time before the write to avoid underestimating RTT */ now = lsquic_time_now(); - for (i = 0; i < (int) n_to_send; ++i) + for (i = skip; i < (int) (n_to_send - skip); ++i) { off = batch->pack_off[i]; count = batch->outs[i].iovlen; @@ -2204,10 +2220,10 @@ send_batch (lsquic_engine_t *engine, const struct send_batch_ctx *sb_ctx, (*packet_out)->po_sent = now; while (++packet_out < end); } - n_sent = engine->packets_out(engine->packets_out_ctx, batch->outs, - n_to_send); + n_sent = engine->packets_out(engine->packets_out_ctx, batch->outs + skip, + n_to_send - skip); e_val = errno; - if (n_sent < (int) n_to_send) + if (n_sent < (int) (n_to_send - skip) && e_val != EMSGSIZE) { engine->pub.enp_flags &= ~ENPUB_CAN_SEND; engine->resume_sending_at = now + 1000000; @@ -2218,7 +2234,8 @@ send_batch (lsquic_engine_t *engine, const struct send_batch_ctx *sb_ctx, n_sent < 0 ? 0 : n_sent, e_val); } if (n_sent >= 0) - LSQ_DEBUG("packets out returned %d (out of %u)", n_sent, n_to_send); + LSQ_DEBUG("packets out returned %d (out of %u)", n_sent, + n_to_send - skip); else { LSQ_DEBUG("packets out returned an error: %s", strerror(e_val)); @@ -2226,7 +2243,7 @@ send_batch (lsquic_engine_t *engine, const struct send_batch_ctx *sb_ctx, } if (n_sent > 0) engine->last_sent = now + n_sent; - for (i = 0; i < n_sent; ++i) + for (i = skip; i < (int) (skip + n_sent); ++i) { eng_hist_inc(&engine->history, now, sl_packets_out); /* `i' is added to maintain relative order */ @@ -2258,6 +2275,27 @@ send_batch (lsquic_engine_t *engine, const struct send_batch_ctx *sb_ctx, } while (++packet_out < end); } + if (i < (int) n_to_send && e_val == EMSGSIZE) + { + LSQ_DEBUG("packet #%d could not be sent out for being too large", i); + if (batch->conns[i]->cn_if->ci_packet_too_large + && batch->outs[i].iovlen == 1) + { + off = batch->pack_off[i]; + packet_out = &batch->packets[off]; + batch->conns[i]->cn_if->ci_packet_too_large(batch->conns[i], + *packet_out); + ++i; + if (i < (int) n_to_send) + { + skip = i; + LSQ_DEBUG("restart batch starting at packet #%u", skip); + goto restart_batch; + } + } + else + close_conn_on_send_error(engine, sb_ctx, i, e_val); + } if (LSQ_LOG_ENABLED_EXT(LSQ_LOG_DEBUG, LSQLM_EVENT)) for ( ; i < (int) n_to_send; ++i) { @@ -2274,7 +2312,7 @@ send_batch (lsquic_engine_t *engine, const struct send_batch_ctx *sb_ctx, /* Return packets to the connection in reverse order so that the packet * ordering is maintained. */ - for (i = (int) n_to_send - 1; i >= n_sent; --i) + for (i = (int) n_to_send - 1; i >= (int) (skip + n_sent); --i) { off = batch->pack_off[i]; count = batch->outs[i].iovlen; @@ -2288,7 +2326,7 @@ send_batch (lsquic_engine_t *engine, const struct send_batch_ctx *sb_ctx, if (!(batch->conns[i]->cn_flags & (LSCONN_COI_ACTIVE|LSCONN_EVANESCENT))) coi_reactivate(sb_ctx->conns_iter, batch->conns[i]); } - return n_sent; + return skip + n_sent; } diff --git a/src/liblsquic/lsquic_engine_public.h b/src/liblsquic/lsquic_engine_public.h index cb04899..54b7aa5 100644 --- a/src/liblsquic/lsquic_engine_public.h +++ b/src/liblsquic/lsquic_engine_public.h @@ -68,6 +68,7 @@ struct lsquic_engine_public { unsigned char *enp_alpn; /* May be set if not HTTP */ /* es_noprogress_timeout converted to microseconds for speed */ lsquic_time_t enp_noprog_timeout; + lsquic_time_t enp_mtu_probe_timer; }; /* Put connection onto the Tickable Queue if it is not already on it. If diff --git a/src/liblsquic/lsquic_full_conn.c b/src/liblsquic/lsquic_full_conn.c index 85ac7c1..9e6e0a9 100644 --- a/src/liblsquic/lsquic_full_conn.c +++ b/src/liblsquic/lsquic_full_conn.c @@ -737,7 +737,11 @@ lsquic_gquic_full_conn_client_new (struct lsquic_engine_public *enpub, lsquic_generate_cid_gquic(&cid); if (!max_packet_size) { - if (is_ipv4) + if (enpub->enp_settings.es_base_plpmtu) + max_packet_size = enpub->enp_settings.es_base_plpmtu + - (is_ipv4 ? 20 : 40) /* IP header */ + - 8; /* UDP header */ + else if (is_ipv4) max_packet_size = GQUIC_MAX_IPv4_PACKET_SZ; else max_packet_size = GQUIC_MAX_IPv6_PACKET_SZ; @@ -1269,6 +1273,12 @@ full_conn_ci_write_ack (struct lsquic_conn *lconn, lsquic_send_ctl_scheduled_ack(&conn->fc_send_ctl, PNS_APP, packet_out->po_ack2ed); packet_out->po_frame_types |= 1 << QUIC_FRAME_ACK; + if (0 != lsquic_packet_out_add_frame(packet_out, conn->fc_pub.mm, 0, + QUIC_FRAME_ACK, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return; + } lsquic_send_ctl_incr_pack_sz(&conn->fc_send_ctl, packet_out, w); packet_out->po_regen_sz += w; if (has_missing) @@ -2529,7 +2539,6 @@ get_writeable_packet (struct full_conn *conn, unsigned need_at_least) lsquic_packet_out_t *packet_out; int is_err; - assert(need_at_least <= GQUIC_MAX_PAYLOAD_SZ); packet_out = lsquic_send_ctl_get_writeable_packet(&conn->fc_send_ctl, PNS_APP, need_at_least, &conn->fc_path, 0, &is_err); if (!packet_out && is_err) @@ -2709,7 +2718,7 @@ generate_rst_stream_frame (struct full_conn *conn, lsquic_stream_t *stream) return 0; /* TODO Possible optimization: instead of using stream->tosend_off as the * offset, keep track of the offset that was actually sent: include it - * into stream_rec and update a new per-stream "maximum offset actually + * into frame_rec and update a new per-stream "maximum offset actually * sent" field. Then, if a stream is reset, the connection cap can be * increased. */ @@ -2802,6 +2811,12 @@ generate_stop_waiting_frame (struct full_conn *conn) ABORT_ERROR("gen_stop_waiting_frame failed"); return; } + if (0 != lsquic_packet_out_add_frame(packet_out, conn->fc_pub.mm, 0, + QUIC_FRAME_STOP_WAITING, packet_out->po_data_sz, sz)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return; + } lsquic_send_ctl_incr_pack_sz(&conn->fc_send_ctl, packet_out, sz); packet_out->po_regen_sz += sz; packet_out->po_frame_types |= 1 << QUIC_FRAME_STOP_WAITING; diff --git a/src/liblsquic/lsquic_full_conn.h b/src/liblsquic/lsquic_full_conn.h index e17f067..d809a0e 100644 --- a/src/liblsquic/lsquic_full_conn.h +++ b/src/liblsquic/lsquic_full_conn.h @@ -17,7 +17,7 @@ struct lsquic_conn * lsquic_ietf_full_conn_client_new (struct lsquic_engine_public *, unsigned versions, unsigned flags /* Only FC_SERVER and FC_HTTP */, - const char *hostname, unsigned short max_packet_size, int is_ipv4, + const char *hostname, unsigned short base_plpmtu, int is_ipv4, const unsigned char *sess_resume, size_t, const unsigned char *token, size_t); diff --git a/src/liblsquic/lsquic_full_conn_ietf.c b/src/liblsquic/lsquic_full_conn_ietf.c index 5281209..c1098e9 100644 --- a/src/liblsquic/lsquic_full_conn_ietf.c +++ b/src/liblsquic/lsquic_full_conn_ietf.c @@ -142,6 +142,7 @@ enum more_flags { MF_VALIDATE_PATH = 1 << 0, MF_NOPROG_TIMEOUT = 1 << 1, + MF_CHECK_MTU_PROBE = 1 << 2, }; @@ -281,6 +282,21 @@ struct conn_err }; +struct dplpmtud_state +{ + lsquic_packno_t ds_probe_packno; +#ifndef NDEBUG + lsquic_time_t ds_probe_sent; +#endif + enum { + DS_PROBE_SENT = 1 << 0, + } ds_flags; + unsigned short ds_probed_size, + ds_failed_size; /* If non-zero, defines ceiling */ + unsigned char ds_probe_count; +}; + + struct conn_path { struct network_path cop_path; @@ -298,8 +314,10 @@ struct conn_path */ COP_GOT_NONPROB = 1 << 2, } cop_flags; + unsigned short cop_max_plpmtu; unsigned char cop_n_chals; unsigned char cop_cce_idx; + struct dplpmtud_state cop_dplpmtud; }; @@ -402,6 +420,7 @@ struct ietf_full_conn unsigned ifc_last_pack_tol; unsigned ifc_max_ack_freq_seqno; /* Incoming */ unsigned ifc_max_peer_ack_usec; + unsigned short ifc_max_udp_payload; /* Cached TP */ lsquic_time_t ifc_last_live_update; struct conn_path ifc_paths[N_PATHS]; union { @@ -481,6 +500,9 @@ insert_new_dcid (struct ietf_full_conn *, uint64_t seqno, static struct conn_cid_elem * find_cce_by_cid (struct ietf_full_conn *, const lsquic_cid_t *); +static void +mtu_probe_too_large (struct ietf_full_conn *, const struct lsquic_packet_out *); + static unsigned highest_bit_set (unsigned sz) { @@ -655,6 +677,18 @@ blocked_ka_alarm_expired (enum alarm_id al_id, void *ctx, } +static void +mtu_probe_alarm_expired (enum alarm_id al_id, void *ctx, + lsquic_time_t expiry, lsquic_time_t now) +{ + struct ietf_full_conn *const conn = (struct ietf_full_conn *) ctx; + + LSQ_DEBUG("MTU probe alarm expired: set `check MTU probe' flag"); + assert(!(conn->ifc_mflags & MF_CHECK_MTU_PROBE)); + conn->ifc_mflags |= MF_CHECK_MTU_PROBE; +} + + static int migra_is_on (const struct ietf_full_conn *conn, unsigned path_id) { @@ -663,6 +697,24 @@ migra_is_on (const struct ietf_full_conn *conn, unsigned path_id) } +#define TRANSPORT_OVERHEAD(is_ipv6) (((is_ipv6) ? 40 : 20) + 8 /* UDP */) + +static unsigned short +calc_base_packet_size (const struct ietf_full_conn *conn, int is_ipv6) +{ + unsigned short size; + + if (conn->ifc_settings->es_base_plpmtu) + size = conn->ifc_settings->es_base_plpmtu - TRANSPORT_OVERHEAD(is_ipv6); + else if (is_ipv6) + size = IQUIC_MAX_IPv6_PACKET_SZ; + else + size = IQUIC_MAX_IPv4_PACKET_SZ; + + return size; +} + + static void migra_begin (struct ietf_full_conn *conn, struct conn_path *copath, struct dcid_elem *dce, const struct sockaddr *dest_sa, @@ -674,15 +726,10 @@ migra_begin (struct ietf_full_conn *conn, struct conn_path *copath, copath->cop_flags |= COP_INITIALIZED; copath->cop_path.np_dcid = dce->de_cid; copath->cop_path.np_peer_ctx = CUR_NPATH(conn)->np_peer_ctx; - if (NP_IS_IPv6(CUR_NPATH(conn))) - copath->cop_path.np_pack_size = IQUIC_MAX_IPv6_PACKET_SZ; - else - copath->cop_path.np_pack_size = IQUIC_MAX_IPv4_PACKET_SZ; - if ((params->tp_set & (1 << TPI_MAX_UDP_PAYLOAD_SIZE)) - && params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE] - < copath->cop_path.np_pack_size) - copath->cop_path.np_pack_size - = params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE]; + copath->cop_path.np_pack_size + = calc_base_packet_size(conn, NP_IS_IPv6(CUR_NPATH(conn))); + if (conn->ifc_max_udp_payload < copath->cop_path.np_pack_size) + copath->cop_path.np_pack_size = conn->ifc_max_udp_payload; memcpy(&copath->cop_path.np_local_addr, NP_LOCAL_SA(CUR_NPATH(conn)), sizeof(copath->cop_path.np_local_addr)); memcpy(&copath->cop_path.np_peer_addr, dest_sa, @@ -1117,6 +1164,7 @@ ietf_full_conn_init (struct ietf_full_conn *conn, lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_PATH_CHAL_2, path_chal_alarm_expired, conn); lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_PATH_CHAL_3, path_chal_alarm_expired, conn); lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_BLOCKED_KA, blocked_ka_alarm_expired, conn); + lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_MTU_PROBE, mtu_probe_alarm_expired, conn); lsquic_rechist_init(&conn->ifc_rechist[PNS_INIT], &conn->ifc_conn, 1); lsquic_rechist_init(&conn->ifc_rechist[PNS_HSK], &conn->ifc_conn, 1); lsquic_rechist_init(&conn->ifc_rechist[PNS_APP], &conn->ifc_conn, 1); @@ -1157,7 +1205,7 @@ ietf_full_conn_init (struct ietf_full_conn *conn, struct lsquic_conn * lsquic_ietf_full_conn_client_new (struct lsquic_engine_public *enpub, unsigned versions, unsigned flags, - const char *hostname, unsigned short max_packet_size, int is_ipv4, + const char *hostname, unsigned short base_plpmtu, int is_ipv4, const unsigned char *sess_resume, size_t sess_resume_sz, const unsigned char *token, size_t token_sz) { @@ -1189,14 +1237,12 @@ lsquic_ietf_full_conn_client_new (struct lsquic_engine_public *enpub, } esfi = select_esf_iquic_by_ver(ver); - if (!max_packet_size) - { - if (is_ipv4) - max_packet_size = IQUIC_MAX_IPv4_PACKET_SZ; - else - max_packet_size = IQUIC_MAX_IPv6_PACKET_SZ; - } - conn->ifc_paths[0].cop_path.np_pack_size = max_packet_size; + if (base_plpmtu) + conn->ifc_paths[0].cop_path.np_pack_size + = base_plpmtu - TRANSPORT_OVERHEAD(!is_ipv4); + else + conn->ifc_paths[0].cop_path.np_pack_size + = calc_base_packet_size(conn, !is_ipv4); if (0 != ietf_full_conn_init(conn, enpub, flags, enpub->enp_settings.es_ecn)) @@ -1368,15 +1414,6 @@ lsquic_ietf_full_conn_server_new (struct lsquic_engine_public *enpub, LSQ_DEBUG("path changed during mini conn: schedule PATH_CHALLENGE"); conn->ifc_send_flags |= SF_SEND_PATH_CHAL_PATH_0; } -#ifndef NDEBUG - if (getenv("LSQUIC_CN_PACK_SIZE")) - { - conn->ifc_paths[0].cop_path.np_pack_size - = atoi(getenv("LSQUIC_CN_PACK_SIZE")); - LSQ_INFO("set packet size to %hu (env)", - conn->ifc_paths[0].cop_path.np_pack_size); - } -#endif conn->ifc_max_streams_in[SD_BIDI] = enpub->enp_settings.es_init_max_streams_bidi; @@ -1607,6 +1644,12 @@ generate_timestamp_frame (struct ietf_full_conn *conn, timestamp << TP_DEF_ACK_DELAY_EXP); EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated TIMESTAMP(%" PRIu64" us) frame", timestamp << TP_DEF_ACK_DELAY_EXP); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_TIMESTAMP, packet_out->po_data_sz, w)) + { + LSQ_DEBUG("%s: adding frame to packet failed: %d", __func__, errno); + return; + } packet_out->po_frame_types |= 1 << QUIC_FRAME_TIMESTAMP; lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); packet_out->po_regen_sz += w; @@ -1640,6 +1683,12 @@ generate_ack_frame_for_pns (struct ietf_full_conn *conn, lsquic_send_ctl_scheduled_ack(&conn->ifc_send_ctl, pns, packet_out->po_ack2ed); packet_out->po_frame_types |= 1 << QUIC_FRAME_ACK; + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_ACK, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return -1; + } lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); packet_out->po_regen_sz += w; if (has_missing) @@ -1753,6 +1802,12 @@ generate_max_data_frame (struct ietf_full_conn *conn) LSQ_DEBUG("generated %d-byte MAX_DATA frame (offset: %"PRIu64")", w, offset); EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated MAX_DATA frame, offset=%" PRIu64, offset); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_MAX_DATA, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return; + } lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); packet_out->po_frame_types |= QUIC_FTBIT_MAX_DATA; conn->ifc_send_flags &= ~SF_SEND_MAX_DATA; @@ -1822,6 +1877,12 @@ generate_new_cid_frame (struct ietf_full_conn *conn, lsquic_time_t now) w, CID_BITS(&cce->cce_cid)); EV_LOG_GENERATED_NEW_CONNECTION_ID_FRAME(LSQUIC_LOG_CONN_ID, conn->ifc_conn.cn_pf, packet_out->po_data + packet_out->po_data_sz, w); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_NEW_CONNECTION_ID, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return -1; + } packet_out->po_frame_types |= QUIC_FTBIT_NEW_CONNECTION_ID; lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); @@ -1929,6 +1990,12 @@ generate_retire_cid_frame (struct ietf_full_conn *conn) w, dce->de_seqno); EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated RETIRE_CONNECTION_ID " "frame, seqno=%u", dce->de_seqno); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_RETIRE_CONNECTION_ID, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return -1; + } packet_out->po_frame_types |= QUIC_FTBIT_RETIRE_CONNECTION_ID; lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); @@ -1979,6 +2046,12 @@ generate_streams_blocked_frame (struct ietf_full_conn *conn, enum stream_dir sd) "limit: %"PRIu64")", w, sd == SD_UNI, limit); EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated %d-byte STREAMS_BLOCKED " "frame (uni: %d, limit: %"PRIu64")", w, sd == SD_UNI, limit); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_STREAMS_BLOCKED, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return; + } packet_out->po_frame_types |= QUIC_FTBIT_STREAM_BLOCKED; lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); conn->ifc_send_flags &= ~(SF_SEND_STREAMS_BLOCKED << sd); @@ -2028,6 +2101,12 @@ generate_max_streams_frame (struct ietf_full_conn *conn, enum stream_dir sd) "limit: %"PRIu64")", w, sd == SD_UNI, limit); EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated %d-byte MAX_STREAMS " "frame (uni: %d, limit: %"PRIu64")", w, sd == SD_UNI, limit); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_MAX_STREAMS, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return; + } packet_out->po_frame_types |= QUIC_FTBIT_MAX_STREAMS; lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); conn->ifc_send_flags &= ~(SF_SEND_MAX_STREAMS << sd); @@ -2078,6 +2157,12 @@ generate_blocked_frame (struct ietf_full_conn *conn) LSQ_DEBUG("generated %d-byte BLOCKED frame (offset: %"PRIu64")", w, offset); EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated BLOCKED frame, offset=%" PRIu64, offset); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_BLOCKED, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return 0; + } packet_out->po_frame_types |= QUIC_FTBIT_BLOCKED; lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); @@ -2110,6 +2195,12 @@ generate_max_stream_data_frame (struct ietf_full_conn *conn, } EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated %d-byte MAX_STREAM_DATA " "frame; stream_id: %"PRIu64"; offset: %"PRIu64, sz, stream->id, off); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_MAX_STREAM_DATA, packet_out->po_data_sz, sz)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return 0; + } lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz); packet_out->po_frame_types |= 1 << QUIC_FRAME_MAX_STREAM_DATA; lsquic_stream_max_stream_data_sent(stream); @@ -2142,6 +2233,12 @@ generate_stream_blocked_frame (struct ietf_full_conn *conn, } EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated %d-byte STREAM_BLOCKED " "frame; stream_id: %"PRIu64"; offset: %"PRIu64, sz, stream->id, off); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_STREAM_BLOCKED, packet_out->po_data_sz, sz)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return 0; + } lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz); packet_out->po_frame_types |= 1 << QUIC_FRAME_STREAM_BLOCKED; lsquic_stream_blocked_frame_sent(stream); @@ -2176,6 +2273,12 @@ generate_stop_sending_frame (struct ietf_full_conn *conn, "error code: %u)", w, stream_id, error_code); EV_LOG_GENERATED_STOP_SENDING_FRAME(LSQUIC_LOG_CONN_ID, stream_id, error_code); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_STOP_SENDING, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return -1; + } packet_out->po_frame_types |= QUIC_FTBIT_STOP_SENDING; lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); @@ -2215,7 +2318,7 @@ generate_rst_stream_frame (struct ietf_full_conn *conn, { lsquic_packet_out_t *packet_out; unsigned need; - int sz, s; + int sz; need = conn->ifc_conn.cn_pf->pf_rst_frame_size(stream->id, stream->tosend_off, stream->error_code); @@ -2234,15 +2337,14 @@ generate_rst_stream_frame (struct ietf_full_conn *conn, ABORT_ERROR("gen_rst_frame failed"); return 0; } - lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz); - packet_out->po_frame_types |= 1 << QUIC_FRAME_RST_STREAM; - s = lsquic_packet_out_add_stream(packet_out, conn->ifc_pub.mm, stream, - QUIC_FRAME_RST_STREAM, packet_out->po_data_sz, sz); - if (s != 0) + if (0 != lsquic_packet_out_add_stream(packet_out, conn->ifc_pub.mm, stream, + QUIC_FRAME_RST_STREAM, packet_out->po_data_sz, sz)) { - ABORT_ERROR("adding stream to packet failed: %s", strerror(errno)); + ABORT_ERROR("adding frame to packet failed: %d", errno); return 0; } + lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz); + packet_out->po_frame_types |= 1 << QUIC_FRAME_RST_STREAM; lsquic_stream_rst_frame_sent(stream); LSQ_DEBUG("wrote RST: stream %"PRIu64"; offset 0x%"PRIX64"; error code " "%"PRIu64, stream->id, stream->tosend_off, stream->error_code); @@ -3161,11 +3263,18 @@ handshake_ok (struct lsquic_conn *lconn) conn->ifc_max_peer_ack_usec = params->tp_max_ack_delay * 1000; if ((params->tp_set & (1 << TPI_MAX_UDP_PAYLOAD_SIZE)) + /* Second check is so that we don't truncate a large value when + * storing it in unsigned short. + */ && params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE] - < CUR_NPATH(conn)->np_pack_size) + < TP_DEF_MAX_UDP_PAYLOAD_SIZE) + conn->ifc_max_udp_payload = params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE]; + else + conn->ifc_max_udp_payload = TP_DEF_MAX_UDP_PAYLOAD_SIZE; + + if (conn->ifc_max_udp_payload < CUR_NPATH(conn)->np_pack_size) { - CUR_NPATH(conn)->np_pack_size - = params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE]; + CUR_NPATH(conn)->np_pack_size = conn->ifc_max_udp_payload; LSQ_DEBUG("decrease packet size to %hu bytes", CUR_NPATH(conn)->np_pack_size); } @@ -3261,6 +3370,9 @@ handshake_ok (struct lsquic_conn *lconn) conn->ifc_active_cids_limit = params->tp_active_connection_id_limit; conn->ifc_first_active_cid_seqno = conn->ifc_scid_seqno; + if (conn->ifc_settings->es_dplpmtud) + conn->ifc_mflags |= MF_CHECK_MTU_PROBE; + if (can_issue_cids(conn) && CN_SCID(&conn->ifc_conn)->len != 0) conn->ifc_send_flags |= SF_SEND_NEW_CID; maybe_create_delayed_streams(conn); @@ -3675,6 +3787,12 @@ immediate_close (struct ietf_full_conn *conn) LSQ_WARN("%s failed", __func__); return TICK_CLOSE; } + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_CONNECTION_CLOSE, packet_out->po_data_sz, sz)) + { + LSQ_WARN("%s: adding frame to packet failed: %d", __func__, errno); + return TICK_CLOSE; + } lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz); packet_out->po_frame_types |= 1 << QUIC_FRAME_CONNECTION_CLOSE; LSQ_DEBUG("generated CONNECTION_CLOSE frame in its own packet"); @@ -3844,6 +3962,12 @@ generate_connection_close_packet (struct ietf_full_conn *conn) ABORT_ERROR("generate_connection_close_packet failed"); return; } + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_CONNECTION_CLOSE, packet_out->po_data_sz, sz)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return; + } lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz); packet_out->po_frame_types |= 1 << QUIC_FRAME_CONNECTION_CLOSE; LSQ_DEBUG("generated CONNECTION_CLOSE frame in its own packet"); @@ -3870,6 +3994,12 @@ generate_ping_frame (struct ietf_full_conn *conn, lsquic_time_t unused) ABORT_ERROR("gen_ping_frame failed"); return; } + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_PING, packet_out->po_data_sz, sz)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return; + } lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz); packet_out->po_frame_types |= 1 << QUIC_FRAME_PING; LSQ_DEBUG("wrote PING frame"); @@ -3898,6 +4028,12 @@ generate_handshake_done_frame (struct ietf_full_conn *conn, return; } + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_HANDSHAKE_DONE, packet_out->po_data_sz, sz)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return; + } lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz); packet_out->po_frame_types |= QUIC_FTBIT_HANDSHAKE_DONE; LSQ_DEBUG("generated HANDSHAKE_DONE frame"); @@ -3929,7 +4065,13 @@ generate_ack_frequency_frame (struct ietf_full_conn *conn, lsquic_time_t unused) conn->ifc_max_peer_ack_usec); if (sz < 0) { - ABORT_ERROR("gen_rst_frame failed"); + ABORT_ERROR("gen_ack_frequency_frame failed"); + return; + } + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_ACK_FREQUENCY, packet_out->po_data_sz, sz)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); return; } lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz); @@ -3989,6 +4131,12 @@ generate_path_chal_frame (struct ietf_full_conn *conn, lsquic_time_t now, ++copath->cop_n_chals; EV_LOG_GENERATED_PATH_CHAL_FRAME(LSQUIC_LOG_CONN_ID, conn->ifc_conn.cn_pf, packet_out->po_data + packet_out->po_data_sz, w); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_PATH_CHALLENGE, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return; + } packet_out->po_frame_types |= QUIC_FTBIT_PATH_CHALLENGE; lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); packet_out->po_regen_sz += w; @@ -4054,6 +4202,12 @@ generate_path_resp_frame (struct ietf_full_conn *conn, lsquic_time_t now, w, copath->cop_inc_chal); EV_LOG_GENERATED_PATH_RESP_FRAME(LSQUIC_LOG_CONN_ID, conn->ifc_conn.cn_pf, packet_out->po_data + packet_out->po_data_sz, w); + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_PATH_RESPONSE, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding frame to packet failed: %d", errno); + return; + } packet_out->po_frame_types |= QUIC_FTBIT_PATH_RESPONSE; lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); packet_out->po_regen_sz += w; @@ -5820,8 +5974,6 @@ static int init_new_path (struct ietf_full_conn *conn, struct conn_path *path, int dcid_changed) { - struct lsquic_conn *const lconn = &conn->ifc_conn; - const struct transport_params *params; struct dcid_elem *dce; dce = find_unassigned_dcid(conn); @@ -5849,17 +6001,11 @@ init_new_path (struct ietf_full_conn *conn, struct conn_path *path, return -1; } - if (NP_IS_IPv6(&path->cop_path)) - path->cop_path.np_pack_size = IQUIC_MAX_IPv6_PACKET_SZ; - else - path->cop_path.np_pack_size = IQUIC_MAX_IPv4_PACKET_SZ; - params = lconn->cn_esf.i->esfi_get_peer_transport_params( - lconn->cn_enc_session); - if (params && (params->tp_set & (1 << TPI_MAX_UDP_PAYLOAD_SIZE)) - && params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE] - < path->cop_path.np_pack_size) - path->cop_path.np_pack_size - = params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE]; + path->cop_path.np_pack_size + = calc_base_packet_size(conn, NP_IS_IPv6(&path->cop_path)); + + if (conn->ifc_max_udp_payload < path->cop_path.np_pack_size) + path->cop_path.np_pack_size = conn->ifc_max_udp_payload; LSQ_DEBUG("initialized path %u", (unsigned) (path - conn->ifc_paths)); @@ -6621,6 +6767,35 @@ ietf_full_conn_ci_packet_not_sent (struct lsquic_conn *lconn, } +static void +ietf_full_conn_ci_packet_too_large (struct lsquic_conn *lconn, + struct lsquic_packet_out *packet_out) +{ + struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn; + +#ifndef NDEBUG + assert(packet_out->po_lflags & POL_HEADER_PROT); +#endif + + lsquic_senhist_add(&conn->ifc_send_ctl.sc_senhist, packet_out->po_packno); + lsquic_send_ctl_sanity_check(&conn->ifc_send_ctl); + if (packet_out->po_flags & PO_MTU_PROBE) + { + LSQ_DEBUG("%zu-byte MTU probe in packet %"PRIu64" is too large", + lsquic_packet_out_sent_sz(&conn->ifc_conn, packet_out), + packet_out->po_packno); + mtu_probe_too_large(conn, packet_out); + } + else + ABORT_WARN("non-MTU probe %zu-byte packet %"PRIu64" is too large", + lsquic_packet_out_sent_sz(&conn->ifc_conn, packet_out), + packet_out->po_packno); + + lsquic_packet_out_destroy(packet_out, conn->ifc_enpub, + packet_out->po_path->np_peer_ctx); +} + + /* Calling of ignore_init() must be delayed until all batched packets have * been returned by the engine. */ @@ -6752,6 +6927,204 @@ maybe_set_noprogress_alarm (struct ietf_full_conn *conn, lsquic_time_t now) } +static void +check_or_schedule_mtu_probe (struct ietf_full_conn *conn, lsquic_time_t now) +{ + struct conn_path *const cpath = CUR_CPATH(conn); + struct dplpmtud_state *const ds = &cpath->cop_dplpmtud; + struct lsquic_packet_out *packet_out; + unsigned short saved_packet_sz, avail, mtu_ceiling, net_header_sz, probe_sz; + int sz; + + if (ds->ds_flags & DS_PROBE_SENT) + { + assert(ds->ds_probe_sent + conn->ifc_enpub->enp_mtu_probe_timer < now); + LSQ_DEBUG("MTU probe of %hu bytes lost", ds->ds_probed_size); + ds->ds_flags &= ~DS_PROBE_SENT; + conn->ifc_mflags |= MF_CHECK_MTU_PROBE; + if (ds->ds_probe_count >= 3) + { + LSQ_DEBUG("MTU probe of %hu bytes lost after %hhu tries", + ds->ds_probed_size, ds->ds_probe_count); + ds->ds_failed_size = ds->ds_probed_size; + ds->ds_probe_count = 0; + } + } + + assert(0 == ds->ds_probe_sent + || ds->ds_probe_sent + conn->ifc_enpub->enp_mtu_probe_timer < now); + + if (!(conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE) + || lsquic_senhist_largest(&conn->ifc_send_ctl.sc_senhist) < 30 + || lsquic_send_ctl_in_recovery(&conn->ifc_send_ctl) + || !lsquic_send_ctl_can_send_probe(&conn->ifc_send_ctl, + &cpath->cop_path)) + { + return; + } + + net_header_sz = TRANSPORT_OVERHEAD(NP_IS_IPv6(&cpath->cop_path)); + if (ds->ds_failed_size) + mtu_ceiling = ds->ds_failed_size; /* Don't subtract net_header_sz */ + else if (conn->ifc_settings->es_max_plpmtu) + mtu_ceiling = conn->ifc_settings->es_max_plpmtu - net_header_sz; + else + mtu_ceiling = 1500 - net_header_sz; + + if (conn->ifc_max_udp_payload < mtu_ceiling) + { + LSQ_DEBUG("cap MTU ceiling to peer's max_udp_payload_size TP of %hu " + "bytes", conn->ifc_max_udp_payload); + mtu_ceiling = conn->ifc_max_udp_payload; + } + + if (cpath->cop_path.np_pack_size >= mtu_ceiling + || (float) cpath->cop_path.np_pack_size / (float) mtu_ceiling >= 0.99) + { + LSQ_DEBUG("stop MTU probing on path %hhu having achieved about " + "%.1f%% efficiency (detected MTU: %hu; failed MTU: %hu)", + cpath->cop_path.np_path_id, + 100. * (float) cpath->cop_path.np_pack_size / (float) mtu_ceiling, + cpath->cop_path.np_pack_size, ds->ds_failed_size); + conn->ifc_mflags &= ~MF_CHECK_MTU_PROBE; + return; + } + + LSQ_DEBUG("MTU ratio: %hu / %hu = %.4f", + cpath->cop_path.np_pack_size, mtu_ceiling, + (float) cpath->cop_path.np_pack_size / (float) mtu_ceiling); + + if (!ds->ds_failed_size && mtu_ceiling < 1500) + /* Try the largest ethernet MTU immediately */ + probe_sz = mtu_ceiling; + else if (cpath->cop_path.np_pack_size * 2 >= mtu_ceiling) + /* Pick half-way point */ + probe_sz = (mtu_ceiling + cpath->cop_path.np_pack_size) / 2; + else + probe_sz = cpath->cop_path.np_pack_size * 2; + + /* XXX Changing np_pack_size is action at a distance */ + saved_packet_sz = cpath->cop_path.np_pack_size; + cpath->cop_path.np_pack_size = probe_sz; + packet_out = lsquic_send_ctl_new_packet_out(&conn->ifc_send_ctl, + 0, PNS_APP, CUR_NPATH(conn)); + if (!packet_out) + goto restore_packet_size; + sz = conn->ifc_conn.cn_pf->pf_gen_ping_frame( + packet_out->po_data + packet_out->po_data_sz, + lsquic_packet_out_avail(packet_out)); + if (sz < 0) { + ABORT_ERROR("gen_ping_frame failed"); + goto restore_packet_size; + } + /* We don't record frame records for MTU probes as they are never + * resized, only discarded. + */ + lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz); + packet_out->po_frame_types |= 1 << QUIC_FRAME_PING; + avail = lsquic_packet_out_avail(packet_out); + if (avail) + { + memset(packet_out->po_data + packet_out->po_data_sz, 0, avail); + lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, avail); + packet_out->po_frame_types |= 1 << QUIC_FRAME_PADDING; + } + packet_out->po_flags |= PO_MTU_PROBE; + lsquic_send_ctl_scheduled_one(&conn->ifc_send_ctl, packet_out); + LSQ_DEBUG("generated MTU probe of %hu bytes in packet %"PRIu64, + cpath->cop_path.np_pack_size, packet_out->po_packno); +#ifndef NDEBUG + ds->ds_probe_sent = now; +#endif + ds->ds_probe_packno = packet_out->po_packno; + ds->ds_probed_size = probe_sz; + ds->ds_flags |= DS_PROBE_SENT; + ++ds->ds_probe_count; + conn->ifc_mflags &= ~MF_CHECK_MTU_PROBE; + assert(!lsquic_alarmset_is_set(&conn->ifc_alset, AL_MTU_PROBE)); + lsquic_alarmset_set(&conn->ifc_alset, AL_MTU_PROBE, + now + conn->ifc_enpub->enp_mtu_probe_timer); + restore_packet_size: + cpath->cop_path.np_pack_size = saved_packet_sz; +} + + +static void +ietf_full_conn_ci_mtu_probe_acked (struct lsquic_conn *lconn, + const struct lsquic_packet_out *packet_out) +{ + struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn; + struct conn_path *cpath; + struct dplpmtud_state *ds; + unsigned char path_id; + + path_id = packet_out->po_path->np_path_id; + cpath = &conn->ifc_paths[path_id]; + ds = &cpath->cop_dplpmtud; + if (ds->ds_probe_packno != packet_out->po_packno) + { + LSQ_DEBUG("Acked MTU probe packet %"PRIu64" on path %hhu, but it is " + "old: discard", packet_out->po_packno, path_id); + return; + } + ds->ds_flags &= ~DS_PROBE_SENT; + ds->ds_probe_count = 0; + + cpath->cop_path.np_pack_size = lsquic_packet_out_sent_sz(&conn->ifc_conn, + packet_out); + LSQ_INFO("update path %hhu MTU to %hu bytes", path_id, + cpath->cop_path.np_pack_size); + conn->ifc_mflags &= ~MF_CHECK_MTU_PROBE; + lsquic_alarmset_set(&conn->ifc_alset, AL_MTU_PROBE, + packet_out->po_sent + conn->ifc_enpub->enp_mtu_probe_timer); + LSQ_DEBUG("set alarm to %"PRIu64" usec ", packet_out->po_sent + conn->ifc_enpub->enp_mtu_probe_timer); +} + + +static void +mtu_probe_too_large (struct ietf_full_conn *conn, + const struct lsquic_packet_out *packet_out) +{ + struct conn_path *cpath; + unsigned char path_id; + + path_id = packet_out->po_path->np_path_id; + cpath = &conn->ifc_paths[path_id]; + cpath->cop_dplpmtud.ds_failed_size + = lsquic_packet_out_sent_sz(&conn->ifc_conn, packet_out); +} + + +static void +ietf_full_conn_ci_retx_timeout (struct lsquic_conn *lconn) +{ + struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn; + unsigned short pack_size; + struct conn_path *cpath; + int resize; + + resize = 0; + for (cpath = conn->ifc_paths; cpath < conn->ifc_paths + N_PATHS; ++cpath) + if (cpath->cop_flags & COP_INITIALIZED) + { + pack_size = calc_base_packet_size(conn, + NP_IS_IPv6(&cpath->cop_path)); + if (cpath->cop_path.np_pack_size > pack_size) + { + LSQ_DEBUG("RTO occurred: change packet size of path %hhu " + "to %hu bytes", cpath->cop_path.np_path_id, pack_size); + cpath->cop_path.np_pack_size = pack_size; + resize |= 1; + } + } + + if (resize) + lsquic_send_ctl_resize(&conn->ifc_send_ctl); + else + LSQ_DEBUG("RTO occurred, but no MTUs to reset"); +} + + static enum tick_st ietf_full_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now) { @@ -6878,6 +7251,9 @@ ietf_full_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now) } } + if (conn->ifc_mflags & MF_CHECK_MTU_PROBE) + check_or_schedule_mtu_probe(conn, now); + n = lsquic_send_ctl_reschedule_packets(&conn->ifc_send_ctl); if (n > 0) CLOSE_IF_NECESSARY(); @@ -7358,6 +7734,7 @@ ietf_full_conn_ci_count_garbage (struct lsquic_conn *lconn, size_t garbage_sz) .ci_is_push_enabled = ietf_full_conn_ci_is_push_enabled, \ .ci_is_tickable = ietf_full_conn_ci_is_tickable, \ .ci_make_stream = ietf_full_conn_ci_make_stream, \ + .ci_mtu_probe_acked = ietf_full_conn_ci_mtu_probe_acked, \ .ci_n_avail_streams = ietf_full_conn_ci_n_avail_streams, \ .ci_n_pending_streams = ietf_full_conn_ci_n_pending_streams, \ .ci_next_tick_time = ietf_full_conn_ci_next_tick_time, \ @@ -7365,6 +7742,7 @@ ietf_full_conn_ci_count_garbage (struct lsquic_conn *lconn, size_t garbage_sz) .ci_push_stream = ietf_full_conn_ci_push_stream, \ .ci_record_addrs = ietf_full_conn_ci_record_addrs, \ .ci_report_live = ietf_full_conn_ci_report_live, \ + .ci_retx_timeout = ietf_full_conn_ci_retx_timeout, \ .ci_set_ctx = ietf_full_conn_ci_set_ctx, \ .ci_status = ietf_full_conn_ci_status, \ .ci_stateless_reset = ietf_full_conn_ci_stateless_reset, \ @@ -7377,6 +7755,7 @@ static const struct conn_iface ietf_full_conn_iface = { .ci_next_packet_to_send = ietf_full_conn_ci_next_packet_to_send, .ci_packet_not_sent = ietf_full_conn_ci_packet_not_sent, .ci_packet_sent = ietf_full_conn_ci_packet_sent, + .ci_packet_too_large = ietf_full_conn_ci_packet_too_large, }; static const struct conn_iface *ietf_full_conn_iface_ptr = &ietf_full_conn_iface; diff --git a/src/liblsquic/lsquic_logger.c b/src/liblsquic/lsquic_logger.c index 5cc8330..58a0d97 100644 --- a/src/liblsquic/lsquic_logger.c +++ b/src/liblsquic/lsquic_logger.c @@ -94,6 +94,7 @@ enum lsq_log_level lsq_log_levels[N_LSQUIC_LOGGER_MODULES] = { [LSQLM_QPACK_DEC] = LSQ_LOG_WARN, [LSQLM_PRIO] = LSQ_LOG_WARN, [LSQLM_BW_SAMPLER] = LSQ_LOG_WARN, + [LSQLM_PACKET_RESIZE] = LSQ_LOG_WARN, }; const char *const lsqlm_to_str[N_LSQUIC_LOGGER_MODULES] = { @@ -136,6 +137,7 @@ const char *const lsqlm_to_str[N_LSQUIC_LOGGER_MODULES] = { [LSQLM_QPACK_DEC] = "qpack-dec", [LSQLM_PRIO] = "prio", [LSQLM_BW_SAMPLER] = "bw-sampler", + [LSQLM_PACKET_RESIZE] = "packet-resize", }; const char *const lsq_loglevel2str[N_LSQUIC_LOG_LEVELS] = { diff --git a/src/liblsquic/lsquic_logger.h b/src/liblsquic/lsquic_logger.h index 4da5cb6..0473bb2 100644 --- a/src/liblsquic/lsquic_logger.h +++ b/src/liblsquic/lsquic_logger.h @@ -85,6 +85,7 @@ enum lsquic_logger_module { LSQLM_QPACK_DEC, LSQLM_PRIO, LSQLM_BW_SAMPLER, + LSQLM_PACKET_RESIZE, N_LSQUIC_LOGGER_MODULES }; diff --git a/src/liblsquic/lsquic_mini_conn.c b/src/liblsquic/lsquic_mini_conn.c index 52728ce..a0d5b01 100644 --- a/src/liblsquic/lsquic_mini_conn.c +++ b/src/liblsquic/lsquic_mini_conn.c @@ -826,7 +826,7 @@ mini_stream_read (void *stream, void *buf, size_t len, int *reached_fin) /* Wrapper to throw out reached_fin */ static size_t -mini_stream_read_for_crypto (void *stream, void *buf, size_t len) +mini_stream_read_for_crypto (void *stream, void *buf, size_t len, int *fin) { size_t retval; int reached_fin; @@ -969,7 +969,7 @@ to_packet_Q050plus (struct mini_conn *mc, struct mini_stream_ctx *ms_ctx, cur_off = ms_ctx->off; len = mc->mc_conn.cn_pf->pf_gen_crypto_frame( packet_out->po_data + packet_out->po_data_sz, - lsquic_packet_out_avail(packet_out), mc->mc_write_off, + lsquic_packet_out_avail(packet_out), 0, mc->mc_write_off, 0, mini_stream_size(ms_ctx), mini_stream_read_for_crypto, ms_ctx); if (len < 0) { diff --git a/src/liblsquic/lsquic_mini_conn_ietf.c b/src/liblsquic/lsquic_mini_conn_ietf.c index 3121fee..7dfd564 100644 --- a/src/liblsquic/lsquic_mini_conn_ietf.c +++ b/src/liblsquic/lsquic_mini_conn_ietf.c @@ -154,7 +154,7 @@ struct msg_ctx static size_t -read_from_msg_ctx (void *ctx, void *buf, size_t len) +read_from_msg_ctx (void *ctx, void *buf, size_t len, int *fin) { struct msg_ctx *msg_ctx = ctx; if (len > (uintptr_t) (msg_ctx->end - msg_ctx->buf)) @@ -244,7 +244,7 @@ imico_stream_write (void *stream, const void *bufp, size_t bufsz) p = msg_ctx.buf; len = pf->pf_gen_crypto_frame(packet_out->po_data + packet_out->po_data_sz, - lsquic_packet_out_avail(packet_out), cryst->mcs_write_off, + lsquic_packet_out_avail(packet_out), 0, cryst->mcs_write_off, 0, msg_ctx.end - msg_ctx.buf, read_from_msg_ctx, &msg_ctx); if (len < 0) return len; @@ -513,12 +513,14 @@ lsquic_mini_conn_ietf_new (struct lsquic_engine_public *enpub, conn->imc_enpub = enpub; conn->imc_created = packet_in->pi_received; - conn->imc_path.np_pack_size = is_ipv4 ? IQUIC_MAX_IPv4_PACKET_SZ - : IQUIC_MAX_IPv6_PACKET_SZ; -#ifndef NDEBUG - if (getenv("LSQUIC_CN_PACK_SIZE")) - conn->imc_path.np_pack_size = atoi(getenv("LSQUIC_CN_PACK_SIZE")); -#endif + if (enpub->enp_settings.es_base_plpmtu) + conn->imc_path.np_pack_size = enpub->enp_settings.es_base_plpmtu + - (is_ipv4 ? 20 : 40) /* IP header */ + - 8; /* UDP header */ + else if (is_ipv4) + conn->imc_path.np_pack_size = IQUIC_MAX_IPv4_PACKET_SZ; + else + conn->imc_path.np_pack_size = IQUIC_MAX_IPv6_PACKET_SZ; conn->imc_conn.cn_pf = select_pf_by_ver(version); conn->imc_conn.cn_esf.i = esfi; conn->imc_conn.cn_enc_session = enc_sess; diff --git a/src/liblsquic/lsquic_mm.c b/src/liblsquic/lsquic_mm.c index de87ca6..387c0ac 100644 --- a/src/liblsquic/lsquic_mm.c +++ b/src/liblsquic/lsquic_mm.c @@ -80,7 +80,7 @@ lsquic_mm_init (struct lsquic_mm *mm) mm->acki = malloc(sizeof(*mm->acki)); mm->malo.stream_frame = lsquic_malo_create(sizeof(struct stream_frame)); - mm->malo.stream_rec_arr = lsquic_malo_create(sizeof(struct stream_rec_arr)); + mm->malo.frame_rec_arr = lsquic_malo_create(sizeof(struct frame_rec_arr)); mm->malo.mini_conn = lsquic_malo_create(sizeof(struct mini_conn)); mm->malo.mini_conn_ietf = lsquic_malo_create(sizeof(struct ietf_mini_conn)); mm->malo.packet_in = lsquic_malo_create(sizeof(struct lsquic_packet_in)); @@ -98,7 +98,7 @@ lsquic_mm_init (struct lsquic_mm *mm) SLIST_INIT(&mm->four_k_pages); SLIST_INIT(&mm->sixteen_k_pages); #endif - if (mm->acki && mm->malo.stream_frame && mm->malo.stream_rec_arr + if (mm->acki && mm->malo.stream_frame && mm->malo.frame_rec_arr && mm->malo.mini_conn && mm->malo.mini_conn_ietf && mm->malo.packet_in && mm->malo.packet_out && mm->malo.dcid_elem && mm->malo.stream_hq_frame && mm->ack_str) @@ -127,7 +127,7 @@ lsquic_mm_cleanup (struct lsquic_mm *mm) lsquic_malo_destroy(mm->malo.packet_in); lsquic_malo_destroy(mm->malo.packet_out); lsquic_malo_destroy(mm->malo.stream_frame); - lsquic_malo_destroy(mm->malo.stream_rec_arr); + lsquic_malo_destroy(mm->malo.frame_rec_arr); lsquic_malo_destroy(mm->malo.mini_conn); lsquic_malo_destroy(mm->malo.mini_conn_ietf); free(mm->ack_str); @@ -553,7 +553,7 @@ lsquic_mm_mem_used (const struct lsquic_mm *mm) size = sizeof(*mm); size += sizeof(*mm->acki); size += lsquic_malo_mem_used(mm->malo.stream_frame); - size += lsquic_malo_mem_used(mm->malo.stream_rec_arr); + size += lsquic_malo_mem_used(mm->malo.frame_rec_arr); size += lsquic_malo_mem_used(mm->malo.mini_conn); size += lsquic_malo_mem_used(mm->malo.mini_conn_ietf); size += lsquic_malo_mem_used(mm->malo.packet_in); diff --git a/src/liblsquic/lsquic_mm.h b/src/liblsquic/lsquic_mm.h index c3abe9e..88ee802 100644 --- a/src/liblsquic/lsquic_mm.h +++ b/src/liblsquic/lsquic_mm.h @@ -33,7 +33,7 @@ struct lsquic_mm { struct ack_info *acki; struct { struct malo *stream_frame; /* For struct stream_frame */ - struct malo *stream_rec_arr;/* For struct stream_rec_arr */ + struct malo *frame_rec_arr; /* For struct frame_rec_arr */ struct malo *mini_conn; /* For struct mini_conn */ struct malo *mini_conn_ietf;/* For struct ietf_mini_conn */ struct malo *retry_conn; /* For struct retry_conn */ diff --git a/src/liblsquic/lsquic_pacer.c b/src/liblsquic/lsquic_pacer.c index 24ee71a..1129154 100644 --- a/src/liblsquic/lsquic_pacer.c +++ b/src/liblsquic/lsquic_pacer.c @@ -125,6 +125,16 @@ lsquic_pacer_can_schedule (struct pacer *pacer, unsigned n_in_flight) } +int +lsquic_pacer_can_schedule_probe (const struct pacer *pacer, + unsigned n_in_flight, lsquic_time_t tx_time) +{ + return pacer->pa_burst_tokens > 1 /* Double packet size, want two tokens */ + || n_in_flight == 0 + || pacer->pa_next_sched > pacer->pa_now + tx_time / 2; +} + + void lsquic_pacer_tick_in (struct pacer *pacer, lsquic_time_t now) { diff --git a/src/liblsquic/lsquic_pacer.h b/src/liblsquic/lsquic_pacer.h index 75017c5..63cac3a 100644 --- a/src/liblsquic/lsquic_pacer.h +++ b/src/liblsquic/lsquic_pacer.h @@ -59,4 +59,8 @@ lsquic_pacer_loss_event (struct pacer *); #define lsquic_pacer_next_sched(pacer) (+(pacer)->pa_next_sched) +int +lsquic_pacer_can_schedule_probe (const struct pacer *, + unsigned n_in_flight, lsquic_time_t tx_time); + #endif diff --git a/src/liblsquic/lsquic_packet_gquic.h b/src/liblsquic/lsquic_packet_gquic.h index 4ed979f..138e36c 100644 --- a/src/liblsquic/lsquic_packet_gquic.h +++ b/src/liblsquic/lsquic_packet_gquic.h @@ -16,6 +16,9 @@ enum PACKET_PUBLIC_FLAGS PACKET_PUBLIC_FLAGS_TWO_OR_MORE_BYTES = 1 << 7, }; +/* XXX The name of this macro no longer matches: it applies both to gQUIC and + * IETF QUIC. + */ #define GQUIC_FRAME_REGEN_MASK ((1 << QUIC_FRAME_ACK) \ | (1 << QUIC_FRAME_PATH_CHALLENGE) | (1 << QUIC_FRAME_PATH_RESPONSE) \ | (1 << QUIC_FRAME_STOP_WAITING) | (1 << QUIC_FRAME_TIMESTAMP)) diff --git a/src/liblsquic/lsquic_packet_out.c b/src/liblsquic/lsquic_packet_out.c index f9986bb..b0ae855 100644 --- a/src/liblsquic/lsquic_packet_out.c +++ b/src/liblsquic/lsquic_packet_out.c @@ -30,84 +30,84 @@ #include "lsquic_enc_sess.h" typedef char _stream_rec_arr_is_at_most_64bytes[ - (sizeof(struct stream_rec_arr) <= 64)? 1: - 1]; + (sizeof(struct frame_rec_arr) <= 64)? 1: - 1]; -static struct stream_rec * -srec_one_posi_first (struct packet_out_srec_iter *posi, +static struct frame_rec * +frec_one_pofi_first (struct packet_out_frec_iter *pofi, struct lsquic_packet_out *packet_out) { - if (packet_out->po_srecs.one.sr_frame_type) - return &packet_out->po_srecs.one; + if (packet_out->po_frecs.one.fe_frame_type) + return &packet_out->po_frecs.one; else return NULL; } -static struct stream_rec * -srec_one_posi_next (struct packet_out_srec_iter *posi) +static struct frame_rec * +frec_one_pofi_next (struct packet_out_frec_iter *pofi) { return NULL; } -static struct stream_rec * -srec_arr_posi_next (struct packet_out_srec_iter *posi) +static struct frame_rec * +frec_arr_pofi_next (struct packet_out_frec_iter *pofi) { - while (posi->cur_srec_arr) + while (pofi->cur_frec_arr) { - for (; posi->srec_idx < sizeof(posi->cur_srec_arr->srecs) / sizeof(posi->cur_srec_arr->srecs[0]); - ++posi->srec_idx) + for (; pofi->frec_idx < sizeof(pofi->cur_frec_arr->frecs) / sizeof(pofi->cur_frec_arr->frecs[0]); + ++pofi->frec_idx) { - if (posi->cur_srec_arr->srecs[ posi->srec_idx ].sr_frame_type) - return &posi->cur_srec_arr->srecs[ posi->srec_idx++ ]; + if (pofi->cur_frec_arr->frecs[ pofi->frec_idx ].fe_frame_type) + return &pofi->cur_frec_arr->frecs[ pofi->frec_idx++ ]; } - posi->cur_srec_arr = TAILQ_NEXT(posi->cur_srec_arr, next_stream_rec_arr); - posi->srec_idx = 0; + pofi->cur_frec_arr = TAILQ_NEXT(pofi->cur_frec_arr, next_stream_rec_arr); + pofi->frec_idx = 0; } return NULL; } -static struct stream_rec * -srec_arr_posi_first (struct packet_out_srec_iter *posi, +static struct frame_rec * +frec_arr_pofi_first (struct packet_out_frec_iter *pofi, struct lsquic_packet_out *packet_out) { - posi->packet_out = packet_out; - posi->cur_srec_arr = TAILQ_FIRST(&packet_out->po_srecs.arr); - posi->srec_idx = 0; - return srec_arr_posi_next(posi); + pofi->packet_out = packet_out; + pofi->cur_frec_arr = TAILQ_FIRST(&packet_out->po_frecs.arr); + pofi->frec_idx = 0; + return frec_arr_pofi_next(pofi); } -static struct stream_rec * (* const posi_firsts[]) - (struct packet_out_srec_iter *, struct lsquic_packet_out *) = +static struct frame_rec * (* const pofi_firsts[]) + (struct packet_out_frec_iter *, struct lsquic_packet_out *) = { - srec_one_posi_first, - srec_arr_posi_first, + frec_one_pofi_first, + frec_arr_pofi_first, }; -static struct stream_rec * (* const posi_nexts[]) - (struct packet_out_srec_iter *posi) = +static struct frame_rec * (* const pofi_nexts[]) + (struct packet_out_frec_iter *pofi) = { - srec_one_posi_next, - srec_arr_posi_next, + frec_one_pofi_next, + frec_arr_pofi_next, }; -struct stream_rec * -lsquic_posi_first (struct packet_out_srec_iter *posi, +struct frame_rec * +lsquic_pofi_first (struct packet_out_frec_iter *pofi, lsquic_packet_out_t *packet_out) { - posi->impl_idx = !!(packet_out->po_flags & PO_SREC_ARR); - return posi_firsts[posi->impl_idx](posi, packet_out); + pofi->impl_idx = !!(packet_out->po_flags & PO_FREC_ARR); + return pofi_firsts[pofi->impl_idx](pofi, packet_out); } -struct stream_rec * -lsquic_posi_next (struct packet_out_srec_iter *posi) +struct frame_rec * +lsquic_pofi_next (struct packet_out_frec_iter *pofi) { - return posi_nexts[posi->impl_idx](posi); + return pofi_nexts[pofi->impl_idx](pofi); } @@ -116,73 +116,87 @@ lsquic_posi_next (struct packet_out_srec_iter *posi) * in packet_out->po_data. There is no assertion to guard for for this. */ int -lsquic_packet_out_add_stream (lsquic_packet_out_t *packet_out, +lsquic_packet_out_add_frame (lsquic_packet_out_t *packet_out, struct lsquic_mm *mm, - struct lsquic_stream *new_stream, + uintptr_t data, enum quic_frame_type frame_type, unsigned short off, unsigned short len) { - struct stream_rec_arr *srec_arr; + struct frame_rec_arr *frec_arr; int last_taken; unsigned i; - assert(!(new_stream->stream_flags & STREAM_FINISHED)); - - if (!(packet_out->po_flags & PO_SREC_ARR)) + if (!(packet_out->po_flags & PO_FREC_ARR)) { - if (!srec_taken(&packet_out->po_srecs.one)) + if (!frec_taken(&packet_out->po_frecs.one)) { - packet_out->po_srecs.one.sr_frame_type = frame_type; - packet_out->po_srecs.one.sr_stream = new_stream; - packet_out->po_srecs.one.sr_off = off; - packet_out->po_srecs.one.sr_len = len; - ++new_stream->n_unacked; + packet_out->po_frecs.one.fe_frame_type = frame_type; + packet_out->po_frecs.one.fe_u.data = data; + packet_out->po_frecs.one.fe_off = off; + packet_out->po_frecs.one.fe_len = len; return 0; /* Insert in first slot */ } - srec_arr = lsquic_malo_get(mm->malo.stream_rec_arr); - if (!srec_arr) + frec_arr = lsquic_malo_get(mm->malo.frame_rec_arr); + if (!frec_arr) return -1; - memset(srec_arr, 0, sizeof(*srec_arr)); - srec_arr->srecs[0] = packet_out->po_srecs.one; - TAILQ_INIT(&packet_out->po_srecs.arr); - TAILQ_INSERT_TAIL(&packet_out->po_srecs.arr, srec_arr, + memset(frec_arr, 0, sizeof(*frec_arr)); + frec_arr->frecs[0] = packet_out->po_frecs.one; + TAILQ_INIT(&packet_out->po_frecs.arr); + TAILQ_INSERT_TAIL(&packet_out->po_frecs.arr, frec_arr, next_stream_rec_arr); - packet_out->po_flags |= PO_SREC_ARR; + packet_out->po_flags |= PO_FREC_ARR; i = 1; goto set_elem; } /* New records go at the very end: */ - srec_arr = TAILQ_LAST(&packet_out->po_srecs.arr, stream_rec_arr_tailq); + frec_arr = TAILQ_LAST(&packet_out->po_frecs.arr, frame_rec_arr_tailq); last_taken = -1; - for (i = 0; i < sizeof(srec_arr->srecs) / sizeof(srec_arr->srecs[0]); ++i) - if (srec_taken(&srec_arr->srecs[i])) + for (i = 0; i < sizeof(frec_arr->frecs) / sizeof(frec_arr->frecs[0]); ++i) + if (frec_taken(&frec_arr->frecs[i])) last_taken = i; i = last_taken + 1; - if (i < sizeof(srec_arr->srecs) / sizeof(srec_arr->srecs[0])) + if (i < sizeof(frec_arr->frecs) / sizeof(frec_arr->frecs[0])) { set_elem: - srec_arr->srecs[i].sr_frame_type = frame_type; - srec_arr->srecs[i].sr_stream = new_stream; - srec_arr->srecs[i].sr_off = off; - srec_arr->srecs[i].sr_len = len; - ++new_stream->n_unacked; - return 0; /* Insert in existing srec */ + frec_arr->frecs[i].fe_frame_type = frame_type; + frec_arr->frecs[i].fe_u.data = data; + frec_arr->frecs[i].fe_off = off; + frec_arr->frecs[i].fe_len = len; + return 0; /* Insert in existing frec */ } - srec_arr = lsquic_malo_get(mm->malo.stream_rec_arr); - if (!srec_arr) + frec_arr = lsquic_malo_get(mm->malo.frame_rec_arr); + if (!frec_arr) return -1; - memset(srec_arr, 0, sizeof(*srec_arr)); - srec_arr->srecs[0].sr_frame_type = frame_type; - srec_arr->srecs[0].sr_stream = new_stream; - srec_arr->srecs[0].sr_off = off; - srec_arr->srecs[0].sr_len = len; - TAILQ_INSERT_TAIL(&packet_out->po_srecs.arr, srec_arr, next_stream_rec_arr); - ++new_stream->n_unacked; - return 0; /* Insert in new srec */ + memset(frec_arr, 0, sizeof(*frec_arr)); + frec_arr->frecs[0].fe_frame_type = frame_type; + frec_arr->frecs[0].fe_u.data = data; + frec_arr->frecs[0].fe_off = off; + frec_arr->frecs[0].fe_len = len; + TAILQ_INSERT_TAIL(&packet_out->po_frecs.arr, frec_arr, next_stream_rec_arr); + return 0; /* Insert in new frec */ +} + + +int +lsquic_packet_out_add_stream (struct lsquic_packet_out *packet_out, + struct lsquic_mm *mm, struct lsquic_stream *new_stream, + enum quic_frame_type frame_type, unsigned short off, unsigned short len) +{ + assert(!(new_stream->stream_flags & STREAM_FINISHED)); + assert((1 << frame_type) + & (QUIC_FTBIT_STREAM|QUIC_FTBIT_CRYPTO|QUIC_FTBIT_RST_STREAM)); + if (0 == lsquic_packet_out_add_frame(packet_out, mm, + (uintptr_t) new_stream, frame_type, off, len)) + { + ++new_stream->n_unacked; + return 0; + } + else + return -1; } @@ -262,14 +276,14 @@ void lsquic_packet_out_destroy (lsquic_packet_out_t *packet_out, struct lsquic_engine_public *enpub, void *peer_ctx) { - if (packet_out->po_flags & PO_SREC_ARR) + if (packet_out->po_flags & PO_FREC_ARR) { - struct stream_rec_arr *srec_arr, *next; - for (srec_arr = TAILQ_FIRST(&packet_out->po_srecs.arr); - srec_arr; srec_arr = next) + struct frame_rec_arr *frec_arr, *next; + for (frec_arr = TAILQ_FIRST(&packet_out->po_frecs.arr); + frec_arr; frec_arr = next) { - next = TAILQ_NEXT(srec_arr, next_stream_rec_arr); - lsquic_malo_put(srec_arr); + next = TAILQ_NEXT(frec_arr, next_stream_rec_arr); + lsquic_malo_put(frec_arr); } } if (packet_out->po_flags & PO_ENCRYPTED) @@ -290,46 +304,46 @@ unsigned lsquic_packet_out_elide_reset_stream_frames (lsquic_packet_out_t *packet_out, lsquic_stream_id_t stream_id) { - struct packet_out_srec_iter posi; - struct stream_rec *srec; + struct packet_out_frec_iter pofi; + struct frame_rec *frec; unsigned short adj = 0; int n_stream_frames = 0, n_elided = 0; int victim; - for (srec = lsquic_posi_first(&posi, packet_out); srec; - srec = lsquic_posi_next(&posi)) + for (frec = lsquic_pofi_first(&pofi, packet_out); frec; + frec = lsquic_pofi_next(&pofi)) { - if (srec->sr_frame_type == QUIC_FRAME_STREAM) + /* Offsets of all frame records should be adjusted */ + frec->fe_off -= adj; + + if (frec->fe_frame_type == QUIC_FRAME_STREAM) { ++n_stream_frames; - /* Offsets of all STREAM frames should be adjusted */ - srec->sr_off -= adj; - if (stream_id) { - victim = srec->sr_stream->id == stream_id; + victim = frec->fe_stream->id == stream_id; if (victim) { - assert(lsquic_stream_is_reset(srec->sr_stream)); + assert(lsquic_stream_is_reset(frec->fe_stream)); } } else - victim = lsquic_stream_is_reset(srec->sr_stream); + victim = lsquic_stream_is_reset(frec->fe_stream); if (victim) { ++n_elided; /* Move the data and adjust sizes */ - adj += srec->sr_len; - memmove(packet_out->po_data + srec->sr_off, - packet_out->po_data + srec->sr_off + srec->sr_len, - packet_out->po_data_sz - srec->sr_off - srec->sr_len); - packet_out->po_data_sz -= srec->sr_len; + adj += frec->fe_len; + memmove(packet_out->po_data + frec->fe_off, + packet_out->po_data + frec->fe_off + frec->fe_len, + packet_out->po_data_sz - frec->fe_off - frec->fe_len); + packet_out->po_data_sz -= frec->fe_len; - lsquic_stream_acked(srec->sr_stream, srec->sr_frame_type); - srec->sr_frame_type = 0; + lsquic_stream_acked(frec->fe_stream, frec->fe_frame_type); + frec->fe_frame_type = 0; } } } @@ -348,385 +362,45 @@ lsquic_packet_out_elide_reset_stream_frames (lsquic_packet_out_t *packet_out, void lsquic_packet_out_chop_regen (lsquic_packet_out_t *packet_out) { - struct packet_out_srec_iter posi; - struct stream_rec *srec; - unsigned delta; + struct packet_out_frec_iter pofi; + struct frame_rec *frec; + unsigned short adj; - delta = packet_out->po_regen_sz; - packet_out->po_data_sz -= delta; - memmove(packet_out->po_data, packet_out->po_data + delta, - packet_out->po_data_sz); + adj = 0; + for (frec = lsquic_pofi_first(&pofi, packet_out); frec; + frec = lsquic_pofi_next(&pofi)) + { + frec->fe_off -= adj; + if (GQUIC_FRAME_REGEN_MASK & (1 << frec->fe_frame_type)) + { + assert(frec->fe_off == 0); /* This checks that all the regen + frames are at the beginning of the packet. It can be removed + when this is no longer the case. */ + adj += frec->fe_len; + memmove(packet_out->po_data + frec->fe_off, + packet_out->po_data + frec->fe_off + frec->fe_len, + packet_out->po_data_sz - frec->fe_off - frec->fe_len); + packet_out->po_data_sz -= frec->fe_len; + frec->fe_frame_type = 0; + } + } + + assert(adj); /* Otherwise why are we called? */ + assert(packet_out->po_regen_sz == adj); packet_out->po_regen_sz = 0; - - for (srec = lsquic_posi_first(&posi, packet_out); srec; - srec = lsquic_posi_next(&posi)) - if (srec->sr_frame_type == QUIC_FRAME_STREAM) - srec->sr_off -= delta; } void lsquic_packet_out_ack_streams (lsquic_packet_out_t *packet_out) { - struct packet_out_srec_iter posi; - struct stream_rec *srec; - for (srec = lsquic_posi_first(&posi, packet_out); srec; - srec = lsquic_posi_next(&posi)) - lsquic_stream_acked(srec->sr_stream, srec->sr_frame_type); -} - - -static int -split_off_last_frames (struct lsquic_mm *mm, lsquic_packet_out_t *packet_out, - lsquic_packet_out_t *new_packet_out, struct stream_rec **srecs, - unsigned n_srecs, enum quic_frame_type frame_type) -{ - unsigned n; - - for (n = 0; n < n_srecs; ++n) - { - struct stream_rec *const srec = srecs[n]; - memcpy(new_packet_out->po_data + new_packet_out->po_data_sz, - packet_out->po_data + srec->sr_off, srec->sr_len); - if (0 != lsquic_packet_out_add_stream(new_packet_out, mm, - srec->sr_stream, frame_type, - new_packet_out->po_data_sz, srec->sr_len)) - return -1; - srec->sr_frame_type = 0; - assert(srec->sr_stream->n_unacked > 1); - --srec->sr_stream->n_unacked; - new_packet_out->po_data_sz += srec->sr_len; - } - - packet_out->po_data_sz = srecs[0]->sr_off; - - return 0; -} - - -static int -move_largest_frame (struct lsquic_mm *mm, lsquic_packet_out_t *packet_out, - lsquic_packet_out_t *new_packet_out, struct stream_rec **srecs, - unsigned n_srecs, unsigned max_idx, enum quic_frame_type frame_type) -{ - unsigned n; - struct stream_rec *const max_srec = srecs[max_idx]; - - memcpy(new_packet_out->po_data + new_packet_out->po_data_sz, - packet_out->po_data + max_srec->sr_off, max_srec->sr_len); - memmove(packet_out->po_data + max_srec->sr_off, - packet_out->po_data + max_srec->sr_off + max_srec->sr_len, - packet_out->po_data_sz - max_srec->sr_off - max_srec->sr_len); - if (0 != lsquic_packet_out_add_stream(new_packet_out, mm, - max_srec->sr_stream, frame_type, - new_packet_out->po_data_sz, max_srec->sr_len)) - return -1; - - max_srec->sr_frame_type = 0; - assert(max_srec->sr_stream->n_unacked > 1); - --max_srec->sr_stream->n_unacked; - new_packet_out->po_data_sz += max_srec->sr_len; - packet_out->po_data_sz -= max_srec->sr_len; - - for (n = max_idx + 1; n < n_srecs; ++n) - srecs[n]->sr_off -= max_srec->sr_len; - - return 0; -} - - -struct split_reader_ctx -{ - unsigned off; - unsigned len; - signed char fin; - unsigned char buf[GQUIC_MAX_PAYLOAD_SZ / 2 + 1]; -}; - - -static int -split_reader_fin (void *ctx) -{ - struct split_reader_ctx *const reader_ctx = ctx; - return reader_ctx->off == reader_ctx->len && reader_ctx->fin; -} - - -static size_t -split_reader_size (void *ctx) -{ - struct split_reader_ctx *const reader_ctx = ctx; - return reader_ctx->len - reader_ctx->off; -} - - -static size_t -split_stream_reader_read (void *ctx, void *buf, size_t len, int *fin) -{ - struct split_reader_ctx *const reader_ctx = ctx; - if (len > reader_ctx->len - reader_ctx->off) - len = reader_ctx->len - reader_ctx->off; - memcpy(buf, reader_ctx->buf, len); - reader_ctx->off += len; - *fin = split_reader_fin(reader_ctx); - return len; -} - - -static size_t -split_crypto_reader_read (void *ctx, void *buf, size_t len) -{ - struct split_reader_ctx *const reader_ctx = ctx; - if (len > reader_ctx->len - reader_ctx->off) - len = reader_ctx->len - reader_ctx->off; - memcpy(buf, reader_ctx->buf, len); - reader_ctx->off += len; - return len; -} - - -static int -split_largest_frame (struct lsquic_mm *mm, lsquic_packet_out_t *packet_out, - lsquic_packet_out_t *new_packet_out, const struct parse_funcs *pf, - struct stream_rec **srecs, unsigned n_srecs, unsigned max_idx, - enum quic_frame_type frame_type) -{ - struct stream_rec *const max_srec = srecs[max_idx]; - struct stream_frame frame; - int len; - unsigned n; - struct split_reader_ctx reader_ctx; - - if (frame_type == QUIC_FRAME_STREAM) - len = pf->pf_parse_stream_frame(packet_out->po_data + max_srec->sr_off, - max_srec->sr_len, &frame); - else - len = pf->pf_parse_crypto_frame(packet_out->po_data + max_srec->sr_off, - max_srec->sr_len, &frame); - if (len < 0) - { - LSQ_ERROR("could not parse own frame"); - return -1; - } - - assert(frame.data_frame.df_size / 2 <= sizeof(reader_ctx.buf)); - if (frame.data_frame.df_size / 2 > sizeof(reader_ctx.buf)) - return -1; - - memcpy(reader_ctx.buf, - frame.data_frame.df_data + frame.data_frame.df_size / 2, - frame.data_frame.df_size - frame.data_frame.df_size / 2); - reader_ctx.off = 0; - reader_ctx.len = frame.data_frame.df_size - frame.data_frame.df_size / 2; - reader_ctx.fin = frame.data_frame.df_fin; - - if (frame_type == QUIC_FRAME_STREAM) - len = pf->pf_gen_stream_frame( - new_packet_out->po_data + new_packet_out->po_data_sz, - lsquic_packet_out_avail(new_packet_out), frame.stream_id, - frame.data_frame.df_offset + frame.data_frame.df_size / 2, - split_reader_fin(&reader_ctx), split_reader_size(&reader_ctx), - split_stream_reader_read, &reader_ctx); - else - len = pf->pf_gen_crypto_frame( - new_packet_out->po_data + new_packet_out->po_data_sz, - lsquic_packet_out_avail(new_packet_out), - frame.data_frame.df_offset + frame.data_frame.df_size / 2, - split_reader_size(&reader_ctx), - split_crypto_reader_read, &reader_ctx); - if (len < 0) - { - LSQ_ERROR("could not generate new frame 1"); - return -1; - } - if (0 != lsquic_packet_out_add_stream(new_packet_out, mm, - max_srec->sr_stream, max_srec->sr_frame_type, - new_packet_out->po_data_sz, len)) - return -1; - new_packet_out->po_data_sz += len; - if (0 == lsquic_packet_out_avail(new_packet_out)) - { - assert(0); /* We really should not fill here, but JIC */ - new_packet_out->po_flags |= PO_STREAM_END; - } - - memcpy(reader_ctx.buf, frame.data_frame.df_data, - frame.data_frame.df_size / 2); - reader_ctx.off = 0; - reader_ctx.len = frame.data_frame.df_size / 2; - reader_ctx.fin = 0; - if (frame_type == QUIC_FRAME_STREAM) - len = pf->pf_gen_stream_frame( - packet_out->po_data + max_srec->sr_off, max_srec->sr_len, - frame.stream_id, frame.data_frame.df_offset, - split_reader_fin(&reader_ctx), split_reader_size(&reader_ctx), - split_stream_reader_read, &reader_ctx); - else - len = pf->pf_gen_crypto_frame( - packet_out->po_data + max_srec->sr_off, max_srec->sr_len, - frame.data_frame.df_offset, - split_reader_size(&reader_ctx), - split_crypto_reader_read, &reader_ctx); - if (len < 0) - { - LSQ_ERROR("could not generate new frame 2"); - return -1; - } - - const unsigned short adj = max_srec->sr_len - (unsigned short) len; - max_srec->sr_len = len; - for (n = max_idx + 1; n < n_srecs; ++n) - srecs[n]->sr_off -= adj; - packet_out->po_data_sz -= adj; - - return 0; -} - - -#ifndef NDEBUG -static void -verify_srecs (lsquic_packet_out_t *packet_out, enum quic_frame_type frame_type) -{ - struct packet_out_srec_iter posi; - const struct stream_rec *srec; - unsigned off; - - srec = lsquic_posi_first(&posi, packet_out); - assert(srec); - - off = 0; - for ( ; srec; srec = lsquic_posi_next(&posi)) - { - assert(srec->sr_off == off); - assert(srec->sr_frame_type == frame_type); - off += srec->sr_len; - } - - assert(packet_out->po_data_sz == off); -} -#endif - - -int -lsquic_packet_out_split_in_two (struct lsquic_mm *mm, - lsquic_packet_out_t *packet_out, lsquic_packet_out_t *new_packet_out, - const struct parse_funcs *pf, unsigned excess_bytes) -{ - struct packet_out_srec_iter posi; - struct stream_rec *local_arr[4]; - struct stream_rec **new_srecs, **srecs = local_arr; - struct stream_rec *srec; - unsigned n_srecs_alloced = sizeof(local_arr) / sizeof(local_arr[0]); - unsigned n_srecs, max_idx, n, nbytes; - enum quic_frame_type frame_type; -#ifndef NDEBUG - unsigned short frame_sum = 0; -#endif - int rv; - - /* We only split buffered packets or initial packets with CRYPTO frames. - * Either contain just one frame type: STREAM or CRYPTO. - */ - assert(packet_out->po_frame_types == (1 << QUIC_FRAME_STREAM) - || packet_out->po_frame_types == (1 << QUIC_FRAME_CRYPTO)); - if (packet_out->po_frame_types & (1 << QUIC_FRAME_STREAM)) - frame_type = QUIC_FRAME_STREAM; - else - frame_type = QUIC_FRAME_CRYPTO; - - n_srecs = 0; -#ifdef WIN32 - max_idx = 0; -#endif - for (srec = lsquic_posi_first(&posi, packet_out); srec; - srec = lsquic_posi_next(&posi)) - { - assert(srec->sr_frame_type == QUIC_FRAME_STREAM - || srec->sr_frame_type == QUIC_FRAME_CRYPTO); - if (n_srecs >= n_srecs_alloced) - { - n_srecs_alloced *= 2; - if (srecs == local_arr) - { - srecs = malloc(sizeof(srecs[0]) * n_srecs_alloced); - if (!srecs) - goto err; - memcpy(srecs, local_arr, sizeof(local_arr)); - } - else - { - new_srecs = realloc(srecs, sizeof(srecs[0]) * n_srecs_alloced); - if (!new_srecs) - goto err; - srecs = new_srecs; - } - } - -#ifndef NDEBUG - frame_sum += srec->sr_len; -#endif - if (n_srecs == 0 || srecs[max_idx]->sr_len < srec->sr_len) - max_idx = n_srecs; - - srecs[n_srecs++] = srec; - } - - assert(frame_sum == packet_out->po_data_sz); - - if (n_srecs == 1) - goto common_case; - - if (n_srecs < 1) - goto err; - - /* Case 1: see if we can remove one or more trailing frames to make - * packet smaller. - */ - nbytes = 0; - for (n = n_srecs - 1; n > max_idx && nbytes < excess_bytes; --n) - nbytes += srecs[n]->sr_len; - if (nbytes >= excess_bytes) - { - rv = split_off_last_frames(mm, packet_out, new_packet_out, - srecs + n + 1, n_srecs - n - 1, frame_type); - goto end; - } - - /* Case 2: see if we can move the largest frame to new packet. */ - nbytes = 0; - for (n = 0; n < n_srecs; ++n) - if (n != max_idx) - nbytes += srecs[n]->sr_len; - if (nbytes >= excess_bytes) - { - rv = move_largest_frame(mm, packet_out, new_packet_out, srecs, - n_srecs, max_idx, frame_type); - goto end; - } - - common_case: - /* Case 3: we have to split the largest frame (which could be the - * the only frame) in two. - */ - rv = split_largest_frame(mm, packet_out, new_packet_out, pf, srecs, - n_srecs, max_idx, frame_type); - - end: - if (srecs != local_arr) - free(srecs); - if (0 == rv) - { - new_packet_out->po_frame_types |= 1 << frame_type; -#ifndef NDEBUG - verify_srecs(packet_out, frame_type); - verify_srecs(new_packet_out, frame_type); -#endif - } - return rv; - - err: - rv = -1; - goto end; + struct packet_out_frec_iter pofi; + struct frame_rec *frec; + for (frec = lsquic_pofi_first(&pofi, packet_out); frec; + frec = lsquic_pofi_next(&pofi)) + if ((1 << frec->fe_frame_type) + & (QUIC_FTBIT_STREAM|QUIC_FTBIT_CRYPTO|QUIC_FTBIT_RST_STREAM)) + lsquic_stream_acked(frec->fe_stream, frec->fe_frame_type); } @@ -746,7 +420,7 @@ lsquic_packet_out_zero_pad (lsquic_packet_out_t *packet_out) size_t lsquic_packet_out_mem_used (const struct lsquic_packet_out *packet_out) { - const struct stream_rec_arr *srec_arr; + const struct frame_rec_arr *frec_arr; size_t size; size = 0; /* The struct is allocated using malo */ @@ -757,9 +431,9 @@ lsquic_packet_out_mem_used (const struct lsquic_packet_out *packet_out) if (packet_out->po_nonce) size += 32; - if (packet_out->po_flags & PO_SREC_ARR) - TAILQ_FOREACH(srec_arr, &packet_out->po_srecs.arr, next_stream_rec_arr) - size += sizeof(*srec_arr); + if (packet_out->po_flags & PO_FREC_ARR) + TAILQ_FOREACH(frec_arr, &packet_out->po_frecs.arr, next_stream_rec_arr) + size += sizeof(*frec_arr); return size; } @@ -770,19 +444,19 @@ lsquic_packet_out_turn_on_fin (struct lsquic_packet_out *packet_out, const struct parse_funcs *pf, const struct lsquic_stream *stream) { - struct packet_out_srec_iter posi; - const struct stream_rec *srec; + struct packet_out_frec_iter pofi; + const struct frame_rec *frec; struct stream_frame stream_frame; uint64_t last_offset; int len; - for (srec = lsquic_posi_first(&posi, packet_out); srec; - srec = lsquic_posi_next(&posi)) - if (srec->sr_frame_type == QUIC_FRAME_STREAM - && srec->sr_stream == stream) + for (frec = lsquic_pofi_first(&pofi, packet_out); frec; + frec = lsquic_pofi_next(&pofi)) + if (frec->fe_frame_type == QUIC_FRAME_STREAM + && frec->fe_stream == stream) { - len = pf->pf_parse_stream_frame(packet_out->po_data + srec->sr_off, - srec->sr_len, &stream_frame); + len = pf->pf_parse_stream_frame(packet_out->po_data + frec->fe_off, + frec->fe_len, &stream_frame); assert(len >= 0); if (len < 0) return -1; @@ -790,10 +464,10 @@ lsquic_packet_out_turn_on_fin (struct lsquic_packet_out *packet_out, + stream_frame.data_frame.df_size; if (last_offset == stream->tosend_off) { - pf->pf_turn_on_fin(packet_out->po_data + srec->sr_off); + pf->pf_turn_on_fin(packet_out->po_data + frec->fe_off); EV_LOG_UPDATED_STREAM_FRAME( lsquic_conn_log_cid(lsquic_stream_conn(stream)), - pf, packet_out->po_data + srec->sr_off, srec->sr_len); + pf, packet_out->po_data + frec->fe_off, frec->fe_len); return 0; } } diff --git a/src/liblsquic/lsquic_packet_out.h b/src/liblsquic/lsquic_packet_out.h index 5a32fdf..0478cc7 100644 --- a/src/liblsquic/lsquic_packet_out.h +++ b/src/liblsquic/lsquic_packet_out.h @@ -17,14 +17,15 @@ struct network_path; struct parse_funcs; struct bwp_state; -/* Each stream_rec is associated with one packet_out. packet_out can have - * zero or more stream_rec structures. stream_rec keeps a pointer to a stream - * that has STREAM or RST_STREAM frames inside packet_out. `sr_frame_type' - * specifies the type of the frame; if this value is zero, values of the - * other struct members are not valid. `sr_off' indicates where inside - * packet_out->po_data the frame begins and `sr_len' is its length. +/* Each frame_rec is associated with one packet_out. packet_out can have + * zero or more frame_rec structures. frame_rec keeps a pointer to a stream + * that has STREAM, CRYPTO, or RST_STREAM frames inside packet_out. + * `fe_frame_type' specifies the type of the frame; if this value is zero + * (this happens when a frame is elided), values of the other struct members + * are not valid. `fe_off' indicates where inside packet_out->po_data the + * frame begins and `fe_len' is its length. * - * We need this information for three reasons: + * We need this information for four reasons: * 1. A stream is not destroyed until all of its STREAM and RST_STREAM * frames are acknowledged. This is to make sure that we do not exceed * maximum allowed number of streams. @@ -34,27 +35,37 @@ struct bwp_state; * occurs if we guessed incorrectly the number of bytes required to * encode the packet number and the actual number would make packet * larger than the max). + * 4. A lost or scheduled packet may need to be resized (down) when path + * changes or MTU is reduced due to an RTO. * + * In IETF, all frames are recorded. In gQUIC, only STREAM, RST_STREAM, + * ACK, and STOP_WAITING are recorded. The latter two are done so that + * ACK-deleting code in send controller (see po_regen_sz) is the same for + * both QUIC versions. */ -struct stream_rec { - struct lsquic_stream *sr_stream; - unsigned short sr_off, - sr_len; - enum quic_frame_type sr_frame_type:16; +struct frame_rec { + union { + struct lsquic_stream *stream; + uintptr_t data; + } fe_u; +#define fe_stream fe_u.stream + unsigned short fe_off, + fe_len; + enum quic_frame_type fe_frame_type; }; -#define srec_taken(srec) ((srec)->sr_frame_type) +#define frec_taken(frec) ((frec)->fe_frame_type) -struct stream_rec_arr { - TAILQ_ENTRY(stream_rec_arr) next_stream_rec_arr; - struct stream_rec srecs[ +struct frame_rec_arr { + TAILQ_ENTRY(frame_rec_arr) next_stream_rec_arr; + struct frame_rec frecs[ ( 64 /* Efficient size for malo allocator */ - - sizeof(TAILQ_ENTRY(stream_rec)) /* next_stream_rec_arr */ - ) / sizeof(struct stream_rec) + - sizeof(TAILQ_ENTRY(frame_rec)) /* next_stream_rec_arr */ + ) / sizeof(struct frame_rec) ]; }; -TAILQ_HEAD(stream_rec_arr_tailq, stream_rec_arr); +TAILQ_HEAD(frame_rec_arr_tailq, frame_rec_arr); typedef struct lsquic_packet_out @@ -76,14 +87,14 @@ typedef struct lsquic_packet_out enum packet_out_flags { /* TODO XXX Phase out PO_MINI in favor of a more specialized flag: * we only need an indicator that a packet contains STREAM frames - * but no associated srecs. This type of packets in only created + * but no associated frecs. This type of packets in only created * by GQUIC mini conn. */ PO_MINI = (1 << 0), /* Allocated by mini connection */ PO_HELLO = (1 << 1), /* Packet contains SHLO or CHLO data */ PO_SENT = (1 << 2), /* Packet has been sent (mini only) */ PO_ENCRYPTED= (1 << 3), /* po_enc_data has encrypted data */ - PO_SREC_ARR = (1 << 4), + PO_FREC_ARR = (1 << 4), #define POBIT_SHIFT 5 PO_BITS_0 = (1 << 5), /* PO_BITS_0 and PO_BITS_1 encode the */ PO_BITS_1 = (1 << 6), /* packet number length. See macros below. */ @@ -104,7 +115,7 @@ typedef struct lsquic_packet_out PO_IPv6 = (1 <<20), /* Set if pmi_allocate was passed is_ipv6=1, * otherwise unset. */ - PO_LIMITED = (1 <<21), /* Used to credit sc_next_limit if needed. */ + PO_MTU_PROBE= (1 <<21), /* Special loss and ACK rules apply */ #define POPNS_SHIFT 22 PO_PNS_HSK = (1 <<22), /* PNS bits contain the value of the */ PO_PNS_APP = (1 <<23), /* packet number space. */ @@ -124,6 +135,11 @@ typedef struct lsquic_packet_out unsigned short po_data_sz; /* Number of usable bytes in data */ unsigned short po_enc_data_sz; /* Number of usable bytes in data */ unsigned short po_sent_sz; /* If PO_SENT_SZ is set, real size of sent buffer. */ + /* TODO Revisit po_regen_sz once gQUIC is dropped. Now that all frames + * are recorded, we have more flexibility where to place ACK frames; they + * no longer really have to be at the beginning of the packet, since we + * can locate them. + */ unsigned short po_regen_sz; /* Number of bytes at the beginning * of data containing bytes that are * not to be retransmitted, e.g. ACK @@ -132,7 +148,6 @@ typedef struct lsquic_packet_out unsigned short po_n_alloc; /* Total number of bytes allocated in po_data */ unsigned short po_token_len; enum header_type po_header_type:8; - unsigned char po_path_id; enum { POL_GQUIC = 1 << 0, /* Used for logging */ #define POLEV_SHIFT 1 @@ -149,18 +164,18 @@ typedef struct lsquic_packet_out #ifndef NDEBUG POL_HEADER_PROT = 1 << 9, /* Header protection applied */ #endif + POL_LIMITED = 1 << 10, /* Used to credit sc_next_limit if needed. */ } po_lflags:16; unsigned char *po_data; - /* A lot of packets contain data belonging to only one stream. Thus, - * `one' is used first. If this is not enough, any number of - * stream_rec_arr structures can be allocated to handle more stream - * records. + /* A lot of packets contain only one frame. Thus, `one' is used first. + * If this is not enough, any number of frame_rec_arr structures can be + * allocated to handle more frame records. */ union { - struct stream_rec one; - struct stream_rec_arr_tailq arr; - } po_srecs; + struct frame_rec one; + struct frame_rec_arr_tailq arr; + } po_frecs; /* If PO_ENCRYPTED is set, this points to the buffer that holds encrypted * data. @@ -274,19 +289,19 @@ typedef struct lsquic_packet_out #define lsquic_packet_out_ecn(p) (((p)->po_lflags >> POECN_SHIFT) & 3) -struct packet_out_srec_iter { +struct packet_out_frec_iter { lsquic_packet_out_t *packet_out; - struct stream_rec_arr *cur_srec_arr; - unsigned srec_idx; + struct frame_rec_arr *cur_frec_arr; + unsigned frec_idx; int impl_idx; }; -struct stream_rec * -lsquic_posi_first (struct packet_out_srec_iter *posi, lsquic_packet_out_t *); +struct frame_rec * +lsquic_pofi_first (struct packet_out_frec_iter *pofi, lsquic_packet_out_t *); -struct stream_rec * -lsquic_posi_next (struct packet_out_srec_iter *posi); +struct frame_rec * +lsquic_pofi_next (struct packet_out_frec_iter *pofi); lsquic_packet_out_t * lsquic_packet_out_new (struct lsquic_mm *, struct malo *, int use_cid, @@ -298,6 +313,11 @@ void lsquic_packet_out_destroy (lsquic_packet_out_t *, struct lsquic_engine_public *, void *peer_ctx); +int +lsquic_packet_out_add_frame (struct lsquic_packet_out *, + struct lsquic_mm *, uintptr_t data, enum quic_frame_type, + unsigned short off, unsigned short len); + int lsquic_packet_out_add_stream (lsquic_packet_out_t *packet_out, struct lsquic_mm *mm, @@ -309,10 +329,6 @@ unsigned lsquic_packet_out_elide_reset_stream_frames (lsquic_packet_out_t *, lsquic_stream_id_t); -int -lsquic_packet_out_split_in_two (struct lsquic_mm *, lsquic_packet_out_t *, - lsquic_packet_out_t *, const struct parse_funcs *, unsigned excess_bytes); - void lsquic_packet_out_chop_regen (lsquic_packet_out_t *); diff --git a/src/liblsquic/lsquic_packet_resize.c b/src/liblsquic/lsquic_packet_resize.c new file mode 100644 index 0000000..39aab79 --- /dev/null +++ b/src/liblsquic/lsquic_packet_resize.c @@ -0,0 +1,258 @@ +/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ +/* Functions to resize packets */ + +#include +#include +#include +#include +#include + +#include "lsquic.h" +#include "lsquic_int_types.h" +#include "lsquic_packet_common.h" +#include "lsquic_packet_in.h" +#include "lsquic_packet_out.h" +#include "lsquic_packet_resize.h" +#include "lsquic_parse.h" +#include "lsquic_hash.h" +#include "lsquic_varint.h" +#include "lsquic_hq.h" +#include "lsquic_sfcw.h" +#include "lsquic_stream.h" +#include "lsquic_mm.h" +#include "lsquic_engine_public.h" +#include "lsquic_conn.h" + +#define LSQUIC_LOGGER_MODULE LSQLM_PACKET_RESIZE +#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(prctx->prc_conn) +#include "lsquic_logger.h" + + +void +lsquic_packet_resize_init (struct packet_resize_ctx *prctx, + struct lsquic_engine_public *enpub, struct lsquic_conn *lconn, void *ctx, + const struct packet_resize_if *pr_if) +{ + memset(prctx, 0, sizeof(*prctx)); + prctx->prc_conn = lconn; + prctx->prc_pri = pr_if; + prctx->prc_enpub = enpub; + prctx->prc_data = ctx; + LSQ_DEBUG("initialized"); +} + + +static const struct frame_rec * +packet_resize_next_frec (struct packet_resize_ctx *prctx) +{ + const struct frame_rec *frec; + + assert(!prctx->prc_cur_frec); + if (prctx->prc_cur_packet) + { + LSQ_DEBUG("get next frec from current packet %"PRIu64, + prctx->prc_cur_packet->po_packno); + frec = lsquic_pofi_next(&prctx->prc_pofi); + if (frec) + return frec; + LSQ_DEBUG("discard packet %"PRIu64, prctx->prc_cur_packet->po_packno); + prctx->prc_pri->pri_discard_packet(prctx->prc_data, + prctx->prc_cur_packet); + prctx->prc_cur_packet = NULL; /* Not necessary; just future-proofing */ + } + + prctx->prc_cur_packet = prctx->prc_pri->pri_next_packet(prctx->prc_data); + if (!prctx->prc_cur_packet) + { + LSQ_DEBUG("out of input packets"); + return NULL; + } + frec = lsquic_pofi_first(&prctx->prc_pofi, prctx->prc_cur_packet); + assert(frec); + LSQ_DEBUG("return first frec from new current packet %"PRIu64, + prctx->prc_cur_packet->po_packno); + return frec; +} + + +static const struct frame_rec * +packet_resize_get_frec (struct packet_resize_ctx *prctx) +{ + if (!prctx->prc_cur_frec) + { + prctx->prc_cur_frec = packet_resize_next_frec(prctx); + if (prctx->prc_cur_frec) + prctx->prc_flags |= PRC_NEW_FREC; + } + return prctx->prc_cur_frec; +} + + +static size_t +packet_resize_gsf_read (void *ctx, void *buf, size_t len, int *fin) +{ + struct packet_resize_ctx *const prctx = ctx; + size_t left; + + left = (size_t) prctx->prc_data_frame.df_size + - (size_t) prctx->prc_data_frame.df_read_off; + if (len > left) + len = left; + memcpy(buf, + prctx->prc_data_frame.df_data + prctx->prc_data_frame.df_read_off, len); + prctx->prc_data_frame.df_read_off += len; + *fin = prctx->prc_data_frame.df_fin + && prctx->prc_data_frame.df_size == prctx->prc_data_frame.df_read_off; + + return len; +} + + +struct lsquic_packet_out * +lsquic_packet_resize_next (struct packet_resize_ctx *prctx) +{ + const unsigned char *data_in; + struct lsquic_packet_out *new; + struct stream_frame stream_frame; + const struct frame_rec *frec; + int s, w, fin, parsed_len; + size_t nbytes; + + if (frec = packet_resize_get_frec(prctx), frec == NULL) + return NULL; + + new = prctx->prc_pri->pri_new_packet(prctx->prc_data); + if (!new) + { + LSQ_DEBUG("cannot allocate new packet"); + goto err; + } + + proc_frec: + if ((1 << frec->fe_frame_type) & (QUIC_FTBIT_STREAM|QUIC_FTBIT_CRYPTO)) + { + if (prctx->prc_flags & PRC_NEW_FREC) + { + data_in = prctx->prc_cur_packet->po_data + frec->fe_off; + parsed_len = (&prctx->prc_conn->cn_pf->pf_parse_stream_frame) + [frec->fe_frame_type == QUIC_FRAME_CRYPTO] + (data_in, frec->fe_len, &stream_frame); + if (parsed_len < 0) + { + LSQ_WARN("cannot parse %s frame", + frame_type_2_str[frec->fe_frame_type]); + goto err; + } + if ((unsigned) parsed_len != frec->fe_len) + { + LSQ_WARN("parsed %s frame size does not match frame record", + frame_type_2_str[frec->fe_frame_type]); + goto err; + } + prctx->prc_data_frame = stream_frame.data_frame; + prctx->prc_flags &= ~PRC_NEW_FREC; + LSQ_DEBUG("parsed %s frame record for stream %"PRIu64 + "; off: %"PRIu64"; size: %"PRIu16"; fin: %d", + frame_type_2_str[frec->fe_frame_type], + frec->fe_stream->id, + stream_frame.data_frame.df_offset, + stream_frame.data_frame.df_size, + stream_frame.data_frame.df_fin); + } + fin = prctx->prc_data_frame.df_fin + && prctx->prc_data_frame.df_read_off == prctx->prc_data_frame.df_size; + nbytes = prctx->prc_data_frame.df_size - prctx->prc_data_frame.df_read_off; + w = (&prctx->prc_conn->cn_pf->pf_gen_stream_frame) + [frec->fe_frame_type == QUIC_FRAME_CRYPTO]( + new->po_data + new->po_data_sz, lsquic_packet_out_avail(new), + frec->fe_stream->id, + prctx->prc_data_frame.df_offset + prctx->prc_data_frame.df_read_off, + fin, nbytes, packet_resize_gsf_read, prctx); + if (w < 0) + { + /* We rely on stream-generating function returning an error instead + * of pre-calculating required size and checking. + */ + LSQ_DEBUG("cannot fit another %s frame, new packet done", + frame_type_2_str[frec->fe_frame_type]); + goto done; + } + if (0 != lsquic_packet_out_add_stream(new, &prctx->prc_enpub->enp_mm, + frec->fe_stream, frec->fe_frame_type, + new->po_data_sz, w)) + { + LSQ_WARN("cannot add stream frame record to new packet"); + goto err; + } + new->po_data_sz += w; + new->po_frame_types |= 1 << frec->fe_frame_type; + if (0 == lsquic_packet_out_avail(new)) + new->po_flags |= PO_STREAM_END; + if (prctx->prc_data_frame.df_size == prctx->prc_data_frame.df_read_off) + { + LSQ_DEBUG("finished using %s frame record", + frame_type_2_str[frec->fe_frame_type]); + --frec->fe_stream->n_unacked; + frec = prctx->prc_cur_frec = NULL; + if (lsquic_packet_out_avail(new) > 0) + if (frec = packet_resize_get_frec(prctx), frec != NULL) + goto proc_frec; + } + } + else if (prctx->prc_cur_frec->fe_len <= lsquic_packet_out_avail(new)) + { + if ((1 << frec->fe_frame_type) & GQUIC_FRAME_REGEN_MASK) + { + if (new->po_regen_sz == new->po_data_sz) + new->po_regen_sz += frec->fe_len; + else + { + LSQ_DEBUG("got non-contiguous regen frame %s, packet done", + frame_type_2_str[frec->fe_frame_type]); + goto done; + } + } + memcpy(new->po_data + new->po_data_sz, + prctx->prc_cur_packet->po_data + frec->fe_off, frec->fe_len); + if (frec->fe_frame_type == QUIC_FRAME_RST_STREAM) + s = lsquic_packet_out_add_stream(new, &prctx->prc_enpub->enp_mm, + frec->fe_stream, frec->fe_frame_type, + new->po_data_sz, frec->fe_len); + else + s = lsquic_packet_out_add_frame(new, &prctx->prc_enpub->enp_mm, + frec->fe_u.data, frec->fe_frame_type, + new->po_data_sz, frec->fe_len); + if (s != 0) + { + LSQ_WARN("cannot add %s frame record to new packet", + frame_type_2_str[frec->fe_frame_type]); + goto err; + } + new->po_data_sz += frec->fe_len; + new->po_frame_types |= 1 << frec->fe_frame_type; + LSQ_DEBUG("copy %hu-byte %s frame into new packet", frec->fe_len, + frame_type_2_str[frec->fe_frame_type]); + if (frec->fe_frame_type == QUIC_FRAME_RST_STREAM) + --frec->fe_stream->n_unacked; + frec = prctx->prc_cur_frec = NULL; + if (lsquic_packet_out_avail(new) > 0) + if (frec = packet_resize_get_frec(prctx), frec != NULL) + goto proc_frec; + } + + done: + if (0 == new->po_data_sz) + { + LSQ_WARN("frame too large"); + goto err; + } + + return new; + + err: + if (new) + lsquic_packet_out_destroy(new, prctx->prc_enpub, + new->po_path->np_peer_ctx); + prctx->prc_flags |= PRC_ERROR; + return NULL; +} diff --git a/src/liblsquic/lsquic_packet_resize.h b/src/liblsquic/lsquic_packet_resize.h new file mode 100644 index 0000000..0201901 --- /dev/null +++ b/src/liblsquic/lsquic_packet_resize.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ +/* + * lsquic_packet_resize.h -- functions to resize packets + */ + +#ifndef LSQUIC_PACKET_RESIZE_H +#define LSQUIC_PACKET_RESIZE_H 1 + +struct lsquic_packet_out; +struct lsquic_conn; +struct frame_rec; +struct lsquic_engine_public; + +struct packet_resize_if +{ + /* Get next packet to convert */ + struct lsquic_packet_out * + (*pri_next_packet)(void *ctx); + /* Discard packet after it was converted */ + void (*pri_discard_packet)(void *ctx, struct lsquic_packet_out *); + /* Get new packet to write frames to */ + struct lsquic_packet_out * + (*pri_new_packet)(void *ctx); +}; + +struct packet_resize_ctx +{ + const struct lsquic_conn *prc_conn; + void *prc_data; /* First arg to prc_pri */ + const struct packet_resize_if *prc_pri; + struct lsquic_engine_public *prc_enpub; + const struct frame_rec *prc_cur_frec; + struct lsquic_packet_out *prc_cur_packet; + struct data_frame prc_data_frame; + struct packet_out_frec_iter prc_pofi; + enum { + PRC_ERROR = 1 << 0, + PRC_NEW_FREC = 1 << 1, + } prc_flags; +}; + +void +lsquic_packet_resize_init (struct packet_resize_ctx *, + struct lsquic_engine_public *, struct lsquic_conn *, void *ctx, + const struct packet_resize_if *); + +struct lsquic_packet_out * +lsquic_packet_resize_next (struct packet_resize_ctx *); + +#define lsquic_packet_resize_is_error(prctx_) \ + (!!((prctx_)->prc_flags & PRC_ERROR)) + +#endif diff --git a/src/liblsquic/lsquic_parse.h b/src/liblsquic/lsquic_parse.h index 2776039..339dce6 100644 --- a/src/liblsquic/lsquic_parse.h +++ b/src/liblsquic/lsquic_parse.h @@ -58,9 +58,6 @@ typedef lsquic_time_t /* gsf_: generate stream frame */ typedef size_t (*gsf_read_f) (void *stream, void *buf, size_t len, int *fin); -/* gcf_: generate CRYPTO frame */ -typedef size_t (*gcf_read_f) (void *stream, void *buf, size_t len); - /* This structure contains functions that parse and generate packets and * frames in version-specific manner. To begin with, there is difference * between GQUIC's little-endian (Q038 and lower) and big-endian formats @@ -83,10 +80,23 @@ struct parse_funcs * exception is -1, which is a generic error code, as we always need * more than 1 byte to write a STREAM frame. */ + /* pf_gen_stream_frame and pf_gen_crypto_frame must be adjacent so that + * they can be cast to an array. + */ int (*pf_gen_stream_frame) (unsigned char *buf, size_t bufsz, lsquic_stream_id_t stream_id, uint64_t offset, int fin, size_t size, gsf_read_f, void *stream); + /* The two "UNUSED" parameters are here so that it matches + * pf_gen_stream_frame. + */ + int + (*pf_gen_crypto_frame) (unsigned char *buf, size_t bufsz, + lsquic_stream_id_t UNUSED_1, uint64_t offset, + int UNUSED_2, size_t size, gsf_read_f, void *stream); + /* pf_parse_stream_frame and pf_parse_crypto_frame must be adjacent so that + * they can be cast to an array. + */ int (*pf_parse_stream_frame) (const unsigned char *buf, size_t rem_packet_sz, struct stream_frame *); @@ -94,9 +104,6 @@ struct parse_funcs (*pf_parse_crypto_frame) (const unsigned char *buf, size_t rem_packet_sz, struct stream_frame *); int - (*pf_gen_crypto_frame) (unsigned char *buf, size_t bufsz, uint64_t offset, - size_t size, gcf_read_f, void *stream); - int (*pf_parse_ack_frame) (const unsigned char *buf, size_t buf_len, struct ack_info *ack_info, uint8_t exp); int diff --git a/src/liblsquic/lsquic_parse_Q046.c b/src/liblsquic/lsquic_parse_Q046.c index 2181d32..0b87424 100644 --- a/src/liblsquic/lsquic_parse_Q046.c +++ b/src/liblsquic/lsquic_parse_Q046.c @@ -264,7 +264,8 @@ gquic_Q046_parse_packet_in_finish (struct lsquic_packet_in *packet_in, static int gquic_Q046_gen_crypto_frame (unsigned char *buf, size_t buf_len, - uint64_t offset, size_t size, gcf_read_f gcf_read, void *stream) + lsquic_stream_id_t stream_id, uint64_t offset, int fin, size_t size, + gsf_read_f gsf_read, void *stream) { assert(0); return -1; diff --git a/src/liblsquic/lsquic_parse_Q050.c b/src/liblsquic/lsquic_parse_Q050.c index ba48205..405c20e 100644 --- a/src/liblsquic/lsquic_parse_Q050.c +++ b/src/liblsquic/lsquic_parse_Q050.c @@ -796,10 +796,11 @@ gquic_Q050_parse_frame_type (const unsigned char *buf, size_t len) static int gquic_Q050_gen_crypto_frame (unsigned char *buf, size_t buf_len, - uint64_t offset, size_t size, gcf_read_f gcf_read, void *stream) + lsquic_stream_id_t stream_id, uint64_t offset, int fin, + size_t size, gsf_read_f gsf_read, void *stream) { - return lsquic_ietf_v1_gen_crypto_frame(buf, 0x8, buf_len, offset, - size, gcf_read, stream); + return lsquic_ietf_v1_gen_crypto_frame(buf, 0x8, stream_id, buf_len, + offset, fin, size, gsf_read, stream); } diff --git a/src/liblsquic/lsquic_parse_gquic_be.c b/src/liblsquic/lsquic_parse_gquic_be.c index ff0f674..b3de902 100644 --- a/src/liblsquic/lsquic_parse_gquic_be.c +++ b/src/liblsquic/lsquic_parse_gquic_be.c @@ -986,7 +986,8 @@ lsquic_gquic_be_gen_ack_frame (unsigned char *outbuf, size_t outbuf_sz, static int lsquic_gquic_be_gen_crypto_frame (unsigned char *buf, size_t buf_len, - uint64_t offset, size_t size, gcf_read_f gcf_read, void *stream) + lsquic_stream_id_t stream_if, uint64_t offset, int fin, + size_t size, gsf_read_f gsf_read, void *stream) { assert(0); return -1; diff --git a/src/liblsquic/lsquic_parse_ietf.h b/src/liblsquic/lsquic_parse_ietf.h index 728c271..4dbcfca 100644 --- a/src/liblsquic/lsquic_parse_ietf.h +++ b/src/liblsquic/lsquic_parse_ietf.h @@ -10,7 +10,7 @@ lsquic_ietf_v1_parse_crypto_frame (const unsigned char *buf, size_t rem_packet_s struct stream_frame *stream_frame); int lsquic_ietf_v1_gen_crypto_frame (unsigned char *buf, unsigned char first_byte, - size_t buf_len, uint64_t offset, size_t size, gcf_read_f gcf_read, - void *stream); + size_t buf_len, lsquic_stream_id_t UNUSED_1, uint64_t offset, + int UNUSED_2, size_t size, gsf_read_f gsf_read, void *stream); #endif diff --git a/src/liblsquic/lsquic_parse_ietf_v1.c b/src/liblsquic/lsquic_parse_ietf_v1.c index 6580af7..db566a7 100644 --- a/src/liblsquic/lsquic_parse_ietf_v1.c +++ b/src/liblsquic/lsquic_parse_ietf_v1.c @@ -443,14 +443,15 @@ ietf_v1_gen_stream_frame (unsigned char *buf, size_t buf_len, int lsquic_ietf_v1_gen_crypto_frame (unsigned char *buf, unsigned char first_byte, - size_t buf_len, uint64_t offset, size_t size, gcf_read_f gcf_read, - void *stream) + size_t buf_len, lsquic_stream_id_t UNUSED_1, uint64_t offset, + int UNUSED_2, size_t size, gsf_read_f gsf_read, void *stream) { unsigned char *const end = buf + buf_len; unsigned char *p; unsigned obits, dbits; unsigned olen, dlen; size_t nr, n_avail; + int dummy_fin; obits = vint_val2bits(offset); olen = 1 << obits; @@ -470,7 +471,7 @@ lsquic_ietf_v1_gen_crypto_frame (unsigned char *buf, unsigned char first_byte, vint_write(p, offset, obits, olen); p += olen; - nr = gcf_read(stream, p + dlen, size); + nr = gsf_read(stream, p + dlen, size, &dummy_fin); assert(nr != 0); /* This indicates error in the caller */ assert(nr <= size); /* This also indicates an error in the caller */ @@ -483,10 +484,11 @@ lsquic_ietf_v1_gen_crypto_frame (unsigned char *buf, unsigned char first_byte, static int ietf_v1_gen_crypto_frame (unsigned char *buf, size_t buf_len, - uint64_t offset, size_t size, gcf_read_f gcf_read, void *stream) + lsquic_stream_id_t stream_id, uint64_t offset, int fin, + size_t size, gsf_read_f gsf_read, void *stream) { - return lsquic_ietf_v1_gen_crypto_frame(buf, 0x6, buf_len, offset, - size, gcf_read, stream); + return lsquic_ietf_v1_gen_crypto_frame(buf, 0x6, buf_len, stream_id, + offset, fin, size, gsf_read, stream); } diff --git a/src/liblsquic/lsquic_send_ctl.c b/src/liblsquic/lsquic_send_ctl.c index 635e3cb..27adb02 100644 --- a/src/liblsquic/lsquic_send_ctl.c +++ b/src/liblsquic/lsquic_send_ctl.c @@ -20,7 +20,9 @@ #include "lsquic_packet_common.h" #include "lsquic_alarmset.h" #include "lsquic_parse.h" +#include "lsquic_packet_in.h" #include "lsquic_packet_out.h" +#include "lsquic_packet_resize.h" #include "lsquic_senhist.h" #include "lsquic_rtt.h" #include "lsquic_cubic.h" @@ -125,6 +127,9 @@ send_ctl_can_send_pre_hsk (struct lsquic_send_ctl *ctl); static int send_ctl_can_send (struct lsquic_send_ctl *ctl); +static int +split_lost_packet (struct lsquic_send_ctl *, struct lsquic_packet_out *const); + #ifdef NDEBUG static #elif __GNUC__ @@ -244,6 +249,7 @@ static void retx_alarm_rings (enum alarm_id al_id, void *ctx, lsquic_time_t expiry, lsquic_time_t now) { lsquic_send_ctl_t *ctl = ctx; + struct lsquic_conn *const lconn = ctl->sc_conn_pub->lconn; lsquic_packet_out_t *packet_out; enum packnum_space pns; enum retx_mode rm; @@ -276,6 +282,8 @@ retx_alarm_rings (enum alarm_id al_id, void *ctx, lsquic_time_t expiry, lsquic_t LSQ_DEBUG("packet RTO is %"PRIu64" usec", expiry); send_ctl_expire(ctl, pns, EXFI_ALL); ctl->sc_ci->cci_timeout(CGP(ctl)); + if (lconn->cn_if->ci_retx_timeout) + lconn->cn_if->ci_retx_timeout(lconn); break; } @@ -479,14 +487,6 @@ set_retx_alarm (struct lsquic_send_ctl *ctl, enum packnum_space pns, } -static int -send_ctl_in_recovery (lsquic_send_ctl_t *ctl) -{ - return ctl->sc_largest_acked_packno - && ctl->sc_largest_acked_packno <= ctl->sc_largest_sent_at_cutback; -} - - #define SC_PACK_SIZE(ctl_) (+(ctl_)->sc_conn_pub->path->np_pack_size) static lsquic_time_t @@ -840,11 +840,8 @@ send_ctl_record_loss (struct lsquic_send_ctl *ctl, } -/* Returns true if packet was rescheduled, false otherwise. In the latter - * case, you should not dereference packet_out after the function returns. - */ static int -send_ctl_handle_lost_packet (lsquic_send_ctl_t *ctl, +send_ctl_handle_regular_lost_packet (struct lsquic_send_ctl *ctl, lsquic_packet_out_t *packet_out, struct lsquic_packet_out **next) { unsigned packet_sz; @@ -895,6 +892,35 @@ send_ctl_handle_lost_packet (lsquic_send_ctl_t *ctl, } +static int +send_ctl_handle_lost_mtu_probe (struct lsquic_send_ctl *ctl, + struct lsquic_packet_out *packet_out) +{ + unsigned packet_sz; + + LSQ_DEBUG("lost MTU probe in packet %"PRIu64, packet_out->po_packno); + packet_sz = packet_out_sent_sz(packet_out); + send_ctl_unacked_remove(ctl, packet_out, packet_sz); + assert(packet_out->po_loss_chain == packet_out); + send_ctl_destroy_packet(ctl, packet_out); + return 0; +} + + +/* Returns true if packet was rescheduled, false otherwise. In the latter + * case, you should not dereference packet_out after the function returns. + */ +static int +send_ctl_handle_lost_packet (struct lsquic_send_ctl *ctl, + struct lsquic_packet_out *packet_out, struct lsquic_packet_out **next) +{ + if (0 == (packet_out->po_flags & PO_MTU_PROBE)) + return send_ctl_handle_regular_lost_packet(ctl, packet_out, next); + else + return send_ctl_handle_lost_mtu_probe(ctl, packet_out); +} + + static lsquic_packno_t largest_retx_packet_number (const struct lsquic_send_ctl *ctl, enum packnum_space pns) @@ -936,13 +962,15 @@ send_ctl_detect_losses (struct lsquic_send_ctl *ctl, enum packnum_space pns, { LSQ_DEBUG("loss by FACK detected, packet %"PRIu64, packet_out->po_packno); - largest_lost_packno = packet_out->po_packno; + if (0 == (packet_out->po_flags & PO_MTU_PROBE)) + largest_lost_packno = packet_out->po_packno; (void) send_ctl_handle_lost_packet(ctl, packet_out, &next); continue; } if (largest_retx_packno && (packet_out->po_frame_types & ctl->sc_retx_frames) + && 0 == (packet_out->po_flags & PO_MTU_PROBE) && largest_retx_packno <= ctl->sc_largest_acked_packno) { LSQ_DEBUG("loss by early retransmit detected, packet %"PRIu64, @@ -961,7 +989,8 @@ send_ctl_detect_losses (struct lsquic_send_ctl *ctl, enum packnum_space pns, { LSQ_DEBUG("loss by sent time detected: packet %"PRIu64, packet_out->po_packno); - if (packet_out->po_frame_types & ctl->sc_retx_frames) + if ((packet_out->po_frame_types & ctl->sc_retx_frames) + && 0 == (packet_out->po_flags & PO_MTU_PROBE)) largest_lost_packno = packet_out->po_packno; else { /* don't count it as a loss */; } (void) send_ctl_handle_lost_packet(ctl, packet_out, &next); @@ -989,6 +1018,20 @@ send_ctl_detect_losses (struct lsquic_send_ctl *ctl, enum packnum_space pns, } +static void +send_ctl_mtu_probe_acked (struct lsquic_send_ctl *ctl, + struct lsquic_packet_out *packet_out) +{ + struct lsquic_conn *const lconn = ctl->sc_conn_pub->lconn; + + LSQ_DEBUG("MTU probe in packet %"PRIu64" has been ACKed", + packet_out->po_packno); + assert(lconn->cn_if->ci_mtu_probe_acked); + if (lconn->cn_if->ci_mtu_probe_acked) + lconn->cn_if->ci_mtu_probe_acked(lconn, packet_out); +} + + int lsquic_send_ctl_got_ack (lsquic_send_ctl_t *ctl, const struct ack_info *acki, @@ -1092,7 +1135,8 @@ lsquic_send_ctl_got_ack (lsquic_send_ctl_t *ctl, ecn_total_acked += lsquic_packet_out_ecn(packet_out) != ECN_NOT_ECT; ecn_ce_cnt += lsquic_packet_out_ecn(packet_out) == ECN_CE; one_rtt_cnt += lsquic_packet_out_enc_level(packet_out) == ENC_LEV_FORW; - if (0 == (packet_out->po_flags & (PO_LOSS_REC|PO_POISON))) + if (0 == (packet_out->po_flags + & (PO_LOSS_REC|PO_POISON|PO_MTU_PROBE))) { packet_sz = packet_out_sent_sz(packet_out); send_ctl_unacked_remove(ctl, packet_out, packet_sz); @@ -1111,6 +1155,12 @@ lsquic_send_ctl_got_ack (lsquic_send_ctl_t *ctl, ++ctl->sc_conn_pub->conn_stats->out.acked_via_loss; #endif } + else if (packet_out->po_flags & PO_MTU_PROBE) + { + packet_sz = packet_out_sent_sz(packet_out); + send_ctl_unacked_remove(ctl, packet_out, packet_sz); + send_ctl_mtu_probe_acked(ctl, packet_out); + } else { LSQ_WARN("poisoned packet %"PRIu64" acked", @@ -1231,6 +1281,7 @@ lsquic_send_ctl_smallest_unacked (lsquic_send_ctl_t *ctl) static struct lsquic_packet_out * send_ctl_next_lost (lsquic_send_ctl_t *ctl) { + struct lsquic_conn *const lconn = ctl->sc_conn_pub->lconn; struct lsquic_packet_out *lost_packet; get_next_lost: @@ -1265,9 +1316,27 @@ send_ctl_next_lost (lsquic_send_ctl_t *ctl) if (!lsquic_send_ctl_can_send(ctl)) return NULL; - TAILQ_REMOVE(&ctl->sc_lost_packets, lost_packet, po_next); - lost_packet->po_flags &= ~PO_LOST; - lost_packet->po_flags |= PO_RETX; + if (packet_out_total_sz(lost_packet) <= SC_PACK_SIZE(ctl)) + { + pop_lost_packet: + TAILQ_REMOVE(&ctl->sc_lost_packets, lost_packet, po_next); + lost_packet->po_flags &= ~PO_LOST; + lost_packet->po_flags |= PO_RETX; + } + else + { + /* We delay resizing lost packets as long as possible, hoping that + * it may be ACKed. At this point, however, we have to resize. + */ + if (0 == split_lost_packet(ctl, lost_packet)) + { + lost_packet = TAILQ_FIRST(&ctl->sc_lost_packets); + goto pop_lost_packet; + } + lconn->cn_if->ci_internal_error(lconn, + "error resizing lost packet"); + return NULL; + } } return lost_packet; @@ -1770,10 +1839,10 @@ lsquic_send_ctl_next_packet_to_send (struct lsquic_send_ctl *ctl, size_t size) if (dec_limit) { --ctl->sc_next_limit; - packet_out->po_flags |= PO_LIMITED; + packet_out->po_lflags |= POL_LIMITED; } else - packet_out->po_flags &= ~PO_LIMITED; + packet_out->po_lflags &= ~POL_LIMITED; if (UNLIKELY(packet_out->po_header_type == HETY_INITIAL) && !(ctl->sc_conn_pub->lconn->cn_flags & LSCONN_SERVER) @@ -1812,7 +1881,7 @@ lsquic_send_ctl_delayed_one (lsquic_send_ctl_t *ctl, lsquic_packet_out_t *packet_out) { send_ctl_sched_prepend(ctl, packet_out); - if (packet_out->po_flags & PO_LIMITED) + if (packet_out->po_lflags & POL_LIMITED) ++ctl->sc_next_limit; LSQ_DEBUG("packet %"PRIu64" has been delayed", packet_out->po_packno); #if LSQUIC_SEND_STATS @@ -1902,7 +1971,17 @@ send_ctl_allocate_packet (struct lsquic_send_ctl *ctl, enum packno_bits bits, { packet_out->po_header_type = HETY_INITIAL; if (ctl->sc_token) + { (void) send_ctl_set_packet_out_token(ctl, packet_out); + if (packet_out->po_n_alloc > packet_out->po_token_len) + packet_out->po_n_alloc -= packet_out->po_token_len; + else + { + /* XXX fail earlier: when retry token is parsed out */ + LSQ_INFO("token is too long: cannot allocate packet"); + return NULL; + } + } } else packet_out->po_header_type = HETY_HANDSHAKE; @@ -2607,38 +2686,112 @@ lsquic_send_ctl_packno_bits (lsquic_send_ctl_t *ctl) } +struct resize_one_packet_ctx +{ + struct lsquic_send_ctl *const ctl; + struct lsquic_packet_out *const victim; + const struct network_path *const path; + const enum packnum_space pns; + int discarded, fetched; +}; + + +static struct lsquic_packet_out * +resize_one_next_packet (void *ctx) +{ + struct resize_one_packet_ctx *const one_ctx = ctx; + + if (one_ctx->fetched) + return NULL; + + ++one_ctx->fetched; + return one_ctx->victim; +} + + +static void +resize_one_discard_packet (void *ctx, struct lsquic_packet_out *packet_out) +{ + struct resize_one_packet_ctx *const one_ctx = ctx; + + /* Delay discarding the packet: we need it for TAILQ_INSERT_BEFORE */ + ++one_ctx->discarded; +} + + +static struct lsquic_packet_out * +resize_one_new_packet (void *ctx) +{ + struct resize_one_packet_ctx *const one_ctx = ctx; + struct lsquic_send_ctl *const ctl = one_ctx->ctl; + struct lsquic_packet_out *packet_out; + enum packno_bits bits; + + bits = lsquic_send_ctl_calc_packno_bits(ctl); + packet_out = send_ctl_allocate_packet(ctl, bits, 0, one_ctx->pns, + one_ctx->path); + return packet_out; +} + + +static const struct packet_resize_if resize_one_funcs = +{ + resize_one_next_packet, + resize_one_discard_packet, + resize_one_new_packet, +}; + + static int split_buffered_packet (lsquic_send_ctl_t *ctl, - enum buf_packet_type packet_type, lsquic_packet_out_t *packet_out, - enum packno_bits bits, unsigned excess_bytes) + enum buf_packet_type packet_type, struct lsquic_packet_out *packet_out) { struct buf_packet_q *const packet_q = &ctl->sc_buffered_packets[packet_type]; - lsquic_packet_out_t *new_packet_out; + struct lsquic_conn *const lconn = ctl->sc_conn_pub->lconn; + struct lsquic_packet_out *new; + struct packet_resize_ctx prctx; + struct resize_one_packet_ctx one_ctx = { + ctl, packet_out, packet_out->po_path, + lsquic_packet_out_pns(packet_out), 0, 0, + }; + unsigned count; assert(TAILQ_FIRST(&packet_q->bpq_packets) == packet_out); - new_packet_out = send_ctl_allocate_packet(ctl, bits, 0, - lsquic_packet_out_pns(packet_out), packet_out->po_path); - if (!new_packet_out) - return -1; - - if (0 == lsquic_packet_out_split_in_two(&ctl->sc_enpub->enp_mm, packet_out, - new_packet_out, ctl->sc_conn_pub->lconn->cn_pf, excess_bytes)) + lsquic_packet_resize_init(&prctx, ctl->sc_enpub, lconn, &one_ctx, + &resize_one_funcs); + count = 0; + while (new = lsquic_packet_resize_next(&prctx), new != NULL) { - lsquic_packet_out_set_packno_bits(packet_out, bits); - TAILQ_INSERT_AFTER(&packet_q->bpq_packets, packet_out, new_packet_out, - po_next); + ++count; + TAILQ_INSERT_BEFORE(packet_out, new, po_next); ++packet_q->bpq_count; LSQ_DEBUG("Add split packet to buffered queue #%u; count: %u", packet_type, packet_q->bpq_count); - return 0; } - else + if (lsquic_packet_resize_is_error(&prctx)) { - send_ctl_destroy_packet(ctl, new_packet_out); + LSQ_WARN("error resizing buffered packet #%"PRIu64, + packet_out->po_packno); return -1; } + if (!(count > 1 && one_ctx.fetched == 1 && one_ctx.discarded == 1)) + { + /* A bit of insurance, this being new code */ + LSQ_WARN("unexpected values resizing buffered packet: count: %u; " + "fetched: %d; discarded: %d", count, one_ctx.fetched, + one_ctx.discarded); + return -1; + } + LSQ_DEBUG("added %u packets to the buffered queue #%u", count, packet_type); + + LSQ_DEBUG("drop oversized buffered packet #%"PRIu64, packet_out->po_packno); + TAILQ_REMOVE(&packet_q->bpq_packets, packet_out, po_next); + ++packet_q->bpq_count; + assert(packet_out->po_loss_chain == packet_out); + send_ctl_destroy_packet(ctl, packet_out); + return 0; } @@ -2650,7 +2803,7 @@ lsquic_send_ctl_schedule_buffered (lsquic_send_ctl_t *ctl, &ctl->sc_buffered_packets[packet_type]; const struct parse_funcs *const pf = ctl->sc_conn_pub->lconn->cn_pf; lsquic_packet_out_t *packet_out; - unsigned used, excess; + unsigned used; assert(lsquic_send_ctl_schedule_stream_packets_immediately(ctl)); const enum packno_bits bits = lsquic_send_ctl_calc_packno_bits(ctl); @@ -2685,12 +2838,10 @@ lsquic_send_ctl_schedule_buffered (lsquic_send_ctl_t *ctl, if (need > used && need - used > lsquic_packet_out_avail(packet_out)) { - excess = need - used - lsquic_packet_out_avail(packet_out); - if (0 != split_buffered_packet(ctl, packet_type, - packet_out, bits, excess)) - { + if (0 == split_buffered_packet(ctl, packet_type, packet_out)) + packet_out = TAILQ_FIRST(&packet_q->bpq_packets); + else return -1; - } } } TAILQ_REMOVE(&packet_q->bpq_packets, packet_out, po_next); @@ -2774,14 +2925,14 @@ lsquic_send_ctl_verneg_done (struct lsquic_send_ctl *ctl) static void strip_trailing_padding (struct lsquic_packet_out *packet_out) { - struct packet_out_srec_iter posi; - const struct stream_rec *srec; + struct packet_out_frec_iter pofi; + const struct frame_rec *frec; unsigned off; off = 0; - for (srec = lsquic_posi_first(&posi, packet_out); srec; - srec = lsquic_posi_next(&posi)) - off = srec->sr_off + srec->sr_len; + for (frec = lsquic_pofi_first(&pofi, packet_out); frec; + frec = lsquic_pofi_next(&pofi)) + off = frec->fe_off + frec->fe_len; assert(off); @@ -2790,11 +2941,59 @@ strip_trailing_padding (struct lsquic_packet_out *packet_out) } +static int +split_lost_packet (struct lsquic_send_ctl *ctl, + struct lsquic_packet_out *const packet_out) +{ + struct lsquic_conn *const lconn = ctl->sc_conn_pub->lconn; + struct lsquic_packet_out *new; + struct packet_resize_ctx prctx; + struct resize_one_packet_ctx one_ctx = { + ctl, packet_out, packet_out->po_path, + lsquic_packet_out_pns(packet_out), 0, 0, + }; + unsigned count; + + assert(packet_out->po_flags & PO_LOST); + + lsquic_packet_resize_init(&prctx, ctl->sc_enpub, lconn, &one_ctx, + &resize_one_funcs); + count = 0; + while (new = lsquic_packet_resize_next(&prctx), new != NULL) + { + ++count; + TAILQ_INSERT_BEFORE(packet_out, new, po_next); + new->po_flags |= PO_LOST; + } + if (lsquic_packet_resize_is_error(&prctx)) + { + LSQ_WARN("error resizing lost packet #%"PRIu64, packet_out->po_packno); + return -1; + } + if (!(count > 1 && one_ctx.fetched == 1 && one_ctx.discarded == 1)) + { + /* A bit of insurance, this being new code */ + LSQ_WARN("unexpected values resizing lost packet: count: %u; " + "fetched: %d; discarded: %d", count, one_ctx.fetched, + one_ctx.discarded); + return -1; + } + LSQ_DEBUG("added %u packets to the lost queue", count); + + LSQ_DEBUG("drop oversized lost packet #%"PRIu64, packet_out->po_packno); + TAILQ_REMOVE(&ctl->sc_lost_packets, packet_out, po_next); + packet_out->po_flags &= ~PO_LOST; + send_ctl_destroy_chain(ctl, packet_out, NULL); + send_ctl_destroy_packet(ctl, packet_out); + return 0; +} + + int lsquic_send_ctl_retry (struct lsquic_send_ctl *ctl, const unsigned char *token, size_t token_sz) { - struct lsquic_packet_out *packet_out, *next, *new_packet_out; + struct lsquic_packet_out *packet_out, *next; struct lsquic_conn *const lconn = ctl->sc_conn_pub->lconn; size_t sz; @@ -2839,38 +3038,9 @@ lsquic_send_ctl_retry (struct lsquic_send_ctl *ctl, strip_trailing_padding(packet_out); sz = lconn->cn_pf->pf_packout_size(lconn, packet_out); - if (sz > 1200) - { - const enum packno_bits bits = lsquic_send_ctl_calc_packno_bits(ctl); - new_packet_out = send_ctl_allocate_packet(ctl, bits, 0, PNS_INIT, - packet_out->po_path); - if (!new_packet_out) - return -1; - if (0 != send_ctl_set_packet_out_token(ctl, new_packet_out)) - { - send_ctl_destroy_packet(ctl, new_packet_out); - LSQ_INFO("cannot set out token on packet"); - return -1; - } - if (0 == lsquic_packet_out_split_in_two(&ctl->sc_enpub->enp_mm, - packet_out, new_packet_out, - ctl->sc_conn_pub->lconn->cn_pf, sz - 1200)) - { - LSQ_DEBUG("split lost packet %"PRIu64" into two", - packet_out->po_packno); - lsquic_packet_out_set_packno_bits(packet_out, bits); - TAILQ_INSERT_AFTER(&ctl->sc_lost_packets, packet_out, - new_packet_out, po_next); - new_packet_out->po_flags |= PO_LOST; - packet_out->po_flags &= ~PO_SENT_SZ; - } - else - { - LSQ_DEBUG("could not split lost packet into two"); - send_ctl_destroy_packet(ctl, new_packet_out); - return -1; - } - } + if (sz > packet_out->po_path->np_pack_size + && 0 != split_lost_packet(ctl, packet_out)) + return -1; } return 0; @@ -2967,6 +3137,159 @@ lsquic_send_ctl_empty_pns (struct lsquic_send_ctl *ctl, enum packnum_space pns) } +struct resize_many_packet_ctx +{ + struct lsquic_send_ctl *ctl; + struct lsquic_packets_tailq input_q; + const struct network_path *path; +}; + + +static struct lsquic_packet_out * +resize_many_next_packet (void *ctx) +{ + struct resize_many_packet_ctx *const many_ctx = ctx; + struct lsquic_packet_out *packet_out; + + packet_out = TAILQ_FIRST(&many_ctx->input_q); + if (packet_out) + TAILQ_REMOVE(&many_ctx->input_q, packet_out, po_next); + + return packet_out; +} + + +static void +resize_many_discard_packet (void *ctx, struct lsquic_packet_out *packet_out) +{ + struct resize_many_packet_ctx *const many_ctx = ctx; + struct lsquic_send_ctl *const ctl = many_ctx->ctl; + + send_ctl_destroy_chain(ctl, packet_out, NULL); + send_ctl_destroy_packet(ctl, packet_out); +} + + +static struct lsquic_packet_out * +resize_many_new_packet (void *ctx) +{ + struct resize_many_packet_ctx *const many_ctx = ctx; + struct lsquic_send_ctl *const ctl = many_ctx->ctl; + struct lsquic_packet_out *packet_out; + enum packno_bits bits; + + bits = lsquic_send_ctl_calc_packno_bits(ctl); + packet_out = send_ctl_allocate_packet(ctl, bits, 0, PNS_APP, + many_ctx->path); + return packet_out; +} + + +static const struct packet_resize_if resize_many_funcs = +{ + resize_many_next_packet, + resize_many_discard_packet, + resize_many_new_packet, +}; + + +static void +send_ctl_resize_q (struct lsquic_send_ctl *ctl, struct lsquic_packets_tailq *q, + const struct network_path *const path) +{ + struct lsquic_conn *const lconn = ctl->sc_conn_pub->lconn; + struct lsquic_packet_out *next, *packet_out; + struct resize_many_packet_ctx many_ctx; + struct packet_resize_ctx prctx; + const char *q_name; + unsigned count_src = 0, count_dst = 0; + int idx; + + /* Initialize input, removing packets from source queue, filtering by path. + * Note: this may reorder packets from different paths. + */ + many_ctx.ctl = ctl; + many_ctx.path = path; + TAILQ_INIT(&many_ctx.input_q); + if (q == &ctl->sc_scheduled_packets) + { + ctl->sc_cur_packno = lsquic_senhist_largest(&ctl->sc_senhist); + q_name = "scheduled"; + for (packet_out = TAILQ_FIRST(q); packet_out != NULL; packet_out = next) + { + next = TAILQ_NEXT(packet_out, po_next); + if (packet_out->po_path == path + && !(packet_out->po_flags & PO_MTU_PROBE)) + { + send_ctl_sched_remove(ctl, packet_out); + TAILQ_INSERT_TAIL(&many_ctx.input_q, packet_out, po_next); + ++count_src; + } + } + } + else + { + /* This function only deals with scheduled or buffered queues */ + assert(q == &ctl->sc_buffered_packets[0].bpq_packets + || q == &ctl->sc_buffered_packets[1].bpq_packets); + idx = q == &ctl->sc_buffered_packets[1].bpq_packets; + q_name = "buffered"; + for (packet_out = TAILQ_FIRST(q); packet_out != NULL; packet_out = next) + { + next = TAILQ_NEXT(packet_out, po_next); + if (packet_out->po_path == path) + { + TAILQ_REMOVE(q, packet_out, po_next); + --ctl->sc_buffered_packets[idx].bpq_count; + TAILQ_INSERT_TAIL(&many_ctx.input_q, packet_out, po_next); + ++count_src; + } + } + } + lsquic_packet_resize_init(&prctx, ctl->sc_enpub, lconn, &many_ctx, + &resize_many_funcs); + + /* Get new packets, appending them to appropriate queue */ + if (q == &ctl->sc_scheduled_packets) + while (packet_out = lsquic_packet_resize_next(&prctx), packet_out != NULL) + { + ++count_dst; + packet_out->po_packno = send_ctl_next_packno(ctl); + send_ctl_sched_append(ctl, packet_out); + LSQ_DEBUG("created packet %"PRIu64, packet_out->po_packno); + EV_LOG_PACKET_CREATED(LSQUIC_LOG_CONN_ID, packet_out); + } + else + while (packet_out = lsquic_packet_resize_next(&prctx), packet_out != NULL) + { + ++count_dst; + TAILQ_INSERT_TAIL(q, packet_out, po_next); + ++ctl->sc_buffered_packets[idx].bpq_count; + } + + /* Verify success */ + if (lsquic_packet_resize_is_error(&prctx)) + { + LSQ_WARN("error resizing packets in %s queue", q_name); + goto err; + } + if (count_dst < 1 || !TAILQ_EMPTY(&many_ctx.input_q)) + { + /* A bit of insurance, this being new code */ + LSQ_WARN("unexpected values resizing packets in %s queue: count: %d; " + "empty: %d", q_name, count_dst, TAILQ_EMPTY(&many_ctx.input_q)); + goto err; + } + LSQ_DEBUG("resized %u packets in %s queue, outputting %u packets", + count_src, q_name, count_dst); + return; + + err: + lconn->cn_if->ci_internal_error(lconn, "error resizing packets"); + return; +} + + void lsquic_send_ctl_repath (struct lsquic_send_ctl *ctl, struct network_path *old, struct network_path *new) @@ -2999,12 +3322,58 @@ lsquic_send_ctl_repath (struct lsquic_send_ctl *ctl, struct network_path *old, LSQ_DEBUG("repathed %u packet%.*s", count, count != 1, "s"); + lsquic_send_ctl_resize(ctl); + memset(&ctl->sc_conn_pub->rtt_stats, 0, sizeof(ctl->sc_conn_pub->rtt_stats)); ctl->sc_ci->cci_reinit(CGP(ctl)); } +/* Examine packets in scheduled and buffered queues and resize packets if + * they exceed path MTU. + */ +void +lsquic_send_ctl_resize (struct lsquic_send_ctl *ctl) +{ + struct lsquic_conn *const lconn = ctl->sc_conn_pub->lconn; + struct lsquic_packet_out *packet_out; + struct lsquic_packets_tailq *const *q; + struct lsquic_packets_tailq *const queues[] = { + &ctl->sc_scheduled_packets, + &ctl->sc_buffered_packets[0].bpq_packets, + &ctl->sc_buffered_packets[1].bpq_packets, + }; + size_t size; + int path_ids /* assuming a reasonable number of paths */, q_idxs; + + assert(ctl->sc_flags & SC_IETF); + + q_idxs = 0; + for (q = queues; q < queues + sizeof(queues) / sizeof(queues[0]); ++q) + { + path_ids = 0; + redo_q: + TAILQ_FOREACH(packet_out, *q, po_next) + if (0 == (path_ids & (1 << packet_out->po_path->np_path_id)) + && !(packet_out->po_flags & PO_MTU_PROBE)) + { + size = lsquic_packet_out_total_sz(lconn, packet_out); + if (size > packet_out->po_path->np_pack_size) + { + send_ctl_resize_q(ctl, *q, packet_out->po_path); + path_ids |= 1 << packet_out->po_path->np_path_id; + q_idxs |= 1 << (q - queues); + goto redo_q; + } + } + } + + LSQ_DEBUG("resized packets in queues: 0x%X", q_idxs); + lsquic_send_ctl_sanity_check(ctl); +} + + void lsquic_send_ctl_return_enc_data (struct lsquic_send_ctl *ctl) { @@ -3073,3 +3442,29 @@ lsquic_send_ctl_path_validated (struct lsquic_send_ctl *ctl) LSQ_DEBUG("path validated: switch to regular can_send"); ctl->sc_can_send = send_ctl_can_send; } + + +int +lsquic_send_ctl_can_send_probe (const struct lsquic_send_ctl *ctl, + const struct network_path *path) +{ + uint64_t cwnd, pacing_rate; + lsquic_time_t tx_time; + unsigned n_out; + + assert(!send_ctl_in_recovery(ctl)); + + n_out = send_ctl_all_bytes_out(ctl); + cwnd = ctl->sc_ci->cci_get_cwnd(CGP(ctl)); + if (ctl->sc_flags & SC_PACE) + { + if (n_out + path->np_pack_size >= cwnd) + return 0; + pacing_rate = ctl->sc_ci->cci_pacing_rate(CGP(ctl), 0); + tx_time = (uint64_t) path->np_pack_size * 1000000 / pacing_rate; + return lsquic_pacer_can_schedule_probe(&ctl->sc_pacer, + ctl->sc_n_scheduled + ctl->sc_n_in_flight_all, tx_time); + } + else + return n_out + path->np_pack_size < cwnd; +} diff --git a/src/liblsquic/lsquic_send_ctl.h b/src/liblsquic/lsquic_send_ctl.h index c1cfdeb..2d2e101 100644 --- a/src/liblsquic/lsquic_send_ctl.h +++ b/src/liblsquic/lsquic_send_ctl.h @@ -365,6 +365,9 @@ void lsquic_send_ctl_repath (struct lsquic_send_ctl *, struct network_path *old, struct network_path *new); +void +lsquic_send_ctl_resize (struct lsquic_send_ctl *); + void lsquic_send_ctl_return_enc_data (struct lsquic_send_ctl *); @@ -395,4 +398,13 @@ lsquic_send_ctl_path_validated (struct lsquic_send_ctl *); (lsquic_send_ctl_n_scheduled(ctl_) > 0 \ && lsquic_send_ctl_next_packet_to_send_predict(ctl_)) +#define lsquic_send_ctl_in_recovery(ctl_) ((ctl_)->sc_largest_acked_packno \ + && (ctl_)->sc_largest_acked_packno <= (ctl_)->sc_largest_sent_at_cutback) + +#define send_ctl_in_recovery lsquic_send_ctl_in_recovery + +int +lsquic_send_ctl_can_send_probe (const struct lsquic_send_ctl *, + const struct network_path *); + #endif diff --git a/src/liblsquic/lsquic_stream.c b/src/liblsquic/lsquic_stream.c index aa42593..63d5477 100644 --- a/src/liblsquic/lsquic_stream.c +++ b/src/liblsquic/lsquic_stream.c @@ -52,6 +52,7 @@ #include "lsquic_conn.h" #include "lsquic_data_in_if.h" #include "lsquic_parse.h" +#include "lsquic_packet_in.h" #include "lsquic_packet_out.h" #include "lsquic_engine_public.h" #include "lsquic_senhist.h" @@ -585,6 +586,7 @@ lsquic_stream_destroy (lsquic_stream_t *stream) STREAM_ONNEW_DONE) { stream->stream_flags |= STREAM_ONCLOSE_DONE; + SM_HISTORY_APPEND(stream, SHE_ONCLOSE_CALL); stream->stream_if->on_close(stream, stream->st_ctx); } if (stream->sm_qflags & SMQF_SENDING_FLAGS) @@ -2752,15 +2754,6 @@ frame_hq_gen_read (void *ctx, void *begin_buf, size_t len, int *fin) } -static size_t -crypto_frame_gen_read (void *ctx, void *buf, size_t len) -{ - int fin_ignored; - - return frame_std_gen_read(ctx, buf, len, &fin_ignored); -} - - static void check_flush_threshold (lsquic_stream_t *stream) { @@ -2985,8 +2978,8 @@ stream_write_to_packet_crypto (struct frame_gen_ctx *fg_ctx, const size_t size) off = packet_out->po_data_sz; len = pf->pf_gen_crypto_frame(packet_out->po_data + packet_out->po_data_sz, - lsquic_packet_out_avail(packet_out), stream->tosend_off, - size, crypto_frame_gen_read, fg_ctx); + lsquic_packet_out_avail(packet_out), 0, stream->tosend_off, 0, + size, frame_std_gen_read, fg_ctx); if (len < 0) return len; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 34fd3cf..1727e3b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -48,6 +48,7 @@ SET(TESTS hkdf lsquic_hash packet_out + packet_resize packno_len parse_packet_in purga diff --git a/tests/test_crypto_gen.c b/tests/test_crypto_gen.c index 582d5fe..be8d751 100644 --- a/tests/test_crypto_gen.c +++ b/tests/test_crypto_gen.c @@ -74,7 +74,7 @@ struct test_ctx { static size_t -crypto_read (void *ctx, void *buf, size_t len) +crypto_read (void *ctx, void *buf, size_t len, int *fin) { struct test_ctx *test_ctx = ctx; if (test_ctx->test->data_sz - test_ctx->off < len) @@ -109,20 +109,20 @@ run_test (int i) for (min = 0; min < test->min_sz; ++min) { init_ctx(&test_ctx, test); - len = test->pf->pf_gen_crypto_frame(out, min, test->offset, + len = test->pf->pf_gen_crypto_frame(out, min, 0, test->offset, 0, test->data_sz, crypto_read, &test_ctx); assert(-1 == len); } /* Test that it succeeds now: */ init_ctx(&test_ctx, test); - len = test->pf->pf_gen_crypto_frame(out, min, test->offset, + len = test->pf->pf_gen_crypto_frame(out, min, 0, test->offset, 0, test->data_sz, crypto_read, &test_ctx); assert(len == (int) min); } init_ctx(&test_ctx, test); - len = test->pf->pf_gen_crypto_frame(out, test->avail, test->offset, + len = test->pf->pf_gen_crypto_frame(out, test->avail, 0, test->offset, 0, test->data_sz, crypto_read, &test_ctx); if (test->len > 0) { diff --git a/tests/test_elision.c b/tests/test_elision.c index ab61658..0bf1fbb 100644 --- a/tests/test_elision.c +++ b/tests/test_elision.c @@ -96,7 +96,7 @@ lsquic_stream_acked (lsquic_stream_t *stream, enum quic_frame_type frame_type) static void elide_single_stream_frame (void) { - struct packet_out_srec_iter posi; + struct packet_out_frec_iter pofi; struct lsquic_engine_public enpub; lsquic_stream_t streams[1]; lsquic_packet_out_t *packet_out; @@ -120,14 +120,14 @@ elide_single_stream_frame (void) lsquic_packet_out_add_stream(packet_out, &enpub.enp_mm, &streams[0], QUIC_FRAME_STREAM, off, len); assert(1 == streams[0].n_unacked); - assert(lsquic_posi_first(&posi, packet_out)); + assert(lsquic_pofi_first(&pofi, packet_out)); streams[0].stream_flags |= STREAM_RST_SENT; lsquic_packet_out_elide_reset_stream_frames(packet_out, 0); assert(0 == streams[0].n_unacked); assert(0 == packet_out->po_frame_types); - assert(!lsquic_posi_first(&posi, packet_out)); + assert(!lsquic_pofi_first(&pofi, packet_out)); lsquic_packet_out_destroy(packet_out, &enpub, NULL); lsquic_mm_cleanup(&enpub.enp_mm); @@ -142,11 +142,11 @@ elide_single_stream_frame (void) static void shrink_packet_post_elision (void) { - struct packet_out_srec_iter posi; + struct packet_out_frec_iter pofi; struct lsquic_engine_public enpub; lsquic_stream_t streams[2]; lsquic_packet_out_t *packet_out; - const struct stream_rec *srec; + const struct frame_rec *frec; int len, off = 0; unsigned char stream2_data[0x1000]; @@ -189,7 +189,7 @@ shrink_packet_post_elision (void) assert(1 == streams[0].n_unacked); assert(1 == streams[1].n_unacked); - assert(lsquic_posi_first(&posi, packet_out)); + assert(lsquic_pofi_first(&pofi, packet_out)); streams[0].stream_flags |= STREAM_RST_SENT; @@ -197,8 +197,8 @@ shrink_packet_post_elision (void) assert(0 == streams[0].n_unacked); assert(QUIC_FTBIT_STREAM == packet_out->po_frame_types); - srec = lsquic_posi_first(&posi, packet_out); - assert(srec->sr_stream == &streams[1]); + frec = lsquic_pofi_first(&pofi, packet_out); + assert(frec->fe_stream == &streams[1]); assert(packet_out->po_data_sz == exp); lsquic_packet_out_destroy(packet_out, &enpub, NULL); @@ -222,11 +222,11 @@ shrink_packet_post_elision (void) static void elide_three_stream_frames (int chop_regen) { - struct packet_out_srec_iter posi; + struct packet_out_frec_iter pofi; struct lsquic_engine_public enpub; lsquic_stream_t streams[5]; lsquic_packet_out_t *packet_out, *ref_out; - struct stream_rec *srec; + struct frame_rec *frec; unsigned short b_off, d_off; int len; @@ -241,6 +241,8 @@ elide_three_stream_frames (int chop_regen) ref_out = lsquic_mm_get_packet_out(&enpub.enp_mm, NULL, GQUIC_MAX_PAYLOAD_SZ); /* This is fake data for regeneration */ strcpy((char *) ref_out->po_data, "REGEN"); + lsquic_packet_out_add_frame(ref_out, &enpub.enp_mm, 0, + QUIC_FRAME_ACK, ref_out->po_data_sz, 5); ref_out->po_data_sz = ref_out->po_regen_sz = 5; /* STREAM B */ setup_stream_contents(123, "BBBBBBBBBB"); @@ -278,6 +280,8 @@ elide_three_stream_frames (int chop_regen) packet_out = lsquic_mm_get_packet_out(&enpub.enp_mm, NULL, GQUIC_MAX_PAYLOAD_SZ); /* This is fake data for regeneration */ strcpy((char *) packet_out->po_data, "REGEN"); + lsquic_packet_out_add_frame(packet_out, &enpub.enp_mm, 0, + QUIC_FRAME_ACK, packet_out->po_data_sz, 5); packet_out->po_data_sz = packet_out->po_regen_sz = 5; /* STREAM A */ setup_stream_contents(123, "AAAAAAAAAA"); @@ -377,22 +381,24 @@ elide_three_stream_frames (int chop_regen) assert(packet_out->po_frame_types == ((1 << QUIC_FRAME_STREAM) | (1 << QUIC_FRAME_RST_STREAM))); - srec = lsquic_posi_first(&posi, packet_out); - assert(srec->sr_stream == &streams[1]); - assert(srec->sr_frame_type == QUIC_FRAME_STREAM); - assert(srec->sr_off == b_off - (chop_regen ? 5 : 0)); + frec = lsquic_pofi_first(&pofi, packet_out); + if (!chop_regen) + frec = lsquic_pofi_next(&pofi); + assert(frec->fe_stream == &streams[1]); + assert(frec->fe_frame_type == QUIC_FRAME_STREAM); + assert(frec->fe_off == b_off - (chop_regen ? 5 : 0)); - srec = lsquic_posi_next(&posi); - assert(srec->sr_stream == &streams[0]); - assert(srec->sr_frame_type == QUIC_FRAME_RST_STREAM); + frec = lsquic_pofi_next(&pofi); + assert(frec->fe_stream == &streams[0]); + assert(frec->fe_frame_type == QUIC_FRAME_RST_STREAM); - srec = lsquic_posi_next(&posi); - assert(srec->sr_stream == &streams[3]); - assert(srec->sr_frame_type == QUIC_FRAME_STREAM); - assert(srec->sr_off == d_off - (chop_regen ? 5 : 0)); + frec = lsquic_pofi_next(&pofi); + assert(frec->fe_stream == &streams[3]); + assert(frec->fe_frame_type == QUIC_FRAME_STREAM); + assert(frec->fe_off == d_off - (chop_regen ? 5 : 0)); - srec = lsquic_posi_next(&posi); - assert(!srec); + frec = lsquic_pofi_next(&pofi); + assert(!frec); lsquic_packet_out_destroy(packet_out, &enpub, NULL); lsquic_packet_out_destroy(ref_out, &enpub, NULL); diff --git a/tests/test_h3_framing.c b/tests/test_h3_framing.c index bf51c05..e7063af 100644 --- a/tests/test_h3_framing.c +++ b/tests/test_h3_framing.c @@ -184,8 +184,8 @@ read_from_scheduled_packets (lsquic_send_ctl_t *send_ctl, lsquic_stream_id_t str const struct parse_funcs *const pf_local = send_ctl->sc_conn_pub->lconn->cn_pf; unsigned char *p = begin; unsigned char *const end = p + bufsz; - const struct stream_rec *srec; - struct packet_out_srec_iter posi; + const struct frame_rec *frec; + struct packet_out_frec_iter pofi; struct lsquic_packet_out *packet_out; struct stream_frame frame; enum quic_frame_type expected_type; @@ -194,38 +194,38 @@ read_from_scheduled_packets (lsquic_send_ctl_t *send_ctl, lsquic_stream_id_t str expected_type = QUIC_FRAME_STREAM; TAILQ_FOREACH(packet_out, &send_ctl->sc_scheduled_packets, po_next) - for (srec = lsquic_posi_first(&posi, packet_out); srec; - srec = lsquic_posi_next(&posi)) + for (frec = lsquic_pofi_first(&pofi, packet_out); frec; + frec = lsquic_pofi_next(&pofi)) { if (fullcheck) { - assert(srec->sr_frame_type == expected_type); + assert(frec->fe_frame_type == expected_type); if (0 && packet_out->po_packno != 1) { /* First packet may contain two stream frames, do not * check it. */ - assert(!lsquic_posi_next(&posi)); + assert(!lsquic_pofi_next(&pofi)); if (TAILQ_NEXT(packet_out, po_next)) { assert(packet_out->po_data_sz == packet_out->po_n_alloc); - assert(srec->sr_len == packet_out->po_data_sz); + assert(frec->fe_len == packet_out->po_data_sz); } } } - if (srec->sr_frame_type == expected_type && - srec->sr_stream->id == stream_id) + if (frec->fe_frame_type == expected_type && + frec->fe_stream->id == stream_id) { assert(!fin); if (QUIC_FRAME_STREAM == expected_type) - len = pf_local->pf_parse_stream_frame(packet_out->po_data + srec->sr_off, - packet_out->po_data_sz - srec->sr_off, &frame); + len = pf_local->pf_parse_stream_frame(packet_out->po_data + frec->fe_off, + packet_out->po_data_sz - frec->fe_off, &frame); else - len = pf_local->pf_parse_crypto_frame(packet_out->po_data + srec->sr_off, - packet_out->po_data_sz - srec->sr_off, &frame); + len = pf_local->pf_parse_crypto_frame(packet_out->po_data + frec->fe_off, + packet_out->po_data_sz - frec->fe_off, &frame); assert(len > 0); if (QUIC_FRAME_STREAM == expected_type) - assert(frame.stream_id == srec->sr_stream->id); + assert(frame.stream_id == frec->fe_stream->id); else assert(frame.stream_id == ~0ULL); /* Otherwise not enough to copy to: */ @@ -238,7 +238,7 @@ read_from_scheduled_packets (lsquic_send_ctl_t *send_ctl, lsquic_stream_id_t str assert(!fin); fin = 1; } - memcpy(p, packet_out->po_data + srec->sr_off + len - + memcpy(p, packet_out->po_data + frec->fe_off + len - frame.data_frame.df_size, frame.data_frame.df_size); p += frame.data_frame.df_size; } diff --git a/tests/test_packet_out.c b/tests/test_packet_out.c index 9c568a9..2de33a4 100644 --- a/tests/test_packet_out.c +++ b/tests/test_packet_out.c @@ -37,10 +37,10 @@ int main (void) { struct lsquic_engine_public enpub; - struct packet_out_srec_iter posi; + struct packet_out_frec_iter pofi; lsquic_packet_out_t *packet_out; struct lsquic_stream streams[6]; - struct stream_rec *srec; + struct frame_rec *frec; memset(&enpub, 0, sizeof(enpub)); memset(&streams, 0, sizeof(streams)); @@ -55,45 +55,45 @@ main (void) lsquic_packet_out_add_stream(packet_out, &enpub.enp_mm, &streams[4], QUIC_FRAME_STREAM, 12, 1); lsquic_packet_out_add_stream(packet_out, &enpub.enp_mm, &streams[5], QUIC_FRAME_STREAM, 13, 1); - srec = lsquic_posi_first(&posi, packet_out); - assert(srec->sr_stream == &streams[0]); - assert(srec->sr_off == 7); - assert(srec->sr_frame_type == QUIC_FRAME_STREAM); + frec = lsquic_pofi_first(&pofi, packet_out); + assert(frec->fe_stream == &streams[0]); + assert(frec->fe_off == 7); + assert(frec->fe_frame_type == QUIC_FRAME_STREAM); - srec = lsquic_posi_next(&posi); - assert(srec->sr_stream == &streams[1]); - assert(srec->sr_off == 8); - assert(srec->sr_frame_type == QUIC_FRAME_STREAM); + frec = lsquic_pofi_next(&pofi); + assert(frec->fe_stream == &streams[1]); + assert(frec->fe_off == 8); + assert(frec->fe_frame_type == QUIC_FRAME_STREAM); - srec = lsquic_posi_next(&posi); - assert(srec->sr_stream == &streams[2]); - assert(srec->sr_off == 9); - assert(srec->sr_frame_type == QUIC_FRAME_STREAM); + frec = lsquic_pofi_next(&pofi); + assert(frec->fe_stream == &streams[2]); + assert(frec->fe_off == 9); + assert(frec->fe_frame_type == QUIC_FRAME_STREAM); - srec = lsquic_posi_next(&posi); - assert(srec->sr_stream == &streams[1]); - assert(srec->sr_off == 10); - assert(srec->sr_frame_type == QUIC_FRAME_RST_STREAM); + frec = lsquic_pofi_next(&pofi); + assert(frec->fe_stream == &streams[1]); + assert(frec->fe_off == 10); + assert(frec->fe_frame_type == QUIC_FRAME_RST_STREAM); - srec = lsquic_posi_next(&posi); - assert(srec->sr_stream == &streams[3]); - assert(srec->sr_off == 11); - assert(srec->sr_frame_type == QUIC_FRAME_STREAM); + frec = lsquic_pofi_next(&pofi); + assert(frec->fe_stream == &streams[3]); + assert(frec->fe_off == 11); + assert(frec->fe_frame_type == QUIC_FRAME_STREAM); - srec = lsquic_posi_next(&posi); - assert(srec->sr_stream == &streams[4]); - assert(srec->sr_off == 12); - assert(srec->sr_frame_type == QUIC_FRAME_STREAM); + frec = lsquic_pofi_next(&pofi); + assert(frec->fe_stream == &streams[4]); + assert(frec->fe_off == 12); + assert(frec->fe_frame_type == QUIC_FRAME_STREAM); - srec = lsquic_posi_next(&posi); - assert(srec->sr_stream == &streams[5]); - assert(srec->sr_off == 13); - assert(srec->sr_frame_type == QUIC_FRAME_STREAM); + frec = lsquic_pofi_next(&pofi); + assert(frec->fe_stream == &streams[5]); + assert(frec->fe_off == 13); + assert(frec->fe_frame_type == QUIC_FRAME_STREAM); - assert((void *) 0 == lsquic_posi_next(&posi)); + assert((void *) 0 == lsquic_pofi_next(&pofi)); lsquic_packet_out_destroy(packet_out, &enpub, NULL); - assert(!lsquic_malo_first(enpub.enp_mm.malo.stream_rec_arr)); + assert(!lsquic_malo_first(enpub.enp_mm.malo.frame_rec_arr)); lsquic_mm_cleanup(&enpub.enp_mm); return 0; diff --git a/tests/test_packet_resize.c b/tests/test_packet_resize.c new file mode 100644 index 0000000..9cd7c6c --- /dev/null +++ b/tests/test_packet_resize.c @@ -0,0 +1,1640 @@ +/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ +/* Test packet resizing */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LSQUIC_TEST 1 +#include "lsquic.h" +#include "lsquic_types.h" +#include "lsquic_int_types.h" +#include "lsquic_packet_common.h" +#include "lsquic_packet_in.h" +#include "lsquic_packet_out.h" +#include "lsquic_packet_resize.h" +#include "lsquic_parse.h" +#include "lsquic_hash.h" +#include "lsquic_conn.h" +#include "lsquic_mm.h" +#include "lsquic_enc_sess.h" +#include "lsquic_sfcw.h" +#include "lsquic_varint.h" +#include "lsquic_hq.h" +#include "lsquic_stream.h" +#include "lsquic_engine_public.h" + +#include "lsquic_logger.h" + +#define N_STREAMS 4 + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +static const char *s_data[N_STREAMS]; +static size_t s_data_sz[N_STREAMS]; + + +struct test_spec +{ + int lineno; + int expect_error; + unsigned versions; + const char *desc; + const char *prog; +}; + +/* Here we rely on the fact that QUIC_FRAME_STREAM is 1 and other valid frames + * are in a contiguous range. + */ +#define letter_2_frame_type(letter_) ((int) ((letter_) - 'a') + QUIC_FRAME_ACK) +#define frame_type_2_letter(frame_type_) ('a' + ((frame_type_) - QUIC_FRAME_ACK)) + + +/* DSL specification: + * + * P\d+ Set maximum packet size + * N Create new packet, append to input queue, and set as current + * S\d+-\d+f? The first number is stream ID; these values must be in + * range [0, 3]. The second number is the maximum number of + * bytes to read from stream, potentially filling the current + * packet. If `f' is set, set FIN flag. + * C\d+-\d+ Like 'S' above, but CRYPTO frame. Note that there is no 'f' + * flag as CRYPTO frames have no FINs. + * c\d+-\d+ RST_STREAM frame. It's different from frames [abd-z] in that + * n_unacked is changed. + * V Verify contents of packets, both STREAM and non-STREAM frames. + * R Resize packets + * L\d Label, valid values in range [0, 9] + * J\d[=<>]\d+ Jump to label if packet size is valid [1200, 65527] + * I\d+ Increase packet size + * D\d+ Decrease packet size + * [abd-z]\d+ Frame of type [a-z] of some bytes. See letter_2_frame_type() + * to see how the mapping works. + * F\d+ Standalone FIN frame. + */ + + +static struct test_spec test_specs[] = +{ + { + .lineno = __LINE__, + .desc = "split one packet with single STREAM frame into two", + .prog = "P2000;N;a7;S0-2000;V;P1500;R;V;", + }, + { + .lineno = __LINE__, + .desc = "split one 6000-byte packet with single STREAM frame into many, looping", + .prog = "P6000;N;S0-6000;V;L0;D100;R;V;J0>1200;L1;I29;R;V;J1<7000;", + }, + { + .lineno = __LINE__, + .desc = "split three 1500-byte packets with several STREAM frames from different streams", + .prog = "P1500;" + "N;p20;S0-200;S1-300;S2-200;h18;S3-20f;t2;S2-2000;" + "N;c0-30;j11;S2-2000;" + "N;S2-2000;" + "V;" + "L0;D1;R;V;J0>1200;" + , + }, + { + .lineno = __LINE__, + .desc = "one packet, STREAM frame and and empty STREAM FIN frame, split down by 1", + .prog = "P2000;N;S0-1900;F0;V;L0;D1;R;V;J0>1200;", + }, + { + .lineno = __LINE__, + .desc = "one packet, STREAM frame and and empty STREAM FIN frame, split down by 31", + .prog = "P2000;N;S0-1900;F0;V;L0;D31;R;V;J0>1200;", + }, + { + .lineno = __LINE__, + .desc = "one packet, STREAM frame with a FIN, split down by 1", + .prog = "P2000;N;S0-1900f;V;L0;D1;R;V;J0>1200;", + }, + { + .lineno = __LINE__, + .desc = "one packet, STREAM frame with a FIN, split down by 31", + .prog = "P2000;N;S0-1900f;V;L0;D31;R;V;J0>1200;", + }, + { + .lineno = __LINE__, + .desc = "one packet, frame too large", + .prog = "P2000;N;m1500;V;P1000;R;", + .expect_error = 1, + }, + { + .lineno = __LINE__, + .desc = "split one packet with single CRYPTO frame into two", + .prog = "P1252;N;C0-2000;V;P1200;R;V;", + .versions = LSQUIC_IETF_VERSIONS, + }, +}; + + +struct stream_read_cursor +{ + const char *data; /* Points to data that is used as circular buffer */ + unsigned data_sz; /* Size of data pointed to by data */ + unsigned off; /* Current offset */ + unsigned nread; /* Total number of bytes consumed from stream (packetized) */ + int fin; /* FIN is set, see fin_off */ + unsigned fin_off; /* Value of final offset */ +}; + + +struct test_ctx +{ + TAILQ_HEAD(, lsquic_packet_out) packets[2]; /* We move them from one queue to the other */ + int cur_input; /* 0 or 1, indexes packets */ + unsigned n_non_stream_frames; + struct stream_read_cursor stream_cursors[N_STREAMS]; + struct lsquic_stream streams[N_STREAMS]; + struct lsquic_engine_public enpub; + struct lsquic_conn lconn; + struct network_path path; +}; + + +static void +init_test_ctx (struct test_ctx *ctx, const struct test_spec *spec, + enum lsquic_version version) +{ + unsigned i; + + memset(ctx, 0, sizeof(*ctx)); + TAILQ_INIT(&ctx->packets[0]); + TAILQ_INIT(&ctx->packets[1]); + for (i = 0; i < N_STREAMS; ++i) + { + ctx->stream_cursors[i].data = s_data[i]; + ctx->stream_cursors[i].data_sz = s_data_sz[i]; + } + lsquic_mm_init(&ctx->enpub.enp_mm); + ctx->lconn.cn_flags |= LSCONN_HANDSHAKE_DONE; /* For short packet headers */ + ctx->lconn.cn_pf = select_pf_by_ver(version); + ctx->lconn.cn_esf_c = select_esf_common_by_ver(version); + LSCONN_INITIALIZE(&ctx->lconn); + ctx->lconn.cn_cces_buf[0].cce_cid.len = sizeof(spec->lineno); + memcpy(ctx->lconn.cn_cces_buf[0].cce_cid.idbuf, &spec->lineno, sizeof(spec->lineno)); +} + + +static void +cleanup_test_ctx (struct test_ctx *ctx) +{ + struct lsquic_packet_out *packet_out; + unsigned i; + + for (i = 0; i < 2; ++i) + while (packet_out = TAILQ_FIRST(&ctx->packets[i]), packet_out != NULL) + { + TAILQ_REMOVE(&ctx->packets[i], packet_out, po_next); + lsquic_packet_out_destroy(packet_out, &ctx->enpub, NULL); + } + lsquic_mm_cleanup(&ctx->enpub.enp_mm); +} + + +static struct lsquic_packet_out * +new_packet (struct test_ctx *ctx) +{ + struct lsquic_packet_out *packet_out; + static lsquic_packno_t packno; /* Each packet gets unique packet number + * to make them easier to track. + */ + + packet_out = lsquic_packet_out_new(&ctx->enpub.enp_mm, ctx->enpub.enp_mm.malo.packet_out, 1, + &ctx->lconn, PACKNO_BITS_0, 0, NULL, &ctx->path); + if (packet_out) + packet_out->po_packno = packno++; + + return packet_out; +} + + +static struct lsquic_packet_out * +new_input_packet (struct test_ctx *ctx) +{ + struct lsquic_packet_out *packet_out; + + packet_out = new_packet(ctx); + if (packet_out) + TAILQ_INSERT_TAIL(&ctx->packets[ctx->cur_input], packet_out, po_next); + + return packet_out; +} + + +struct my_read_ctx { + struct stream_read_cursor *cursor; + /* XXX Turns out, gQUIC and IETF QUIC STREAM frame generators differ in + * what they pass to the read() function. The former does not limit + * itself to pf_gen_stream_frame()'s `size'. Rather than change and + * retest gQUIC code, put a limiter in this unit test file instead. + */ + size_t max; + int fin; +}; + + +static size_t +my_gsf_read (void *stream, void *buf, size_t len, int *fin) +{ + struct my_read_ctx *const mctx = stream; + struct stream_read_cursor *const cursor = mctx->cursor; + unsigned char *p = buf, *end; + size_t n; + + if (len > mctx->max) + len = mctx->max; + + end = p + len; + + while (p < end) + { + n = MIN(end - p, cursor->data_sz - cursor->off); + memcpy(p, cursor->data + cursor->off, n); + cursor->off += n; + if (cursor->off == cursor->data_sz) + cursor->off = 0; + cursor->nread += n; + p += n; + } + + if (mctx->fin) + { + cursor->fin = 1; + cursor->fin_off = cursor->nread; + LSQ_DEBUG("set FIN at offset %u", cursor->fin_off); + } + + *fin = mctx->fin; + return len; +} + + +static void +make_stream_frame (struct test_ctx *ctx, struct lsquic_packet_out *packet_out, + enum quic_frame_type frame_type, lsquic_stream_id_t stream_id, + size_t nbytes, int fin) +{ + struct my_read_ctx mctx = { &ctx->stream_cursors[stream_id], nbytes, fin, }; + int w; + + assert(!ctx->stream_cursors[stream_id].fin); + + if (nbytes == 0 && fin) + { + ctx->stream_cursors[stream_id].fin = 1; + ctx->stream_cursors[stream_id].fin_off + = ctx->stream_cursors[stream_id].nread; + LSQ_DEBUG("set FIN at offset %u", ctx->stream_cursors[stream_id].fin_off); + } + + w = (&ctx->lconn.cn_pf->pf_gen_stream_frame) + [frame_type == QUIC_FRAME_CRYPTO]( + packet_out->po_data + packet_out->po_data_sz, + lsquic_packet_out_avail(packet_out), + stream_id, ctx->stream_cursors[stream_id].nread, + nbytes == 0 && fin, nbytes, my_gsf_read, &mctx); + assert(w > 0); + LSQ_DEBUG("wrote %s frame of %d bytes", frame_type_2_str[frame_type], w); + lsquic_packet_out_add_stream(packet_out, &ctx->enpub.enp_mm, + &ctx->streams[stream_id], frame_type, + packet_out->po_data_sz, w); + packet_out->po_data_sz += w; + packet_out->po_frame_types |= 1 << frame_type; + if (0 == lsquic_packet_out_avail(packet_out)) + packet_out->po_flags |= PO_STREAM_END; +} + + +static void +make_non_stream_frame (struct test_ctx *ctx, + struct lsquic_packet_out *packet_out, enum quic_frame_type frame_type, + size_t nbytes) +{ + static unsigned char fill_byte; + + /* We don't truncate non-STREAM frames because we don't chop them up */ + assert(nbytes <= lsquic_packet_out_avail(packet_out)); + + memset(packet_out->po_data + packet_out->po_data_sz, fill_byte, nbytes); + lsquic_packet_out_add_frame(packet_out, &ctx->enpub.enp_mm, + fill_byte, frame_type, packet_out->po_data_sz, nbytes); + packet_out->po_data_sz += nbytes; + packet_out->po_frame_types |= 1 << frame_type; + if ((1 << frame_type) & GQUIC_FRAME_REGEN_MASK) + packet_out->po_regen_sz += nbytes; + LSQ_DEBUG("wrote %s frame of %zd bytes", frame_type_2_str[frame_type], + nbytes); + ++fill_byte; + ++ctx->n_non_stream_frames; +} + + +static void +make_rst_stream_frame (struct test_ctx *ctx, + struct lsquic_packet_out *packet_out, lsquic_stream_id_t stream_id, + size_t nbytes) +{ + int s; + + /* We don't truncate non-STREAM frames because we don't chop them up */ + assert(nbytes <= lsquic_packet_out_avail(packet_out)); + + memset(packet_out->po_data + packet_out->po_data_sz, 'R', nbytes); + s = lsquic_packet_out_add_stream(packet_out, &ctx->enpub.enp_mm, + &ctx->streams[stream_id], QUIC_FRAME_RST_STREAM, + packet_out->po_data_sz, nbytes); + assert(s == 0); + packet_out->po_data_sz += nbytes; + packet_out->po_frame_types |= 1 << QUIC_FRAME_RST_STREAM; + LSQ_DEBUG("wrote %s frame of %zd bytes", + frame_type_2_str[QUIC_FRAME_RST_STREAM], nbytes); + ++ctx->n_non_stream_frames; +} + + +/* STREAM frame ordering assumptions, with or without FINs, are specific to + * this unit test. These assumptions do not have to hold in real code. + * The assumptions are made in order to verify the operation of the "packet + * resize" module. + */ +static void +verify_stream_contents (struct test_ctx *ctx, lsquic_stream_id_t stream_id) +{ + char *data; + size_t len; + int dummy_fin = -1, parsed_len, seen_fin; + struct lsquic_packet_out *packet_out; + struct stream_read_cursor cursor; + struct my_read_ctx mctx; + struct stream_frame stream_frame; + struct packet_out_frec_iter pofi; + struct frame_rec *frec; + unsigned off, frec_count; + + LSQ_DEBUG("verifying stream #%"PRIu64, stream_id); + data = malloc(ctx->stream_cursors[stream_id].nread); + assert(data); + /* Copy cursor to re-read from the beginning and not affect real cursor */ + cursor = ctx->stream_cursors[stream_id]; + cursor.off = 0; + mctx = (struct my_read_ctx) { &cursor, ctx->stream_cursors[stream_id].nread, 0, }; + len = my_gsf_read(&mctx, data, ctx->stream_cursors[stream_id].nread, &dummy_fin); + assert(len == ctx->stream_cursors[stream_id].nread); + assert(dummy_fin == 0); /* Self-check */ + + /* Go packet by packet, and within each packet, frame by frame, and + * compare STREAM frame contents. + */ + off = 0; + seen_fin = 0; + frec_count = 0; + TAILQ_FOREACH(packet_out, &ctx->packets[ctx->cur_input], po_next) + { + LSQ_DEBUG("examining packet #%"PRIu64, packet_out->po_packno); + assert(packet_out->po_data_sz <= packet_out->po_n_alloc); + assert(packet_out->po_data_sz <= ctx->path.np_pack_size); + for (frec = lsquic_pofi_first(&pofi, packet_out); frec; + frec = lsquic_pofi_next(&pofi)) + { + if (!(((1 << frec->fe_frame_type) & (QUIC_FTBIT_STREAM|QUIC_FTBIT_CRYPTO|QUIC_FTBIT_RST_STREAM)) + && frec->fe_stream == &ctx->streams[stream_id])) + continue; + assert(!seen_fin); + ++frec_count; + if (frec->fe_frame_type == QUIC_FRAME_RST_STREAM) + continue; + parsed_len = (&ctx->lconn.cn_pf->pf_parse_stream_frame) + [frec->fe_frame_type == QUIC_FRAME_CRYPTO] + (packet_out->po_data + frec->fe_off, frec->fe_len, &stream_frame); + assert(parsed_len > 0); + assert(parsed_len == frec->fe_len); + LSQ_DEBUG("verify stream %"PRIu64", contents %hu bytes", + stream_id, stream_frame.data_frame.df_size); + assert(stream_frame.data_frame.df_offset == off); + assert(stream_frame.data_frame.df_size <= len - off); + assert(0 == memcmp(stream_frame.data_frame.df_data, data + off, + stream_frame.data_frame.df_size)); + off += stream_frame.data_frame.df_size; + if (stream_frame.data_frame.df_fin) + { + assert(ctx->stream_cursors[stream_id].fin); + assert(ctx->stream_cursors[stream_id].fin_off == off); + seen_fin = 1; + } + if (frec->fe_off + frec->fe_len == packet_out->po_n_alloc) + assert(packet_out->po_flags & PO_STREAM_END); + if (!(packet_out->po_flags & PO_STREAM_END)) + assert(frec->fe_off + frec->fe_len < packet_out->po_n_alloc); + } + } + + if (ctx->stream_cursors[stream_id].fin) + assert(seen_fin); + assert(frec_count == ctx->streams[stream_id].n_unacked); + free(data); +} + + +/* Verify that non-STREAM frames are in the same order, of the same size, and + * same contents. + */ +static void +verify_non_stream_frames (struct test_ctx *ctx, const struct test_spec *spec) +{ + const char *pos; + int w; + unsigned count, regen_sz, off; + struct lsquic_packet_out *packet_out; + struct packet_out_frec_iter pofi; + struct frame_rec *frec; + unsigned char fill; + char frame_str[30]; + + LSQ_DEBUG("verifying non-STREAM frames"); + + /* Go packet by packet, and within each packet, frame by frame, and + * verify relative position of non-STREAM frames (must be in the same + * order as in the order they were inserted) and their contents. + */ + count = 0; + pos = spec->prog; + TAILQ_FOREACH(packet_out, &ctx->packets[ctx->cur_input], po_next) + { + regen_sz = 0; + off = 0; + LSQ_DEBUG("examining packet #%"PRIu64, packet_out->po_packno); + assert(packet_out->po_data_sz <= packet_out->po_n_alloc); + assert(packet_out->po_data_sz <= ctx->path.np_pack_size); + for (frec = lsquic_pofi_first(&pofi, packet_out); frec; + frec = lsquic_pofi_next(&pofi)) + { + if ((1 << frec->fe_frame_type) & GQUIC_FRAME_REGEN_MASK) + { + assert(regen_sz == 0 || regen_sz == off); + regen_sz += frec->fe_len; + } + off += frec->fe_len; + if ((1 << frec->fe_frame_type) + & (QUIC_FTBIT_STREAM|QUIC_FTBIT_CRYPTO)) + continue; + ++count; + LSQ_DEBUG("checking %hu-byte %s", frec->fe_len, + frame_type_2_str[frec->fe_frame_type]); + if (frec->fe_frame_type == QUIC_FRAME_RST_STREAM) + w = snprintf(frame_str, sizeof(frame_str), "%c%u-%hu;", + frame_type_2_letter(frec->fe_frame_type), + (unsigned) (frec->fe_stream - ctx->streams), frec->fe_len); + else + w = snprintf(frame_str, sizeof(frame_str), "%c%hu;", + frame_type_2_letter(frec->fe_frame_type), frec->fe_len); + pos = strstr(pos, frame_str); + assert(pos); + pos += w; + /* Now check contents */ + fill = frec->fe_frame_type == QUIC_FRAME_RST_STREAM + ? 'R' : (unsigned char) frec->fe_u.data; + for (w = 0; w < (int) frec->fe_len; ++w) + assert(packet_out->po_data[frec->fe_off + w] == fill); + } + assert(packet_out->po_regen_sz == regen_sz); + assert(packet_out->po_data_sz == off); + } + + assert(count == ctx->n_non_stream_frames); +} + + +static void +verify_packet_contents (struct test_ctx *ctx, const struct test_spec *spec) +{ + lsquic_stream_id_t stream_id; + + for (stream_id = 0; stream_id < N_STREAMS; ++stream_id) + verify_stream_contents(ctx, stream_id); + verify_non_stream_frames(ctx, spec); +} + + +static struct lsquic_packet_out * +my_pri_next_packet (void *ctxp) +{ + struct test_ctx *ctx = ctxp; + struct lsquic_packet_out *packet_out; + + packet_out = TAILQ_FIRST(&ctx->packets[ctx->cur_input]); + if (packet_out) + LSQ_DEBUG("%s: return packet #%"PRIu64, __func__, + packet_out->po_packno); + else + LSQ_DEBUG("%s: out of packets", __func__); + + return packet_out; +} + + +static void +my_pri_discard_packet (void *ctxp, struct lsquic_packet_out *packet_out) +{ + struct test_ctx *ctx = ctxp; + + LSQ_DEBUG("%s: discard packet #%"PRIu64, __func__, packet_out->po_packno); + TAILQ_REMOVE(&ctx->packets[ctx->cur_input], packet_out, po_next); + lsquic_packet_out_destroy(packet_out, &ctx->enpub, NULL); +} + + +static struct lsquic_packet_out * +my_pri_new_packet (void *ctx) +{ + LSQ_DEBUG("%s: grab a new packet", __func__); + return new_packet(ctx); +} + + +static const struct packet_resize_if my_pr_if = +{ + .pri_next_packet = my_pri_next_packet, + .pri_new_packet = my_pri_new_packet, + .pri_discard_packet = my_pri_discard_packet, +}; + + +static int +resize_packets (struct test_ctx *ctx) +{ + struct packet_resize_ctx prctx; + struct lsquic_packet_out *new; + + lsquic_packet_resize_init(&prctx, &ctx->enpub, &ctx->lconn, ctx, &my_pr_if); + + while (new = lsquic_packet_resize_next(&prctx), new != NULL) + { + TAILQ_INSERT_TAIL(&ctx->packets[!ctx->cur_input], new, po_next); + LSQ_DEBUG("append new packet #%"PRIu64, new->po_packno); + } + ctx->cur_input = !ctx->cur_input; + LSQ_DEBUG("switch cur_input to %d", ctx->cur_input); + return lsquic_packet_resize_is_error(&prctx) ? -1 : 0; +} + + +static void +run_test (const struct test_spec *spec, enum lsquic_version version) +{ + struct lsquic_packet_out *packet_out; + struct test_ctx ctx; + long stream_id, nbytes; + char L[4] = "L?;", op, cmd; + const char *pc, *addr; + int jump, s; + enum quic_frame_type frame_type; + + LSQ_INFO("Running test on line %d: %s", spec->lineno, spec->desc); + if (spec->versions && !(spec->versions & (1 << version))) + { + LSQ_INFO("Not applicable to version %s, skip", + lsquic_ver2str[version]); + return; + } + + init_test_ctx(&ctx, spec, version); + + packet_out = NULL; + for (pc = spec->prog; *pc; ++pc) + { + cmd = *pc++; + switch (cmd) + { + case 'P': + ctx.path.np_pack_size = strtol(pc, (char **) &pc, 10); + LSQ_DEBUG("P: set packet size to %hu bytes", ctx.path.np_pack_size); + break; + case 'N': + packet_out = new_input_packet(&ctx); + LSQ_DEBUG("N: create new input packet"); + break; + case 'S': + case 'C': + stream_id = strtol(pc, (char **) &pc, 10); + assert('-' == *pc); + assert(stream_id >= 0 && stream_id < N_STREAMS); + nbytes = strtol(pc + 1, (char **) &pc, 10); + assert(nbytes > 0); + LSQ_DEBUG("%c: create frame for stream %ld of at most %ld bytes", + cmd, stream_id, nbytes); + if (cmd == 'S' && *pc == 'f') + ++pc; + make_stream_frame(&ctx, packet_out, + cmd == 'S' ? QUIC_FRAME_STREAM : QUIC_FRAME_CRYPTO, + stream_id, nbytes, pc[-1] == 'f'); + break; + case 'F': + stream_id = strtol(pc, (char **) &pc, 10); + make_stream_frame(&ctx, packet_out, QUIC_FRAME_STREAM, stream_id, + 0, 1); + break; + case 'V': + LSQ_DEBUG("V: verify packet contents"); + verify_packet_contents(&ctx, spec); + break; + case 'R': + LSQ_DEBUG("R: resize packets"); + s = resize_packets(&ctx); + if (0 != s) + { + LSQ_DEBUG("got error, expected: %d", spec->expect_error); + assert(spec->expect_error); + assert(pc[0] == ';'); + assert(pc[1] == '\0'); + goto end; + } + break; + case 'D': + nbytes = strtol(pc, (char **) &pc, 10); + ctx.path.np_pack_size -= nbytes; + LSQ_DEBUG("D: decrease packet size by %ld to %hu bytes", + nbytes, ctx.path.np_pack_size); + break; + case 'I': + nbytes = strtol(pc, (char **) &pc, 10); + ctx.path.np_pack_size += nbytes; + LSQ_DEBUG("I: increase packet size by %ld to %hu bytes", + nbytes, ctx.path.np_pack_size); + break; + case 'L': + assert(*pc >= '0' && *pc <= '9'); + ++pc; + break; + case 'J': + assert(*pc >= '0' && *pc <= '9'); + L[1] = *pc++; + addr = strstr(spec->prog, L); + assert(addr); + op = *pc++; + nbytes = strtol(pc, (char **) &pc, 10); + switch (op) + { + case '=': jump = ctx.path.np_pack_size == nbytes; break; + case '<': jump = ctx.path.np_pack_size < nbytes; break; + case '>': jump = ctx.path.np_pack_size > nbytes; break; + default: jump = 0; assert(0); break; + } + LSQ_DEBUG("J: jump if (%hu %c %ld) -> %sjumping", + ctx.path.np_pack_size, op, nbytes, jump ? "" : "not "); + if (jump) + pc = addr + 2; + break; + case 'c': + stream_id = strtol(pc, (char **) &pc, 10); + assert('-' == *pc); + assert(stream_id >= 0 && stream_id < N_STREAMS); + nbytes = strtol(pc + 1, (char **) &pc, 10); + assert(nbytes > 0); + make_rst_stream_frame(&ctx, packet_out, stream_id, nbytes); + break; + case 'a': case 'b': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + frame_type = letter_2_frame_type(cmd); + nbytes = strtol(pc, (char **) &pc, 10); + make_non_stream_frame(&ctx, packet_out, frame_type, nbytes); + break; + default: + assert(0); + goto end; + } + assert(*pc == ';'); + } + + end: + cleanup_test_ctx(&ctx); +} + + +int +main (int argc, char **argv) +{ + const struct test_spec *spec; + enum lsquic_version version; + int opt; + + lsquic_log_to_fstream(stderr, LLTS_HHMMSSMS); + (void) lsquic_set_log_level("info"); + while (opt = getopt(argc, argv, "l:L:h"), opt != -1) + { + switch (opt) + { + case 'L': + if (0 != lsquic_set_log_level(optarg)) + { + perror("lsquic_set_log_level"); + return 1; + } + break; + case 'l': + if (0 != lsquic_logger_lopt(optarg)) + { + perror("lsquic_logger_lopt"); + return 1; + } + break; + case 'h': + printf("usage: %s [options]\n", argv[0]); + return 0; + default: + return 1; + } + } + + for (version = 0; version < N_LSQVER; ++version) + { + if (!((1 << version) & LSQUIC_DF_VERSIONS)) + continue; + LSQ_INFO("testing version %s", lsquic_ver2str[version]); + for (spec = test_specs; spec < test_specs + sizeof(test_specs) / sizeof(test_specs[0]); ++spec) + run_test(spec, version); + } + + return 0; +} + + +#define DATA_0 \ +"ON BEING IDLE.\n" \ +"\n" \ +"Now, this is a subject on which I flatter myself I really am _au fait_.\n" \ +"The gentleman who, when I was young, bathed me at wisdom's font for nine\n" \ +"guineas a term--no extras--used to say he never knew a boy who could\n" \ +"do less work in more time; and I remember my poor grandmother once\n" \ +"incidentally observing, in the course of an instruction upon the use\n" \ +"of the Prayer-book, that it was highly improbable that I should ever do\n" \ +"much that I ought not to do, but that she felt convinced beyond a doubt\n" \ +"that I should leave undone pretty well everything that I ought to do.\n" \ +"\n" \ +"I am afraid I have somewhat belied half the dear old lady's prophecy.\n" \ +"Heaven help me! I have done a good many things that I ought not to have\n" \ +"done, in spite of my laziness. But I have fully confirmed the accuracy\n" \ +"of her judgment so far as neglecting much that I ought not to have\n" \ +"neglected is concerned. Idling always has been my strong point. I take\n" \ +"no credit to myself in the matter--it is a gift. Few possess it. There\n" \ +"are plenty of lazy people and plenty of slow-coaches, but a genuine\n" \ +"idler is a rarity. He is not a man who slouches about with his hands in\n" \ +"his pockets. On the contrary, his most startling characteristic is that\n" \ +"he is always intensely busy.\n" \ +"\n" \ +"It is impossible to enjoy idling thoroughly unless one has plenty of\n" \ +"work to do. There is no fun in doing nothing when you have nothing to\n" \ +"do. Wasting time is merely an occupation then, and a most exhausting\n" \ +"one. Idleness, like kisses, to be sweet must be stolen.\n" \ +"\n" \ +"Many years ago, when I was a young man, I was taken very ill--I never\n" \ +"could see myself that much was the matter with me, except that I had\n" \ +"a beastly cold. But I suppose it was something very serious, for the\n" \ +"doctor said that I ought to have come to him a month before, and that\n" \ +"if it (whatever it was) had gone on for another week he would not have\n" \ +"answered for the consequences. It is an extraordinary thing, but I\n" \ +"never knew a doctor called into any case yet but what it transpired\n" \ +"that another day's delay would have rendered cure hopeless. Our medical\n" \ +"guide, philosopher, and friend is like the hero in a melodrama--he\n" \ +"always comes upon the scene just, and only just, in the nick of time. It\n" \ +"is Providence, that is what it is.\n" \ +"\n" \ +"Well, as I was saying, I was very ill and was ordered to Buxton for a\n" \ +"month, with strict injunctions to do nothing whatever all the while\n" \ +"that I was there. \"Rest is what you require,\" said the doctor, \"perfect\n" \ +"rest.\"\n" \ +"\n" \ +"It seemed a delightful prospect. \"This man evidently understands my\n" \ +"complaint,\" said I, and I pictured to myself a glorious time--a four\n" \ +"weeks' _dolce far niente_ with a dash of illness in it. Not too much\n" \ +"illness, but just illness enough--just sufficient to give it the flavor\n" \ +"of suffering and make it poetical. I should get up late, sip chocolate,\n" \ +"and have my breakfast in slippers and a dressing-gown. I should lie out\n" \ +"in the garden in a hammock and read sentimental novels with a melancholy\n" \ +"ending, until the books should fall from my listless hand, and I should\n" \ +"recline there, dreamily gazing into the deep blue of the firmament,\n" \ +"watching the fleecy clouds floating like white-sailed ships across\n" \ +"its depths, and listening to the joyous song of the birds and the low\n" \ +"rustling of the trees. Or, on becoming too weak to go out of doors,\n" \ +"I should sit propped up with pillows at the open window of the\n" \ +"ground-floor front, and look wasted and interesting, so that all the\n" \ +"pretty girls would sigh as they passed by.\n" \ +"\n" \ +"And twice a day I should go down in a Bath chair to the Colonnade to\n" \ +"drink the waters. Oh, those waters! I knew nothing about them then,\n" \ +"and was rather taken with the idea. \"Drinking the waters\" sounded\n" \ +"fashionable and Queen Anne-fied, and I thought I should like them. But,\n" \ +"ugh! after the first three or four mornings! Sam Weller's description of\n" \ +"them as \"having a taste of warm flat-irons\" conveys only a faint idea of\n" \ +"their hideous nauseousness. If anything could make a sick man get well\n" \ +"quickly, it would be the knowledge that he must drink a glassful of them\n" \ +"every day until he was recovered. I drank them neat for six consecutive\n" \ +"days, and they nearly killed me; but after then I adopted the plan of\n" \ +"taking a stiff glass of brandy-and-water immediately on the top of them,\n" \ +"and found much relief thereby. I have been informed since, by various\n" \ +"eminent medical gentlemen, that the alcohol must have entirely\n" \ +"counteracted the effects of the chalybeate properties contained in the\n" \ +"water. I am glad I was lucky enough to hit upon the right thing.\n" \ +"\n" \ +"But \"drinking the waters\" was only a small portion of the torture I\n" \ +"experienced during that memorable month--a month which was, without\n" \ +"exception, the most miserable I have ever spent. During the best part of\n" \ +"it I religiously followed the doctor's mandate and did nothing whatever,\n" \ +"except moon about the house and garden and go out for two hours a day in\n" \ +"a Bath chair. That did break the monotony to a certain extent. There is\n" \ +"more excitement about Bath-chairing--especially if you are not used to\n" \ +"the exhilarating exercise--than might appear to the casual observer. A\n" \ +"sense of danger, such as a mere outsider might not understand, is ever\n" \ +"present to the mind of the occupant. He feels convinced every minute\n" \ +"that the whole concern is going over, a conviction which becomes\n" \ +"especially lively whenever a ditch or a stretch of newly macadamized\n" \ +"road comes in sight. Every vehicle that passes he expects is going to\n" \ +"run into him; and he never finds himself ascending or descending a\n" \ +"hill without immediately beginning to speculate upon his chances,\n" \ +"supposing--as seems extremely probable--that the weak-kneed controller\n" \ +"of his destiny should let go.\n" \ +"\n" \ +"But even this diversion failed to enliven after awhile, and the _ennui_\n" \ +"became perfectly unbearable. I felt my mind giving way under it. It is\n" \ +"not a strong mind, and I thought it would be unwise to tax it too far.\n" \ +"So somewhere about the twentieth morning I got up early, had a good\n" \ +"breakfast, and walked straight off to Hayfield, at the foot of the\n" \ +"Kinder Scout--a pleasant, busy little town, reached through a lovely\n" \ +"valley, and with two sweetly pretty women in it. At least they were\n" \ +"sweetly pretty then; one passed me on the bridge and, I think, smiled;\n" \ +"and the other was standing at an open door, making an unremunerative\n" \ +"investment of kisses upon a red-faced baby. But it is years ago, and I\n" \ +"dare say they have both grown stout and snappish since that time.\n" \ +"Coming back, I saw an old man breaking stones, and it roused such strong\n" \ +"longing in me to use my arms that I offered him a drink to let me take\n" \ +"his place. He was a kindly old man and he humored me. I went for those\n" \ +"stones with the accumulated energy of three weeks, and did more work in\n" \ +"half an hour than he had done all day. But it did not make him jealous.\n" \ +"\n" \ +"Having taken the plunge, I went further and further into dissipation,\n" \ +"going out for a long walk every morning and listening to the band in\n" \ +"the pavilion every evening. But the days still passed slowly\n" \ +"notwithstanding, and I was heartily glad when the last one came and I\n" \ +"was being whirled away from gouty, consumptive Buxton to London with its\n" \ +"stern work and life. I looked out of the carriage as we rushed through\n" \ +"Hendon in the evening. The lurid glare overhanging the mighty city\n" \ +"seemed to warm my heart, and when, later on, my cab rattled out of St.\n" \ +"Pancras' station, the old familiar roar that came swelling up around me\n" \ +"sounded the sweetest music I had heard for many a long day.\n" \ +"\n" \ +"I certainly did not enjoy that month's idling. I like idling when I\n" \ +"ought not to be idling; not when it is the only thing I have to do. That\n" \ +"is my pig-headed nature. The time when I like best to stand with my\n" \ +"back to the fire, calculating how much I owe, is when my desk is heaped\n" \ +"highest with letters that must be answered by the next post. When I like\n" \ +"to dawdle longest over my dinner is when I have a heavy evening's work\n" \ +"before me. And if, for some urgent reason, I ought to be up particularly\n" \ +"early in the morning, it is then, more than at any other time, that I\n" \ +"love to lie an extra half-hour in bed.\n" \ +"\n" \ +"Ah! how delicious it is to turn over and go to sleep again: \"just for\n" \ +"five minutes.\" Is there any human being, I wonder, besides the hero of\n" \ +"a Sunday-school \"tale for boys,\" who ever gets up willingly? There\n" \ +"are some men to whom getting up at the proper time is an utter\n" \ +"impossibility. If eight o'clock happens to be the time that they should\n" \ +"turn out, then they lie till half-past. If circumstances change and\n" \ +"half-past eight becomes early enough for them, then it is nine before\n" \ +"they can rise. They are like the statesman of whom it was said that he\n" \ +"was always punctually half an hour late. They try all manner of schemes.\n" \ +"They buy alarm-clocks (artful contrivances that go off at the wrong time\n" \ +"and alarm the wrong people). They tell Sarah Jane to knock at the door\n" \ +"and call them, and Sarah Jane does knock at the door and does call them,\n" \ +"and they grunt back \"awri\" and then go comfortably to sleep again. I\n" \ +"knew one man who would actually get out and have a cold bath; and even\n" \ +"that was of no use, for afterward he would jump into bed again to warm\n" \ +"himself.\n" \ +"\n" \ +"I think myself that I could keep out of bed all right if I once got\n" \ +"out. It is the wrenching away of the head from the pillow that I find so\n" \ +"hard, and no amount of over-night determination makes it easier. I say\n" \ +"to myself, after having wasted the whole evening, \"Well, I won't do\n" \ +"any more work to-night; I'll get up early to-morrow morning;\" and I am\n" \ +"thoroughly resolved to do so--then. In the morning, however, I feel less\n" \ +"enthusiastic about the idea, and reflect that it would have been much\n" \ +"better if I had stopped up last night. And then there is the trouble of\n" \ +"dressing, and the more one thinks about that the more one wants to put\n" \ +"it off.\n" \ +"\n" \ +"It is a strange thing this bed, this mimic grave, where we stretch our\n" \ +"tired limbs and sink away so quietly into the silence and rest. \"O bed,\n" \ +"O bed, delicious bed, that heaven on earth to the weary head,\" as sang\n" \ +"poor Hood, you are a kind old nurse to us fretful boys and girls. Clever\n" \ +"and foolish, naughty and good, you take us all in your motherly lap and\n" \ +"hush our wayward crying. The strong man full of care--the sick man\n" \ +"full of pain--the little maiden sobbing for her faithless lover--like\n" \ +"children we lay our aching heads on your white bosom, and you gently\n" \ +"soothe us off to by-by.\n" \ +"\n" \ +"Our trouble is sore indeed when you turn away and will not comfort us.\n" \ +"How long the dawn seems coming when we cannot sleep! Oh! those hideous\n" \ +"nights when we toss and turn in fever and pain, when we lie, like living\n" \ +"men among the dead, staring out into the dark hours that drift so slowly\n" \ +"between us and the light. And oh! those still more hideous nights when\n" \ +"we sit by another in pain, when the low fire startles us every now and\n" \ +"then with a falling cinder, and the tick of the clock seems a hammer\n" \ +"beating out the life that we are watching.\n" \ +"\n" \ +"But enough of beds and bedrooms. I have kept to them too long, even for\n" \ +"an idle fellow. Let us come out and have a smoke. That wastes time just\n" \ +"as well and does not look so bad. Tobacco has been a blessing to us\n" \ +"idlers. What the civil-service clerk before Sir Walter's time found\n" \ +"to occupy their minds with it is hard to imagine. I attribute the\n" \ +"quarrelsome nature of the Middle Ages young men entirely to the want of\n" \ +"the soothing weed. They had no work to do and could not smoke, and\n" \ +"the consequence was they were forever fighting and rowing. If, by any\n" \ +"extraordinary chance, there was no war going, then they got up a deadly\n" \ +"family feud with the next-door neighbor, and if, in spite of this, they\n" \ +"still had a few spare moments on their hands, they occupied them with\n" \ +"discussions as to whose sweetheart was the best looking, the arguments\n" \ +"employed on both sides being battle-axes, clubs, etc. Questions of taste\n" \ +"were soon decided in those days. When a twelfth-century youth fell in\n" \ +"love he did not take three paces backward, gaze into her eyes, and tell\n" \ +"her she was too beautiful to live. He said he would step outside and see\n" \ +"about it. And if, when he got out, he met a man and broke his head--the\n" \ +"other man's head, I mean--then that proved that his--the first\n" \ +"fellow's--girl was a pretty girl. But if the other fellow broke _his_\n" \ +"head--not his own, you know, but the other fellow's--the other fellow\n" \ +"to the second fellow, that is, because of course the other fellow would\n" \ +"only be the other fellow to him, not the first fellow who--well, if he\n" \ +"broke his head, then _his_ girl--not the other fellow's, but the fellow\n" \ +"who _was_ the--Look here, if A broke B's head, then A's girl was a\n" \ +"pretty girl; but if B broke A's head, then A's girl wasn't a pretty\n" \ +"girl, but B's girl was. That was their method of conducting art\n" \ +"criticism.\n" \ +"\n" \ +"Nowadays we light a pipe and let the girls fight it out among\n" \ +"themselves.\n" \ +"\n" \ +"They do it very well. They are getting to do all our work. They are\n" \ +"doctors, and barristers, and artists. They manage theaters, and promote\n" \ +"swindles, and edit newspapers. I am looking forward to the time when we\n" \ +"men shall have nothing to do but lie in bed till twelve, read two novels\n" \ +"a day, have nice little five-o'clock teas all to ourselves, and tax\n" \ +"our brains with nothing more trying than discussions upon the latest\n" \ +"patterns in trousers and arguments as to what Mr. Jones' coat was\n" \ +"made of and whether it fitted him. It is a glorious prospect--for idle\n" \ +"fellows.\n" + +#define DATA_1 \ +"ON BEING IN LOVE.\n" \ +"\n" \ +"You've been in love, of course! If not you've got it to come. Love is\n" \ +"like the measles; we all have to go through it. Also like the measles,\n" \ +"we take it only once. One never need be afraid of catching it a second\n" \ +"time. The man who has had it can go into the most dangerous places and\n" \ +"play the most foolhardy tricks with perfect safety. He can picnic in\n" \ +"shady woods, ramble through leafy aisles, and linger on mossy seats to\n" \ +"watch the sunset. He fears a quiet country-house no more than he would\n" \ +"his own club. He can join a family party to go down the Rhine. He can,\n" \ +"to see the last of a friend, venture into the very jaws of the marriage\n" \ +"ceremony itself. He can keep his head through the whirl of a ravishing\n" \ +"waltz, and rest afterward in a dark conservatory, catching nothing more\n" \ +"lasting than a cold. He can brave a moonlight walk adown sweet-scented\n" \ +"lanes or a twilight pull among the somber rushes. He can get over a\n" \ +"stile without danger, scramble through a tangled hedge without being\n" \ +"caught, come down a slippery path without falling. He can look into\n" \ +"sunny eyes and not be dazzled. He listens to the siren voices, yet sails\n" \ +"on with unveered helm. He clasps white hands in his, but no electric\n" \ +"\"Lulu\"-like force holds him bound in their dainty pressure.\n" \ +"\n" \ +"No, we never sicken with love twice. Cupid spends no second arrow on\n" \ +"the same heart. Love's handmaids are our life-long friends. Respect, and\n" \ +"admiration, and affection, our doors may always be left open for, but\n" \ +"their great celestial master, in his royal progress, pays but one visit\n" \ +"and departs. We like, we cherish, we are very, very fond of--but we\n" \ +"never love again. A man's heart is a firework that once in its time\n" \ +"flashes heavenward. Meteor-like, it blazes for a moment and lights\n" \ +"with its glory the whole world beneath. Then the night of our sordid\n" \ +"commonplace life closes in around it, and the burned-out case, falling\n" \ +"back to earth, lies useless and uncared for, slowly smoldering into\n" \ +"ashes. Once, breaking loose from our prison bonds, we dare, as mighty\n" \ +"old Prometheus dared, to scale the Olympian mount and snatch from\n" \ +"Phoebus' chariot the fire of the gods. Happy those who, hastening down\n" \ +"again ere it dies out, can kindle their earthly altars at its flame.\n" \ +"Love is too pure a light to burn long among the noisome gases that we\n" \ +"breathe, but before it is choked out we may use it as a torch to ignite\n" \ +"the cozy fire of affection.\n" \ +"\n" \ +"And, after all, that warming glow is more suited to our cold little back\n" \ +"parlor of a world than is the burning spirit love. Love should be the\n" \ +"vestal fire of some mighty temple--some vast dim fane whose organ music\n" \ +"is the rolling of the spheres. Affection will burn cheerily when the\n" \ +"white flame of love is flickered out. Affection is a fire that can be\n" \ +"fed from day to day and be piled up ever higher as the wintry years draw\n" \ +"nigh. Old men and women can sit by it with their thin hands clasped, the\n" \ +"little children can nestle down in front, the friend and neighbor has\n" \ +"his welcome corner by its side, and even shaggy Fido and sleek Titty can\n" \ +"toast their noses at the bars.\n" \ +"\n" \ +"Let us heap the coals of kindness upon that fire. Throw on your pleasant\n" \ +"words, your gentle pressures of the hand, your thoughtful and unselfish\n" \ +"deeds. Fan it with good-humor, patience, and forbearance. You can let\n" \ +"the wind blow and the rain fall unheeded then, for your hearth will be\n" \ +"warm and bright, and the faces round it will make sunshine in spite of\n" \ +"the clouds without.\n" \ +"\n" \ +"I am afraid, dear Edwin and Angelina, you expect too much from love.\n" \ +"You think there is enough of your little hearts to feed this fierce,\n" \ +"devouring passion for all your long lives. Ah, young folk! don't rely\n" \ +"too much upon that unsteady flicker. It will dwindle and dwindle as the\n" \ +"months roll on, and there is no replenishing the fuel. You will watch it\n" \ +"die out in anger and disappointment. To each it will seem that it is the\n" \ +"other who is growing colder. Edwin sees with bitterness that Angelina no\n" \ +"longer runs to the gate to meet him, all smiles and blushes; and when he\n" \ +"has a cough now she doesn't begin to cry and, putting her arms round his\n" \ +"neck, say that she cannot live without him. The most she will probably\n" \ +"do is to suggest a lozenge, and even that in a tone implying that it is\n" \ +"the noise more than anything else she is anxious to get rid of.\n" \ +"\n" \ +"Poor little Angelina, too, sheds silent tears, for Edwin has given up\n" \ +"carrying her old handkerchief in the inside pocket of his waistcoat.\n" \ +"\n" \ +"Both are astonished at the falling off in the other one, but neither\n" \ +"sees their own change. If they did they would not suffer as they do.\n" \ +"They would look for the cause in the right quarter--in the littleness\n" \ +"of poor human nature--join hands over their common failing, and start\n" \ +"building their house anew on a more earthly and enduring foundation.\n" \ +"But we are so blind to our own shortcomings, so wide awake to those\n" \ +"of others. Everything that happens to us is always the other person's\n" \ +"fault. Angelina would have gone on loving Edwin forever and ever and\n" \ +"ever if only Edwin had not grown so strange and different. Edwin would\n" \ +"have adored Angelina through eternity if Angelina had only remained the\n" \ +"same as when he first adored her.\n" \ +"\n" \ +"It is a cheerless hour for you both when the lamp of love has gone out\n" \ +"and the fire of affection is not yet lit, and you have to grope about\n" \ +"in the cold, raw dawn of life to kindle it. God grant it catches light\n" \ +"before the day is too far spent. Many sit shivering by the dead coals\n" \ +"till night come.\n" \ +"\n" \ +"But, there, of what use is it to preach? Who that feels the rush of\n" \ +"young love through his veins can think it will ever flow feeble and\n" \ +"slow! To the boy of twenty it seems impossible that he will not love as\n" \ +"wildly at sixty as he does then. He cannot call to mind any middle-aged\n" \ +"or elderly gentleman of his acquaintance who is known to exhibit\n" \ +"symptoms of frantic attachment, but that does not interfere in his\n" \ +"belief in himself. His love will never fall, whoever else's may. Nobody\n" \ +"ever loved as he loves, and so, of course, the rest of the world's\n" \ +"experience can be no guide in his case. Alas! alas! ere thirty he has\n" \ +"joined the ranks of the sneerers. It is not his fault. Our passions,\n" \ +"both the good and bad, cease with our blushes. We do not hate, nor\n" \ +"grieve, nor joy, nor despair in our thirties like we did in our teens.\n" \ +"Disappointment does not suggest suicide, and we quaff success without\n" \ +"intoxication.\n" \ +"\n" \ +"We take all things in a minor key as we grow older. There are few\n" \ +"majestic passages in the later acts of life's opera. Ambition takes\n" \ +"a less ambitious aim. Honor becomes more reasonable and conveniently\n" \ +"adapts itself to circumstances. And love--love dies. \"Irreverence for\n" \ +"the dreams of youth\" soon creeps like a killing frost upon our hearts.\n" \ +"The tender shoots and the expanding flowers are nipped and withered, and\n" \ +"of a vine that yearned to stretch its tendrils round the world there is\n" \ +"left but a sapless stump.\n" \ +"\n" \ +"My fair friends will deem all this rank heresy, I know. So far from a\n" \ +"man's not loving after he has passed boyhood, it is not till there is a\n" \ +"good deal of gray in his hair that they think his protestations at all\n" \ +"worthy of attention. Young ladies take their notions of our sex from the\n" \ +"novels written by their own, and compared with the monstrosities\n" \ +"that masquerade for men in the pages of that nightmare literature,\n" \ +"Pythagoras' plucked bird and Frankenstein's demon were fair average\n" \ +"specimens of humanity.\n" \ +"\n" \ +"In these so-called books, the chief lover, or Greek god, as he is\n" \ +"admiringly referred to--by the way, they do not say which \"Greek god\"\n" \ +"it is that the gentleman bears such a striking likeness to; it might be\n" \ +"hump-backed Vulcan, or double-faced Janus, or even driveling Silenus,\n" \ +"the god of abstruse mysteries. He resembles the whole family of them,\n" \ +"however, in being a blackguard, and perhaps this is what is meant. To\n" \ +"even the little manliness his classical prototypes possessed, though,\n" \ +"he can lay no claim whatever, being a listless effeminate noodle, on\n" \ +"the shady side of forty. But oh! the depth and strength of this elderly\n" \ +"party's emotion for some bread-and-butter school-girl! Hide your heads,\n" \ +"ye young Romeos and Leanders! this _blase_ old beau loves with an\n" \ +"hysterical fervor that requires four adjectives to every noun to\n" \ +"properly describe.\n" \ +"\n" \ +"It is well, dear ladies, for us old sinners that you study only books.\n" \ +"Did you read mankind, you would know that the lad's shy stammering tells\n" \ +"a truer tale than our bold eloquence. A boy's love comes from a full\n" \ +"heart; a man's is more often the result of a full stomach. Indeed, a\n" \ +"man's sluggish current may not be called love, compared with the rushing\n" \ +"fountain that wells up when a boy's heart is struck with the heavenly\n" \ +"rod. If you would taste love, drink of the pure stream that youth pours\n" \ +"out at your feet. Do not wait till it has become a muddy river before\n" \ +"you stoop to catch its waves.\n" \ +"\n" \ +"Or is it that you like its bitter flavor--that the clear, limpid water\n" \ +"is insipid to your palate and that the pollution of its after-course\n" \ +"gives it a relish to your lips? Must we believe those who tell us that a\n" \ +"hand foul with the filth of a shameful life is the only one a young girl\n" \ +"cares to be caressed by?\n" \ +"\n" \ +"That is the teaching that is bawled out day by day from between those\n" \ +"yellow covers. Do they ever pause to think, I wonder, those devil's\n" \ +"ladyhelps, what mischief they are doing crawling about God's garden, and\n" \ +"telling childish Eves and silly Adams that sin is sweet and that decency\n" \ +"is ridiculous and vulgar? How many an innocent girl do they not degrade\n" \ +"into an evil-minded woman? To how many a weak lad do they not point out\n" \ +"the dirty by-path as the shortest cut to a maiden's heart? It is not as\n" \ +"if they wrote of life as it really is. Speak truth, and right will take\n" \ +"care of itself. But their pictures are coarse daubs painted from the\n" \ +"sickly fancies of their own diseased imagination.\n" \ +"\n" \ +"We want to think of women not--as their own sex would show them--as\n" \ +"Lorleis luring us to destruction, but as good angels beckoning us\n" \ +"upward. They have more power for good or evil than they dream of. It is\n" \ +"just at the very age when a man's character is forming that he tumbles\n" \ +"into love, and then the lass he loves has the making or marring of him.\n" \ +"Unconsciously he molds himself to what she would have him, good or bad.\n" \ +"I am sorry to have to be ungallant enough to say that I do not think\n" \ +"they always use their influence for the best. Too often the female world\n" \ +"is bounded hard and fast within the limits of the commonplace. Their\n" \ +"ideal hero is a prince of littleness, and to become that many a powerful\n" \ +"mind, enchanted by love, is \"lost to life and use and name and fame.\"\n" \ +"\n" \ +"And yet, women, you could make us so much better if you only would. It\n" \ +"rests with you, more than with all the preachers, to roll this world a\n" \ +"little nearer heaven. Chivalry is not dead: it only sleeps for want\n" \ +"of work to do. It is you who must wake it to noble deeds. You must be\n" \ +"worthy of knightly worship.\n" \ +"\n" \ +"You must be higher than ourselves. It was for Una that the Red Cross\n" \ +"Knight did war. For no painted, mincing court dame could the dragon have\n" \ +"been slain. Oh, ladies fair, be fair in mind and soul as well as face,\n" \ +"so that brave knights may win glory in your service! Oh, woman, throw\n" \ +"off your disguising cloaks of selfishness, effrontery, and affectation!\n" \ +"Stand forth once more a queen in your royal robe of simple purity. A\n" \ +"thousand swords, now rusting in ignoble sloth, shall leap from their\n" \ +"scabbards to do battle for your honor against wrong. A thousand Sir\n" \ +"Rolands shall lay lance in rest, and Fear, Avarice, Pleasure, and\n" \ +"Ambition shall go down in the dust before your colors.\n" \ +"\n" \ +"What noble deeds were we not ripe for in the days when we loved?\n" \ +"What noble lives could we not have lived for her sake? Our love was\n" \ +"a religion we could have died for. It was no mere human creature like\n" \ +"ourselves that we adored. It was a queen that we paid homage to, a\n" \ +"goddess that we worshiped.\n" \ +"\n" \ +"And how madly we did worship! And how sweet it was to worship! Ah, lad,\n" \ +"cherish love's young dream while it lasts! You will know too soon how\n" \ +"truly little Tom Moore sang when he said that there was nothing half so\n" \ +"sweet in life. Even when it brings misery it is a wild, romantic misery,\n" \ +"all unlike the dull, worldly pain of after-sorrows. When you have lost\n" \ +"her--when the light is gone out from your life and the world stretches\n" \ +"before you a long, dark horror, even then a half-enchantment mingles\n" \ +"with your despair.\n" \ +"\n" \ +"And who would not risk its terrors to gain its raptures? Ah, what\n" \ +"raptures they were! The mere recollection thrills you. How delicious\n" \ +"it was to tell her that you loved her, that you lived for her, that\n" \ +"you would die for her! How you did rave, to be sure, what floods of\n" \ +"extravagant nonsense you poured forth, and oh, how cruel it was of\n" \ +"her to pretend not to believe you! In what awe you stood of her! How\n" \ +"miserable you were when you had offended her! And yet, how pleasant to\n" \ +"be bullied by her and to sue for pardon without having the slightest\n" \ +"notion of what your fault was! How dark the world was when she snubbed\n" \ +"you, as she often did, the little rogue, just to see you look wretched;\n" \ +"how sunny when she smiled! How jealous you were of every one about\n" \ +"her! How you hated every man she shook hands with, every woman she\n" \ +"kissed--the maid that did her hair, the boy that cleaned her shoes, the\n" \ +"dog she nursed--though you had to be respectful to the last-named! How\n" \ +"you looked forward to seeing her, how stupid you were when you did see\n" \ +"her, staring at her without saying a word! How impossible it was for\n" \ +"you to go out at any time of the day or night without finding yourself\n" \ +"eventually opposite her windows! You hadn't pluck enough to go in, but\n" \ +"you hung about the corner and gazed at the outside. Oh, if the house had\n" \ +"only caught fire--it was insured, so it wouldn't have mattered--and you\n" \ +"could have rushed in and saved her at the risk of your life, and have\n" \ +"been terribly burned and injured! Anything to serve her. Even in little\n" \ +"things that was so sweet. How you would watch her, spaniel-like, to\n" \ +"anticipate her slightest wish! How proud you were to do her bidding! How\n" \ +"delightful it was to be ordered about by her! To devote your whole life\n" \ +"to her and to never think of yourself seemed such a simple thing. You\n" \ +"would go without a holiday to lay a humble offering at her shrine, and\n" \ +"felt more than repaid if she only deigned to accept it. How precious to\n" \ +"you was everything that she had hallowed by her touch--her little glove,\n" \ +"the ribbon she had worn, the rose that had nestled in her hair and whose\n" \ +"withered leaves still mark the poems you never care to look at now.\n" \ +"\n" \ +"And oh, how beautiful she was, how wondrous beautiful! It was as some\n" \ +"angel entering the room, and all else became plain and earthly. She was\n" \ +"too sacred to be touched. It seemed almost presumption to gaze at her.\n" \ +"You would as soon have thought of kissing her as of singing comic songs\n" \ +"in a cathedral. It was desecration enough to kneel and timidly raise the\n" \ +"gracious little hand to your lips.\n" \ +"\n" \ +"Ah, those foolish days, those foolish days when we were unselfish and\n" \ +"pure-minded; those foolish days when our simple hearts were full\n" \ +"of truth, and faith, and reverence! Ah, those foolish days of noble\n" \ +"longings and of noble strivings! And oh, these wise, clever days when we\n" \ +"know that money is the only prize worth striving for, when we believe in\n" \ +"nothing else but meanness and lies, when we care for no living creature\n" \ +"but ourselves!\n" + +#define DATA_2 \ +"ON BEING IN THE BLUES.\n" \ +"\n" \ +"I can enjoy feeling melancholy, and there is a good deal of satisfaction\n" \ +"about being thoroughly miserable; but nobody likes a fit of the blues.\n" \ +"Nevertheless, everybody has them; notwithstanding which, nobody can tell\n" \ +"why. There is no accounting for them. You are just as likely to have one\n" \ +"on the day after you have come into a large fortune as on the day after\n" \ +"you have left your new silk umbrella in the train. Its effect upon you\n" \ +"is somewhat similar to what would probably be produced by a combined\n" \ +"attack of toothache, indigestion, and cold in the head. You become\n" \ +"stupid, restless, and irritable; rude to strangers and dangerous toward\n" \ +"your friends; clumsy, maudlin, and quarrelsome; a nuisance to yourself\n" \ +"and everybody about you.\n" \ +"\n" \ +"While it is on you can do nothing and think of nothing, though feeling\n" \ +"at the time bound to do something. You can't sit still so put on your\n" \ +"hat and go for a walk; but before you get to the corner of the street\n" \ +"you wish you hadn't come out and you turn back. You open a book and try\n" \ +"to read, but you find Shakespeare trite and commonplace, Dickens is dull\n" \ +"and prosy, Thackeray a bore, and Carlyle too sentimental. You throw the\n" \ +"book aside and call the author names. Then you \"shoo\" the cat out of\n" \ +"the room and kick the door to after her. You think you will write your\n" \ +"letters, but after sticking at \"Dearest Auntie: I find I have five\n" \ +"minutes to spare, and so hasten to write to you,\" for a quarter of an\n" \ +"hour, without being able to think of another sentence, you tumble the\n" \ +"paper into the desk, fling the wet pen down upon the table-cloth,\n" \ +"and start up with the resolution of going to see the Thompsons. While\n" \ +"pulling on your gloves, however, it occurs to you that the Thompsons are\n" \ +"idiots; that they never have supper; and that you will be expected to\n" \ +"jump the baby. You curse the Thompsons and decide not to go.\n" \ +"\n" \ +"By this time you feel completely crushed. You bury your face in your\n" \ +"hands and think you would like to die and go to heaven. You picture to\n" \ +"yourself your own sick-bed, with all your friends and relations standing\n" \ +"round you weeping. You bless them all, especially the young and pretty\n" \ +"ones. They will value you when you are gone, so you say to yourself,\n" \ +"and learn too late what they have lost; and you bitterly contrast their\n" \ +"presumed regard for you then with their decided want of veneration now.\n" \ +"\n" \ +"These reflections make you feel a little more cheerful, but only for a\n" \ +"brief period; for the next moment you think what a fool you must be\n" \ +"to imagine for an instant that anybody would be sorry at anything that\n" \ +"might happen to you. Who would care two straws (whatever precise amount\n" \ +"of care two straws may represent) whether you are blown up, or hung\n" \ +"up, or married, or drowned? Nobody cares for you. You never have\n" \ +"been properly appreciated, never met with your due deserts in any one\n" \ +"particular. You review the whole of your past life, and it is painfully\n" \ +"apparent that you have been ill-used from your cradle.\n" \ +"\n" \ +"Half an hour's indulgence in these considerations works you up into\n" \ +"a state of savage fury against everybody and everything, especially\n" \ +"yourself, whom anatomical reasons alone prevent your kicking. Bed-time\n" \ +"at last comes, to save you from doing something rash, and you spring\n" \ +"upstairs, throw off your clothes, leaving them strewn all over the room,\n" \ +"blow out the candle, and jump into bed as if you had backed yourself\n" \ +"for a heavy wager to do the whole thing against time. There you toss\n" \ +"and tumble about for a couple of hours or so, varying the monotony by\n" \ +"occasionally jerking the clothes off and getting out and putting them\n" \ +"on again. At length you drop into an uneasy and fitful slumber, have bad\n" \ +"dreams, and wake up late the next morning.\n" \ +"\n" \ +"At least, this is all we poor single men can do under the circumstances.\n" \ +"Married men bully their wives, grumble at the dinner, and insist on the\n" \ +"children's going to bed. All of which, creating, as it does, a good deal\n" \ +"of disturbance in the house, must be a great relief to the feelings of a\n" \ +"man in the blues, rows being the only form of amusement in which he can\n" \ +"take any interest.\n" \ +"\n" \ +"The symptoms of the infirmity are much the same in every case, but the\n" \ +"affliction itself is variously termed. The poet says that \"a feeling\n" \ +"of sadness comes o'er him.\" 'Arry refers to the heavings of his wayward\n" \ +"heart by confiding to Jimee that he has \"got the blooming hump.\" Your\n" \ +"sister doesn't know what is the matter with her to-night. She feels out\n" \ +"of sorts altogether and hopes nothing is going to happen. The every-day\n" \ +"young man is \"so awful glad to meet you, old fellow,\" for he does \"feel\n" \ +"so jolly miserable this evening.\" As for myself, I generally say that \"I\n" \ +"have a strange, unsettled feeling to-night\" and \"think I'll go out.\"\n" \ +"\n" \ +"By the way, it never does come except in the evening. In the sun-time,\n" \ +"when the world is bounding forward full of life, we cannot stay to sigh\n" \ +"and sulk. The roar of the working day drowns the voices of the elfin\n" \ +"sprites that are ever singing their low-toned _miserere_ in our ears.\n" \ +"In the day we are angry, disappointed, or indignant, but never \"in the\n" \ +"blues\" and never melancholy. When things go wrong at ten o'clock in the\n" \ +"morning we--or rather you--swear and knock the furniture about; but if\n" \ +"the misfortune comes at ten P.M., we read poetry or sit in the dark and\n" \ +"think what a hollow world this is.\n" \ +"\n" \ +"But, as a rule, it is not trouble that makes us melancholy. The\n" \ +"actuality is too stern a thing for sentiment. We linger to weep over\n" \ +"a picture, but from the original we should quickly turn our eyes away.\n" \ +"There is no pathos in real misery: no luxury in real grief. We do not\n" \ +"toy with sharp swords nor hug a gnawing fox to our breast for choice.\n" \ +"When a man or woman loves to brood over a sorrow and takes care to keep\n" \ +"it green in their memory, you may be sure it is no longer a pain to\n" \ +"them. However they may have suffered from it at first, the recollection\n" \ +"has become by then a pleasure. Many dear old ladies who daily look at\n" \ +"tiny shoes lying in lavender-scented drawers, and weep as they think of\n" \ +"the tiny feet whose toddling march is done, and sweet-faced young ones\n" \ +"who place each night beneath their pillow some lock that once curled on\n" \ +"a boyish head that the salt waves have kissed to death, will call me\n" \ +"a nasty cynical brute and say I'm talking nonsense; but I believe,\n" \ +"nevertheless, that if they will ask themselves truthfully whether they\n" \ +"find it unpleasant to dwell thus on their sorrow, they will be compelled\n" \ +"to answer \"No.\" Tears are as sweet as laughter to some natures. The\n" \ +"proverbial Englishman, we know from old chronicler Froissart, takes his\n" \ +"pleasures sadly, and the Englishwoman goes a step further and takes her\n" \ +"pleasures in sadness itself.\n" \ +"\n" \ +"I am not sneering. I would not for a moment sneer at anything that\n" \ +"helps to keep hearts tender in this hard old world. We men are cold and\n" \ +"common-sensed enough for all; we would not have women the same. No, no,\n" \ +"ladies dear, be always sentimental and soft-hearted, as you are--be the\n" \ +"soothing butter to our coarse dry bread. Besides, sentiment is to women\n" \ +"what fun is to us. They do not care for our humor, surely it would be\n" \ +"unfair to deny them their grief. And who shall say that their mode of\n" \ +"enjoyment is not as sensible as ours? Why assume that a doubled-up\n" \ +"body, a contorted, purple face, and a gaping mouth emitting a series\n" \ +"of ear-splitting shrieks point to a state of more intelligent happiness\n" \ +"than a pensive face reposing upon a little white hand, and a pair of\n" \ +"gentle tear-dimmed eyes looking back through Time's dark avenue upon a\n" \ +"fading past?\n" \ +"\n" \ +"I am glad when I see Regret walked with as a friend--glad because I know\n" \ +"the saltness has been washed from out the tears, and that the sting must\n" \ +"have been plucked from the beautiful face of Sorrow ere we dare press\n" \ +"her pale lips to ours. Time has laid his healing hand upon the wound\n" \ +"when we can look back upon the pain we once fainted under and no\n" \ +"bitterness or despair rises in our hearts. The burden is no longer\n" \ +"heavy when we have for our past troubles only the same sweet mingling of\n" \ +"pleasure and pity that we feel when old knight-hearted Colonel Newcome\n" \ +"answers \"_adsum_\" to the great roll-call, or when Tom and Maggie\n" \ +"Tulliver, clasping hands through the mists that have divided them, go\n" \ +"down, locked in each other's arms, beneath the swollen waters of the\n" \ +"Floss.\n" \ +"\n" \ +"Talking of poor Tom and Maggie Tulliver brings to my mind a saying of\n" \ +"George Eliot's in connection with this subject of melancholy. She\n" \ +"speaks somewhere of the \"sadness of a summer's evening.\" How wonderfully\n" \ +"true--like everything that came from that wonderful pen--the observation\n" \ +"is! Who has not felt the sorrowful enchantment of those lingering\n" \ +"sunsets? The world belongs to Melancholy then, a thoughtful deep-eyed\n" \ +"maiden who loves not the glare of day. It is not till \"light thickens\n" \ +"and the crow wings to the rocky wood\" that she steals forth from her\n" \ +"groves. Her palace is in twilight land. It is there she meets us. At her\n" \ +"shadowy gate she takes our hand in hers and walks beside us through\n" \ +"her mystic realm. We see no form, but seem to hear the rustling of her\n" \ +"wings.\n" \ +"\n" \ +"Even in the toiling hum-drum city her spirit comes to us. There is a\n" \ +"somber presence in each long, dull street; and the dark river creeps\n" \ +"ghostlike under the black arches, as if bearing some hidden secret\n" \ +"beneath its muddy waves.\n" \ +"\n" \ +"In the silent country, when the trees and hedges loom dim and blurred\n" \ +"against the rising night, and the bat's wing flutters in our face, and\n" \ +"the land-rail's cry sounds drearily across the fields, the spell sinks\n" \ +"deeper still into our hearts. We seem in that hour to be standing by\n" \ +"some unseen death-bed, and in the swaying of the elms we hear the sigh\n" \ +"of the dying day.\n" \ +"\n" \ +"A solemn sadness reigns. A great peace is around us. In its light\n" \ +"our cares of the working day grow small and trivial, and bread and\n" \ +"cheese--ay, and even kisses--do not seem the only things worth striving\n" \ +"for. Thoughts we cannot speak but only listen to flood in upon us, and\n" \ +"standing in the stillness under earth's darkening dome, we feel that we\n" \ +"are greater than our petty lives. Hung round with those dusky curtains,\n" \ +"the world is no longer a mere dingy workshop, but a stately temple\n" \ +"wherein man may worship, and where at times in the dimness his groping\n" \ +"hands touch God's.\n" + +#define DATA_3 \ +"ON BEING HARD UP.\n" \ +"\n" \ +"It is a most remarkable thing. I sat down with the full intention of\n" \ +"writing something clever and original; but for the life of me I can't\n" \ +"think of anything clever and original--at least, not at this moment. The\n" \ +"only thing I can think about now is being hard up. I suppose having my\n" \ +"hands in my pockets has made me think about this. I always do sit with\n" \ +"my hands in my pockets except when I am in the company of my sisters,\n" \ +"my cousins, or my aunts; and they kick up such a shindy--I should say\n" \ +"expostulate so eloquently upon the subject--that I have to give in and\n" \ +"take them out--my hands I mean. The chorus to their objections is that\n" \ +"it is not gentlemanly. I am hanged if I can see why. I could understand\n" \ +"its not being considered gentlemanly to put your hands in other people's\n" \ +"pockets (especially by the other people), but how, O ye sticklers for\n" \ +"what looks this and what looks that, can putting his hands in his own\n" \ +"pockets make a man less gentle? Perhaps you are right, though. Now I\n" \ +"come to think of it, I have heard some people grumble most savagely when\n" \ +"doing it. But they were mostly old gentlemen. We young fellows, as a\n" \ +"rule, are never quite at ease unless we have our hands in our pockets.\n" \ +"We are awkward and shifty. We are like what a music-hall Lion Comique\n" \ +"would be without his opera-hat, if such a thing can be imagined. But let\n" \ +"us put our hands in our trousers pockets, and let there be some small\n" \ +"change in the right-hand one and a bunch of keys in the left, and we\n" \ +"will face a female post-office clerk.\n" \ +"\n" \ +"It is a little difficult to know what to do with your hands, even in\n" \ +"your pockets, when there is nothing else there. Years ago, when my whole\n" \ +"capital would occasionally come down to \"what in town the people call\n" \ +"a bob,\" I would recklessly spend a penny of it, merely for the sake of\n" \ +"having the change, all in coppers, to jingle. You don't feel nearly so\n" \ +"hard up with eleven pence in your pocket as you do with a shilling. Had\n" \ +"I been \"La-di-da,\" that impecunious youth about whom we superior folk\n" \ +"are so sarcastic, I would have changed my penny for two ha'pennies.\n" \ +"\n" \ +"I can speak with authority on the subject of being hard up. I have been\n" \ +"a provincial actor. If further evidence be required, which I do not\n" \ +"think likely, I can add that I have been a \"gentleman connected with the\n" \ +"press.\" I have lived on 15 shilling a week. I have lived a week on 10,\n" \ +"owing the other 5; and I have lived for a fortnight on a great-coat.\n" \ +"\n" \ +"It is wonderful what an insight into domestic economy being really hard\n" \ +"up gives one. If you want to find out the value of money, live on\n" \ +"15 shillings a week and see how much you can put by for clothes and\n" \ +"recreation. You will find out that it is worth while to wait for the\n" \ +"farthing change, that it is worth while to walk a mile to save a\n" \ +"penny, that a glass of beer is a luxury to be indulged in only at rare\n" \ +"intervals, and that a collar can be worn for four days.\n" \ +"\n" \ +"Try it just before you get married. It will be excellent practice. Let\n" \ +"your son and heir try it before sending him to college. He won't grumble\n" \ +"at a hundred a year pocket-money then. There are some people to whom it\n" \ +"would do a world of good. There is that delicate blossom who can't drink\n" \ +"any claret under ninety-four, and who would as soon think of dining\n" \ +"off cat's meat as off plain roast mutton. You do come across these\n" \ +"poor wretches now and then, though, to the credit of humanity, they are\n" \ +"principally confined to that fearful and wonderful society known only\n" \ +"to lady novelists. I never hear of one of these creatures discussing a\n" \ +"_menu_ card but I feel a mad desire to drag him off to the bar of\n" \ +"some common east-end public-house and cram a sixpenny dinner down his\n" \ +"throat--beefsteak pudding, fourpence; potatoes, a penny; half a pint of\n" \ +"porter, a penny. The recollection of it (and the mingled fragrance of\n" \ +"beer, tobacco, and roast pork generally leaves a vivid impression) might\n" \ +"induce him to turn up his nose a little less frequently in the future\n" \ +"at everything that is put before him. Then there is that generous party,\n" \ +"the cadger's delight, who is so free with his small change, but who\n" \ +"never thinks of paying his debts. It might teach even him a little\n" \ +"common sense. \"I always give the waiter a shilling. One can't give the\n" \ +"fellow less, you know,\" explained a young government clerk with whom I\n" \ +"was lunching the other day in Regent Street. I agreed with him as to the\n" \ +"utter impossibility of making it elevenpence ha'penny; but at the same\n" \ +"time I resolved to one day decoy him to an eating-house I remembered\n" \ +"near Covent Garden, where the waiter, for the better discharge of his\n" \ +"duties, goes about in his shirt-sleeves--and very dirty sleeves they\n" \ +"are, too, when it gets near the end of the month. I know that waiter.\n" \ +"If my friend gives him anything beyond a penny, the man will insist on\n" \ +"shaking hands with him then and there as a mark of his esteem; of that I\n" \ +"feel sure.\n" \ +"\n" \ +"There have been a good many funny things said and written about\n" \ +"hardupishness, but the reality is not funny, for all that. It is not\n" \ +"funny to have to haggle over pennies. It isn't funny to be thought\n" \ +"mean and stingy. It isn't funny to be shabby and to be ashamed of your\n" \ +"address. No, there is nothing at all funny in poverty--to the poor. It\n" \ +"is hell upon earth to a sensitive man; and many a brave gentleman who\n" \ +"would have faced the labors of Hercules has had his heart broken by its\n" \ +"petty miseries.\n" \ +"\n" \ +"It is not the actual discomforts themselves that are hard to bear.\n" \ +"Who would mind roughing it a bit if that were all it meant? What cared\n" \ +"Robinson Crusoe for a patch on his trousers? Did he wear trousers? I\n" \ +"forget; or did he go about as he does in the pantomimes? What did it\n" \ +"matter to him if his toes did stick out of his boots? and what if\n" \ +"his umbrella was a cotton one, so long as it kept the rain off? His\n" \ +"shabbiness did not trouble him; there was none of his friends round\n" \ +"about to sneer him.\n" \ +"\n" \ +"Being poor is a mere trifle. It is being known to be poor that is the\n" \ +"sting. It is not cold that makes a man without a great-coat hurry along\n" \ +"so quickly. It is not all shame at telling lies--which he knows will\n" \ +"not be believed--that makes him turn so red when he informs you that\n" \ +"he considers great-coats unhealthy and never carries an umbrella on\n" \ +"principle. It is easy enough to say that poverty is no crime. No; if\n" \ +"it were men wouldn't be ashamed of it. It's a blunder, though, and is\n" \ +"punished as such. A poor man is despised the whole world over; despised\n" \ +"as much by a Christian as by a lord, as much by a demagogue as by a\n" \ +"footman, and not all the copy-book maxims ever set for ink stained youth\n" \ +"will make him respected. Appearances are everything, so far as human\n" \ +"opinion goes, and the man who will walk down Piccadilly arm in arm with\n" \ +"the most notorious scamp in London, provided he is a well-dressed one,\n" \ +"will slink up a back street to say a couple of words to a seedy-looking\n" \ +"gentleman. And the seedy-looking gentleman knows this--no one\n" \ +"better--and will go a mile round to avoid meeting an acquaintance. Those\n" \ +"that knew him in his prosperity need never trouble themselves to look\n" \ +"the other way. He is a thousand times more anxious that they should not\n" \ +"see him than they can be; and as to their assistance, there is nothing\n" \ +"he dreads more than the offer of it. All he wants is to be forgotten;\n" \ +"and in this respect he is generally fortunate enough to get what he\n" \ +"wants.\n" \ +"\n" \ +"One becomes used to being hard up, as one becomes used to everything\n" \ +"else, by the help of that wonderful old homeopathic doctor, Time. You\n" \ +"can tell at a glance the difference between the old hand and the novice;\n" \ +"between the case-hardened man who has been used to shift and struggle\n" \ +"for years and the poor devil of a beginner striving to hide his misery,\n" \ +"and in a constant agony of fear lest he should be found out. Nothing\n" \ +"shows this difference more clearly than the way in which each will pawn\n" \ +"his watch. As the poet says somewhere: \"True ease in pawning comes from\n" \ +"art, not chance.\" The one goes into his \"uncle's\" with as much composure\n" \ +"as he would into his tailor's--very likely with more. The assistant is\n" \ +"even civil and attends to him at once, to the great indignation of the\n" \ +"lady in the next box, who, however, sarcastically observes that she\n" \ +"don't mind being kept waiting \"if it is a regular customer.\" Why, from\n" \ +"the pleasant and businesslike manner in which the transaction is carried\n" \ +"out, it might be a large purchase in the three per cents. Yet what a\n" \ +"piece of work a man makes of his first \"pop.\" A boy popping his first\n" \ +"question is confidence itself compared with him. He hangs about outside\n" \ +"the shop until he has succeeded in attracting the attention of all the\n" \ +"loafers in the neighborhood and has aroused strong suspicions in the\n" \ +"mind of the policeman on the beat. At last, after a careful examination\n" \ +"of the contents of the windows, made for the purpose of impressing the\n" \ +"bystanders with the notion that he is going in to purchase a diamond\n" \ +"bracelet or some such trifle, he enters, trying to do so with a careless\n" \ +"swagger, and giving himself really the air of a member of the swell mob.\n" \ +"When inside he speaks in so low a voice as to be perfectly inaudible,\n" \ +"and has to say it all over again. When, in the course of his rambling\n" \ +"conversation about a \"friend\" of his, the word \"lend\" is reached, he is\n" \ +"promptly told to go up the court on the right and take the first door\n" \ +"round the corner. He comes out of the shop with a face that you could\n" \ +"easily light a cigarette at, and firmly under the impression that the\n" \ +"whole population of the district is watching him. When he does get\n" \ +"to the right place he has forgotten his name and address and is in a\n" \ +"general condition of hopeless imbecility. Asked in a severe tone how he\n" \ +"came by \"this,\" he stammers and contradicts himself, and it is only a\n" \ +"miracle if he does not confess to having stolen it that very day. He is\n" \ +"thereupon informed that they don't want anything to do with his sort,\n" \ +"and that he had better get out of this as quickly as possible, which he\n" \ +"does, recollecting nothing more until he finds himself three miles off,\n" \ +"without the slightest knowledge how he got there.\n" \ +"\n" \ +"By the way, how awkward it is, though, having to depend on public-houses\n" \ +"and churches for the time. The former are generally too fast and the\n" \ +"latter too slow. Besides which, your efforts to get a glimpse of\n" \ +"the public house clock from the outside are attended with great\n" \ +"difficulties. If you gently push the swing-door ajar and peer in you\n" \ +"draw upon yourself the contemptuous looks of the barmaid, who at once\n" \ +"puts you down in the same category with area sneaks and cadgers. You\n" \ +"also create a certain amount of agitation among the married portion of\n" \ +"the customers. You don't see the clock because it is behind the door;\n" \ +"and in trying to withdraw quietly you jam your head. The only other\n" \ +"method is to jump up and down outside the window. After this latter\n" \ +"proceeding, however, if you do not bring out a banjo and commence to\n" \ +"sing, the youthful inhabitants of the neighborhood, who have gathered\n" \ +"round in expectation, become disappointed.\n" \ +"\n" \ +"I should like to know, too, by what mysterious law of nature it is that\n" \ +"before you have left your watch \"to be repaired\" half an hour, some one\n" \ +"is sure to stop you in the street and conspicuously ask you the time.\n" \ +"Nobody even feels the slightest curiosity on the subject when you've got\n" \ +"it on.\n" \ +"\n" \ +"Dear old ladies and gentlemen who know nothing about being hard up--and\n" \ +"may they never, bless their gray old heads--look upon the pawn-shop\n" \ +"as the last stage of degradation; but those who know it better (and my\n" \ +"readers have no doubt, noticed this themselves) are often surprised,\n" \ +"like the little boy who dreamed he went to heaven, at meeting so many\n" \ +"people there that they never expected to see. For my part, I think it a\n" \ +"much more independent course than borrowing from friends, and I always\n" \ +"try to impress this upon those of my acquaintance who incline toward\n" \ +"\"wanting a couple of pounds till the day after to-morrow.\" But they\n" \ +"won't all see it. One of them once remarked that he objected to the\n" \ +"principle of the thing. I fancy if he had said it was the interest that\n" \ +"he objected to he would have been nearer the truth: twenty-five per\n" \ +"cent. certainly does come heavy.\n" \ +"\n" \ +"There are degrees in being hard up. We are all hard up, more or\n" \ +"less--most of us more. Some are hard up for a thousand pounds; some for\n" \ +"a shilling. Just at this moment I am hard up myself for a fiver. I only\n" \ +"want it for a day or two. I should be certain of paying it back within a\n" \ +"week at the outside, and if any lady or gentleman among my readers would\n" \ +"kindly lend it me, I should be very much obliged indeed. They could send\n" \ +"it to me under cover to Messrs. Field & Tuer, only, in such case, please\n" \ +"let the envelope be carefully sealed. I would give you my I.O.U. as\n" \ +"security.\n" + +static const char *s_data[N_STREAMS] = { + DATA_0, + DATA_1, + DATA_2, + DATA_3, +}; + +static size_t s_data_sz[N_STREAMS] = { + sizeof(DATA_0) - 1, + sizeof(DATA_1) - 1, + sizeof(DATA_2) - 1, + sizeof(DATA_3) - 1, +}; diff --git a/tests/test_stream.c b/tests/test_stream.c index 07808b3..3ce8771 100644 --- a/tests/test_stream.c +++ b/tests/test_stream.c @@ -205,8 +205,8 @@ read_from_scheduled_packets (lsquic_send_ctl_t *send_ctl, lsquic_stream_id_t str const struct parse_funcs *const pf_local = send_ctl->sc_conn_pub->lconn->cn_pf; unsigned char *p = begin; unsigned char *const end = p + bufsz; - const struct stream_rec *srec; - struct packet_out_srec_iter posi; + const struct frame_rec *frec; + struct packet_out_frec_iter pofi; struct lsquic_packet_out *packet_out; struct stream_frame frame; enum quic_frame_type expected_type; @@ -218,38 +218,38 @@ read_from_scheduled_packets (lsquic_send_ctl_t *send_ctl, lsquic_stream_id_t str expected_type = QUIC_FRAME_STREAM; TAILQ_FOREACH(packet_out, &send_ctl->sc_scheduled_packets, po_next) - for (srec = lsquic_posi_first(&posi, packet_out); srec; - srec = lsquic_posi_next(&posi)) + for (frec = lsquic_pofi_first(&pofi, packet_out); frec; + frec = lsquic_pofi_next(&pofi)) { if (fullcheck) { - assert(srec->sr_frame_type == expected_type); + assert(frec->fe_frame_type == expected_type); if (packet_out->po_packno != 1) { /* First packet may contain two stream frames, do not * check it. */ - assert(!lsquic_posi_next(&posi)); + assert(!lsquic_pofi_next(&pofi)); if (TAILQ_NEXT(packet_out, po_next)) { assert(packet_out->po_data_sz == packet_out->po_n_alloc); - assert(srec->sr_len == packet_out->po_data_sz); + assert(frec->fe_len == packet_out->po_data_sz); } } } - if (srec->sr_frame_type == expected_type && - srec->sr_stream->id == stream_id) + if (frec->fe_frame_type == expected_type && + frec->fe_stream->id == stream_id) { assert(!fin); if (QUIC_FRAME_STREAM == expected_type) - len = pf_local->pf_parse_stream_frame(packet_out->po_data + srec->sr_off, - packet_out->po_data_sz - srec->sr_off, &frame); + len = pf_local->pf_parse_stream_frame(packet_out->po_data + frec->fe_off, + packet_out->po_data_sz - frec->fe_off, &frame); else - len = pf_local->pf_parse_crypto_frame(packet_out->po_data + srec->sr_off, - packet_out->po_data_sz - srec->sr_off, &frame); + len = pf_local->pf_parse_crypto_frame(packet_out->po_data + frec->fe_off, + packet_out->po_data_sz - frec->fe_off, &frame); assert(len > 0); if (QUIC_FRAME_STREAM == expected_type) - assert(frame.stream_id == srec->sr_stream->id); + assert(frame.stream_id == frec->fe_stream->id); else assert(frame.stream_id == ~0ULL); /* Otherwise not enough to copy to: */ @@ -262,7 +262,7 @@ read_from_scheduled_packets (lsquic_send_ctl_t *send_ctl, lsquic_stream_id_t str assert(!fin); fin = 1; } - memcpy(p, packet_out->po_data + srec->sr_off + len - + memcpy(p, packet_out->po_data + frec->fe_off + len - frame.data_frame.df_size, frame.data_frame.df_size); p += frame.data_frame.df_size; } @@ -2366,9 +2366,7 @@ test_window_update1 (void) } -/* Test two: large frame in the middle -- it is the one that is moved out - * into new packet. - */ +/* Test two: large frame in the middle */ static void test_bad_packbits_guess_2 (void) { @@ -2455,8 +2453,8 @@ test_bad_packbits_guess_2 (void) assert(1 == streams[2]->n_unacked); ack_packet(&tobjs.send_ctl, 1); assert(0 == streams[0]->n_unacked); - assert(1 == streams[1]->n_unacked); - assert(0 == streams[2]->n_unacked); + assert(0 == streams[1]->n_unacked); + assert(1 == streams[2]->n_unacked); ack_packet(&tobjs.send_ctl, 2); assert(0 == streams[0]->n_unacked); assert(0 == streams[1]->n_unacked); @@ -2508,7 +2506,7 @@ test_bad_packbits_guess_3 (void) assert(1 == streams[0]->n_unacked); g_ctl_settings.tcs_schedule_stream_packets_immediately = 1; - g_ctl_settings.tcs_calc_packno_bits = GQUIC_PACKNO_LEN_4; + g_ctl_settings.tcs_calc_packno_bits = GQUIC_PACKNO_LEN_6; s = lsquic_send_ctl_schedule_buffered(&tobjs.send_ctl, g_ctl_settings.tcs_bp_type); assert(2 == lsquic_send_ctl_n_scheduled(&tobjs.send_ctl)); @@ -2522,12 +2520,12 @@ test_bad_packbits_guess_3 (void) /* Verify packets */ packet_out = lsquic_send_ctl_next_packet_to_send(&tobjs.send_ctl, 0); - assert(lsquic_packet_out_packno_bits(packet_out) == GQUIC_PACKNO_LEN_4); + assert(lsquic_packet_out_packno_bits(packet_out) == GQUIC_PACKNO_LEN_6); assert(1 == packet_out->po_packno); assert(packet_out->po_frame_types & (1 << QUIC_FRAME_STREAM)); lsquic_send_ctl_sent_packet(&tobjs.send_ctl, packet_out); packet_out = lsquic_send_ctl_next_packet_to_send(&tobjs.send_ctl, 0); - assert(lsquic_packet_out_packno_bits(packet_out) == GQUIC_PACKNO_LEN_4); + assert(lsquic_packet_out_packno_bits(packet_out) == GQUIC_PACKNO_LEN_6); assert(2 == packet_out->po_packno); assert(packet_out->po_frame_types & (1 << QUIC_FRAME_STREAM)); lsquic_send_ctl_sent_packet(&tobjs.send_ctl, packet_out); @@ -2543,6 +2541,128 @@ test_bad_packbits_guess_3 (void) } +/* Test resizing of buffered packets: + * 1. Write data to buffered packets + * 2. Reduce packet size + * 3. Resize buffered packets + * 4. Schedule them + * 5. Check contents + */ +static void +test_resize_buffered (void) +{ + ssize_t nw; + struct test_objs tobjs; + struct lsquic_stream *streams[1]; + const struct parse_funcs *const pf = select_pf_by_ver(LSQVER_ID27); + char buf[0x10000]; + unsigned char buf_out[0x10000]; + int s, fin; + unsigned packet_counts[2]; + + init_buf(buf, sizeof(buf)); + + lsquic_send_ctl_set_max_bpq_count(UINT_MAX); + init_test_ctl_settings(&g_ctl_settings); + g_ctl_settings.tcs_schedule_stream_packets_immediately = 0; + + init_test_objs(&tobjs, 0x100000, 0x100000, pf); + tobjs.send_ctl.sc_flags |= SC_IETF; /* work around asserts lsquic_send_ctl_resize() */ + network_path.np_pack_size = 4096; + streams[0] = new_stream_ext(&tobjs, 7, 0x100000); + + nw = lsquic_stream_write(streams[0], buf, sizeof(buf)); + assert(nw == sizeof(buf)); + s = lsquic_stream_shutdown(streams[0], 1); + assert(s == 0); + packet_counts[0] = tobjs.send_ctl.sc_buffered_packets[g_ctl_settings.tcs_bp_type].bpq_count; + + assert(streams[0]->n_unacked > 0); + + network_path.np_pack_size = 1234; + lsquic_send_ctl_resize(&tobjs.send_ctl); + packet_counts[1] = tobjs.send_ctl.sc_buffered_packets[g_ctl_settings.tcs_bp_type].bpq_count; + assert(packet_counts[1] > packet_counts[0]); + + g_ctl_settings.tcs_schedule_stream_packets_immediately = 1; + s = lsquic_send_ctl_schedule_buffered(&tobjs.send_ctl, + g_ctl_settings.tcs_bp_type); + assert(lsquic_send_ctl_n_scheduled(&tobjs.send_ctl) > 0); + + /* Verify written data: */ + nw = read_from_scheduled_packets(&tobjs.send_ctl, streams[0]->id, buf_out, + sizeof(buf_out), 0, &fin, 0); + assert(nw == sizeof(buf)); + assert(fin); + assert(0 == memcmp(buf, buf_out, nw)); + + lsquic_stream_destroy(streams[0]); + deinit_test_objs(&tobjs); + lsquic_send_ctl_set_max_bpq_count(10); +} + + +/* Test resizing of buffered packets: + * 1. Write data to buffered packets + * 2. Schedule them + * 3. Reduce packet size + * 4. Resize packets + * 5. Check contents + */ +static void +test_resize_scheduled (void) +{ + ssize_t nw; + struct test_objs tobjs; + struct lsquic_stream *streams[1]; + const struct parse_funcs *const pf = select_pf_by_ver(LSQVER_ID27); + char buf[0x10000]; + unsigned char buf_out[0x10000]; + int s, fin; + unsigned packet_counts[2]; + + init_buf(buf, sizeof(buf)); + + lsquic_send_ctl_set_max_bpq_count(UINT_MAX); + init_test_ctl_settings(&g_ctl_settings); + g_ctl_settings.tcs_schedule_stream_packets_immediately = 0; + + init_test_objs(&tobjs, 0x100000, 0x100000, pf); + tobjs.send_ctl.sc_flags |= SC_IETF; /* work around asserts lsquic_send_ctl_resize() */ + network_path.np_pack_size = 4096; + streams[0] = new_stream_ext(&tobjs, 7, 0x100000); + + nw = lsquic_stream_write(streams[0], buf, sizeof(buf)); + assert(nw == sizeof(buf)); + s = lsquic_stream_shutdown(streams[0], 1); + assert(s == 0); + + assert(streams[0]->n_unacked > 0); + + g_ctl_settings.tcs_schedule_stream_packets_immediately = 1; + s = lsquic_send_ctl_schedule_buffered(&tobjs.send_ctl, + g_ctl_settings.tcs_bp_type); + packet_counts[0] = lsquic_send_ctl_n_scheduled(&tobjs.send_ctl); + assert(packet_counts[0] > 0); + + network_path.np_pack_size = 1234; + lsquic_send_ctl_resize(&tobjs.send_ctl); + packet_counts[1] = lsquic_send_ctl_n_scheduled(&tobjs.send_ctl); + assert(packet_counts[1] > packet_counts[0]); + + /* Verify written data: */ + nw = read_from_scheduled_packets(&tobjs.send_ctl, streams[0]->id, buf_out, + sizeof(buf_out), 0, &fin, 0); + assert(nw == sizeof(buf)); + assert(fin); + assert(0 == memcmp(buf, buf_out, nw)); + + lsquic_stream_destroy(streams[0]); + deinit_test_objs(&tobjs); + lsquic_send_ctl_set_max_bpq_count(10); +} + + struct packetization_test_stream_ctx { const unsigned char *buf; @@ -3164,6 +3284,9 @@ main (int argc, char **argv) test_bad_packbits_guess_2(); test_bad_packbits_guess_3(); + test_resize_buffered(); + test_resize_scheduled(); + main_test_packetization(); enum lsquic_version ver;