Add ECN counters to ACKs sent by the QuicBufferedPacketStore.
Also add a test that checks for the existence of the ACK, with the ECN count.
Protected by quic_reloadable_flag_quic_ecn_in_first_ack.
PiperOrigin-RevId: 694503577
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index 48bd038..b4f0c7b 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -31,6 +31,7 @@
QUICHE_FLAG(bool, quiche_reloadable_flag_quic_disable_version_q046, false, true, "If true, disable QUIC version Q046.")
QUICHE_FLAG(bool, quiche_reloadable_flag_quic_disable_version_rfcv1, false, false, "If true, disable QUIC version h3 (RFCv1).")
QUICHE_FLAG(bool, quiche_reloadable_flag_quic_discard_initial_packet_with_key_dropped, false, true, "If true, discard INITIAL packet if the key has been dropped.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_ecn_in_first_ack, false, false, "When true, reports ECN in counts in the ACK of the a client initial that goes in the buffered packet store.")
QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_disable_resumption, true, true, "If true, disable resumption when receiving NRES connection option.")
QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_mtu_discovery_at_server, false, false, "If true, QUIC will default enable MTU discovery at server, with a target of 1450 bytes.")
QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_server_on_wire_ping, true, true, "If true, enable server retransmittable on wire PING.")
diff --git a/quiche/quic/core/quic_buffered_packet_store.cc b/quiche/quic/core/quic_buffered_packet_store.cc
index 0392ecf..4421b66 100644
--- a/quiche/quic/core/quic_buffered_packet_store.cc
+++ b/quiche/quic/core/quic_buffered_packet_store.cc
@@ -69,6 +69,19 @@
QuicBufferedPacketStore* connection_store_;
};
+std::optional<QuicEcnCounts> SinglePacketEcnCount(
+ QuicEcnCodepoint ecn_codepoint) {
+ switch (ecn_codepoint) {
+ case ECN_CE:
+ return QuicEcnCounts(0, 0, 1);
+ case ECN_ECT0:
+ return QuicEcnCounts(1, 0, 0);
+ case ECN_ECT1:
+ return QuicEcnCounts(0, 1, 0);
+ default:
+ return std::nullopt;
+ }
+}
} // namespace
BufferedPacket::BufferedPacket(std::unique_ptr<QuicReceivedPacket> packet,
@@ -313,7 +326,11 @@
initial_ack_frame.packets.Add(sent_packet.received_packet_number);
}
initial_ack_frame.largest_acked = initial_ack_frame.packets.Max();
-
+ if (GetQuicReloadableFlag(quic_ecn_in_first_ack)) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_ecn_in_first_ack);
+ initial_ack_frame.ecn_counters =
+ SinglePacketEcnCount(packet_info.packet.ecn_codepoint());
+ }
if (!creator.AddFrame(QuicFrame(&initial_ack_frame), NOT_RETRANSMISSION)) {
QUIC_BUG(quic_dispatcher_add_ack_frame_failed)
<< "Unable to add ack frame to an empty packet while acking packet "
diff --git a/quiche/quic/core/quic_buffered_packet_store_test.cc b/quiche/quic/core/quic_buffered_packet_store_test.cc
index 107acaa..60495dc 100644
--- a/quiche/quic/core/quic_buffered_packet_store_test.cc
+++ b/quiche/quic/core/quic_buffered_packet_store_test.cc
@@ -6,6 +6,7 @@
#include <cstddef>
#include <cstdint>
+#include <cstring>
#include <list>
#include <memory>
#include <optional>
@@ -16,12 +17,16 @@
#include "absl/strings/string_view.h"
#include "quiche/quic/core/connection_id_generator.h"
#include "quiche/quic/core/crypto/transport_parameters.h"
+#include "quiche/quic/core/frames/quic_frame.h"
+#include "quiche/quic/core/frames/quic_padding_frame.h"
#include "quiche/quic/core/quic_connection_id.h"
#include "quiche/quic/core/quic_constants.h"
#include "quiche/quic/core/quic_dispatcher.h"
#include "quiche/quic/core/quic_dispatcher_stats.h"
#include "quiche/quic/core/quic_error_codes.h"
#include "quiche/quic/core/quic_framer.h"
+#include "quiche/quic/core/quic_packet_number.h"
+#include "quiche/quic/core/quic_packet_writer.h"
#include "quiche/quic/core/quic_packets.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/core/quic_types.h"
@@ -34,6 +39,7 @@
#include "quiche/quic/test_tools/mock_connection_id_generator.h"
#include "quiche/quic/test_tools/quic_buffered_packet_store_peer.h"
#include "quiche/quic/test_tools/quic_test_utils.h"
+#include "quiche/common/quiche_endian.h"
namespace quic {
static const size_t kDefaultMaxConnectionsInStore = 100;
@@ -50,11 +56,14 @@
using BufferedPacket = QuicBufferedPacketStore::BufferedPacket;
using BufferedPacketList = QuicBufferedPacketStore::BufferedPacketList;
using EnqueuePacketResult = QuicBufferedPacketStore::EnqueuePacketResult;
+using ::testing::_;
using ::testing::A;
using ::testing::Conditional;
using ::testing::Each;
using ::testing::ElementsAre;
+using ::testing::Invoke;
using ::testing::Ne;
+using ::testing::Return;
using ::testing::SizeIs;
using ::testing::Truly;
@@ -149,6 +158,83 @@
EXPECT_FALSE(store_.HasBufferedPackets(connection_id));
}
+TEST_F(QuicBufferedPacketStoreTest, SimpleEnqueueAckSent) {
+ SetQuicReloadableFlag(quic_ecn_in_first_ack, true);
+ QuicConnectionId connection_id = TestConnectionId(1);
+ MockPacketWriter writer;
+ store_.set_writer(&writer);
+ // Build a decryptable Initial packet with PADDING.
+ QuicFramer client_framer(ParsedQuicVersionVector{ParsedQuicVersion::RFCv1()},
+ QuicTime::Zero(), Perspective::IS_CLIENT, 8);
+ client_framer.SetInitialObfuscators(connection_id);
+ QuicPacketHeader header;
+ header.destination_connection_id = connection_id;
+ header.version_flag = true;
+ header.packet_number = QuicPacketNumber(1);
+ header.packet_number_length = PACKET_1BYTE_PACKET_NUMBER;
+ header.long_packet_type = INITIAL;
+ header.length_length = quiche::VARIABLE_LENGTH_INTEGER_LENGTH_2;
+ header.retry_token_length_length = quiche::VARIABLE_LENGTH_INTEGER_LENGTH_1;
+ QuicFrames frames = {QuicFrame(QuicPaddingFrame(1200))};
+
+ char* buffer = new char[1500];
+ EncryptionLevel level = HeaderToEncryptionLevel(header);
+ size_t length =
+ client_framer.BuildDataPacket(header, frames, buffer, 1500, level);
+
+ ASSERT_GT(length, 0);
+
+ // Re-construct the data packet with data ownership.
+ auto data = std::make_unique<QuicPacket>(
+ buffer, length, /* owns_buffer */ true,
+ GetIncludedDestinationConnectionIdLength(header),
+ GetIncludedSourceConnectionIdLength(header), header.version_flag,
+ header.nonce != nullptr, header.packet_number_length,
+ header.retry_token_length_length, header.retry_token.length(),
+ header.length_length);
+ unsigned char raw[1500] = {};
+ size_t final_size = client_framer.EncryptPayload(
+ ENCRYPTION_INITIAL, header.packet_number, *data, (char*)raw, 1500);
+ QuicReceivedPacket packet((char*)raw, final_size, QuicTime::Zero(), false, 0,
+ true, nullptr, 0, false, ECN_ECT1);
+
+ EXPECT_CALL(writer, IsWriteBlocked()).WillOnce(Return(false));
+ std::unique_ptr<QuicEncryptedPacket> ack_packet;
+ EXPECT_CALL(writer, WritePacket(_, _, _, _, _, _))
+ .WillOnce(Invoke([&](const char* buffer, size_t buf_len,
+ const QuicIpAddress& /*self_address*/,
+ const QuicSocketAddress& /*peer_address*/,
+ PerPacketOptions* /*options*/,
+ const QuicPacketWriterParams& /*params*/) {
+ auto tmp_packet =
+ std::make_unique<QuicEncryptedPacket>(buffer, buf_len);
+ ack_packet = tmp_packet->Clone();
+ return WriteResult(WRITE_STATUS_OK, 1);
+ }));
+ EXPECT_CALL(writer, Flush());
+ EnqueuePacketToStore(store_, connection_id, IETF_QUIC_LONG_HEADER_PACKET,
+ INITIAL, packet, self_address_, peer_address_,
+ ParsedQuicVersion::RFCv1(), kNoParsedChlo,
+ connection_id_generator_);
+ const BufferedPacketList* buffered_list = store_.GetPacketList(connection_id);
+ ASSERT_NE(buffered_list, nullptr);
+ ASSERT_EQ(buffered_list->dispatcher_sent_packets.size(), 1);
+ EXPECT_EQ(buffered_list->dispatcher_sent_packets[0].largest_acked,
+ QuicPacketNumber(1));
+
+ // Decrypt the packet, and verify it reports ECN.
+ MockFramerVisitor mock_framer_visitor;
+ client_framer.set_visitor(&mock_framer_visitor);
+ EXPECT_CALL(mock_framer_visitor, OnPacket()).Times(1);
+ EXPECT_CALL(mock_framer_visitor, OnAckFrameStart(_, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_framer_visitor, OnAckRange(_, _)).WillOnce(Return(true));
+ std::optional<QuicEcnCounts> counts = QuicEcnCounts(0, 1, 0);
+ EXPECT_CALL(mock_framer_visitor, OnAckFrameEnd(_, counts))
+ .WillOnce(Return(true));
+ client_framer.ProcessPacket(*ack_packet);
+}
+
TEST_F(QuicBufferedPacketStoreTest, DifferentPacketAddressOnOneConnection) {
QuicSocketAddress addr_with_new_port(QuicIpAddress::Any4(), 256);
QuicConnectionId connection_id = TestConnectionId(1);