Frame/parse IMMEDIATE_ACK frame. Does not cause sending of the frame under any circumstances. Protected by FLAGS_quic_reloadable_flag_quic_receive_ack_frequency. PiperOrigin-RevId: 715943023
diff --git a/build/source_list.bzl b/build/source_list.bzl index ac160d7..2467288 100644 --- a/build/source_list.bzl +++ b/build/source_list.bzl
@@ -229,6 +229,7 @@ "quic/core/frames/quic_frame.h", "quic/core/frames/quic_goaway_frame.h", "quic/core/frames/quic_handshake_done_frame.h", + "quic/core/frames/quic_immediate_ack_frame.h", "quic/core/frames/quic_inlined_frame.h", "quic/core/frames/quic_max_streams_frame.h", "quic/core/frames/quic_message_frame.h", @@ -574,6 +575,7 @@ "quic/core/frames/quic_frame.cc", "quic/core/frames/quic_goaway_frame.cc", "quic/core/frames/quic_handshake_done_frame.cc", + "quic/core/frames/quic_immediate_ack_frame.cc", "quic/core/frames/quic_max_streams_frame.cc", "quic/core/frames/quic_message_frame.cc", "quic/core/frames/quic_new_connection_id_frame.cc",
diff --git a/build/source_list.gni b/build/source_list.gni index a8bf15a..68b97d7 100644 --- a/build/source_list.gni +++ b/build/source_list.gni
@@ -229,6 +229,7 @@ "src/quiche/quic/core/frames/quic_frame.h", "src/quiche/quic/core/frames/quic_goaway_frame.h", "src/quiche/quic/core/frames/quic_handshake_done_frame.h", + "src/quiche/quic/core/frames/quic_immediate_ack_frame.h", "src/quiche/quic/core/frames/quic_inlined_frame.h", "src/quiche/quic/core/frames/quic_max_streams_frame.h", "src/quiche/quic/core/frames/quic_message_frame.h", @@ -574,6 +575,7 @@ "src/quiche/quic/core/frames/quic_frame.cc", "src/quiche/quic/core/frames/quic_goaway_frame.cc", "src/quiche/quic/core/frames/quic_handshake_done_frame.cc", + "src/quiche/quic/core/frames/quic_immediate_ack_frame.cc", "src/quiche/quic/core/frames/quic_max_streams_frame.cc", "src/quiche/quic/core/frames/quic_message_frame.cc", "src/quiche/quic/core/frames/quic_new_connection_id_frame.cc",
diff --git a/build/source_list.json b/build/source_list.json index ab414ec..8e88967 100644 --- a/build/source_list.json +++ b/build/source_list.json
@@ -228,6 +228,7 @@ "quiche/quic/core/frames/quic_frame.h", "quiche/quic/core/frames/quic_goaway_frame.h", "quiche/quic/core/frames/quic_handshake_done_frame.h", + "quiche/quic/core/frames/quic_immediate_ack_frame.h", "quiche/quic/core/frames/quic_inlined_frame.h", "quiche/quic/core/frames/quic_max_streams_frame.h", "quiche/quic/core/frames/quic_message_frame.h", @@ -573,6 +574,7 @@ "quiche/quic/core/frames/quic_frame.cc", "quiche/quic/core/frames/quic_goaway_frame.cc", "quiche/quic/core/frames/quic_handshake_done_frame.cc", + "quiche/quic/core/frames/quic_immediate_ack_frame.cc", "quiche/quic/core/frames/quic_max_streams_frame.cc", "quiche/quic/core/frames/quic_message_frame.cc", "quiche/quic/core/frames/quic_new_connection_id_frame.cc",
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h index e617338..37c825a 100755 --- a/quiche/common/quiche_feature_flags_list.h +++ b/quiche/common/quiche_feature_flags_list.h
@@ -55,6 +55,7 @@ QUICHE_FLAG(bool, quiche_reloadable_flag_quic_parse_cert_compression_algos_from_chlo, true, true, "If true, parse offered cert compression algorithms from received CHLOs.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_parse_transport_parameters_from_chlo, false, true, "If true, parse QUIC transport parameters from received CHLOs.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_priority_respect_incremental, false, false, "If true, respect the incremental parameter of each stream in QuicWriteBlockedList.") +QUICHE_FLAG(bool, quiche_reloadable_flag_quic_receive_ack_frequency, false, false, "When true, advertises support for ACK_FREQUENCY and IMMEDIATE_ACK from draft-ietf-quic-ack-frequency-10 and processes them correctly.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_require_handshake_confirmation, true, true, "If true, require handshake confirmation for QUIC connections, functionally disabling 0-rtt handshakes.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_send_placeholder_ticket_when_encrypt_ticket_fails, true, true, "If true, when TicketCrypter fails to encrypt a session ticket, quic::TlsServerHandshaker will send a placeholder ticket, instead of an empty one, to the client.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_stop_reading_also_stops_header_decompression, true, true, "If true, QUIC stream will not continue decompressing buffer headers after StopReading() called.")
diff --git a/quiche/quic/core/chlo_extractor.cc b/quiche/quic/core/chlo_extractor.cc index 94af81d..1a105c5 100644 --- a/quiche/quic/core/chlo_extractor.cc +++ b/quiche/quic/core/chlo_extractor.cc
@@ -15,6 +15,7 @@ #include "quiche/quic/core/crypto/quic_decrypter.h" #include "quiche/quic/core/crypto/quic_encrypter.h" #include "quiche/quic/core/frames/quic_ack_frequency_frame.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/quic_framer.h" #include "quiche/quic/core/quic_types.h" @@ -81,7 +82,8 @@ bool OnPaddingFrame(const QuicPaddingFrame& frame) override; bool OnMessageFrame(const QuicMessageFrame& frame) override; bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override; - bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& farme) override; + bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) override; + bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override; bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override; void OnPacketComplete() override {} bool IsValidStatelessResetToken( @@ -311,6 +313,11 @@ return true; } +bool ChloFramerVisitor::OnImmediateAckFrame( + const QuicImmediateAckFrame& /*frame*/) { + return true; +} + bool ChloFramerVisitor::IsValidStatelessResetToken( const StatelessResetToken& /*token*/) const { return false;
diff --git a/quiche/quic/core/frames/quic_frame.cc b/quiche/quic/core/frames/quic_frame.cc index f798703..3353326 100644 --- a/quiche/quic/core/frames/quic_frame.cc +++ b/quiche/quic/core/frames/quic_frame.cc
@@ -9,6 +9,7 @@ #include <string> #include <utility> +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/frames/quic_new_connection_id_frame.h" #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/frames/quic_retire_connection_id_frame.h" @@ -87,6 +88,9 @@ QuicFrame::QuicFrame(QuicAckFrequencyFrame* frame) : type(ACK_FREQUENCY_FRAME), ack_frequency_frame(frame) {} +QuicFrame::QuicFrame(QuicImmediateAckFrame frame) + : immediate_ack_frame(frame) {} + QuicFrame::QuicFrame(QuicResetStreamAtFrame* frame) : type(RESET_STREAM_AT_FRAME), reset_stream_at_frame(frame) {} @@ -127,6 +131,7 @@ case STOP_SENDING_FRAME: case PATH_CHALLENGE_FRAME: case PATH_RESPONSE_FRAME: + case IMMEDIATE_ACK_FRAME: break; case ACK_FRAME: delete frame->ack_frame; @@ -436,6 +441,9 @@ case ACK_FREQUENCY_FRAME: copy = QuicFrame(new QuicAckFrequencyFrame(*frame.ack_frequency_frame)); break; + case IMMEDIATE_ACK_FRAME: + copy = QuicFrame(QuicImmediateAckFrame(frame.immediate_ack_frame)); + break; case RESET_STREAM_AT_FRAME: copy = QuicFrame(new QuicResetStreamAtFrame(*frame.reset_stream_at_frame)); @@ -542,6 +550,9 @@ case ACK_FREQUENCY_FRAME: os << "type { ACK_FREQUENCY_FRAME } " << *(frame.ack_frequency_frame); break; + case IMMEDIATE_ACK_FRAME: + os << "type { IMMEDIATE_ACK_FRAME } " << frame.immediate_ack_frame; + break; case RESET_STREAM_AT_FRAME: os << "type { RESET_STREAM_AT_FRAME } " << *(frame.reset_stream_at_frame); break;
diff --git a/quiche/quic/core/frames/quic_frame.h b/quiche/quic/core/frames/quic_frame.h index ffb9f77..3485e4e 100644 --- a/quiche/quic/core/frames/quic_frame.h +++ b/quiche/quic/core/frames/quic_frame.h
@@ -18,6 +18,7 @@ #include "quiche/quic/core/frames/quic_crypto_frame.h" #include "quiche/quic/core/frames/quic_goaway_frame.h" #include "quiche/quic/core/frames/quic_handshake_done_frame.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/frames/quic_max_streams_frame.h" #include "quiche/quic/core/frames/quic_message_frame.h" #include "quiche/quic/core/frames/quic_mtu_discovery_frame.h" @@ -65,6 +66,7 @@ explicit QuicFrame(QuicStopSendingFrame frame); explicit QuicFrame(QuicPathChallengeFrame frame); explicit QuicFrame(QuicPathResponseFrame frame); + explicit QuicFrame(QuicImmediateAckFrame immediate_ack_frame); explicit QuicFrame(QuicAckFrame* frame); explicit QuicFrame(QuicRstStreamFrame* frame); @@ -99,6 +101,7 @@ QuicStopSendingFrame stop_sending_frame; QuicPathChallengeFrame path_challenge_frame; QuicPathResponseFrame path_response_frame; + QuicImmediateAckFrame immediate_ack_frame; // Out of line frames. struct {
diff --git a/quiche/quic/core/frames/quic_frames_test.cc b/quiche/quic/core/frames/quic_frames_test.cc index 7179d18..e6a843d 100644 --- a/quiche/quic/core/frames/quic_frames_test.cc +++ b/quiche/quic/core/frames/quic_frames_test.cc
@@ -10,6 +10,7 @@ #include "quiche/quic/core/frames/quic_connection_close_frame.h" #include "quiche/quic/core/frames/quic_frame.h" #include "quiche/quic/core/frames/quic_goaway_frame.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/frames/quic_mtu_discovery_frame.h" #include "quiche/quic/core/frames/quic_new_connection_id_frame.h" #include "quiche/quic/core/frames/quic_padding_frame.h" @@ -345,6 +346,15 @@ EXPECT_TRUE(IsControlFrame(frame.type)); } +TEST_F(QuicFramesTest, QuicImmediateAckFrameToString) { + QuicImmediateAckFrame immediate_ack_frame; + QuicFrame frame(immediate_ack_frame); + std::ostringstream stream; + stream << frame.immediate_ack_frame; + EXPECT_EQ("{ }\n", stream.str()); + EXPECT_FALSE(IsControlFrame(frame.type)); +} + TEST_F(QuicFramesTest, StreamFrameToString) { QuicStreamFrame frame; frame.stream_id = 1; @@ -661,6 +671,9 @@ case ACK_FREQUENCY_FRAME: frames.push_back(QuicFrame(new QuicAckFrequencyFrame())); break; + case IMMEDIATE_ACK_FRAME: + frames.push_back(QuicFrame(QuicImmediateAckFrame())); + break; case RESET_STREAM_AT_FRAME: frames.push_back(QuicFrame(new QuicResetStreamAtFrame())); break;
diff --git a/quiche/quic/core/frames/quic_immediate_ack_frame.cc b/quiche/quic/core/frames/quic_immediate_ack_frame.cc new file mode 100644 index 0000000..bd107f8 --- /dev/null +++ b/quiche/quic/core/frames/quic_immediate_ack_frame.cc
@@ -0,0 +1,23 @@ +// Copyright (c) 2025 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" + +#include <ostream> + +#include "quiche/quic/core/frames/quic_inlined_frame.h" +#include "quiche/quic/core/quic_types.h" + +namespace quic { + +QuicImmediateAckFrame::QuicImmediateAckFrame() + : QuicInlinedFrame(IMMEDIATE_ACK_FRAME) {} + +std::ostream& operator<<(std::ostream& os, + const QuicImmediateAckFrame& /*immediate_ack_frame*/) { + os << "{ }\n"; + return os; +} + +} // namespace quic
diff --git a/quiche/quic/core/frames/quic_immediate_ack_frame.h b/quiche/quic/core/frames/quic_immediate_ack_frame.h new file mode 100644 index 0000000..0ae2604 --- /dev/null +++ b/quiche/quic/core/frames/quic_immediate_ack_frame.h
@@ -0,0 +1,30 @@ +// Copyright (c) 2025 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_FRAMES_QUIC_IMMEDIATE_ACK_FRAME_H_ +#define QUICHE_QUIC_CORE_FRAMES_QUIC_IMMEDIATE_ACK_FRAME_H_ + +#include <ostream> + +#include "quiche/quic/core/frames/quic_inlined_frame.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace quic { + +// A frame that allows the sender to request an immediate ack from the receiver. +// Not a retransmittable frame. +struct QUICHE_EXPORT QuicImmediateAckFrame + : QuicInlinedFrame<QuicImmediateAckFrame> { + QuicImmediateAckFrame(); + + friend QUICHE_EXPORT std::ostream& operator<<( + std::ostream& os, const QuicImmediateAckFrame& immediate_ack_frame); + + QuicFrameType type; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_FRAMES_QUIC_IMMEDIATE_ACK_FRAME_H_
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc index 92a9e51..6a12e48 100644 --- a/quiche/quic/core/quic_connection.cc +++ b/quiche/quic/core/quic_connection.cc
@@ -33,6 +33,8 @@ #include "quiche/quic/core/crypto/crypto_utils.h" #include "quiche/quic/core/crypto/quic_decrypter.h" #include "quiche/quic/core/crypto/quic_encrypter.h" +#include "quiche/quic/core/frames/quic_ack_frequency_frame.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/quic_bandwidth.h" #include "quiche/quic/core/quic_config.h" @@ -265,6 +267,9 @@ AddKnownServerAddress(initial_peer_address); } packet_creator_.SetDefaultPeerAddress(initial_peer_address); + can_receive_ack_frequency_immediate_ack_ = + version().HasIetfQuicFrames() && + GetQuicReloadableFlag(quic_receive_ack_frequency); } void QuicConnection::InstallInitialCrypters(QuicConnectionId connection_id) { @@ -2101,6 +2106,26 @@ return true; } +bool QuicConnection::OnImmediateAckFrame(const QuicImmediateAckFrame& frame) { + QUIC_BUG_IF(quic_bug_immediate_ack_frame_connection_closed, !connected_) + << "Processing IMMEDIATE_ACK frame when connection " + "is closed. Received packet info: " + << last_received_packet_info_; + if (debug_visitor_ != nullptr) { + debug_visitor_->OnImmediateAckFrame(frame); + } + if (!UpdatePacketContent(IMMEDIATE_ACK_FRAME)) { + return false; + } + if (!can_receive_ack_frequency_immediate_ack_) { + QUIC_LOG_EVERY_N_SEC(ERROR, 120) << "Got unexpected ImmediateAck Frame."; + return false; + } + QUIC_RELOADABLE_FLAG_COUNT_N(quic_receive_ack_frequency, 1, 1); + MaybeUpdateAckTimeout(); + return true; +} + bool QuicConnection::OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) { QUIC_BUG_IF(OnResetStreamAtFrame_connection_closed, !connected_) << "Processing RESET_STREAM_AT frame while the connection is closed. "
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h index 305d1a9..b295e59 100644 --- a/quiche/quic/core/quic_connection.h +++ b/quiche/quic/core/quic_connection.h
@@ -32,6 +32,7 @@ #include "quiche/quic/core/crypto/quic_random.h" #include "quiche/quic/core/crypto/transport_parameters.h" #include "quiche/quic/core/frames/quic_ack_frequency_frame.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/frames/quic_max_streams_frame.h" #include "quiche/quic/core/frames/quic_new_connection_id_frame.h" #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" @@ -433,6 +434,9 @@ // Called when an AckFrequencyFrame has been parsed. virtual void OnAckFrequencyFrame(const QuicAckFrequencyFrame& /*frame*/) {} + // Called when an ImmediateAckFrame has been parsed. + virtual void OnImmediateAckFrame(const QuicImmediateAckFrame& /*frame*/) {} + // Called when a ResetStreamAtFrame has been parsed. virtual void OnResetStreamAtFrame(const QuicResetStreamAtFrame& /*frame*/) {} @@ -755,6 +759,7 @@ bool OnMessageFrame(const QuicMessageFrame& frame) override; bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override; bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) override; + bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override; bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override; void OnPacketComplete() override; bool IsValidStatelessResetToken( @@ -1318,6 +1323,10 @@ can_receive_ack_frequency_frame_ = true; } + void set_can_receive_ack_frequency_immediate_ack(bool can_receive) { + can_receive_ack_frequency_immediate_ack_ = can_receive; + } + bool is_processing_packet() const { return framer_.is_processing_packet(); } bool HasPendingPathValidation() const; @@ -2481,6 +2490,7 @@ // True if AckFrequencyFrame is supported. bool can_receive_ack_frequency_frame_ = false; + bool can_receive_ack_frequency_immediate_ack_ = false; // Indicate whether coalescing is done. bool coalescing_done_ = false;
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc index c9bc761..9fa3a16 100644 --- a/quiche/quic/core/quic_connection_test.cc +++ b/quiche/quic/core/quic_connection_test.cc
@@ -15798,6 +15798,7 @@ QuicConnectionPeer::GetSelfIssuedConnectionIdManager(&connection_) ->MaybeSendNewConnectionIds(); connection_.set_can_receive_ack_frequency_frame(); + connection_.set_can_receive_ack_frequency_immediate_ack(true); QuicAckFrame ack_frame = InitAckFrame(1); QuicRstStreamFrame rst_stream_frame; @@ -15903,6 +15904,9 @@ case ACK_FREQUENCY_FRAME: frame = QuicFrame(&ack_frequency_frame); break; + case IMMEDIATE_ACK_FRAME: + frame = QuicFrame(QuicImmediateAckFrame()); + break; case RESET_STREAM_AT_FRAME: frame = QuicFrame(&reset_stream_at_frame); break;
diff --git a/quiche/quic/core/quic_framer.cc b/quiche/quic/core/quic_framer.cc index 1cddd90..a727cad 100644 --- a/quiche/quic/core/quic_framer.cc +++ b/quiche/quic/core/quic_framer.cc
@@ -32,11 +32,11 @@ #include "quiche/quic/core/crypto/crypto_protocol.h" #include "quiche/quic/core/crypto/crypto_utils.h" #include "quiche/quic/core/crypto/null_decrypter.h" -#include "quiche/quic/core/crypto/null_encrypter.h" #include "quiche/quic/core/crypto/quic_decrypter.h" #include "quiche/quic/core/crypto/quic_encrypter.h" #include "quiche/quic/core/crypto/quic_random.h" #include "quiche/quic/core/frames/quic_ack_frequency_frame.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/quic_connection_id.h" #include "quiche/quic/core/quic_constants.h" @@ -56,7 +56,6 @@ #include "quiche/quic/platform/api/quic_flags.h" #include "quiche/quic/platform/api/quic_ip_address_family.h" #include "quiche/quic/platform/api/quic_logging.h" -#include "quiche/quic/platform/api/quic_stack_trace.h" #include "quiche/common/quiche_text_utils.h" #include "quiche/common/wire_serialization.h" @@ -693,6 +692,9 @@ return kQuicFrameTypeSize; case ACK_FREQUENCY_FRAME: return GetAckFrequencyFrameSize(*frame.ack_frequency_frame); + case IMMEDIATE_ACK_FRAME: + // IMMEDIATE_ACK has no payload. + return QuicDataWriter::GetVarInt62Len(IETF_IMMEDIATE_ACK); case RESET_STREAM_AT_FRAME: return GetResetStreamAtFrameSize(*frame.reset_stream_at_frame); case STREAM_FRAME: @@ -1209,6 +1211,9 @@ return 0; } break; + case IMMEDIATE_ACK_FRAME: + // IMMEDIATE_ACK has no payload. + break; case RESET_STREAM_AT_FRAME: QUIC_BUG_IF(reset_stream_at_appended_while_disabled, !process_reset_stream_at_) @@ -3143,6 +3148,18 @@ } break; } + case IETF_IMMEDIATE_ACK: { + // IMMEDIATE_ACK has no payload. + QuicImmediateAckFrame frame; + QUIC_DVLOG(2) << ENDPOINT << "Processing IETF immediate ack frame " + << frame; + if (!visitor_->OnImmediateAckFrame(frame)) { + QUIC_DVLOG(1) << "Visitor asked to stop further processing."; + // Returning true since there was no parsing error. + return true; + } + break; + } case IETF_RESET_STREAM_AT: { if (!process_reset_stream_at_) { set_detailed_error("RESET_STREAM_AT not enabled."); @@ -4992,6 +5009,9 @@ case ACK_FREQUENCY_FRAME: type_byte = IETF_ACK_FREQUENCY; break; + case IMMEDIATE_ACK_FRAME: + type_byte = IETF_IMMEDIATE_ACK; + break; case RESET_STREAM_AT_FRAME: type_byte = IETF_RESET_STREAM_AT; break;
diff --git a/quiche/quic/core/quic_framer.h b/quiche/quic/core/quic_framer.h index db92e0a..a941e02 100644 --- a/quiche/quic/core/quic_framer.h +++ b/quiche/quic/core/quic_framer.h
@@ -15,11 +15,11 @@ #include "quiche/quic/core/crypto/quic_decrypter.h" #include "quiche/quic/core/crypto/quic_encrypter.h" #include "quiche/quic/core/crypto/quic_random.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/quic_connection_id.h" #include "quiche/quic/core/quic_packets.h" #include "quiche/quic/core/quic_types.h" -#include "quiche/quic/platform/api/quic_export.h" namespace quic { @@ -223,6 +223,9 @@ // Called when an AckFrequencyFrame has been parsed. virtual bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) = 0; + // Called when an ImmediateAckFrame has been parsed. + virtual bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) = 0; + // Called when an ResetStreamAtFrame has been parsed. virtual bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) = 0;
diff --git a/quiche/quic/core/quic_framer_test.cc b/quiche/quic/core/quic_framer_test.cc index a11b638..44fd4f9 100644 --- a/quiche/quic/core/quic_framer_test.cc +++ b/quiche/quic/core/quic_framer_test.cc
@@ -464,6 +464,15 @@ return true; } + bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override { + ++frame_count_; + immediate_ack_frames_.emplace_back( + std::make_unique<QuicImmediateAckFrame>(frame)); + QUICHE_DCHECK(VersionHasIetfQuicFrames(transport_version_)); + EXPECT_EQ(IETF_IMMEDIATE_ACK, framer_->current_received_frame_type()); + return true; + } + bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override { ++frame_count_; reset_stream_at_frames_.push_back( @@ -664,6 +673,7 @@ std::vector<std::unique_ptr<QuicMessageFrame>> message_frames_; std::vector<std::unique_ptr<QuicHandshakeDoneFrame>> handshake_done_frames_; std::vector<std::unique_ptr<QuicAckFrequencyFrame>> ack_frequency_frames_; + std::vector<std::unique_ptr<QuicImmediateAckFrame>> immediate_ack_frames_; std::vector<std::unique_ptr<QuicResetStreamAtFrame>> reset_stream_at_frames_; std::vector<std::unique_ptr<QuicEncryptedPacket>> coalesced_packets_; std::vector<std::unique_ptr<QuicEncryptedPacket>> undecryptable_packets_; @@ -4807,6 +4817,38 @@ EXPECT_EQ(true, frame->ignore_order); } +TEST_P(QuicFramerTest, ParseImmediateAckFrame) { + SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE); + // clang-format off + unsigned char packet[] = { + // type (short header, 4 byte packet number) + 0x43, + // connection_id + 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, + // packet number + 0x12, 0x34, 0x56, 0x78, + + // immediate ack frame type + 0x1F, + }; + // clang-format on + + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + + QuicEncryptedPacket encrypted(AsChars(packet), ABSL_ARRAYSIZE(packet), false); + EXPECT_TRUE(framer_.ProcessPacket(encrypted)); + + EXPECT_THAT(framer_.error(), IsQuicNoError()); + ASSERT_TRUE(visitor_.header_.get()); + EXPECT_TRUE(CheckDecryption( + encrypted, !kIncludeVersion, !kIncludeDiversificationNonce, + kPacket8ByteConnectionId, kPacket0ByteConnectionId)); + + ASSERT_EQ(1u, visitor_.immediate_ack_frames_.size()); +} + TEST_P(QuicFramerTest, ParseResetStreamAtFrame) { SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE); // clang-format off @@ -8189,6 +8231,41 @@ ABSL_ARRAYSIZE(packet)); } +TEST_P(QuicFramerTest, BuildImmediateAckPacket) { + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + QuicImmediateAckFrame immediate_ack_frame; + QuicFrames frames = {QuicFrame(immediate_ack_frame)}; + + // clang-format off + unsigned char packet[] = { + // type (short header, 4 byte packet number) + 0x43, + // connection_id + 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, + // packet number + 0x12, 0x34, 0x56, 0x78, + + // frame type (Immediate Ack frame) + 0x1f, + }; + // clang-format on + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + + std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames)); + ASSERT_TRUE(data != nullptr); + + quiche::test::CompareCharArraysWithHexError( + "constructed packet", data->data(), data->length(), AsChars(packet), + ABSL_ARRAYSIZE(packet)); +} + TEST_P(QuicFramerTest, BuildResetStreamAtPacket) { if (!VersionHasIetfQuicFrames(framer_.transport_version())) { return;
diff --git a/quiche/quic/core/quic_packet_creator.cc b/quiche/quic/core/quic_packet_creator.cc index 5ce9033..29ede80 100644 --- a/quiche/quic/core/quic_packet_creator.cc +++ b/quiche/quic/core/quic_packet_creator.cc
@@ -1976,6 +1976,8 @@ frame.type != ACK_FREQUENCY_FRAME)) << ENDPOINT << frame.type << " not allowed at " << packet_.encryption_level; + QUICHE_DCHECK(packet_.encryption_level == ENCRYPTION_FORWARD_SECURE || + frame.type != IMMEDIATE_ACK_FRAME); if (frame.type == STREAM_FRAME) { if (MaybeCoalesceStreamFrame(frame.stream_frame)) {
diff --git a/quiche/quic/core/quic_trace_visitor.cc b/quiche/quic/core/quic_trace_visitor.cc index 2b049ca..4644e0d 100644 --- a/quiche/quic/core/quic_trace_visitor.cc +++ b/quiche/quic/core/quic_trace_visitor.cc
@@ -78,6 +78,7 @@ case MTU_DISCOVERY_FRAME: case STOP_WAITING_FRAME: case ACK_FRAME: + case IMMEDIATE_ACK_FRAME: QUIC_BUG(quic_bug_12732_1) << "Frames of type are not retransmittable and are not supposed " "to be in retransmittable_frames"; @@ -227,6 +228,7 @@ case CRYPTO_FRAME: case NEW_TOKEN_FRAME: case ACK_FREQUENCY_FRAME: + case IMMEDIATE_ACK_FRAME: case RESET_STREAM_AT_FRAME: break;
diff --git a/quiche/quic/core/quic_types.cc b/quiche/quic/core/quic_types.cc index fff80a9..aa69d02 100644 --- a/quiche/quic/core/quic_types.cc +++ b/quiche/quic/core/quic_types.cc
@@ -160,6 +160,7 @@ RETURN_STRING_LITERAL(NEW_TOKEN_FRAME) RETURN_STRING_LITERAL(RETIRE_CONNECTION_ID_FRAME) RETURN_STRING_LITERAL(ACK_FREQUENCY_FRAME) + RETURN_STRING_LITERAL(IMMEDIATE_ACK_FRAME) RETURN_STRING_LITERAL(RESET_STREAM_AT_FRAME) RETURN_STRING_LITERAL(NUM_FRAME_TYPES) }
diff --git a/quiche/quic/core/quic_types.h b/quiche/quic/core/quic_types.h index 2ffd824..f0649bd 100644 --- a/quiche/quic/core/quic_types.h +++ b/quiche/quic/core/quic_types.h
@@ -275,6 +275,7 @@ NEW_TOKEN_FRAME, RETIRE_CONNECTION_ID_FRAME, ACK_FREQUENCY_FRAME, + IMMEDIATE_ACK_FRAME, RESET_STREAM_AT_FRAME, NUM_FRAME_TYPES @@ -332,6 +333,8 @@ IETF_APPLICATION_CLOSE = 0x1d, IETF_HANDSHAKE_DONE = 0x1e, + // See draft-ietf-quic-ack-frequency. + IETF_IMMEDIATE_ACK = 0x1f, // The MESSAGE frame type has not yet been fully standardized. // QUIC versions starting with 46 and before 99 use 0x20-0x21.
diff --git a/quiche/quic/core/quic_unacked_packet_map.cc b/quiche/quic/core/quic_unacked_packet_map.cc index d3e3ed3..6192e00 100644 --- a/quiche/quic/core/quic_unacked_packet_map.cc +++ b/quiche/quic/core/quic_unacked_packet_map.cc
@@ -57,6 +57,7 @@ kRetireConnectionIdFrameBitfield = 1 << 21, kAckFrequencyFrameBitfield = 1 << 22, kResetStreamAtFrameBitfield = 1 << 23, + kImmediateAckFrameBitfield = 1 << 24, }; QuicFrameTypeBitfield GetFrameTypeBitfield(QuicFrameType type) { @@ -107,6 +108,8 @@ return kRetireConnectionIdFrameBitfield; case ACK_FREQUENCY_FRAME: return kAckFrequencyFrameBitfield; + case IMMEDIATE_ACK_FRAME: + return kImmediateAckFrameBitfield; case RESET_STREAM_AT_FRAME: return kResetStreamAtFrameBitfield; case NUM_FRAME_TYPES:
diff --git a/quiche/quic/core/quic_utils.cc b/quiche/quic/core/quic_utils.cc index e2663f3..057c087 100644 --- a/quiche/quic/core/quic_utils.cc +++ b/quiche/quic/core/quic_utils.cc
@@ -248,6 +248,7 @@ case MTU_DISCOVERY_FRAME: case PATH_CHALLENGE_FRAME: case PATH_RESPONSE_FRAME: + case IMMEDIATE_ACK_FRAME: return false; default: return true;
diff --git a/quiche/quic/core/tls_chlo_extractor.h b/quiche/quic/core/tls_chlo_extractor.h index 297063c..f24da45 100644 --- a/quiche/quic/core/tls_chlo_extractor.h +++ b/quiche/quic/core/tls_chlo_extractor.h
@@ -13,12 +13,12 @@ #include "absl/types/span.h" #include "openssl/ssl.h" #include "quiche/quic/core/frames/quic_ack_frequency_frame.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/quic_framer.h" #include "quiche/quic/core/quic_packets.h" #include "quiche/quic/core/quic_stream_sequencer.h" #include "quiche/quic/core/quic_types.h" -#include "quiche/quic/platform/api/quic_export.h" namespace quic { @@ -184,6 +184,9 @@ bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& /*frame*/) override { return true; } + bool OnImmediateAckFrame(const QuicImmediateAckFrame& /*frame*/) override { + return true; + } bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& /*frame*/) override { return true; }
diff --git a/quiche/quic/test_tools/quic_test_utils.cc b/quiche/quic/test_tools/quic_test_utils.cc index a2d0798..2b49ce2 100644 --- a/quiche/quic/test_tools/quic_test_utils.cc +++ b/quiche/quic/test_tools/quic_test_utils.cc
@@ -20,11 +20,7 @@ #include "openssl/sha.h" #include "quiche/quic/core/crypto/crypto_framer.h" #include "quiche/quic/core/crypto/crypto_handshake.h" -#include "quiche/quic/core/crypto/crypto_utils.h" -#include "quiche/quic/core/crypto/null_decrypter.h" -#include "quiche/quic/core/crypto/null_encrypter.h" -#include "quiche/quic/core/crypto/quic_decrypter.h" -#include "quiche/quic/core/crypto/quic_encrypter.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/http/quic_spdy_client_session.h" #include "quiche/quic/core/quic_config.h" #include "quiche/quic/core/quic_data_writer.h" @@ -35,8 +31,6 @@ #include "quiche/quic/core/quic_types.h" #include "quiche/quic/core/quic_utils.h" #include "quiche/quic/core/quic_versions.h" -#include "quiche/quic/platform/api/quic_flags.h" -#include "quiche/quic/platform/api/quic_logging.h" #include "quiche/quic/test_tools/crypto_test_utils.h" #include "quiche/quic/test_tools/quic_config_peer.h" #include "quiche/quic/test_tools/quic_connection_peer.h" @@ -449,6 +443,11 @@ return true; } +bool NoOpFramerVisitor::OnImmediateAckFrame( + const QuicImmediateAckFrame& /*frame*/) { + return true; +} + bool NoOpFramerVisitor::OnResetStreamAtFrame( const QuicResetStreamAtFrame& /*frame*/) { return true; @@ -1076,14 +1075,14 @@ BuildUnsizedDataPacket(&framer, header, frames)); EXPECT_TRUE(packet != nullptr); - // Now set the frame type to 0x1F, which is an invalid frame type. + // Now set the frame type to 0x3F, which is an invalid frame type. reinterpret_cast<unsigned char*>( packet->mutable_data())[GetStartOfEncryptedData( framer.transport_version(), GetIncludedDestinationConnectionIdLength(header), GetIncludedSourceConnectionIdLength(header), version_flag, false /* no diversification nonce */, packet_number_length, - header.retry_token_length_length, 0, header.length_length)] = 0x1F; + header.retry_token_length_length, 0, header.length_length)] = 0x3F; char* buffer = new char[kMaxOutgoingPacketSize]; size_t encrypted_length =
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h index 10f9fb1..8946ccf 100644 --- a/quiche/quic/test_tools/quic_test_utils.h +++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -21,6 +21,7 @@ #include "quiche/quic/core/congestion_control/loss_detection_interface.h" #include "quiche/quic/core/congestion_control/send_algorithm_interface.h" #include "quiche/quic/core/crypto/transport_parameters.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/http/http_decoder.h" #include "quiche/quic/core/http/quic_server_session_base.h" @@ -357,6 +358,8 @@ (override)); MOCK_METHOD(bool, OnAckFrequencyFrame, (const QuicAckFrequencyFrame& frame), (override)); + MOCK_METHOD(bool, OnImmediateAckFrame, (const QuicImmediateAckFrame& frame), + (override)); MOCK_METHOD(bool, OnResetStreamAtFrame, (const QuicResetStreamAtFrame& frame), (override)); MOCK_METHOD(void, OnPacketComplete, (), (override)); @@ -426,6 +429,7 @@ bool OnMessageFrame(const QuicMessageFrame& frame) override; bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override; bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) override; + bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override; bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override; void OnPacketComplete() override {} bool IsValidStatelessResetToken(
diff --git a/quiche/quic/test_tools/simple_quic_framer.cc b/quiche/quic/test_tools/simple_quic_framer.cc index 8ad6656..90b75e8 100644 --- a/quiche/quic/test_tools/simple_quic_framer.cc +++ b/quiche/quic/test_tools/simple_quic_framer.cc
@@ -7,13 +7,13 @@ #include <memory> #include <optional> #include <string> -#include <utility> #include <vector> #include "absl/memory/memory.h" #include "absl/strings/string_view.h" #include "quiche/quic/core/crypto/quic_decrypter.h" #include "quiche/quic/core/crypto/quic_encrypter.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/quic_types.h" @@ -240,6 +240,12 @@ return true; } + bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override { + immediate_ack_frames_.push_back(frame); + frame_types_.push_back(IMMEDIATE_ACK_FRAME); + return true; + } + bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override { reset_stream_at_frames_.push_back(frame); frame_types_.push_back(RESET_STREAM_AT_FRAME); @@ -349,6 +355,7 @@ std::vector<QuicMessageFrame> message_frames_; std::vector<QuicHandshakeDoneFrame> handshake_done_frames_; std::vector<QuicAckFrequencyFrame> ack_frequency_frames_; + std::vector<QuicImmediateAckFrame> immediate_ack_frames_; std::vector<QuicResetStreamAtFrame> reset_stream_at_frames_; std::vector<std::unique_ptr<std::string>> stream_data_; std::vector<std::unique_ptr<std::string>> crypto_data_;
diff --git a/quiche/quic/tools/quic_packet_printer_bin.cc b/quiche/quic/tools/quic_packet_printer_bin.cc index 95d0d1d..9aad0aa 100644 --- a/quiche/quic/tools/quic_packet_printer_bin.cc +++ b/quiche/quic/tools/quic_packet_printer_bin.cc
@@ -34,12 +34,10 @@ #include "absl/strings/escaping.h" #include "absl/strings/string_view.h" +#include "quiche/quic/core/frames/quic_immediate_ack_frame.h" #include "quiche/quic/core/quic_framer.h" #include "quiche/quic/core/quic_types.h" -#include "quiche/quic/core/quic_utils.h" -#include "quiche/quic/platform/api/quic_flags.h" #include "quiche/common/platform/api/quiche_command_line_flags.h" -#include "quiche/common/quiche_text_utils.h" DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, quic_version, "", "If set, specify the QUIC version to use."); @@ -217,6 +215,10 @@ std::cerr << "OnAckFrequencyFrame: " << frame; return true; } + bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override { + std::cerr << "OnImmediateAckFrame: " << frame; + return true; + } bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override { std::cerr << "OnResetStreamAtFrame: " << frame; return true;