diff --git a/CHANGELOG b/CHANGELOG index c30e52a..efb5757 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +2019-11-22 + - 2.6.7 + - [FEATURE] Implement the QL extension (offered by default). + - [BUGFIX] Abort when encountering unexpected HTTP/3 frames. + - [BUGFIX] Acknowledge (QPACK) HTTP/3 trailers correctly. + - [DEBUG] Turn on debug message for next advisory tick. + 2019-11-20 - 2.6.6 - [BUGFIX] Using HTTP/3 to HTTP/1.x converter. diff --git a/include/lsquic.h b/include/lsquic.h index 62c0e43..9f82f5a 100644 --- a/include/lsquic.h +++ b/include/lsquic.h @@ -25,7 +25,7 @@ extern "C" { #define LSQUIC_MAJOR_VERSION 2 #define LSQUIC_MINOR_VERSION 6 -#define LSQUIC_PATCH_VERSION 6 +#define LSQUIC_PATCH_VERSION 7 /** * Engine flags: @@ -369,6 +369,9 @@ typedef struct ssl_ctx_st * (*lsquic_lookup_cert_f)( /** Allow migration by default */ #define LSQUIC_DF_ALLOW_MIGRATION 1 +/** Use QL loss bits by default */ +#define LSQUIC_DF_QL_BITS 1 + /* 1: Cubic; 2: BBR */ #define LSQUIC_DF_CC_ALGO 2 @@ -736,6 +739,13 @@ struct lsquic_engine_settings { * 2: BBR */ unsigned es_cc_algo; + + /** + * Use QL loss bits. + * + * Default value is @ref LSQUIC_DF_QL_BITS + */ + int es_ql_bits; }; /* Initialize `settings' to default values */ diff --git a/src/liblsquic/lsquic_enc_sess_ietf.c b/src/liblsquic/lsquic_enc_sess_ietf.c index 42adcb9..d581edb 100644 --- a/src/liblsquic/lsquic_enc_sess_ietf.c +++ b/src/liblsquic/lsquic_enc_sess_ietf.c @@ -243,6 +243,7 @@ struct enc_sess_iquic ESI_CACHED_INFO = 1 << 9, ESI_1RTT_ACKED = 1 << 10, ESI_WANT_TICKET = 1 << 11, + ESI_QL_BITS = 1 << 12, } esi_flags; enum evp_aead_direction_t esi_dir[2]; /* client, server */ @@ -333,7 +334,10 @@ apply_hp (struct enc_sess_iquic *enc_sess, hp->hp_gen_mask(enc_sess, hp, cliser, dst + packno_off + 4, mask); LSQ_DEBUG("apply header protection using mask %s", HEXSTR(mask, 5, mask_str)); - dst[0] ^= (0xF | (((dst[0] & 0x80) == 0) << 4)) & mask[0]; + if (enc_sess->esi_flags & ESI_QL_BITS) + dst[0] ^= (0x7 | ((dst[0] >> 7) << 3)) & mask[0]; + else + dst[0] ^= (0xF | (((dst[0] & 0x80) == 0) << 4)) & mask[0]; switch (packno_len) { case 4: @@ -391,7 +395,10 @@ strip_hp (struct enc_sess_iquic *enc_sess, hp->hp_gen_mask(enc_sess, hp, cliser, iv, mask); LSQ_DEBUG("strip header protection using mask %s", HEXSTR(mask, 5, mask_str)); - dst[0] ^= (0xF | (((dst[0] & 0x80) == 0) << 4)) & mask[0]; + if (enc_sess->esi_flags & ESI_QL_BITS) + dst[0] ^= (0x7 | ((dst[0] >> 7) << 3)) & mask[0]; + else + dst[0] ^= (0xF | (((dst[0] & 0x80) == 0) << 4)) & mask[0]; packno = 0; shift = 0; *packno_len = 1 + (dst[0] & 3); @@ -539,6 +546,8 @@ gen_trans_params (struct enc_sess_iquic *enc_sess, unsigned char *buf, - !!(params.tp_flags & (TRAPA_PREFADDR_IPv4|TRAPA_PREFADDR_IPv6)); if (!settings->es_allow_migration) params.tp_disable_active_migration = 1; + if (settings->es_ql_bits) + params.tp_flags |= TRAPA_QL_BITS; len = lsquic_tp_encode(¶ms, buf, bufsz); if (len >= 0) @@ -1467,6 +1476,13 @@ get_peer_transport_params (struct enc_sess_iquic *enc_sess) } } + if ((trans_params->tp_flags & TRAPA_QL_BITS) + && enc_sess->esi_enpub->enp_settings.es_ql_bits) + { + LSQ_DEBUG("enable QL loss bits"); + enc_sess->esi_flags |= ESI_QL_BITS; + } + return 0; } @@ -1978,7 +1994,15 @@ iquic_esf_decrypt_packet (enc_session_t *enc_session_p, goto err; } - if (dst[0] & (0x0C << (packet_in->pi_header_type == HETY_NOT_SET))) + if (enc_sess->esi_flags & ESI_QL_BITS) + { + packet_in->pi_flags |= PI_LOG_QL_BITS; + if (dst[0] & 0x10) + packet_in->pi_flags |= PI_SQUARE_BIT; + if (dst[0] & 0x08) + packet_in->pi_flags |= PI_LOSS_BIT; + } + else if (dst[0] & (0x0C << (packet_in->pi_header_type == HETY_NOT_SET))) { LSQ_DEBUG("reserved bits are not set to zero"); dec_packin = DECPI_VIOLATION; diff --git a/src/liblsquic/lsquic_engine.c b/src/liblsquic/lsquic_engine.c index 7892de9..f9c12ed 100644 --- a/src/liblsquic/lsquic_engine.c +++ b/src/liblsquic/lsquic_engine.c @@ -80,7 +80,7 @@ #include "lsquic_logger.h" #ifndef LSQUIC_DEBUG_NEXT_ADV_TICK -#define LSQUIC_DEBUG_NEXT_ADV_TICK 0 +#define LSQUIC_DEBUG_NEXT_ADV_TICK 1 #endif #define MIN(a, b) ((a) < (b) ? (a) : (b)) @@ -322,6 +322,7 @@ lsquic_engine_init_settings (struct lsquic_engine_settings *settings, settings->es_qpack_enc_max_size = LSQUIC_DF_QPACK_ENC_MAX_SIZE; settings->es_qpack_enc_max_blocked = LSQUIC_DF_QPACK_ENC_MAX_BLOCKED; settings->es_allow_migration = LSQUIC_DF_ALLOW_MIGRATION; + settings->es_ql_bits = LSQUIC_DF_QL_BITS; } diff --git a/src/liblsquic/lsquic_ev_log.c b/src/liblsquic/lsquic_ev_log.c index c664c3d..c4af127 100644 --- a/src/liblsquic/lsquic_ev_log.c +++ b/src/liblsquic/lsquic_ev_log.c @@ -66,13 +66,24 @@ lsquic_ev_log_packet_in (const lsquic_cid_t *cid, (unsigned) (packet_in->pi_data_sz + GQUIC_PACKET_HASH_SZ)); break; default: - LCID("packet in: %"PRIu64", type: %s, size: %u; ecn: %u, spin: %d; " - "path: %hhu", - packet_in->pi_packno, lsquic_hety2str[packet_in->pi_header_type], - (unsigned) (packet_in->pi_data_sz + IQUIC_TAG_LEN), - lsquic_packet_in_ecn(packet_in), - /* spin bit value is only valid for short packet headers */ - lsquic_packet_in_spin_bit(packet_in), packet_in->pi_path_id); + if (packet_in->pi_flags & PI_LOG_QL_BITS) + LCID("packet in: %"PRIu64", type: %s, size: %u; ecn: %u, spin: %d; " + "path: %hhu; Q: %d; L: %d", + packet_in->pi_packno, lsquic_hety2str[packet_in->pi_header_type], + (unsigned) (packet_in->pi_data_sz + IQUIC_TAG_LEN), + lsquic_packet_in_ecn(packet_in), + /* spin bit value is only valid for short packet headers */ + lsquic_packet_in_spin_bit(packet_in), packet_in->pi_path_id, + ((packet_in->pi_flags & PI_SQUARE_BIT) > 0), + ((packet_in->pi_flags & PI_LOSS_BIT) > 0)); + else + LCID("packet in: %"PRIu64", type: %s, size: %u; ecn: %u, spin: %d; " + "path: %hhu", + packet_in->pi_packno, lsquic_hety2str[packet_in->pi_header_type], + (unsigned) (packet_in->pi_data_sz + IQUIC_TAG_LEN), + lsquic_packet_in_ecn(packet_in), + /* spin bit value is only valid for short packet headers */ + lsquic_packet_in_spin_bit(packet_in), packet_in->pi_path_id); break; } } @@ -220,6 +231,27 @@ lsquic_ev_log_packet_sent (const lsquic_cid_t *cid, */ lsquic_frame_types_to_str(frames, sizeof(frames), packet_out->po_frame_types)); + else if (packet_out->po_lflags & POL_LOG_QL_BITS) + LCID("sent packet %"PRIu64", type %s, crypto: %s, size %hu, frame " + "types: %s, ecn: %u, spin: %d; kp: %u, path: %hhu, flags: %u; " + "Q: %u; L: %u", + packet_out->po_packno, lsquic_hety2str[packet_out->po_header_type], + lsquic_enclev2str[ lsquic_packet_out_enc_level(packet_out) ], + packet_out->po_enc_data_sz, + /* Frame types is a list of different frames types contained + * in the packet, no more. Count and order of frames is not + * printed. + */ + lsquic_frame_types_to_str(frames, sizeof(frames), + packet_out->po_frame_types), + lsquic_packet_out_ecn(packet_out), + /* spin bit value is only valid for short packet headers */ + lsquic_packet_out_spin_bit(packet_out), + lsquic_packet_out_kp(packet_out), + packet_out->po_path->np_path_id, + (unsigned) packet_out->po_flags, + lsquic_packet_out_square_bit(packet_out), + lsquic_packet_out_loss_bit(packet_out)); else LCID("sent packet %"PRIu64", type %s, crypto: %s, size %hu, frame " "types: %s, ecn: %u, spin: %d; kp: %u, path: %hhu, flags: %u", diff --git a/src/liblsquic/lsquic_full_conn_ietf.c b/src/liblsquic/lsquic_full_conn_ietf.c index bad4700..ef3f27b 100644 --- a/src/liblsquic/lsquic_full_conn_ietf.c +++ b/src/liblsquic/lsquic_full_conn_ietf.c @@ -2690,6 +2690,13 @@ handshake_ok (struct lsquic_conn *lconn) LSQ_DEBUG("peer transport parameters: %s", (lsquic_tp_to_str(params, buf, sizeof(buf)), buf)); + if ((params->tp_flags & TRAPA_QL_BITS) + && conn->ifc_settings->es_ql_bits) + { + LSQ_DEBUG("turn on QL loss bits"); + lsquic_send_ctl_do_ql_bits(&conn->ifc_send_ctl); + } + if (params->tp_init_max_streams_bidi > (1ull << 60) || params->tp_init_max_streams_uni > (1ull << 60)) { diff --git a/src/liblsquic/lsquic_packet_in.h b/src/liblsquic/lsquic_packet_in.h index 3562719..b21a922 100644 --- a/src/liblsquic/lsquic_packet_in.h +++ b/src/liblsquic/lsquic_packet_in.h @@ -83,7 +83,11 @@ typedef struct lsquic_packet_in #define PIBIT_BITS_SHIFT 12 PI_BITS_BIT_0 = (1 <<12), PI_BITS_BIT_1 = (1 <<13), - } pi_flags:16; + /* Square bit and loss bit flags are used for logging */ + PI_LOG_QL_BITS = (1 <<14), + PI_SQUARE_BIT = (1 <<15), + PI_LOSS_BIT = (1 <<16), + } pi_flags; /* pi_token and pi_token_size are set in Initial and Retry packets */ unsigned short pi_token_size; /* Size of the token */ unsigned char pi_token; /* Offset to token */ diff --git a/src/liblsquic/lsquic_packet_out.h b/src/liblsquic/lsquic_packet_out.h index e9b89a8..6b11e12 100644 --- a/src/liblsquic/lsquic_packet_out.h +++ b/src/liblsquic/lsquic_packet_out.h @@ -143,7 +143,10 @@ typedef struct lsquic_packet_out #define POECN_SHIFT 4 POL_ECNBIT_0 = 1 << 4, POL_ECNBIT_1 = 1 << 5, - } po_lflags:8; + POL_LOG_QL_BITS = 1 << 6, + POL_SQUARE_BIT = 1 << 7, + POL_LOSS_BIT = 1 << 8, + } po_lflags:16; unsigned char *po_data; /* A lot of packets contain data belonging to only one stream. Thus, @@ -199,6 +202,8 @@ typedef struct lsquic_packet_out } while (0) #define lsquic_packet_out_spin_bit(p) (((p)->po_flags & PO_SPIN_BIT) > 0) +#define lsquic_packet_out_square_bit(p) (((p)->po_lflags & POL_SQUARE_BIT) > 0) +#define lsquic_packet_out_loss_bit(p) (((p)->po_lflags & POL_LOSS_BIT) > 0) #define lsquic_packet_out_set_spin_bit(p, b) do { \ (p)->po_flags &= ~PO_SPIN_BIT; \ diff --git a/src/liblsquic/lsquic_parse_ietf_v1.c b/src/liblsquic/lsquic_parse_ietf_v1.c index 80b3c9b..17cbd27 100644 --- a/src/liblsquic/lsquic_parse_ietf_v1.c +++ b/src/liblsquic/lsquic_parse_ietf_v1.c @@ -260,6 +260,8 @@ gen_short_pkt_header (const struct lsquic_conn *lconn, buf[0] = 0x40 | (lsquic_packet_out_spin_bit(packet_out) << 5) + | (lsquic_packet_out_square_bit(packet_out) << 4) + | (lsquic_packet_out_loss_bit(packet_out) << 3) | (lsquic_packet_out_key_phase(packet_out) << 2) | packno_bits ; diff --git a/src/liblsquic/lsquic_qdec_hdl.c b/src/liblsquic/lsquic_qdec_hdl.c index 1c8852c..31847c9 100644 --- a/src/liblsquic/lsquic_qdec_hdl.c +++ b/src/liblsquic/lsquic_qdec_hdl.c @@ -384,10 +384,12 @@ qdh_supply_hset_to_stream (struct qpack_dec_hdl *qdh, struct uncompressed_headers *uh = NULL; const struct lsqpack_header *header; enum lsquic_header_status st; + int push_promise; unsigned i; void *hset; - hset = hset_if->hsi_create_header_set(qdh->qdh_hsi_ctx, 0); + push_promise = lsquic_stream_header_is_pp(stream); + hset = hset_if->hsi_create_header_set(qdh->qdh_hsi_ctx, push_promise); if (!hset) { LSQ_INFO("call to hsi_create_header_set failed"); @@ -440,6 +442,21 @@ qdh_supply_hset_to_stream (struct qpack_dec_hdl *qdh, } +static int +qdh_process_qlist (struct qpack_dec_hdl *qdh, + struct lsquic_stream *stream, struct lsqpack_header_list *qlist) +{ + if (!lsquic_stream_header_is_trailer(stream)) + return qdh_supply_hset_to_stream(qdh, stream, qlist); + else + { + LSQ_DEBUG("discard trailer header set"); + lsqpack_dec_destroy_header_list(qlist); + return 0; + } +} + + static enum lsqpack_read_header_status qdh_header_read_results (struct qpack_dec_hdl *qdh, struct lsquic_stream *stream, enum lsqpack_read_header_status rhs, @@ -452,7 +469,7 @@ qdh_header_read_results (struct qpack_dec_hdl *qdh, { if (qlist) { - if (0 != qdh_supply_hset_to_stream(qdh, stream, qlist)) + if (0 != qdh_process_qlist(qdh, stream, qlist)) return LQRHS_ERROR; if (qdh->qdh_dec_sm_out) { diff --git a/src/liblsquic/lsquic_qdec_hdl.h b/src/liblsquic/lsquic_qdec_hdl.h index ed26c62..141c54d 100644 --- a/src/liblsquic/lsquic_qdec_hdl.h +++ b/src/liblsquic/lsquic_qdec_hdl.h @@ -21,6 +21,7 @@ struct qpack_dec_hdl struct lsquic_conn *qdh_conn; enum { QDH_INITIALIZED = 1 << 0, + QDH_PUSH_PROMISE = 1 << 1, } qdh_flags; struct lsqpack_dec qdh_decoder; struct lsquic_stream *qdh_enc_sm_in; diff --git a/src/liblsquic/lsquic_send_ctl.c b/src/liblsquic/lsquic_send_ctl.c index 1b37bdd..c2240f8 100644 --- a/src/liblsquic/lsquic_send_ctl.c +++ b/src/liblsquic/lsquic_send_ctl.c @@ -765,6 +765,8 @@ send_ctl_handle_lost_packet (lsquic_send_ctl_t *ctl, assert(ctl->sc_n_in_flight_all); packet_sz = packet_out_sent_sz(packet_out); + ++ctl->sc_loss_count; + if (packet_out->po_frame_types & (1 << QUIC_FRAME_ACK)) { ctl->sc_flags |= SC_LOST_ACK_INIT << lsquic_packet_out_pns(packet_out); @@ -1610,6 +1612,25 @@ lsquic_send_ctl_next_packet_to_send (struct lsquic_send_ctl *ctl, size_t size) send_ctl_maybe_zero_pad(ctl, packet_out, size ? size : 1200); } + if (ctl->sc_flags & SC_QL_BITS) + { + packet_out->po_lflags |= POL_LOG_QL_BITS; + if (ctl->sc_loss_count) + { + --ctl->sc_loss_count; + packet_out->po_lflags |= POL_LOSS_BIT; + } + else + packet_out->po_lflags &= ~POL_LOSS_BIT; + if (packet_out->po_header_type == HETY_NOT_SET) + { + if (ctl->sc_square_count++ & 128) + packet_out->po_lflags |= POL_SQUARE_BIT; + else + packet_out->po_lflags &= ~POL_SQUARE_BIT; + } + } + return packet_out; } @@ -1625,6 +1646,8 @@ lsquic_send_ctl_delayed_one (lsquic_send_ctl_t *ctl, #if LSQUIC_SEND_STATS ++ctl->sc_stats.n_delayed; #endif + if (packet_out->po_lflags & POL_LOSS_BIT) + ++ctl->sc_loss_count; } diff --git a/src/liblsquic/lsquic_send_ctl.h b/src/liblsquic/lsquic_send_ctl.h index b0e7b80..c5b167a 100644 --- a/src/liblsquic/lsquic_send_ctl.h +++ b/src/liblsquic/lsquic_send_ctl.h @@ -44,6 +44,7 @@ enum send_ctl_flags { SC_1RTT_ACKED = 1 << 11, SC_APP_LIMITED = 1 << 12, SC_ECN = 1 << 13, + SC_QL_BITS = 1 << 14, }; typedef struct lsquic_send_ctl { @@ -124,6 +125,8 @@ typedef struct lsquic_send_ctl { unsigned sc_retry_count; unsigned sc_rt_count; /* Count round trips */ lsquic_packno_t sc_cur_rt_end; + unsigned sc_loss_count; /* Used to set loss bit */ + unsigned sc_square_count;/* Used to set square bit */ } lsquic_send_ctl_t; void @@ -357,4 +360,8 @@ void lsquic_send_ctl_maybe_app_limited (struct lsquic_send_ctl *, const struct network_path *); +#define lsquic_send_ctl_do_ql_bits(ctl) do { \ + (ctl)->sc_flags |= SC_QL_BITS; \ +} while (0) + #endif diff --git a/src/liblsquic/lsquic_stream.c b/src/liblsquic/lsquic_stream.c index d215d47..a507993 100644 --- a/src/liblsquic/lsquic_stream.c +++ b/src/liblsquic/lsquic_stream.c @@ -3726,8 +3726,9 @@ lsquic_stream_push_info (const lsquic_stream_t *stream, } -int -lsquic_stream_uh_in (lsquic_stream_t *stream, struct uncompressed_headers *uh) +static int +stream_uh_in_gquic (struct lsquic_stream *stream, + struct uncompressed_headers *uh) { if ((stream->sm_bflags & SMBF_USE_HEADERS) && !(stream->stream_flags & STREAM_HAVE_UH)) @@ -3764,6 +3765,68 @@ lsquic_stream_uh_in (lsquic_stream_t *stream, struct uncompressed_headers *uh) } +static int +stream_uh_in_ietf (struct lsquic_stream *stream, + struct uncompressed_headers *uh) +{ + int push_promise; + + push_promise = lsquic_stream_header_is_pp(stream); + if (!(stream->stream_flags & STREAM_HAVE_UH) && !push_promise) + { + SM_HISTORY_APPEND(stream, SHE_HEADERS_IN); + LSQ_DEBUG("received uncompressed headers"); + stream->stream_flags |= STREAM_HAVE_UH; + if (uh->uh_flags & UH_FIN) + { + /* IETF QUIC only sets UH_FIN for a pushed stream on the server to + * mark request as done: + */ + if (stream->sm_bflags & SMBF_IETF) + assert((stream->sm_bflags & SMBF_SERVER) + && lsquic_stream_is_pushed(stream)); + stream->stream_flags |= STREAM_FIN_RECVD|STREAM_HEAD_IN_FIN; + } + stream->uh = uh; + if (uh->uh_oth_stream_id == 0) + { + if (uh->uh_weight) + lsquic_stream_set_priority_internal(stream, uh->uh_weight); + } + else + LSQ_NOTICE("don't know how to depend on stream %"PRIu64, + uh->uh_oth_stream_id); + } + else + { + /* Trailer should never make here, as we discard it in qdh */ + LSQ_DEBUG("discard %s header set", + push_promise ? "push promise" : "trailer"); + if (uh->uh_hset) + stream->conn_pub->enpub->enp_hsi_if + ->hsi_discard_header_set(uh->uh_hset); + free(uh); + } + + return 0; +} + + +int +lsquic_stream_uh_in (lsquic_stream_t *stream, struct uncompressed_headers *uh) +{ + if (stream->sm_bflags & SMBF_USE_HEADERS) + { + if (stream->sm_bflags & SMBF_IETF) + return stream_uh_in_ietf(stream, uh); + else + return stream_uh_in_gquic(stream, uh); + } + else + return -1; +} + + unsigned lsquic_stream_priority (const lsquic_stream_t *stream) { @@ -3961,7 +4024,8 @@ lsquic_stream_set_stream_if (struct lsquic_stream *stream, static int -update_type_hist_and_check (struct hq_filter *filter) +update_type_hist_and_check (const struct lsquic_stream *stream, + struct hq_filter *filter) { /* 3-bit codes: */ enum { @@ -3988,6 +4052,20 @@ update_type_hist_and_check (struct hq_filter *filter) case HQFT_DATA: code = CODE_DATA; break; + case HQFT_PUSH_PROMISE: + case HQFT_DUPLICATE_PUSH: + /* [draft-ietf-quic-http-24], Section 7 */ + if ((stream->id & SIT_MASK) == SIT_BIDI_CLIENT + && !(stream->sm_bflags & SMBF_SERVER)) + return 0; + else + return -1; + case HQFT_CANCEL_PUSH: + case HQFT_SETTINGS: + case HQFT_GOAWAY: + case HQFT_MAX_PUSH_ID: + /* [draft-ietf-quic-http-24], Section 7 */ + return -1; default: /* Ignore unknown frames */ return 0; @@ -4021,6 +4099,21 @@ update_type_hist_and_check (struct hq_filter *filter) } +int +lsquic_stream_header_is_pp (const struct lsquic_stream *stream) +{ + return stream->sm_hq_filter.hqfi_type == HQFT_PUSH_PROMISE; +} + + +int +lsquic_stream_header_is_trailer (const struct lsquic_stream *stream) +{ + return (stream->stream_flags & STREAM_HAVE_UH) + && stream->sm_hq_filter.hqfi_type == HQFT_HEADERS; +} + + static size_t hq_read (void *ctx, const unsigned char *buf, size_t sz, int fin) { @@ -4037,11 +4130,11 @@ hq_read (void *ctx, const unsigned char *buf, size_t sz, int fin) switch (filter->hqfi_state) { case HQFI_STATE_FRAME_HEADER_BEGIN: - filter->hqfi_vint_state.vr2s_state = 0; + filter->hqfi_vint2_state.vr2s_state = 0; filter->hqfi_state = HQFI_STATE_FRAME_HEADER_CONTINUE; /* fall-through */ case HQFI_STATE_FRAME_HEADER_CONTINUE: - s = lsquic_varint_read_two(&p, end, &filter->hqfi_vint_state); + s = lsquic_varint_read_two(&p, end, &filter->hqfi_vint2_state); if (s < 0) break; filter->hqfi_flags |= HQFI_FLAG_BEGIN; @@ -4049,7 +4142,7 @@ hq_read (void *ctx, const unsigned char *buf, size_t sz, int fin) LSQ_DEBUG("HQ frame type 0x%"PRIX64" at offset %"PRIu64", size %"PRIu64, filter->hqfi_type, stream->read_offset + (unsigned) (p - buf), filter->hqfi_left); - if (0 != update_type_hist_and_check(filter)) + if (0 != update_type_hist_and_check(stream, filter)) { lconn = stream->conn_pub->lconn; filter->hqfi_flags |= HQFI_FLAG_ERROR; @@ -4060,37 +4153,23 @@ hq_read (void *ctx, const unsigned char *buf, size_t sz, int fin) stream->id); goto end; } - if (filter->hqfi_type == HQFT_HEADERS) - { - if (0 == (filter->hqfi_flags & HQFI_FLAG_GOT_HEADERS)) - filter->hqfi_flags |= HQFI_FLAG_GOT_HEADERS; - else - { - filter->hqfi_type = (1ull << 62) - 1; - LSQ_DEBUG("Ignoring HEADERS frame"); - } - } if (filter->hqfi_left > 0) { if (filter->hqfi_type == HQFT_DATA) goto end; else if (filter->hqfi_type == HQFT_PUSH_PROMISE) { - lconn = stream->conn_pub->lconn; if (stream->sm_bflags & SMBF_SERVER) + { + lconn = stream->conn_pub->lconn; lconn->cn_if->ci_abort_error(lconn, 1, HEC_FRAME_UNEXPECTED, "Received PUSH_PROMISE frame " "on stream %"PRIu64" (clients are not supposed to " "send those)", stream->id); + goto end; + } else - /* Our client implementation does not support server - * push. - */ - lconn->cn_if->ci_abort_error(lconn, 1, - HEC_FRAME_UNEXPECTED, - "Received PUSH_PROMISE frame (not supported)" - "on stream %"PRIu64, stream->id); - goto end; + filter->hqfi_state = HQFI_STATE_PUSH_ID_BEGIN; } } else @@ -4115,6 +4194,18 @@ hq_read (void *ctx, const unsigned char *buf, size_t sz, int fin) } } break; + case HQFI_STATE_PUSH_ID_BEGIN: + filter->hqfi_vint1_state.pos = 0; + filter->hqfi_state = HQFI_STATE_PUSH_ID_CONTINUE; + /* Fall-through */ + case HQFI_STATE_PUSH_ID_CONTINUE: + prev = p; + s = lsquic_varint_read_nb(&p, end, &filter->hqfi_vint1_state); + filter->hqfi_left -= p - prev; + if (s == 0) + filter->hqfi_state = HQFI_STATE_READING_PAYLOAD; + /* A bit of a white lie here */ + break; case HQFI_STATE_READING_PAYLOAD: if (filter->hqfi_type == HQFT_DATA) goto end; @@ -4124,6 +4215,7 @@ hq_read (void *ctx, const unsigned char *buf, size_t sz, int fin) switch (filter->hqfi_type) { case HQFT_HEADERS: + case HQFT_PUSH_PROMISE: prev = p; if (filter->hqfi_flags & HQFI_FLAG_BEGIN) { diff --git a/src/liblsquic/lsquic_stream.h b/src/liblsquic/lsquic_stream.h index d76c308..033ab84 100644 --- a/src/liblsquic/lsquic_stream.h +++ b/src/liblsquic/lsquic_stream.h @@ -101,12 +101,14 @@ struct stream_hq_frame struct hq_filter { - struct varint_read2_state hqfi_vint_state; + struct varint_read2_state hqfi_vint2_state; /* No need to copy the values: use it directly */ -#define hqfi_left hqfi_vint_state.vr2s_two -#define hqfi_type hqfi_vint_state.vr2s_one +#define hqfi_left hqfi_vint2_state.vr2s_two +#define hqfi_type hqfi_vint2_state.vr2s_one + struct varint_read_state hqfi_vint1_state; +#define hqfi_push_id hqfi_vint1_state.value enum { - HQFI_FLAG_GOT_HEADERS = 1 << 0, + HQFI_FLAG_UNUSED_0 = 1 << 0, HQFI_FLAG_ERROR = 1 << 1, HQFI_FLAG_BEGIN = 1 << 2, HQFI_FLAG_BLOCKED = 1 << 3, @@ -115,6 +117,8 @@ struct hq_filter HQFI_STATE_FRAME_HEADER_BEGIN, HQFI_STATE_FRAME_HEADER_CONTINUE, HQFI_STATE_READING_PAYLOAD, + HQFI_STATE_PUSH_ID_BEGIN, + HQFI_STATE_PUSH_ID_CONTINUE, } hqfi_state:8; unsigned char hqfi_hist_idx; #define MAX_HQFI_ENTRIES (sizeof(unsigned) * 8 / 3) @@ -263,7 +267,7 @@ struct lsquic_stream struct hq_filter sm_hq_filter; /* We can safely use sm_hq_filter */ -#define sm_uni_type_state sm_hq_filter.hqfi_vint_state.vr2s_varint_state +#define sm_uni_type_state sm_hq_filter.hqfi_vint2_state.vr2s_varint_state /** If @ref SMQF_WANT_FLUSH is set, flush until this offset. */ uint64_t sm_flush_to; @@ -590,4 +594,10 @@ lsquic_stream_push_promise (struct lsquic_stream *, struct push_promise *); void lsquic_stream_force_finish (struct lsquic_stream *); +int +lsquic_stream_header_is_pp (const struct lsquic_stream *); + +int +lsquic_stream_header_is_trailer (const struct lsquic_stream *); + #endif diff --git a/src/liblsquic/lsquic_trans_params.c b/src/liblsquic/lsquic_trans_params.c index b49d97f..8197a82 100644 --- a/src/liblsquic/lsquic_trans_params.c +++ b/src/liblsquic/lsquic_trans_params.c @@ -137,6 +137,9 @@ lsquic_tp_encode (const struct transport_params *params, if (params->tp_disable_active_migration != TP_DEF_DISABLE_ACTIVE_MIGRATION) need += 4 + 0; + if (params->tp_flags & TRAPA_QL_BITS) + need += 4 + 0; + if (need > bufsz || need > UINT16_MAX) { errno = ENOBUFS; @@ -256,6 +259,12 @@ lsquic_tp_encode (const struct transport_params *params, return -1; } + if (params->tp_flags & TRAPA_QL_BITS) + { + WRITE_UINT_TO_P(TPI_QL_BITS, 16); + WRITE_UINT_TO_P(0, 16); + } + #if LSQUIC_TEST_QUANTUM_READINESS if (params->tp_flags & TRAPA_QUANTUM_READY) { @@ -332,7 +341,7 @@ lsquic_tp_decode (const unsigned char *const buf, size_t bufsz, set_of_ids |= 1 << param_id; } else - goto unknown; + goto gt32; if (NUMERIC_TRANS_PARAMS & (1u << param_id)) { switch (len) @@ -372,7 +381,7 @@ lsquic_tp_decode (const unsigned char *const buf, size_t bufsz, } else { - switch (param_id) + gt32: switch (param_id) { case TPI_DISABLE_ACTIVE_MIGRATION: EXPECT_LEN(0); @@ -450,8 +459,11 @@ lsquic_tp_decode (const unsigned char *const buf, size_t bufsz, sizeof(params->tp_preferred_address.ipv6_addr))) params->tp_flags |= TRAPA_PREFADDR_IPv6; break; + case TPI_QL_BITS: + EXPECT_LEN(0); + params->tp_flags |= TRAPA_QL_BITS; + break; } - unknown: p += len; } } @@ -536,6 +548,13 @@ lsquic_tp_to_str (const struct transport_params *params, char *buf, size_t sz) return; } } + if (params->tp_flags & TRAPA_QL_BITS) + { + nw = snprintf(buf, end - buf, "; QL loss bits"); + buf += nw; + if (buf >= end) + return; + } #undef SEMICOLON #undef WRITE_ONE_PARAM diff --git a/src/liblsquic/lsquic_trans_params.h b/src/liblsquic/lsquic_trans_params.h index f59e9d6..33673d1 100644 --- a/src/liblsquic/lsquic_trans_params.h +++ b/src/liblsquic/lsquic_trans_params.h @@ -57,6 +57,8 @@ enum trapa_flags #define TPI_QUANTUM_READINESS 3127 TRAPA_QUANTUM_READY = 1 << 5, /* Include "Quantum Readiness" TP */ #endif +#define TPI_QL_BITS 0x1055 /* 1055 is 133t for "loss" */ + TRAPA_QL_BITS = 1 << 6, }; struct transport_params diff --git a/test/test_common.c b/test/test_common.c index db5e64a..3ce1e0d 100644 --- a/test/test_common.c +++ b/test/test_common.c @@ -1703,6 +1703,11 @@ set_engine_option (struct lsquic_engine_settings *settings, settings->es_cc_algo = atoi(val); return 0; } + else if (0 == strncmp(name, "ql_bits", 7)) + { + settings->es_ql_bits = atoi(val); + return 0; + } break; case 8: if (0 == strncmp(name, "max_cfcw", 8))