Release 2.29.0

- [FEATURE] QUIC and HTTP/3 Internet Draft 34 support and v1 support.
  The latter is turned off by default.
- Drop support for ID-28 and ID-32.
- [BUGFIX] IETF QUIC mini conn receive history (trechist): allow
  unlimited inserts by dropping smallest elements.
- [BUGFIX] gQUIC: set STTL to correct value, issue #226.
- [BUGFIX] Account for poison packet gap when MTU probe was too large.
This commit is contained in:
Dmitri Tikhonov 2021-02-10 08:51:11 -05:00
parent ac0ce07bd0
commit 26e8f082c9
29 changed files with 411 additions and 178 deletions

View file

@ -8,7 +8,7 @@ task:
- cd boringssl
# This is so that both GQUIC and IETF branches build. Just picking
# a known good revision:
- git checkout b117a3a0b7bd11fe6ebd503ec6b45d6b910b41a1
- git checkout a2278d4d2cabe73f6663e3299ea7808edfa306b9
- cmake .
- make
- cd -

View file

@ -32,7 +32,7 @@ before_script:
- cd boringssl
# This is so that both GQUIC and IETF branches build. Just picking
# a known good revision:
- git checkout b117a3a0b7bd11fe6ebd503ec6b45d6b910b41a1
- git checkout a2278d4d2cabe73f6663e3299ea7808edfa306b9
- cmake .
- make
- cd -

View file

@ -1,3 +1,13 @@
2021-02-10
- 2.29.0
- [FEATURE] QUIC and HTTP/3 Internet Draft 34 support and v1 support.
The latter is turned off by default.
- Drop support for ID-28 and ID-32.
- [BUGFIX] IETF QUIC mini conn receive history (trechist): allow
unlimited inserts by dropping smallest elements.
- [BUGFIX] gQUIC: set STTL to correct value, issue #226.
- [BUGFIX] Account for poison packet gap when MTU probe was too large.
2021-02-03
- 2.28.0
- [API] lsquic_ssl_sess_to_resume_info() is the new way to get

View file

@ -17,7 +17,7 @@ COPY ./ /src/lsquic/
RUN git clone https://boringssl.googlesource.com/boringssl && \
cd boringssl && \
git checkout b117a3a0b7bd11fe6ebd503ec6b45d6b910b41a1 && \
git checkout a2278d4d2cabe73f6663e3299ea7808edfa306b9 && \
cmake . && \
make

View file

@ -13,8 +13,9 @@ and HTTP/3 functionality for servers and clients. Most of the code in this
distribution is used in our own products: LiteSpeed Web Server, LiteSpeed ADC,
and OpenLiteSpeed.
Currently supported QUIC versions are Q043, Q046, Q050, ID-27, ID-28, ID-29,
and ID-32. Support for newer versions is added soon after they are released.
Currently supported QUIC versions are v1 (disabled by default until the
QUIC RFC is released); Internet-Draft versions 34, 29, and 27;
and the older "Google" QUIC versions Q043, Q046, an Q050.
Documentation
-------------
@ -48,7 +49,7 @@ You may need to install pre-requisites like zlib and libevent.
2. Use specific BoringSSL version
```
git checkout b117a3a0b7bd11fe6ebd503ec6b45d6b910b41a1
git checkout a2278d4d2cabe73f6663e3299ea7808edfa306b9
```
3. Compile the library

View file

@ -30,7 +30,7 @@ build_script:
cd boringssl
git checkout b117a3a0b7bd11fe6ebd503ec6b45d6b910b41a1
git checkout a2278d4d2cabe73f6663e3299ea7808edfa306b9
cmake -DCMAKE_GENERATOR_PLATFORM=x64 --config Debug -DBUILD_SHARED_LIBS=OFF -DOPENSSL_NO_ASM=1 .

View file

@ -50,17 +50,18 @@ developed by the IETF. Both types are included in a single enum:
IETF QUIC version ID (Internet-Draft) 27; this version is deprecated.
.. member:: LSQVER_ID28
IETF QUIC version ID 28; this version is deprecated.
.. member:: LSQVER_ID29
IETF QUIC version ID 29
.. member:: LSQVER_ID32
.. member:: LSQVER_ID34
IETF QUIC version ID 32
IETF QUIC version ID 34
.. member:: LSQVER_I001
IETF QUIC version 1. (This version is disabled by default until
the QUIC RFC is released).
.. member:: N_LSQVER

View file

@ -24,9 +24,9 @@ copyright = u'2021, LiteSpeed Technologies'
author = u'LiteSpeed Technologies'
# The short X.Y version
version = u'2.28'
version = u'2.29'
# The full version, including alpha/beta/rc tags
release = u'2.28.0'
release = u'2.29.0'
# -- General configuration ---------------------------------------------------

View file

@ -16,9 +16,9 @@ Most of the code in this distribution has been used in our own products
-- `LiteSpeed Web Server`_, `LiteSpeed Web ADC`_, and OpenLiteSpeed_ --
since 2017.
Currently supported QUIC versions are Q043, Q046, Q050, ID-27, ID-28,
ID-29, and ID-32.
Support for newer versions will be added soon after they are released.
Currently supported QUIC versions are v1 (disabled by default until the
QUIC RFC is released); Internet-Draft versions 34, 29, and 27;
and the older "Google" QUIC versions Q043, Q046, an Q050.
LSQUIC is licensed under the `MIT License`_; see LICENSE in the source
distribution for details.

View file

@ -24,7 +24,7 @@ extern "C" {
#endif
#define LSQUIC_MAJOR_VERSION 2
#define LSQUIC_MINOR_VERSION 28
#define LSQUIC_MINOR_VERSION 29
#define LSQUIC_PATCH_VERSION 0
/**
@ -81,20 +81,21 @@ enum lsquic_version
*/
LSQVER_ID27,
/**
* IETF QUIC Draft-28; this version is deprecated.
*/
LSQVER_ID28,
/**
* IETF QUIC Draft-29
*/
LSQVER_ID29,
/**
* IETF QUIC Draft-32
* IETF QUIC Draft-34
*/
LSQVER_ID32,
LSQVER_ID34,
/**
* IETF QUIC v1. Functionally the same as Draft-34, but marked
* experimental for now.
*/
LSQVER_I001,
/**
* Special version to trigger version negotiation.
@ -106,8 +107,8 @@ enum lsquic_version
};
/**
* We currently support versions 43, 46, 50, Draft-27, Draft-28, Draft-29,
* and Draft-32.
* We currently support versions 43, 46, 50, Draft-27, Draft-29, Draft-34,
* and IETF QUIC v1.
* @see lsquic_version
*/
#define LSQUIC_SUPPORTED_VERSIONS ((1 << N_LSQVER) - 1)
@ -118,19 +119,21 @@ enum lsquic_version
#define LSQUIC_FORCED_TCID0_VERSIONS ((1 << LSQVER_046)|(1 << LSQVER_050))
#define LSQUIC_EXPERIMENTAL_VERSIONS ( \
(1 << LSQVER_I001) | \
(1 << LSQVER_VERNEG) | LSQUIC_EXPERIMENTAL_Q098)
#define LSQUIC_DEPRECATED_VERSIONS ((1 << LSQVER_ID27) | (1 << LSQVER_ID28))
#define LSQUIC_DEPRECATED_VERSIONS ((1 << LSQVER_ID27))
#define LSQUIC_GQUIC_HEADER_VERSIONS (1 << LSQVER_043)
#define LSQUIC_IETF_VERSIONS ((1 << LSQVER_ID27) | (1 << LSQVER_ID28) \
#define LSQUIC_IETF_VERSIONS ((1 << LSQVER_ID27) \
| (1 << LSQVER_ID29) \
| (1 << LSQVER_ID32) | (1 << LSQVER_VERNEG))
| (1 << LSQVER_ID34) \
| (1 << LSQVER_I001) | (1 << LSQVER_VERNEG))
#define LSQUIC_IETF_DRAFT_VERSIONS ((1 << LSQVER_ID27) | (1 << LSQVER_ID28) \
#define LSQUIC_IETF_DRAFT_VERSIONS ((1 << LSQVER_ID27) \
| (1 << LSQVER_ID29) \
| (1 << LSQVER_ID32) | (1 << LSQVER_VERNEG))
| (1 << LSQVER_ID34) | (1 << LSQVER_VERNEG))
enum lsquic_hsk_status
{

View file

@ -26,6 +26,12 @@ while (<HEADER>) {
push @all_versions, $1;
push @all_alpns, "h3-$2";
}
if (/^\s*(LSQVER_I(\d{3}))\b/) {
push @all_versions, $1;
if (not grep 'h3' eq $_, @all_alpns) {
push @all_alpns, "h3";
}
}
}
}

View file

@ -339,9 +339,9 @@ extern const struct enc_session_funcs_iquic lsquic_enc_session_iquic_ietf_v1;
#define select_esf_common_by_ver(ver) ( \
ver == LSQVER_ID27 ? &lsquic_enc_session_common_ietf_v1 : \
ver == LSQVER_ID28 ? &lsquic_enc_session_common_ietf_v1 : \
ver == LSQVER_ID29 ? &lsquic_enc_session_common_ietf_v1 : \
ver == LSQVER_ID32 ? &lsquic_enc_session_common_ietf_v1 : \
ver == LSQVER_ID34 ? &lsquic_enc_session_common_ietf_v1 : \
ver == LSQVER_I001 ? &lsquic_enc_session_common_ietf_v1 : \
ver == LSQVER_VERNEG ? &lsquic_enc_session_common_ietf_v1 : \
ver == LSQVER_050 ? &lsquic_enc_session_common_gquic_2 : \
&lsquic_enc_session_common_gquic_1 )

View file

@ -72,10 +72,10 @@ static const struct alpn_map {
const unsigned char *alpn;
} s_h3_alpns[] = {
{ LSQVER_ID27, (unsigned char *) "\x05h3-27", },
{ LSQVER_ID28, (unsigned char *) "\x05h3-28", },
{ LSQVER_ID29, (unsigned char *) "\x05h3-29", },
{ LSQVER_ID32, (unsigned char *) "\x05h3-32", },
{ LSQVER_VERNEG, (unsigned char *) "\x05h3-32", },
{ LSQVER_ID34, (unsigned char *) "\x05h3-34", },
{ LSQVER_I001, (unsigned char *) "\x02h3", },
{ LSQVER_VERNEG, (unsigned char *) "\x05h3-34", },
};
struct enc_sess_iquic;
@ -926,6 +926,10 @@ iquic_esfi_create_client (const char *hostname,
ERR_error_string(ERR_get_error(), errbuf));
goto err;
}
#if BORINGSSL_API_VERSION >= 13
SSL_set_quic_use_legacy_codepoint(enc_sess->esi_ssl,
enc_sess->esi_ver_neg->vn_ver < LSQVER_ID34);
#endif
transpa_len = gen_trans_params(enc_sess, trans_params,
sizeof(trans_params));
@ -1109,6 +1113,7 @@ setup_handshake_keys (struct enc_sess_iquic *enc_sess, const lsquic_cid_t *cid)
struct header_prot *hp;
size_t hsk_secret_sz, key_len;
unsigned cliser, i;
const unsigned char *salt;
unsigned char hsk_secret[EVP_MAX_MD_SIZE];
unsigned char secret[2][SHA256_DIGEST_LENGTH]; /* client, server */
unsigned char key[2][EVP_MAX_KEY_LENGTH];
@ -1131,12 +1136,17 @@ setup_handshake_keys (struct enc_sess_iquic *enc_sess, const lsquic_cid_t *cid)
pair->ykp_thresh = IQUIC_INVALID_PACKNO;
hp = &enc_sess->esi_hsk_hps[ENC_LEV_CLEAR];
if (enc_sess->esi_conn->cn_version < LSQVER_ID29)
salt = HSK_SALT_PRE29;
else if (enc_sess->esi_conn->cn_version < LSQVER_ID34)
salt = HSK_SALT_PRE33;
else
salt = HSK_SALT;
HKDF_extract(hsk_secret, &hsk_secret_sz, md, cid->idbuf, cid->len,
enc_sess->esi_conn->cn_version < LSQVER_ID29
? HSK_SALT_PRE29 : HSK_SALT, HSK_SALT_SZ);
salt, HSK_SALT_SZ);
if (enc_sess->esi_flags & ESI_LOG_SECRETS)
{
LSQ_DEBUG("handshake salt: %s", HEXSTR(HSK_SALT, HSK_SALT_SZ, hexbuf));
LSQ_DEBUG("handshake salt: %s", HEXSTR(salt, HSK_SALT_SZ, hexbuf));
LSQ_DEBUG("handshake secret: %s", HEXSTR(hsk_secret, hsk_secret_sz,
hexbuf));
}
@ -1382,6 +1392,10 @@ iquic_esfi_init_server (enc_session_t *enc_session_p)
ERR_error_string(ERR_get_error(), u.errbuf));
return -1;
}
#if BORINGSSL_API_VERSION >= 13
SSL_set_quic_use_legacy_codepoint(enc_sess->esi_ssl,
enc_sess->esi_conn->cn_version < LSQVER_ID34);
#endif
if (!(SSL_set_quic_method(enc_sess->esi_ssl, &cry_quic_method)))
{
LSQ_INFO("could not set stream method");
@ -3327,6 +3341,9 @@ const unsigned char *const lsquic_retry_key_buf[N_IETF_RETRY_VERSIONS] =
/* [draft-ietf-quic-tls-29] Section 5.8 */
(unsigned char *)
"\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1",
/* [draft-ietf-quic-tls-33] Section 5.8 */
(unsigned char *)
"\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e",
};
@ -3336,6 +3353,8 @@ const unsigned char *const lsquic_retry_nonce_buf[N_IETF_RETRY_VERSIONS] =
(unsigned char *) "\x4d\x16\x11\xd0\x55\x13\xa5\x52\xc5\x87\xd5\x75",
/* [draft-ietf-quic-tls-29] Section 5.8 */
(unsigned char *) "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c",
/* [draft-ietf-quic-tls-33] Section 5.8 */
(unsigned char *) "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb",
};

View file

@ -674,7 +674,7 @@ lsquic_engine_new (unsigned flags,
{
int sz = lsquic_enc_sess_ietf_gen_quic_ctx(
&engine->pub.enp_settings,
i == 0 ? LSQVER_ID27 : LSQVER_ID28,
i == 0 ? LSQVER_ID27 : LSQVER_ID29,
engine->pub.enp_quic_ctx_buf[i],
sizeof(engine->pub.enp_quic_ctx_buf));
if (sz < 0)

View file

@ -3314,7 +3314,7 @@ try_to_begin_migration (struct ietf_full_conn *conn,
return BM_NOT_MIGRATING;
}
if (conn->ifc_conn.cn_version <= LSQVER_ID28 /* Starting with ID-29,
if (conn->ifc_conn.cn_version <= LSQVER_ID27 /* Starting with ID-29,
disable_active_migration TP applies only to the time period during
the handshake. Our client does not migrate during the handshake:
this code runs only after handshake has succeeded. */
@ -7566,16 +7566,16 @@ process_incoming_packet_verneg (struct ietf_full_conn *conn,
*/
if (!verneg_ok(conn))
{
ABORT_ERROR("version negotiation not permitted in this version "
"of QUIC");
ABORT_WITH_FLAG(conn, LSQ_LOG_NOTICE, IFC_ERROR,
"version negotiation not permitted in this version of QUIC");
return -1;
}
versions &= conn->ifc_u.cli.ifcli_ver_neg.vn_supp;
if (0 == versions)
{
ABORT_ERROR("client does not support any of the server-specified "
"versions");
ABORT_WITH_FLAG(conn, LSQ_LOG_NOTICE, IFC_ERROR,
"client does not support any of the server-specified versions");
return -1;
}
@ -7663,13 +7663,12 @@ ietf_full_conn_ci_packet_too_large (struct lsquic_conn *lconn,
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);
lsquic_send_ctl_mtu_not_sent(&conn->ifc_send_ctl, packet_out);
mtu_probe_too_large(conn, packet_out);
}
else
@ -9041,7 +9040,7 @@ on_goaway_server_27 (void *ctx, uint64_t stream_id)
static void
on_goaway_client_28 (void *ctx, uint64_t stream_id)
on_goaway_client_27 (void *ctx, uint64_t stream_id)
{
struct ietf_full_conn *const conn = ctx;
struct lsquic_stream *stream;
@ -9348,30 +9347,7 @@ static const struct hcsi_callbacks hcsi_callbacks_client_27 =
.on_max_push_id = on_max_push_id_client,
.on_settings_frame = on_settings_frame,
.on_setting = on_setting,
.on_goaway = on_goaway_client_28 /* sic */,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_client,
};
static const struct hcsi_callbacks hcsi_callbacks_server_28 =
{
.on_cancel_push = on_cancel_push_server,
.on_max_push_id = on_max_push_id,
.on_settings_frame = on_settings_frame,
.on_setting = on_setting,
.on_goaway = on_goaway_server /* sic */,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_server,
};
static const struct hcsi_callbacks hcsi_callbacks_client_28 =
{
.on_cancel_push = on_cancel_push_client,
.on_max_push_id = on_max_push_id_client,
.on_settings_frame = on_settings_frame,
.on_setting = on_setting,
.on_goaway = on_goaway_client_28,
.on_goaway = on_goaway_client_27,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_client,
};
@ -9416,21 +9392,17 @@ hcsi_on_new (void *stream_if_ctx, struct lsquic_stream *stream)
case (1 << 8) | LSQVER_ID27:
callbacks = &hcsi_callbacks_server_27;
break;
case (0 << 8) | LSQVER_ID28:
callbacks = &hcsi_callbacks_client_28;
break;
case (1 << 8) | LSQVER_ID28:
callbacks = &hcsi_callbacks_server_28;
break;
case (0 << 8) | LSQVER_ID29:
case (0 << 8) | LSQVER_ID32:
case (0 << 8) | LSQVER_ID34:
case (0 << 8) | LSQVER_I001:
callbacks = &hcsi_callbacks_client_29;
break;
default:
assert(0);
/* fallthru */
case (1 << 8) | LSQVER_ID29:
case (1 << 8) | LSQVER_ID32:
case (1 << 8) | LSQVER_ID34:
case (1 << 8) | LSQVER_I001:
callbacks = &hcsi_callbacks_server_29;
break;
}

View file

@ -1963,13 +1963,12 @@ gen_rej1_data (struct lsquic_enc_session *enc_session, uint8_t *data,
int len;
EVP_PKEY * rsa_priv_key;
SSL_CTX *ctx = enc_session->ssl_ctx;
const struct lsquic_engine_settings *const settings =
&enc_session->enpub->enp_settings;
hs_ctx_t *const hs_ctx = &enc_session->hs_ctx;
int scfg_len = enc_session->server_config->lsc_scfg->info.scfg_len;
uint8_t *scfg_data = enc_session->server_config->lsc_scfg->scfg;
size_t msg_len;
struct message_writer mw;
uint64_t sttl;
rsa_priv_key = SSL_CTX_get0_privatekey(ctx);
if (!rsa_priv_key)
@ -2029,7 +2028,7 @@ gen_rej1_data (struct lsquic_enc_session *enc_session, uint8_t *data,
MSG_LEN_ADD(msg_len, scfg_len);
MSG_LEN_ADD(msg_len, STK_LENGTH);
MSG_LEN_ADD(msg_len, SNO_LENGTH);
MSG_LEN_ADD(msg_len, sizeof(settings->es_sttl));
MSG_LEN_ADD(msg_len, sizeof(sttl));
MSG_LEN_ADD(msg_len, lsquic_str_len(&hs_ctx->prof));
if (hs_ctx->ccert)
MSG_LEN_ADD(msg_len, hs_ctx->ccert->len);
@ -2055,6 +2054,8 @@ gen_rej1_data (struct lsquic_enc_session *enc_session, uint8_t *data,
lsquic_str_setlen(&enc_session->ssno, SNO_LENGTH);
}
RAND_bytes((uint8_t *) lsquic_str_buf(&enc_session->ssno), SNO_LENGTH);
sttl = enc_session->enpub->enp_server_config->lsc_scfg->info.expy
- (uint64_t) time(NULL);
MW_BEGIN(&mw, QTAG_REJ, 7, data);
MW_WRITE_LS_STR(&mw, QTAG_STK, &enc_session->sstk);
@ -2062,8 +2063,7 @@ gen_rej1_data (struct lsquic_enc_session *enc_session, uint8_t *data,
MW_WRITE_LS_STR(&mw, QTAG_PROF, &hs_ctx->prof);
MW_WRITE_BUFFER(&mw, QTAG_SCFG, scfg_data, scfg_len);
MW_WRITE_BUFFER(&mw, QTAG_RREJ, &hs_ctx->rrej, sizeof(hs_ctx->rrej));
MW_WRITE_BUFFER(&mw, QTAG_STTL, &settings->es_sttl,
sizeof(settings->es_sttl));
MW_WRITE_BUFFER(&mw, QTAG_STTL, &sttl, sizeof(sttl));
if (hs_ctx->ccert)
MW_WRITE_BUFFER(&mw, QTAG_CRT, hs_ctx->ccert->buf, hs_ctx->ccert->len);
MW_END(&mw);

View file

@ -7,9 +7,13 @@
"\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"
#define HSK_SALT_PRE29 ((unsigned char *) HSK_SALT_BUF)
/* [draft-ietf-quic-tls-29] Section 5.2 */
#define HSK_SALT ((unsigned char *) \
#define HSK_SALT_PRE33 ((unsigned char *) \
"\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97" \
"\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99")
/* [draft-ietf-quic-tls-33] Section 5.2 */
#define HSK_SALT ((unsigned char *) \
"\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" \
"\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a")
#define HSK_SALT_SZ (sizeof(HSK_SALT_BUF) - 1)
#define CLIENT_LABEL "client in"

View file

@ -78,6 +78,7 @@ enum http_error_code
HEC_REQUEST_REJECTED = 0x10B,
HEC_REQUEST_CANCELLED = 0x10C,
HEC_REQUEST_INCOMPLETE = 0x10D,
HEC_MESSAGE_ERROR = 0x10E,
HEC_CONNECT_ERROR = 0x10F,
HEC_VERSION_FALLBACK = 0x110,
HEC_QPACK_DECOMPRESSION_FAILED = 0x200,

View file

@ -4,7 +4,7 @@
/* Things specific to the IETF version of QUIC that do not fit anywhere else */
/* [draft-ietf-quic-transport-31] Section 20 */
/* [draft-ietf-quic-transport-33] Section 20 */
enum trans_error_code
{
TEC_NO_ERROR = 0x0,
@ -23,6 +23,7 @@ enum trans_error_code
TEC_CRYPTO_BUFFER_EXCEEDED = 0xD,
TEC_KEY_UPDATE_ERROR = 0xE,
TEC_AEAD_LIMIT_REACHED = 0xF,
TEC_NO_VIABLE_PATH = 0x10,
};
/* Must be at least two */
@ -32,9 +33,12 @@ enum trans_error_code
#define IETF_RETRY_KEY_SZ 16
#define IETF_RETRY_NONCE_SZ 12
#define N_IETF_RETRY_VERSIONS 2
#define N_IETF_RETRY_VERSIONS 3
extern const unsigned char *const lsquic_retry_key_buf[N_IETF_RETRY_VERSIONS];
extern const unsigned char *const lsquic_retry_nonce_buf[N_IETF_RETRY_VERSIONS];
#define lsquic_version_2_retryver(ver_) ((ver_) > LSQVER_ID28)
#define lsquic_version_2_retryver(ver_) ( \
(ver_) <= LSQVER_ID27 ? 0 : \
(ver_) <= LSQVER_ID34 ? 1 : \
2)
#endif

View file

@ -1378,15 +1378,10 @@ imico_switch_to_trechist (struct ietf_mini_conn *conn)
if (conn->imc_recvd_packnos.bitmasks[pns])
{
lsquic_imico_rechist_init(&iter, conn, pns);
if (0 != lsquic_trechist_copy_ranges(&masks[pns],
lsquic_trechist_copy_ranges(&masks[pns],
elems + TRECHIST_MAX_RANGES * pns, &iter,
lsquic_imico_rechist_first,
lsquic_imico_rechist_next))
{
LSQ_WARN("cannot copy ranges from bitmask to trechist");
free(elems);
return -1;
}
lsquic_imico_rechist_next);
}
else
masks[pns] = 0;

View file

@ -336,7 +336,7 @@ lsquic_dcid_from_packet (const unsigned char *buf, size_t bufsz,
/* See [draft-ietf-quic-transport-28], Section 12.4 (Table 3) */
const enum quic_ft_bit lsquic_legal_frames_by_level[N_LSQVER][N_ENC_LEVS] =
{
[LSQVER_ID32] = {
[LSQVER_I001] = {
[ENC_LEV_CLEAR] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
| QUIC_FTBIT_ACK | QUIC_FTBIT_CONNECTION_CLOSE,
[ENC_LEV_EARLY] = QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
@ -346,7 +346,37 @@ const enum quic_ft_bit lsquic_legal_frames_by_level[N_LSQVER][N_ENC_LEVS] =
| QUIC_FTBIT_MAX_STREAMS | QUIC_FTBIT_STREAM_BLOCKED
| QUIC_FTBIT_STREAMS_BLOCKED
| QUIC_FTBIT_NEW_CONNECTION_ID | QUIC_FTBIT_STOP_SENDING
| QUIC_FTBIT_PATH_CHALLENGE
| QUIC_FTBIT_DATAGRAM
| QUIC_FTBIT_RETIRE_CONNECTION_ID,
[ENC_LEV_INIT] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
| QUIC_FTBIT_ACK| QUIC_FTBIT_CONNECTION_CLOSE,
[ENC_LEV_FORW] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
| QUIC_FTBIT_ACK | QUIC_FTBIT_CONNECTION_CLOSE
| QUIC_FTBIT_STREAM | QUIC_FTBIT_RST_STREAM
| QUIC_FTBIT_BLOCKED
| QUIC_FTBIT_MAX_DATA | QUIC_FTBIT_MAX_STREAM_DATA
| QUIC_FTBIT_MAX_STREAMS | QUIC_FTBIT_STREAM_BLOCKED
| QUIC_FTBIT_STREAMS_BLOCKED
| QUIC_FTBIT_NEW_CONNECTION_ID | QUIC_FTBIT_STOP_SENDING
| QUIC_FTBIT_PATH_CHALLENGE | QUIC_FTBIT_PATH_RESPONSE
| QUIC_FTBIT_HANDSHAKE_DONE | QUIC_FTBIT_ACK_FREQUENCY
| QUIC_FTBIT_RETIRE_CONNECTION_ID | QUIC_FTBIT_NEW_TOKEN
| QUIC_FTBIT_TIMESTAMP
| QUIC_FTBIT_DATAGRAM
,
},
[LSQVER_ID34] = {
[ENC_LEV_CLEAR] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
| QUIC_FTBIT_ACK | QUIC_FTBIT_CONNECTION_CLOSE,
[ENC_LEV_EARLY] = QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
| QUIC_FTBIT_STREAM | QUIC_FTBIT_RST_STREAM
| QUIC_FTBIT_BLOCKED | QUIC_FTBIT_CONNECTION_CLOSE
| QUIC_FTBIT_MAX_DATA | QUIC_FTBIT_MAX_STREAM_DATA
| QUIC_FTBIT_MAX_STREAMS | QUIC_FTBIT_STREAM_BLOCKED
| QUIC_FTBIT_STREAMS_BLOCKED
| QUIC_FTBIT_NEW_CONNECTION_ID | QUIC_FTBIT_STOP_SENDING
| QUIC_FTBIT_PATH_CHALLENGE
| QUIC_FTBIT_DATAGRAM
| QUIC_FTBIT_RETIRE_CONNECTION_ID,
[ENC_LEV_INIT] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
@ -396,34 +426,6 @@ const enum quic_ft_bit lsquic_legal_frames_by_level[N_LSQVER][N_ENC_LEVS] =
| QUIC_FTBIT_DATAGRAM
,
},
[LSQVER_ID28] = {
[ENC_LEV_CLEAR] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
| QUIC_FTBIT_ACK | QUIC_FTBIT_CONNECTION_CLOSE,
[ENC_LEV_EARLY] = QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
| QUIC_FTBIT_STREAM | QUIC_FTBIT_RST_STREAM
| QUIC_FTBIT_BLOCKED | QUIC_FTBIT_CONNECTION_CLOSE
| QUIC_FTBIT_MAX_DATA | QUIC_FTBIT_MAX_STREAM_DATA
| QUIC_FTBIT_MAX_STREAMS | QUIC_FTBIT_STREAM_BLOCKED
| QUIC_FTBIT_STREAMS_BLOCKED
| QUIC_FTBIT_NEW_CONNECTION_ID | QUIC_FTBIT_STOP_SENDING
| QUIC_FTBIT_PATH_CHALLENGE | QUIC_FTBIT_PATH_RESPONSE
| QUIC_FTBIT_RETIRE_CONNECTION_ID,
[ENC_LEV_INIT] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
| QUIC_FTBIT_ACK| QUIC_FTBIT_CONNECTION_CLOSE,
[ENC_LEV_FORW] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
| QUIC_FTBIT_ACK | QUIC_FTBIT_CONNECTION_CLOSE
| QUIC_FTBIT_STREAM | QUIC_FTBIT_RST_STREAM
| QUIC_FTBIT_BLOCKED
| QUIC_FTBIT_MAX_DATA | QUIC_FTBIT_MAX_STREAM_DATA
| QUIC_FTBIT_MAX_STREAMS | QUIC_FTBIT_STREAM_BLOCKED
| QUIC_FTBIT_STREAMS_BLOCKED
| QUIC_FTBIT_NEW_CONNECTION_ID | QUIC_FTBIT_STOP_SENDING
| QUIC_FTBIT_PATH_CHALLENGE | QUIC_FTBIT_PATH_RESPONSE
| QUIC_FTBIT_HANDSHAKE_DONE | QUIC_FTBIT_ACK_FREQUENCY
| QUIC_FTBIT_RETIRE_CONNECTION_ID | QUIC_FTBIT_NEW_TOKEN
| QUIC_FTBIT_TIMESTAMP
,
},
[LSQVER_ID27] = {
[ENC_LEV_CLEAR] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING
| QUIC_FTBIT_ACK | QUIC_FTBIT_CONNECTION_CLOSE,

View file

@ -641,6 +641,10 @@ send_ctl_add_poison (struct lsquic_send_ctl *ctl)
{
struct lsquic_packet_out *poison;
/* XXX Allocating the poison packet out of the regular pool can fail.
* This leads to a lot of error checking that could be skipped if we
* did not have to allocate this packet at all.
*/
poison = lsquic_malo_get(ctl->sc_conn_pub->packet_out_malo);
if (!poison)
return -1;
@ -689,6 +693,33 @@ send_ctl_reschedule_poison (struct lsquic_send_ctl *ctl)
}
static int
send_ctl_update_poison_hist (struct lsquic_send_ctl *ctl,
lsquic_packno_t packno)
{
if (packno == ctl->sc_gap + 1)
{
assert(!(ctl->sc_flags & SC_POISON));
lsquic_senhist_add(&ctl->sc_senhist, ctl->sc_gap);
if (0 != send_ctl_add_poison(ctl))
return -1;
}
return 0;
}
void
lsquic_send_ctl_mtu_not_sent (struct lsquic_send_ctl *ctl,
struct lsquic_packet_out *packet_out)
{
(void) /* See comment in send_ctl_add_poison(): the plan is to make
this code path always succeed. */
send_ctl_update_poison_hist(ctl, packet_out->po_packno);
lsquic_senhist_add(&ctl->sc_senhist, packet_out->po_packno);
}
int
lsquic_send_ctl_sent_packet (lsquic_send_ctl_t *ctl,
struct lsquic_packet_out *packet_out)
@ -699,13 +730,8 @@ lsquic_send_ctl_sent_packet (lsquic_send_ctl_t *ctl,
assert(!(packet_out->po_flags & PO_ENCRYPTED));
ctl->sc_last_sent_time = packet_out->po_sent;
pns = lsquic_packet_out_pns(packet_out);
if (packet_out->po_packno == ctl->sc_gap + 1)
{
assert(!(ctl->sc_flags & SC_POISON));
lsquic_senhist_add(&ctl->sc_senhist, ctl->sc_gap);
if (0 != send_ctl_add_poison(ctl))
return -1;
}
if (0 != send_ctl_update_poison_hist(ctl, packet_out->po_packno))
return -1;
LSQ_DEBUG("packet %"PRIu64" has been sent (frame types: %s)",
packet_out->po_packno, lsquic_frame_types_to_str(frames,
sizeof(frames), packet_out->po_frame_types));

View file

@ -155,6 +155,10 @@ lsquic_send_ctl_init (lsquic_send_ctl_t *, struct lsquic_alarmset *,
int
lsquic_send_ctl_sent_packet (lsquic_send_ctl_t *, struct lsquic_packet_out *);
void
lsquic_send_ctl_mtu_not_sent (struct lsquic_send_ctl *ctl,
struct lsquic_packet_out *);
int
lsquic_send_ctl_got_ack (lsquic_send_ctl_t *, const struct ack_info *,
lsquic_time_t, lsquic_time_t);

View file

@ -1473,7 +1473,7 @@ verify_cl_on_fin (struct lsquic_stream *stream)
if (stream->sm_data_in != 0 && stream->sm_cont_len != stream->sm_data_in)
{
lconn = stream->conn_pub->lconn;
lconn->cn_if->ci_abort_error(lconn, 1, HEC_GENERAL_PROTOCOL_ERROR,
lconn->cn_if->ci_abort_error(lconn, 1, HEC_MESSAGE_ERROR,
"number of bytes in DATA frames of stream %"PRIu64" is %llu, "
"while content-length specified of %llu", stream->id,
stream->sm_data_in, stream->sm_cont_len);
@ -4811,7 +4811,7 @@ verify_cl_on_new_data_frame (struct lsquic_stream *stream,
if (stream->sm_data_in > stream->sm_cont_len)
{
lconn = stream->conn_pub->lconn;
lconn->cn_if->ci_abort_error(lconn, 1, HEC_GENERAL_PROTOCOL_ERROR,
lconn->cn_if->ci_abort_error(lconn, 1, HEC_MESSAGE_ERROR,
"number of bytes in DATA frames of stream %"PRIu64" exceeds "
"content-length limit of %llu", stream->id, stream->sm_cont_len);
}

View file

@ -29,12 +29,15 @@ find_free_slot (uint32_t slots)
}
/* Returns 0 on success, 1 if dup, -1 if out of elements */
/* When capacity is reached, smallest element is removed. When the number
* of elements in a single range cannot be represented by te_count, an
* error is returned. This is the only error this function returns.
*/
int
lsquic_trechist_insert (trechist_mask_t *mask, struct trechist_elem *elems,
uint32_t packno)
{
struct trechist_elem *el, *prev;
struct trechist_elem *el, *prev, *cur, *next;
unsigned idx;
if (*mask == 0)
@ -54,17 +57,22 @@ lsquic_trechist_insert (trechist_mask_t *mask, struct trechist_elem *elems,
goto insert_before;
if (packno == el->te_low - 1)
{
if (el->te_count == UCHAR_MAX)
return -1;
--el->te_low;
++el->te_count;
if (el->te_next && el->te_low == TE_HIGH(&elems[el->te_next]) + 1)
if (el->te_next && el->te_low == TE_HIGH(&elems[el->te_next]) + 2)
{
if (el->te_count + elems[el->te_next].te_count - 1 > UCHAR_MAX)
return -1;
*mask &= ~(1u << el->te_next);
el->te_count += elems[el->te_next].te_count;
el->te_count += elems[el->te_next].te_count + 1;
el->te_low = elems[el->te_next].te_low;
el->te_next = elems[el->te_next].te_next;
}
else
{
if (el->te_count == UCHAR_MAX)
return -1;
--el->te_low;
++el->te_count;
}
return 0;
}
if (packno == TE_HIGH(el) + 1)
@ -75,15 +83,18 @@ lsquic_trechist_insert (trechist_mask_t *mask, struct trechist_elem *elems,
return 0;
}
if (packno >= el->te_low && packno <= TE_HIGH(el))
return 1; /* Dup */
return 0; /* Dup */
if (!el->te_next)
break; /* insert tail */
prev = el;
el = &elems[el->te_next];
}
if (*mask == ((1u << TRECHIST_MAX_RANGES) - 1))
return -1;
if (*mask == TRECHIST_MAX_RANGES_MASK)
/* No need to insert element smaller than the smallest element
* already in our list. The new element "overflows".
*/
return 0;
idx = find_free_slot(*mask);
elems[idx].te_low = packno;
@ -95,10 +106,17 @@ lsquic_trechist_insert (trechist_mask_t *mask, struct trechist_elem *elems,
insert_before:
if (*mask == ((1u << TRECHIST_MAX_RANGES) - 1))
return -1;
if (*mask != TRECHIST_MAX_RANGES_MASK)
idx = find_free_slot(*mask);
else
{ /* Drop last element and reuse its slot */
for (next = &elems[el->te_next], cur = el; next->te_next;
cur = next, next = &elems[cur->te_next])
;
idx = cur->te_next;
cur->te_next = 0;
}
idx = find_free_slot(*mask);
*mask |= 1u << idx;;
if (el == elems)
{
@ -159,7 +177,8 @@ lsquic_trechist_next (void *iter_p)
}
int
/* First TRECHIST_MAX_RANGES ranges are copied */
void
lsquic_trechist_copy_ranges (trechist_mask_t *mask,
struct trechist_elem *elems, void *src_rechist,
const struct lsquic_packno_range * (*first) (void *),
@ -182,19 +201,13 @@ lsquic_trechist_copy_ranges (trechist_mask_t *mask,
el->te_next = i + 1;
}
if (!range && el)
{
if (el)
el->te_next = 0;
if (i < 32)
*mask = (1u << i) - 1;
return 0;
}
else if (!el)
{
*mask = 0;
return 0; /* Must have been an empty */
}
else
return -1;
*mask = UINT32_MAX;
}

View file

@ -19,6 +19,7 @@ struct lsquic_packno_range;
* UCHAR_MAX, which is how many different values can fit into te_next.
*/
#define TRECHIST_MAX_RANGES 16
#define TRECHIST_MAX_RANGES_MASK ((1u << TRECHIST_MAX_RANGES) - 1)
struct trechist_elem
{
@ -58,7 +59,7 @@ lsquic_trechist_first (void *iter);
const struct lsquic_packno_range *
lsquic_trechist_next (void *iter);
int
void
lsquic_trechist_copy_ranges (trechist_mask_t *mask /* This gets overwritten */,
struct trechist_elem *elems, void *src_rechist,
const struct lsquic_packno_range * (*first) (void *),

View file

@ -19,9 +19,9 @@ static const unsigned char version_tags[N_LSQVER][4] =
[LSQVER_098] = { 'Q', '0', '9', '8', },
#endif
[LSQVER_ID27] = { 0xFF, 0, 0, 27, },
[LSQVER_ID28] = { 0xFF, 0, 0, 28, },
[LSQVER_ID29] = { 0xFF, 0, 0, 29, },
[LSQVER_ID32] = { 0xFF, 0, 0, 32, },
[LSQVER_ID34] = { 0xFF, 0, 0, 34, },
[LSQVER_I001] = { 0, 0, 0, 1, },
[LSQVER_VERNEG] = { 0xFA, 0xFA, 0xFA, 0xFA, },
};
@ -59,9 +59,9 @@ const char *const lsquic_ver2str[N_LSQVER] = {
[LSQVER_098] = "Q098",
#endif
[LSQVER_ID27] = "FF00001B",
[LSQVER_ID28] = "FF00001C",
[LSQVER_ID29] = "FF00001D",
[LSQVER_ID32] = "FF000020",
[LSQVER_ID34] = "FF000022",
[LSQVER_I001] = "00000001",
[LSQVER_VERNEG] = "FAFAFAFA",
};

View file

@ -1141,9 +1141,9 @@ fuzz_guided_pwritev_testing (const char *input)
case 1: version = LSQVER_046; break;
case 2: version = LSQVER_050; break;
case 3: version = LSQVER_ID27; break;
case 4: version = LSQVER_ID28; break;
case 4: version = LSQVER_ID29; break;
default:
case 5: version = LSQVER_ID29; break;
case 5: version = LSQVER_ID34; break;
}
sched_immed = !!(buf[8] & 0x08);

View file

@ -16,6 +16,32 @@
#include "lsquic_trechist.h"
struct str_range_iter
{
char *str;
struct lsquic_packno_range range;
};
static const struct lsquic_packno_range *
next_str_range (void *ctx)
{
struct str_range_iter *const str_iter = ctx;
if (str_iter->str && str_iter->str[0] == '[')
{
str_iter->range.high = strtoul(str_iter->str + 1, &str_iter->str, 10);
assert('-' == *str_iter->str);
str_iter->range.low = strtoul(str_iter->str + 1, &str_iter->str, 10);
assert(']' == *str_iter->str);
++str_iter->str;
return &str_iter->range;
}
else
return NULL;
}
static void
test_clone (trechist_mask_t src_mask, struct trechist_elem *src_elems)
{
@ -23,14 +49,12 @@ test_clone (trechist_mask_t src_mask, struct trechist_elem *src_elems)
struct trechist_elem *hist_elems;
const struct lsquic_packno_range *ranges[2];
struct trechist_iter iters[2];
int s;
hist_elems = malloc(sizeof(hist_elems[0]) * TRECHIST_MAX_RANGES);
lsquic_trechist_iter(&iters[0], src_mask, src_elems);
s = lsquic_trechist_copy_ranges(&hist_mask, hist_elems, &iters[0],
lsquic_trechist_copy_ranges(&hist_mask, hist_elems, &iters[0],
lsquic_trechist_first, lsquic_trechist_next);
assert(s == 0);
lsquic_trechist_iter(&iters[0], src_mask, src_elems);
lsquic_trechist_iter(&iters[1], hist_mask, hist_elems);
@ -254,10 +278,6 @@ basic_test (void)
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1);
assert(("inserting packet number one is successful", 0 == s));
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1);
assert(("inserting packet number one again results in duplicate error",
s == 1));
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(("first range returned correctly", range));
@ -338,18 +358,168 @@ test_limits (void)
s = lsquic_trechist_insert(&hist_mask, hist_elems, i);
assert(s == -1); /* Overflow */
for (i = 0; i < TRECHIST_MAX_RANGES - 1; ++i)
/* Always successful inserting new entries: */
for (i = 0; i < TRECHIST_MAX_RANGES * 2; ++i)
{
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1000 + 2 * i);
assert(s == 0);
}
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1000 + 2 * i);
assert(s == -1); /* Out of ranges */
/* Always successful inserting new entries in descending order, too: */
for (i = 0; i < TRECHIST_MAX_RANGES * 2; ++i)
{
s = lsquic_trechist_insert(&hist_mask, hist_elems, 10000 - 2 * i);
assert(s == 0);
}
/* Test merge where count exceeds max: */
hist_mask = 0;
lsquic_trechist_copy_ranges(&hist_mask, hist_elems,
& (struct str_range_iter) { .str = "[400-202][200-1]", },
next_str_range, next_str_range);
s = lsquic_trechist_insert(&hist_mask, hist_elems, 201);
assert(s == -1);
free(hist_elems);
}
static void
test_overflow (void)
{
trechist_mask_t mask;
struct trechist_elem *elems;
int s;
char buf[0x1000];
struct str_range_iter str_iter = { .str =
"[395-390]" /* 1 */
"[385-380]"
"[375-370]"
"[365-360]"
"[355-350]" /* 5 */
"[345-340]"
"[335-330]"
"[325-320]"
"[315-310]"
"[305-300]" /* 10 */
"[295-290]"
"[285-280]"
"[275-270]"
"[265-260]"
"[255-250]" /* 15 */
"[245-240]" /* 16 */
"[235-230]" /* Overflow vvvvvv */
"[225-220]"
"[215-210]"
"[205-200]"
"[195-190]"
"[185-180]" };
elems = malloc(sizeof(elems[0]) * TRECHIST_MAX_RANGES);
lsquic_trechist_copy_ranges(&mask, elems, &str_iter, next_str_range,
next_str_range);
rechist2str(mask, elems, buf, sizeof(buf));
assert(0 == strcmp(buf,
"[395-390]" /* 1 */
"[385-380]"
"[375-370]"
"[365-360]"
"[355-350]" /* 5 */
"[345-340]"
"[335-330]"
"[325-320]"
"[315-310]"
"[305-300]" /* 10 */
"[295-290]"
"[285-280]"
"[275-270]"
"[265-260]"
"[255-250]" /* 15 */
"[245-240]" /* 16 */));
s = lsquic_trechist_insert(&mask, elems, 400);
assert(s == 0);
rechist2str(mask, elems, buf, sizeof(buf));
assert(0 == strcmp(buf,
"[400-400]"
"[395-390]"
"[385-380]"
"[375-370]"
"[365-360]"
"[355-350]"
"[345-340]"
"[335-330]"
"[325-320]"
"[315-310]"
"[305-300]"
"[295-290]"
"[285-280]"
"[275-270]"
"[265-260]"
"[255-250]"
));
/* One more for a good measure */
s = lsquic_trechist_insert(&mask, elems, 402);
assert(s == 0);
rechist2str(mask, elems, buf, sizeof(buf));
assert(0 == strcmp(buf,
"[402-402]"
"[400-400]"
"[395-390]"
"[385-380]"
"[375-370]"
"[365-360]"
"[355-350]"
"[345-340]"
"[335-330]"
"[325-320]"
"[315-310]"
"[305-300]"
"[295-290]"
"[285-280]"
"[275-270]"
"[265-260]"
));
s = lsquic_trechist_insert(&mask, elems, 401);
assert(s == 0);
s = lsquic_trechist_insert(&mask, elems, 500);
assert(s == 0);
s = lsquic_trechist_insert(&mask, elems, 200);
assert(s == 0);
s = lsquic_trechist_insert(&mask, elems, 267);
assert(s == 0);
/* One more for a good measure */
s = lsquic_trechist_insert(&mask, elems, 402);
assert(s == 0);
rechist2str(mask, elems, buf, sizeof(buf));
assert(0 == strcmp(buf,
"[500-500]"
"[402-400]"
"[395-390]"
"[385-380]"
"[375-370]"
"[365-360]"
"[355-350]"
"[345-340]"
"[335-330]"
"[325-320]"
"[315-310]"
"[305-300]"
"[295-290]"
"[285-280]"
"[275-270]"
"[267-267]"
));
free(elems);
}
int
main (void)
{
@ -357,6 +527,7 @@ main (void)
test4();
test5();
test_limits();
test_overflow();
return 0;
}