diff --git a/include/quicly.h b/include/quicly.h index 11beb391..feae0157 100644 --- a/include/quicly.h +++ b/include/quicly.h @@ -166,6 +166,10 @@ QUICLY_CALLBACK_TYPE(void, update_open_count, ssize_t delta); * is complete. */ QUICLY_CALLBACK_TYPE(void, async_handshake, ptls_t *tls); +/** + * + */ +QUICLY_CALLBACK_TYPE(int, qos_is_writing, quicly_conn_t *conn); /** * crypto offload API @@ -417,6 +421,10 @@ struct st_quicly_context_t { * */ quicly_async_handshake_t *async_handshake; + /** + * + */ + quicly_qos_is_writing_t *qos_is_writing; }; /** @@ -597,7 +605,7 @@ struct st_quicly_conn_streamgroup_state_t { uint64_t padding, ping, ack, reset_stream, stop_sending, crypto, new_token, stream, max_data, max_stream_data, \ max_streams_bidi, max_streams_uni, data_blocked, stream_data_blocked, streams_blocked, new_connection_id, \ retire_connection_id, path_challenge, path_response, transport_close, application_close, handshake_done, datagram, \ - ack_frequency; \ + ack_frequency, qs_transport_parameters; \ } num_frames_sent, num_frames_received; \ /** \ * Total number of PTOs observed during the connection. \ @@ -1024,6 +1032,10 @@ static const quicly_cid_t *quicly_get_remote_cid(quicly_conn_t *conn); * */ static const quicly_transport_parameters_t *quicly_get_remote_transport_parameters(quicly_conn_t *conn); +/** + * + */ +int quicly_is_on_streams(quicly_conn_t *conn); /** * */ @@ -1421,6 +1433,10 @@ extern const quicly_stream_callbacks_t quicly_stream_noop_callbacks; }); \ } while (0) +int quicly_qos_send(quicly_conn_t *conn, void *buf, size_t *bufsize); +int quicly_qos_receive(quicly_conn_t *conn, const void *src, size_t *len); +quicly_conn_t *quicly_qos_new(quicly_context_t *ctx, int is_client, void *appdata); + /* inline definitions */ inline int quicly_is_supported_version(uint32_t version) diff --git a/include/quicly/constants.h b/include/quicly/constants.h index 151af360..f6de2406 100644 --- a/include/quicly/constants.h +++ b/include/quicly/constants.h @@ -65,6 +65,8 @@ extern "C" { #define QUICLY_EPOCH_HANDSHAKE 2 #define QUICLY_EPOCH_1RTT 3 #define QUICLY_NUM_EPOCHS 4 +#define QUICLY_EPOCH_ON_STREAMS_TP 4 /* used internally */ +#define QUICLY_EPOCH_ON_STREAMS_OTHER 5 /* coexists with picotls error codes, assuming that int is at least 32-bits */ #define QUICLY_ERROR_IS_QUIC(e) (((e) & ~0x1ffff) == 0x20000) @@ -108,6 +110,7 @@ extern "C" { #define QUICLY_ERROR_STATE_EXHAUSTION 0xff07 #define QUICLY_ERROR_INVALID_INITIAL_VERSION 0xff08 #define QUICLY_ERROR_DECRYPTION_FAILED 0xff09 +#define QUICLY_ERROR_PARTIAL_FRAME 0xff0a typedef int64_t quicly_stream_id_t; diff --git a/include/quicly/frame.h b/include/quicly/frame.h index 6689be6e..e546d8c5 100644 --- a/include/quicly/frame.h +++ b/include/quicly/frame.h @@ -60,6 +60,7 @@ extern "C" { #define QUICLY_FRAME_TYPE_DATAGRAM_NOLEN 48 #define QUICLY_FRAME_TYPE_DATAGRAM_WITHLEN 49 #define QUICLY_FRAME_TYPE_ACK_FREQUENCY 0xaf +#define QUICLY_FRAME_TYPE_QS_TRANSPORT_PARAMETERS 0x3f5153300d0a0d0a #define QUICLY_FRAME_TYPE_STREAM_BITS 0x7 #define QUICLY_FRAME_TYPE_STREAM_BIT_OFF 0x4 @@ -105,7 +106,8 @@ typedef struct st_quicly_stream_frame_t { ptls_iovec_t data; } quicly_stream_frame_t; -static int quicly_decode_stream_frame(uint8_t type_flags, const uint8_t **src, const uint8_t *end, quicly_stream_frame_t *frame); +static int quicly_decode_stream_frame(uint8_t type_flags, uint64_t max_frame_size, const uint8_t **src, const uint8_t *end, + quicly_stream_frame_t *frame); static uint8_t *quicly_encode_crypto_frame_header(uint8_t *dst, uint8_t *dst_end, uint64_t offset, size_t *data_len); static int quicly_decode_crypto_frame(const uint8_t **src, const uint8_t *end, quicly_stream_frame_t *frame); @@ -368,16 +370,17 @@ inline unsigned quicly_clz64(uint64_t v) return v != 0 ? __builtin_clzll(v) : 64; } -inline int quicly_decode_stream_frame(uint8_t type_flags, const uint8_t **src, const uint8_t *end, quicly_stream_frame_t *frame) +inline int quicly_decode_stream_frame(uint8_t type_flags, uint64_t max_frame_size, const uint8_t **src, const uint8_t *end, + quicly_stream_frame_t *frame) { /* obtain stream id */ if ((frame->stream_id = quicly_decodev(src, end)) == UINT64_MAX) - goto Error; + return QUICLY_ERROR_PARTIAL_FRAME; /* obtain offset */ if ((type_flags & QUICLY_FRAME_TYPE_STREAM_BIT_OFF) != 0) { if ((frame->offset = quicly_decodev(src, end)) == UINT64_MAX) - goto Error; + return QUICLY_ERROR_PARTIAL_FRAME; } else { frame->offset = 0; } @@ -386,22 +389,31 @@ inline int quicly_decode_stream_frame(uint8_t type_flags, const uint8_t **src, c if ((type_flags & QUICLY_FRAME_TYPE_STREAM_BIT_LEN) != 0) { uint64_t len; if ((len = quicly_decodev(src, end)) == UINT64_MAX) - goto Error; + return QUICLY_ERROR_PARTIAL_FRAME; if ((uint64_t)(end - *src) < len) - goto Error; + return QUICLY_ERROR_PARTIAL_FRAME; + if (len > max_frame_size) + return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; frame->data = ptls_iovec_init(*src, len); *src += len; } else { - frame->data = ptls_iovec_init(*src, end - *src); - *src = end; + if (max_frame_size == SIZE_MAX) { + /* QUIC v1 */ + frame->data = ptls_iovec_init(*src, end - *src); + *src = end; + } else { + /* QUIC on Streams */ + if ((uint64_t)(end - *src) < max_frame_size) + return QUICLY_ERROR_PARTIAL_FRAME; + frame->data = ptls_iovec_init(*src, max_frame_size); + *src += max_frame_size; + } } /* fin bit */ frame->is_fin = (type_flags & QUICLY_FRAME_TYPE_STREAM_BIT_FIN) != 0; return 0; -Error: - return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; } inline uint8_t *quicly_encode_crypto_frame_header(uint8_t *dst, uint8_t *dst_end, uint64_t offset, size_t *data_len) @@ -470,7 +482,7 @@ inline int quicly_decode_reset_stream_frame(const uint8_t **src, const uint8_t * frame->final_size = quicly_decodev(src, end); return 0; Error: - return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; + return QUICLY_ERROR_PARTIAL_FRAME; } inline int quicly_decode_application_close_frame(const uint8_t **src, const uint8_t *end, quicly_application_close_frame_t *frame) @@ -508,7 +520,7 @@ inline int quicly_decode_transport_close_frame(const uint8_t **src, const uint8_ *src += reason_len; return 0; Error: - return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; + return QUICLY_ERROR_PARTIAL_FRAME; } inline size_t quicly_close_frame_capacity(uint64_t error_code, uint64_t offending_frame_type, const char *reason_phrase) @@ -526,7 +538,7 @@ inline uint8_t *quicly_encode_max_data_frame(uint8_t *dst, uint64_t max_data) inline int quicly_decode_max_data_frame(const uint8_t **src, const uint8_t *end, quicly_max_data_frame_t *frame) { if ((frame->max_data = quicly_decodev(src, end)) == UINT64_MAX) - return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; + return QUICLY_ERROR_PARTIAL_FRAME; return 0; } @@ -546,7 +558,7 @@ inline int quicly_decode_max_stream_data_frame(const uint8_t **src, const uint8_ goto Error; return 0; Error: - return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; + return QUICLY_ERROR_PARTIAL_FRAME; } inline uint8_t *quicly_encode_max_streams_frame(uint8_t *dst, int uni, uint64_t count) @@ -559,7 +571,7 @@ inline uint8_t *quicly_encode_max_streams_frame(uint8_t *dst, int uni, uint64_t inline int quicly_decode_max_streams_frame(const uint8_t **src, const uint8_t *end, quicly_max_streams_frame_t *frame) { if ((frame->count = quicly_decodev(src, end)) == UINT64_MAX) - return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; + return QUICLY_ERROR_PARTIAL_FRAME; if (frame->count > (uint64_t)1 << 60) return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; return 0; @@ -588,7 +600,7 @@ inline uint8_t *quicly_encode_data_blocked_frame(uint8_t *dst, uint64_t offset) inline int quicly_decode_data_blocked_frame(const uint8_t **src, const uint8_t *end, quicly_data_blocked_frame_t *frame) { if ((frame->offset = quicly_decodev(src, end)) == UINT64_MAX) - return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; + return QUICLY_ERROR_PARTIAL_FRAME; return 0; } @@ -622,7 +634,7 @@ inline uint8_t *quicly_encode_streams_blocked_frame(uint8_t *dst, int uni, uint6 inline int quicly_decode_streams_blocked_frame(const uint8_t **src, const uint8_t *end, quicly_streams_blocked_frame_t *frame) { if ((frame->count = quicly_decodev(src, end)) == UINT64_MAX) - return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; + return QUICLY_ERROR_PARTIAL_FRAME; if (frame->count > (uint64_t)1 << 60) return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; return 0; @@ -727,7 +739,7 @@ inline int quicly_decode_stop_sending_frame(const uint8_t **src, const uint8_t * frame->app_error_code = (uint16_t)error_code; return 0; Error: - return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; + return QUICLY_ERROR_PARTIAL_FRAME; } inline size_t quicly_new_token_frame_capacity(ptls_iovec_t token) diff --git a/lib/quicly.c b/lib/quicly.c index c797ef71..44105c3d 100644 --- a/lib/quicly.c +++ b/lib/quicly.c @@ -908,6 +908,11 @@ static int update_max_streams(struct st_quicly_max_streams_t *m, uint64_t count) return 0; } +int quicly_is_on_streams(quicly_conn_t *conn) +{ + return conn->crypto.tls == NULL; +} + int quicly_connection_is_ready(quicly_conn_t *conn) { return conn->application != NULL; @@ -1428,7 +1433,7 @@ static int scheduler_can_send(quicly_conn_t *conn) } /* scheduler would never have data to send, until application keys become available */ - if (conn->application == NULL || conn->application->cipher.egress.key.aead == NULL) + if (!quicly_is_on_streams(conn) && (conn->application == NULL || conn->application->cipher.egress.key.aead == NULL)) return 0; int conn_is_saturated = !(conn->egress.max_data.sent < conn->egress.max_data.permitted); @@ -2018,7 +2023,7 @@ void quicly_free(quicly_conn_t *conn) if (conn->crypto.async_in_progress) { /* When async signature generation is inflight, `ptls_free` will be called from `quicly_resume_handshake` laterwards. */ *ptls_get_data_ptr(conn->crypto.tls) = NULL; - } else { + } else if (conn->crypto.tls != NULL) { ptls_free(conn->crypto.tls); } @@ -2489,6 +2494,36 @@ static int collect_transport_parameters(ptls_t *tls, struct st_ptls_handshake_pr return type == get_transport_parameters_extension_id(conn->super.version); } +static void init_connection_core(quicly_conn_t *conn, int is_client) +{ + conn->super.state = QUICLY_STATE_FIRSTFLIGHT; + conn->super.remote.transport_params = default_transport_params; + if (is_client) { + conn->super.local.bidi.next_stream_id = 0; + conn->super.local.uni.next_stream_id = 2; + conn->super.remote.bidi.next_stream_id = 1; + conn->super.remote.uni.next_stream_id = 3; + } else { + conn->super.local.bidi.next_stream_id = 1; + conn->super.local.uni.next_stream_id = 3; + conn->super.remote.bidi.next_stream_id = 0; + conn->super.remote.uni.next_stream_id = 2; + } + quicly_linklist_init(&conn->super._default_scheduler.active); + quicly_linklist_init(&conn->super._default_scheduler.blocked); + conn->streams = kh_init(quicly_stream_t); + quicly_maxsender_init(&conn->ingress.max_data.sender, conn->super.ctx->transport_params.max_data); + quicly_maxsender_init(&conn->ingress.max_streams.uni, conn->super.ctx->transport_params.max_streams_uni); + quicly_maxsender_init(&conn->ingress.max_streams.bidi, conn->super.ctx->transport_params.max_streams_bidi); + init_max_streams(&conn->egress.max_streams.uni); + init_max_streams(&conn->egress.max_streams.bidi); + quicly_linklist_init(&conn->egress.pending_streams.blocked.uni); + quicly_linklist_init(&conn->egress.pending_streams.blocked.bidi); + quicly_linklist_init(&conn->egress.pending_streams.control); + conn->idle_timeout.at = INT64_MAX; + conn->stash.on_ack_stream.active_acked_cache.stream_id = INT64_MIN; +} + static quicly_conn_t *create_connection(quicly_context_t *ctx, uint32_t protocol_version, const char *server_name, struct sockaddr *remote_addr, struct sockaddr *local_addr, ptls_iovec_t *remote_cid, const quicly_cid_plaintext_t *local_cid, ptls_handshake_properties_t *handshake_properties, @@ -2541,34 +2576,14 @@ static quicly_conn_t *create_connection(quicly_context_t *ctx, uint32_t protocol quicly_remote_cid_init_set(&conn->super.remote.cid_set, remote_cid, ctx->tls->random_bytes); assert(conn->paths[0]->dcid == 0 && conn->super.remote.cid_set.cids[0].sequence == 0 && conn->super.remote.cid_set.cids[0].state == QUICLY_REMOTE_CID_IN_USE && "paths[0].dcid uses cids[0]"); - conn->super.state = QUICLY_STATE_FIRSTFLIGHT; - if (server_name != NULL) { - conn->super.local.bidi.next_stream_id = 0; - conn->super.local.uni.next_stream_id = 2; - conn->super.remote.bidi.next_stream_id = 1; - conn->super.remote.uni.next_stream_id = 3; - } else { - conn->super.local.bidi.next_stream_id = 1; - conn->super.local.uni.next_stream_id = 3; - conn->super.remote.bidi.next_stream_id = 0; - conn->super.remote.uni.next_stream_id = 2; - } - conn->super.remote.transport_params = default_transport_params; conn->super.version = protocol_version; - quicly_linklist_init(&conn->super._default_scheduler.active); - quicly_linklist_init(&conn->super._default_scheduler.blocked); - conn->streams = kh_init(quicly_stream_t); - quicly_maxsender_init(&conn->ingress.max_data.sender, conn->super.ctx->transport_params.max_data); - quicly_maxsender_init(&conn->ingress.max_streams.uni, conn->super.ctx->transport_params.max_streams_uni); - quicly_maxsender_init(&conn->ingress.max_streams.bidi, conn->super.ctx->transport_params.max_streams_bidi); + init_connection_core(conn, server_name != NULL); quicly_loss_init(&conn->egress.loss, &conn->super.ctx->loss, conn->super.ctx->loss.default_initial_rtt /* FIXME remember initial_rtt in session ticket */, &conn->super.remote.transport_params.max_ack_delay, &conn->super.remote.transport_params.ack_delay_exponent); conn->egress.next_pn_to_skip = calc_next_pn_to_skip(conn->super.ctx->tls, 0, initcwnd, conn->super.ctx->initial_egress_max_udp_payload_size); conn->egress.max_udp_payload_size = conn->super.ctx->initial_egress_max_udp_payload_size; - init_max_streams(&conn->egress.max_streams.uni); - init_max_streams(&conn->egress.max_streams.bidi); conn->egress.ack_frequency.update_at = INT64_MAX; conn->egress.send_ack_at = INT64_MAX; conn->egress.send_probe_at = INT64_MAX; @@ -2579,9 +2594,6 @@ static quicly_conn_t *create_connection(quicly_context_t *ctx, uint32_t protocol } conn->egress.ecn.state = conn->super.ctx->enable_ecn ? QUICLY_ECN_PROBING : QUICLY_ECN_OFF; quicly_retire_cid_init(&conn->egress.retire_cid); - quicly_linklist_init(&conn->egress.pending_streams.blocked.uni); - quicly_linklist_init(&conn->egress.pending_streams.blocked.bidi); - quicly_linklist_init(&conn->egress.pending_streams.control); quicly_ratemeter_init(&conn->egress.ratemeter); conn->egress.try_jumpstart = 1; if (handshake_properties != NULL) { @@ -2594,9 +2606,7 @@ static quicly_conn_t *create_connection(quicly_context_t *ctx, uint32_t protocol } conn->crypto.handshake_properties.collect_extension = collect_transport_parameters; conn->retry_scid.len = UINT8_MAX; - conn->idle_timeout.at = INT64_MAX; conn->idle_timeout.should_rearm_on_send = 1; - conn->stash.on_ack_stream.active_acked_cache.stream_id = INT64_MIN; *ptls_get_data_ptr(tls) = conn; @@ -3393,6 +3403,9 @@ static inline uint64_t calc_amplification_limit_allowance(quicly_conn_t *conn) static size_t calc_send_window(quicly_conn_t *conn, size_t min_bytes_to_send, uint64_t amp_window, uint64_t pacer_window, int restrict_sending) { + if (quicly_is_on_streams(conn)) + return conn->super.ctx->qos_is_writing->cb(conn->super.ctx->qos_is_writing, conn) ? 0 : SIZE_MAX; + uint64_t window = 0; if (restrict_sending) { /* Send min_bytes_to_send on PTO */ @@ -3453,7 +3466,8 @@ int64_t quicly_get_first_timeout(quicly_conn_t *conn) if (conn->egress.pending_flows != 0) { /* crypto streams (as indicated by lower 4 bits) can be sent whenever CWND is available; other flows need application * packet number space */ - if ((conn->application != NULL && conn->application->cipher.egress.key.header_protection != NULL) || + if (quicly_is_on_streams(conn) || + (conn->application != NULL && conn->application->cipher.egress.key.header_protection != NULL) || (conn->egress.pending_flows & 0xf) != 0) at = pacer_at; } @@ -3614,6 +3628,12 @@ struct st_quicly_send_context_t { * if `conn->egress.send_probe_at` should be recalculated */ unsigned recalc_send_probe_at : 1; + /** + * + */ + struct { + quicly_sent_t sent; /* if acked is set to non-NULL, it might be called */ + } on_streams; }; static int commit_send_packet(quicly_conn_t *conn, quicly_send_context_t *s, int coalesced) @@ -3756,6 +3776,7 @@ static int do_allocate_frame(quicly_conn_t *conn, quicly_send_context_t *s, size { int coalescible, ret; + assert(conn->crypto.tls != NULL && "cannot be QoS"); assert((s->current.first_byte & QUICLY_QUIC_BIT) != 0); /* allocate and setup the new packet if necessary */ @@ -3882,11 +3903,36 @@ static int do_allocate_frame(quicly_conn_t *conn, quicly_send_context_t *s, size return 0; } +static int qs_call_acked(quicly_conn_t *conn, quicly_send_context_t *s) +{ + if (s->on_streams.sent.acked == NULL) + return 0; + + static const quicly_sent_packet_t dummy_sent_packet = {}; + int ret = s->on_streams.sent.acked(&conn->egress.loss.sentmap, &dummy_sent_packet, 1, &s->on_streams.sent); + s->on_streams.sent.acked = NULL; + + assert(conn->stash.on_ack_stream.active_acked_cache.stream_id == INT64_MIN); + + return ret; +} + static int allocate_ack_eliciting_frame(quicly_conn_t *conn, quicly_send_context_t *s, size_t min_space, quicly_sent_t **sent, quicly_sent_acked_cb acked) { int ret; + if (conn->crypto.tls == NULL) { + /* QoS */ + if ((ret = qs_call_acked(conn, s)) != 0) + return ret; + if (min_space > s->dst_end - s->dst) + return QUICLY_ERROR_SENDBUF_FULL; + *sent = &s->on_streams.sent; + (*sent)->acked = acked; + return 0; + } + if ((ret = do_allocate_frame(conn, s, min_space, ALLOCATE_FRAME_TYPE_ACK_ELICITING)) != 0) return ret; if ((*sent = quicly_sentmap_allocate(&conn->egress.loss.sentmap, acked)) == NULL) @@ -4158,9 +4204,11 @@ static inline void adjust_stream_frame_layout(uint8_t **dst, uint8_t *const dst_ { size_t space_left = (dst_end - *dst) - *len, len_of_len = quicly_encodev_capacity(*len); - if (**frame_at == QUICLY_FRAME_TYPE_CRYPTO) { - /* CRYPTO frame: adjust payload length to make space for the length field, if necessary. */ + if (**frame_at == QUICLY_FRAME_TYPE_CRYPTO || (**frame_at & QUICLY_FRAME_TYPE_STREAM_BIT_LEN) != 0) { + /* CRYPTO frame or QoS, in which case we always prepend length: adjust payload length to make space for the length field, if + * necessary. */ if (space_left < len_of_len) { + assert(dst_end - *dst >= len_of_len); *len = dst_end - *dst - len_of_len; *wrote_all = 0; } @@ -4217,13 +4265,10 @@ int quicly_send_stream(quicly_stream_t *stream, quicly_send_context_t *s) if (off == stream->sendstate.final_size) { assert(!quicly_sendstate_is_open(&stream->sendstate)); /* special case for emitting FIN only */ - header[0] |= QUICLY_FRAME_TYPE_STREAM_BIT_FIN; + header[0] |= QUICLY_FRAME_TYPE_STREAM_BIT_LEN | QUICLY_FRAME_TYPE_STREAM_BIT_FIN; + hp = quicly_encodev(hp, 0); /* length=0 */ if ((ret = allocate_ack_eliciting_frame(stream->conn, s, hp - header, &sent, on_ack_stream)) != 0) return ret; - if (hp - header != s->dst_end - s->dst) { - header[0] |= QUICLY_FRAME_TYPE_STREAM_BIT_LEN; - *hp++ = 0; /* empty length */ - } memcpy(s->dst, header, hp - header); s->dst += hp - header; len = 0; @@ -4231,6 +4276,8 @@ int quicly_send_stream(quicly_stream_t *stream, quicly_send_context_t *s) is_fin = 1; goto UpdateState; } + if (quicly_is_on_streams(stream->conn)) + header[0] |= QUICLY_FRAME_TYPE_STREAM_BIT_LEN; if ((ret = allocate_ack_eliciting_frame(stream->conn, s, hp - header + 1, &sent, on_ack_stream)) != 0) return ret; dst = s->dst; @@ -5192,6 +5239,29 @@ static int send_other_control_frames(quicly_conn_t *conn, quicly_send_context_t return 0; } +static int do_send_core(quicly_conn_t *conn, quicly_send_context_t *s) +{ + int ret; + + /* send stream-level control frames */ + if ((ret = send_stream_control_frames(conn, s)) != 0) + goto Exit; + /* send STREAM frames */ + if ((ret = conn->super.ctx->stream_scheduler->do_send(conn->super.ctx->stream_scheduler, conn, s)) != 0) + goto Exit; + /* once more, send control frames related to streams, as the state might have changed */ + if ((ret = send_stream_control_frames(conn, s)) != 0) + goto Exit; + if ((conn->egress.pending_flows & QUICLY_PENDING_FLOW_OTHERS_BIT) != 0) { + if ((ret = send_other_control_frames(conn, s)) != 0) + goto Exit; + conn->egress.pending_flows &= ~QUICLY_PENDING_FLOW_OTHERS_BIT; + } + +Exit: + return ret; +} + static int do_send(quicly_conn_t *conn, quicly_send_context_t *s) { int restrict_sending = 0, ack_only = 0, ret; @@ -5376,20 +5446,9 @@ static int do_send(quicly_conn_t *conn, quicly_send_context_t *s) (ret = send_resumption_token(conn, s)) != 0) goto Exit; } - /* send stream-level control frames */ - if ((ret = send_stream_control_frames(conn, s)) != 0) + /* send streams and flow control information */ + if ((ret = do_send_core(conn, s)) != 0) goto Exit; - /* send STREAM frames */ - if ((ret = conn->super.ctx->stream_scheduler->do_send(conn->super.ctx->stream_scheduler, conn, s)) != 0) - goto Exit; - /* once more, send control frames related to streams, as the state might have changed */ - if ((ret = send_stream_control_frames(conn, s)) != 0) - goto Exit; - if ((conn->egress.pending_flows & QUICLY_PENDING_FLOW_OTHERS_BIT) != 0) { - if ((ret = send_other_control_frames(conn, s)) != 0) - goto Exit; - conn->egress.pending_flows &= ~QUICLY_PENDING_FLOW_OTHERS_BIT; - } } /* stream operations might have requested emission of NEW_TOKEN at the tail; if so, try to bundle it */ if ((conn->egress.pending_flows & QUICLY_PENDING_FLOW_NEW_TOKEN_BIT) != 0) { @@ -5823,7 +5882,10 @@ static int handle_stream_frame(quicly_conn_t *conn, struct st_quicly_handle_payl quicly_stream_t *stream; int ret; - if ((ret = quicly_decode_stream_frame(state->frame_type, &state->src, state->end, &frame)) != 0) + if ((ret = quicly_decode_stream_frame( + state->frame_type, + quicly_is_on_streams(conn) ? 16384 /* hard-coded, until TP ID of max_frame_size is defined */ : SIZE_MAX, &state->src, + state->end, &frame)) != 0) return ret; QUICLY_PROBE(QUICTRACE_RECV_STREAM, conn, conn->stash.now, frame.stream_id, frame.offset, frame.data.len, (int)frame.is_fin); if ((ret = quicly_get_or_open_stream(conn, frame.stream_id, &stream)) != 0 || stream == NULL) @@ -6664,8 +6726,35 @@ static int handle_ack_frequency_frame(quicly_conn_t *conn, struct st_quicly_hand return 0; } -static int handle_payload(quicly_conn_t *conn, size_t epoch, size_t path_index, const uint8_t *_src, size_t _len, - uint64_t *offending_frame_type, int *is_ack_only, int *is_probe_only) +static int handle_qs_transport_parameters_frame(quicly_conn_t *conn, struct st_quicly_handle_payload_state_t *state) +{ + uint64_t len; + int ret; + + if ((len = quicly_decodev(&state->src, state->end)) == UINT64_MAX) + return QUICLY_ERROR_PARTIAL_FRAME; + if (state->end - state->src < len) + return QUICLY_ERROR_PARTIAL_FRAME; + if ((ret = quicly_decode_transport_parameter_list(&conn->super.remote.transport_params, NULL, NULL, NULL, NULL, state->src, + state->src + len)) != 0) + return ret; + state->src += len; + + if ((ret = apply_remote_transport_params(conn)) != 0) + return ret; + + state->epoch = QUICLY_EPOCH_ON_STREAMS_OTHER; + + if (conn->super.remote.transport_params.max_streams_uni != 0) + open_blocked_streams(conn, 0); + if (conn->super.remote.transport_params.max_streams_bidi != 0) + open_blocked_streams(conn, 1); + + return 0; +} + +static int handle_payload(quicly_conn_t *conn, size_t _epoch, size_t path_index, const uint8_t *_src, size_t _len, + size_t *decoded_len, uint64_t *offending_frame_type, int *is_ack_only, int *is_probe_only) { /* clang-format off */ @@ -6677,83 +6766,86 @@ static int handle_payload(quicly_conn_t *conn, size_t epoch, size_t path_index, uint8_t probing; /* boolean indicating if the frame is a "probing frame" */ size_t counter_offset; /* offset of corresponding `conn->super.stats.num_frames_received.type` within quicly_conn_t */ } frame_handlers[] = { -#define FRAME(n, i, z, h, o, ae, p) \ +#define FRAME(n, i, z, h, o, qs0, qs1, ae, p) \ { \ handle_##n##_frame, \ - (i << QUICLY_EPOCH_INITIAL) | (z << QUICLY_EPOCH_0RTT) | (h << QUICLY_EPOCH_HANDSHAKE) | (o << QUICLY_EPOCH_1RTT), \ + (i << QUICLY_EPOCH_INITIAL) | (z << QUICLY_EPOCH_0RTT) | (h << QUICLY_EPOCH_HANDSHAKE) | (o << QUICLY_EPOCH_1RTT) | \ + (qs0 << QUICLY_EPOCH_ON_STREAMS_TP) | (qs1 << QUICLY_EPOCH_ON_STREAMS_OTHER), \ ae, \ p, \ offsetof(quicly_conn_t, super.stats.num_frames_received.n) \ } - /* +----------------------+-------------------+---------------+---------+ - * | | permitted epochs | | | - * | frame +----+----+----+----+ ack-eliciting | probing | - * | | IN | 0R | HS | 1R | | | - * +----------------------+----+----+----+----+---------------+---------+ */ - FRAME( padding , 1 , 1 , 1 , 1 , 0 , 1 ), /* 0 */ - FRAME( ping , 1 , 1 , 1 , 1 , 1 , 0 ), - FRAME( ack , 1 , 0 , 1 , 1 , 0 , 0 ), - FRAME( ack , 1 , 0 , 1 , 1 , 0 , 0 ), - FRAME( reset_stream , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( stop_sending , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( crypto , 1 , 0 , 1 , 1 , 1 , 0 ), - FRAME( new_token , 0 , 0 , 0 , 1 , 1 , 0 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), /* 8 */ - FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( max_data , 0 , 1 , 0 , 1 , 1 , 0 ), /* 16 */ - FRAME( max_stream_data , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( max_streams_bidi , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( max_streams_uni , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( data_blocked , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( stream_data_blocked , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( streams_blocked , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( streams_blocked , 0 , 1 , 0 , 1 , 1 , 0 ), - FRAME( new_connection_id , 0 , 1 , 0 , 1 , 1 , 1 ), /* 24 */ - FRAME( retire_connection_id , 0 , 0 , 0 , 1 , 1 , 0 ), - FRAME( path_challenge , 0 , 1 , 0 , 1 , 1 , 1 ), - FRAME( path_response , 0 , 0 , 0 , 1 , 1 , 1 ), - FRAME( transport_close , 1 , 1 , 1 , 1 , 0 , 0 ), - FRAME( application_close , 0 , 1 , 0 , 1 , 0 , 0 ), - FRAME( handshake_done , 0, 0 , 0 , 1 , 1 , 0 ), - /* +----------------------+----+----+----+----+---------------+---------+ */ + /* +----------------------+-------------------------------+---------------+---------+ + * | | permitted epochs | | | + * | frame +----+----+----+----+-----+-----+ ack-eliciting | probing | + * | | IN | 0R | HS | 1R | QS0 | QS1 | | | + * +----------------------+----+----+----+----+-----+-----+---------------+---------+ */ + FRAME( padding , 1 , 1 , 1 , 1 , 0 , 1 , 0 , 1 ), /* 0 */ + FRAME( ping , 1 , 1 , 1 , 1 , 0 , 0 , 1 , 0 ), + FRAME( ack , 1 , 0 , 1 , 1 , 0 , 0 , 0 , 0 ), + FRAME( ack , 1 , 0 , 1 , 1 , 0 , 0 , 0 , 0 ), + FRAME( reset_stream , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stop_sending , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( crypto , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 0 ), + FRAME( new_token , 0 , 0 , 0 , 1 , 0 , 0 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), /* 8 */ + FRAME( stream , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( max_data , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), /* 16 */ + FRAME( max_stream_data , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( max_streams_bidi , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( max_streams_uni , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( data_blocked , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream_data_blocked , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( streams_blocked , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( streams_blocked , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( new_connection_id , 0 , 1 , 0 , 1 , 0 , 0 , 1 , 1 ), /* 24 */ + FRAME( retire_connection_id , 0 , 0 , 0 , 1 , 0 , 0 , 1 , 0 ), + FRAME( path_challenge , 0 , 1 , 0 , 1 , 0 , 0 , 1 , 1 ), + FRAME( path_response , 0 , 0 , 0 , 1 , 0 , 0 , 1 , 1 ), + FRAME( transport_close , 1 , 1 , 1 , 1 , 0 , 1 , 0 , 0 ), + FRAME( application_close , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 0 ), + FRAME( handshake_done , 0 , 0 , 0 , 1 , 0 , 0 , 1 , 0 ), + /* +----------------------+----+----+----+----+-----+-----+---------------+---------+ */ #undef FRAME }; static const struct { uint64_t type; struct st_quicly_frame_handler_t _; } ex_frame_handlers[] = { -#define FRAME(uc, lc, i, z, h, o, ae, p) \ +#define FRAME(uc, lc, i, z, h, o, qs0, qs1, ae, p) \ { \ QUICLY_FRAME_TYPE_##uc, \ { \ handle_##lc##_frame, \ - (i << QUICLY_EPOCH_INITIAL) | (z << QUICLY_EPOCH_0RTT) | (h << QUICLY_EPOCH_HANDSHAKE) | (o << QUICLY_EPOCH_1RTT), \ + (i << QUICLY_EPOCH_INITIAL) | (z << QUICLY_EPOCH_0RTT) | (h << QUICLY_EPOCH_HANDSHAKE) | (o << QUICLY_EPOCH_1RTT) | \ + (qs0 << QUICLY_EPOCH_ON_STREAMS_TP) | (qs1 << QUICLY_EPOCH_ON_STREAMS_OTHER), \ ae, \ p, \ offsetof(quicly_conn_t, super.stats.num_frames_received.lc) \ }, \ } - /* +----------------------------------+-------------------+---------------+---------+ - * | frame | permitted epochs | | | - * |------------------+---------------+----+----+----+----+ ack-eliciting | probing | - * | upper-case | lower-case | IN | 0R | HS | 1R | | | - * +------------------+---------------+----+----+----+----+---------------+---------+ */ - FRAME( DATAGRAM_NOLEN , datagram , 0 , 1, 0, 1 , 1 , 0 ), - FRAME( DATAGRAM_WITHLEN , datagram , 0 , 1, 0, 1 , 1 , 0 ), - FRAME( ACK_FREQUENCY , ack_frequency , 0 , 0 , 0 , 1 , 1 , 0 ), - /* +------------------+---------------+-------------------+---------------+---------+ */ + /* +----------------------------------------------------+-------------------------------+---------------+---------+ + * | frame | permitted epochs | | | + * |-------------------------+--------------------------+----+----+----+----+-----+-----+ ack-eliciting | probing | + * | upper-case | lower-case | IN | 0R | HS | 1R | QS0 | QS1 | | | + * +-------------------------+--------------------------+----+----+----+----+-----+-----+---------------+---------+ */ + FRAME( DATAGRAM_NOLEN , datagram , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( DATAGRAM_WITHLEN , datagram , 0 , 1 , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( ACK_FREQUENCY , ack_frequency , 0 , 0 , 0 , 1 , 0 , 0 , 1 , 0 ), + FRAME( QS_TRANSPORT_PARAMETERS , qs_transport_parameters , 0 , 0 , 0 , 0 , 1 , 0 , 1 , 0 ), + /* +-------------------------+--------------------------+----+----+----+----+-----+-----+---------------+---------+ */ #undef FRAME {UINT64_MAX}, }; /* clang-format on */ - struct st_quicly_handle_payload_state_t state = {.epoch = epoch, .path_index = path_index, .src = _src, .end = _src + _len}; + struct st_quicly_handle_payload_state_t state = {.epoch = _epoch, .path_index = path_index, .src = _src, .end = _src + _len}; size_t num_frames_ack_eliciting = 0, num_frames_non_probing = 0; int ret; @@ -6769,7 +6861,7 @@ static int handle_payload(quicly_conn_t *conn, size_t epoch, size_t path_index, if ((state.frame_type = quicly_decodev(&state.src, state.end)) == UINT64_MAX) { state.frame_type = QUICLY_FRAME_TYPE_PADDING; /* we cannot signal the offending frame type when failing to decode the frame type */ - ret = QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; + ret = QUICLY_ERROR_PARTIAL_FRAME; break; } size_t i; @@ -6782,7 +6874,7 @@ static int handle_payload(quicly_conn_t *conn, size_t epoch, size_t path_index, frame_handler = &ex_frame_handlers[i]._; } /* check if frame is allowed, then process */ - if ((frame_handler->permitted_epochs & (1 << epoch)) == 0) { + if ((frame_handler->permitted_epochs & (1 << state.epoch)) == 0) { ret = QUICLY_TRANSPORT_ERROR_PROTOCOL_VIOLATION; break; } @@ -6797,8 +6889,19 @@ static int handle_payload(quicly_conn_t *conn, size_t epoch, size_t path_index, *is_ack_only = num_frames_ack_eliciting == 0; *is_probe_only = num_frames_non_probing == 0; - if (ret != 0) + + if (ret == QUICLY_ERROR_PARTIAL_FRAME && decoded_len == NULL) + ret = QUICLY_TRANSPORT_ERROR_FRAME_ENCODING; + switch (ret) { + case 0: + case QUICLY_ERROR_PARTIAL_FRAME: + if (decoded_len != NULL) + *decoded_len = _len - (state.end - state.src); + break; + default: *offending_frame_type = state.frame_type; + break; + } return ret; } @@ -6922,7 +7025,7 @@ int quicly_accept(quicly_conn_t **conn, quicly_context_t *ctx, struct sockaddr * if (packet->ecn != 0) (*conn)->super.stats.num_packets.received_ecn_counts[get_ecn_index_from_bits(packet->ecn)] += 1; (*conn)->super.stats.num_bytes.received += packet->datagram_size; - if ((ret = handle_payload(*conn, QUICLY_EPOCH_INITIAL, 0, payload.base, payload.len, &offending_frame_type, &is_ack_only, + if ((ret = handle_payload(*conn, QUICLY_EPOCH_INITIAL, 0, payload.base, payload.len, NULL, &offending_frame_type, &is_ack_only, &is_probe_only)) != 0) goto Exit; if ((ret = record_receipt(&(*conn)->initial->super, pn, packet->ecn, 0, (*conn)->stash.now, &(*conn)->egress.send_ack_at, @@ -7203,7 +7306,7 @@ int quicly_receive(quicly_conn_t *conn, struct sockaddr *dest_addr, struct socka } /* handle the payload */ - if ((ret = handle_payload(conn, epoch, path_index, payload.base, payload.len, &offending_frame_type, &is_ack_only, + if ((ret = handle_payload(conn, epoch, path_index, payload.base, payload.len, NULL, &offending_frame_type, &is_ack_only, &is_probe_only)) != 0) goto Exit; if (!is_probe_only && conn->paths[path_index]->probe_only) { @@ -7740,5 +7843,93 @@ void quicly__debug_printf(quicly_conn_t *conn, const char *function, int line, c } } +static int emit_qs_transport_parameters(quicly_conn_t *conn, quicly_send_context_t *s) +{ + quicly_sent_t *sent; + ptls_buffer_t buf; + int ret; + + if ((ret = allocate_ack_eliciting_frame(conn, s, 100, &sent, NULL)) != 0) + return ret; + ptls_buffer_init(&buf, s->dst, 100); + + ptls_buffer_push_quicint(&buf, QUICLY_FRAME_TYPE_QS_TRANSPORT_PARAMETERS); + ptls_buffer_push_block(&buf, -1, { + if ((ret = quicly_encode_transport_parameter_list(&buf, &conn->super.ctx->transport_params, NULL, NULL, NULL, NULL, 0)) != + 0) + goto Exit; + }); + + assert(!buf.is_allocated); + s->dst += buf.off; + +Exit: + return ret; +} + +int quicly_qos_send(quicly_conn_t *conn, void *buf, size_t *bufsize) +{ + quicly_send_context_t s = {.dst = buf, .dst_end = (uint8_t *)buf + *bufsize, .max_datagrams = 1}; + int ret; + + lock_now(conn, 0); + + if (conn->idle_timeout.at <= conn->stash.now) { + conn->super.state = QUICLY_STATE_DRAINING; + destroy_all_streams(conn, 0, 0); + return QUICLY_ERROR_FREE_CONNECTION; + } + + if (conn->super.state == QUICLY_STATE_FIRSTFLIGHT) { + if ((ret = emit_qs_transport_parameters(conn, &s)) != 0) + goto Exit; + conn->super.state = QUICLY_STATE_CONNECTED; + } + if ((ret = do_send_core(conn, &s)) != 0 && ret != QUICLY_ERROR_SENDBUF_FULL) + goto Exit; + if ((ret = qs_call_acked(conn, &s)) != 0) + goto Exit; + +Exit: + if (ret == 0) + *bufsize = s.dst - (uint8_t *)buf; + unlock_now(conn); + return ret; +} + +int quicly_qos_receive(quicly_conn_t *conn, const void *src, size_t *len) +{ + size_t epoch = conn->super.stats.num_frames_received.qs_transport_parameters == 0 ? QUICLY_EPOCH_ON_STREAMS_TP + : QUICLY_EPOCH_ON_STREAMS_OTHER; + uint64_t offending_frame_type = QUICLY_FRAME_TYPE_PADDING; + int is_ack_only, is_probe_only, ret = 0; + + lock_now(conn, 0); + + if (*len != 0) + ret = handle_payload(conn, epoch, 0, src, *len, len, &offending_frame_type, &is_ack_only, &is_probe_only); + + unlock_now(conn); + + return ret; +} + +quicly_conn_t *quicly_qos_new(quicly_context_t *ctx, int is_client, void *appdata) +{ + quicly_conn_t *conn; + + if ((conn = malloc(sizeof(*conn))) == NULL) + return NULL; + + memset(conn, 0, sizeof(*conn)); + conn->super.ctx = ctx; + conn->super.data = appdata; + lock_now(conn, 0); + init_connection_core(conn, is_client); + unlock_now(conn); + + return conn; +} + const uint32_t quicly_supported_versions[] = {QUICLY_PROTOCOL_VERSION_1, QUICLY_PROTOCOL_VERSION_DRAFT29, QUICLY_PROTOCOL_VERSION_DRAFT27, 0}; diff --git a/t/frame.c b/t/frame.c index 2e53fc95..d99f4dea 100644 --- a/t/frame.c +++ b/t/frame.c @@ -177,7 +177,7 @@ static void test_mozquic(void) quicly_stream_frame_t frame; static const char *mess = "\xc5\0\0\0\0\0\0\xb6\x16\x03"; const uint8_t *p = (void *)mess, type_flags = *p++; - quicly_decode_stream_frame(type_flags, &p, p + 9, &frame); + quicly_decode_stream_frame(type_flags, SIZE_MAX, &p, p + 9, &frame); } void test_frame(void) diff --git a/t/test.c b/t/test.c index bc1f5b59..a4b97657 100644 --- a/t/test.c +++ b/t/test.c @@ -740,6 +740,73 @@ static void test_jumpstart_cwnd(void) ok(derive_jumpstart_cwnd(&bounded_max, 250, 1000000, 250) == 80000); } +static void test_on_streams(void) +{ + static char message16k[16384]; + + if (message16k[0] == '\0') { + for (size_t i = 0; i < sizeof(message16k); i += 16) + memcpy(message16k + i, "helloworldhello\n", 16); + } + + quicly_conn_t *cc = quicly_qos_new(&quic_ctx, 1, NULL), *sc = quicly_qos_new(&quic_ctx, 0, NULL); + quicly_stream_t *cs1 = NULL, *cs2 = NULL, *ss1, *ss2; + quicly_streambuf_t *ss1buf, *ss2buf; + char buf[16384]; + size_t bufsize, decoded_len; + int ret; + + bufsize = sizeof(buf); + ret = quicly_qos_send(sc, buf, &bufsize); + ok(ret == 0); + + ret = quicly_qos_receive(cc, buf, &bufsize); + ok(ret == 0); + + ret = quicly_open_stream(cc, &cs1, 0); + ok(ret == 0); + ret = quicly_streambuf_egress_write(cs1, "hello", 5); + ok(ret == 0); + ret = quicly_open_stream(cc, &cs2, 0); + ok(ret == 0); + ret = quicly_streambuf_egress_write(cs2, message16k, sizeof(message16k)); + ok(ret == 0); + + bufsize = sizeof(buf); + ret = quicly_qos_send(cc, buf, &bufsize); + ok(ret == 0); + + decoded_len = bufsize; /* TODO add test for partial frame receive */ + ret = quicly_qos_receive(sc, buf, &decoded_len); + ok(ret == 0); + ok(decoded_len == bufsize); + + ss1 = quicly_get_stream(sc, cs1->stream_id); + ok(ss1 != NULL); + ss1buf = ss1->data; + ok(ss1buf->ingress.off == 5); + ok(memcmp(ss1buf->ingress.base, "hello", 5) == 0); + + ss2 = quicly_get_stream(sc, cs2->stream_id); + ok(ss2 != NULL); + ss2buf = ss2->data; + ok(ss2buf->ingress.off >= sizeof(message16k) - 400); + ok(ss2buf->ingress.off < sizeof(message16k)); + ok(memcmp(ss2buf->ingress.base, message16k, ss2buf->ingress.off) == 0); + + bufsize = sizeof(buf); + ret = quicly_qos_send(cc, buf, &bufsize); + ok(ret == 0); + + decoded_len = bufsize; + ret = quicly_qos_receive(sc, buf, &decoded_len); + ok(ret == 0); + ok(decoded_len == bufsize); + + ok(ss2buf->ingress.off == sizeof(message16k)); + ok(memcmp(ss2buf->ingress.base, message16k, ss2buf->ingress.off) == 0); +} + int main(int argc, char **argv) { static ptls_iovec_t cert; @@ -811,6 +878,7 @@ int main(int argc, char **argv) subtest("ecn-index-from-bits", test_ecn_index_from_bits); subtest("jumpstart-cwnd", test_jumpstart_cwnd); subtest("jumpstart", test_jumpstart); + subtest("on-streams", test_on_streams); return done_testing(); }