Internal change

PiperOrigin-RevId: 515182736
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 9d4f882..dbede25 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -1324,6 +1324,20 @@
         last_received_packet_info_.header.packet_number;
   }
 
+  switch (last_received_packet_info_.ecn_codepoint) {
+    case ECN_NOT_ECT:
+      break;
+    case ECN_ECT0:
+      stats_.num_ecn_marks_received.ect0++;
+      break;
+    case ECN_ECT1:
+      stats_.num_ecn_marks_received.ect1++;
+      break;
+    case ECN_CE:
+      stats_.num_ecn_marks_received.ce++;
+      break;
+  }
+
   // Record packet receipt to populate ack info before processing stream
   // frames, since the processing may result in sending a bundled ack.
   QuicTime receipt_time = idle_network_detector_.time_of_last_received_packet();
@@ -3630,6 +3644,9 @@
 
   stats_.bytes_sent += encrypted_length;
   ++stats_.packets_sent;
+  if (packet->has_ack_ecn) {
+    stats_.num_ack_frames_sent_with_ecn++;
+  }
 
   QuicByteCount bytes_not_retransmitted =
       packet->bytes_not_retransmitted.value_or(0);
diff --git a/quiche/quic/core/quic_connection_stats.h b/quiche/quic/core/quic_connection_stats.h
index 82b0255..336435e 100644
--- a/quiche/quic/core/quic_connection_stats.h
+++ b/quiche/quic/core/quic_connection_stats.h
@@ -188,6 +188,14 @@
   QuicPacketCount
       num_tls_server_zero_rtt_packets_received_after_discarding_decrypter = 0;
 
+  // Counts the number of packets received with each Explicit Congestion
+  // Notification (ECN) codepoint, except Not-ECT. There is one counter across
+  // all packet number spaces.
+  QuicEcnCounts num_ecn_marks_received;
+
+  // Counts the number of ACK frames sent with ECN counts.
+  QuicPacketCount num_ack_frames_sent_with_ecn = 0;
+
   // True if address is validated via decrypting HANDSHAKE or 1-RTT packet.
   bool address_validated_via_decrypting_packet = false;
 
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 84c8cdb..a235314 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -16877,12 +16877,26 @@
       connection_.SupportsMultiplePacketNumberSpaces()
           ? connection_.received_packet_manager().GetAckFrame(APPLICATION_DATA)
           : connection_.received_packet_manager().ack_frame();
+  // Send two PINGs so that the ACK goes too. The second packet should not
+  // include an ACK, which checks that the packet state is cleared properly.
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  if (connection_.version().HasIetfQuicFrames()) {
+    QuicConnectionPeer::SendPing(&connection_);
+    QuicConnectionPeer::SendPing(&connection_);
+  }
+  QuicConnectionStats stats = connection_.GetStats();
   if (GetQuicRestartFlag(quic_receive_ecn)) {
     ASSERT_TRUE(ack_frame.ecn_counters.has_value());
     EXPECT_EQ(ack_frame.ecn_counters->ect0, 1);
+    EXPECT_EQ(stats.num_ack_frames_sent_with_ecn,
+              connection_.version().HasIetfQuicFrames() ? 1 : 0);
   } else {
     EXPECT_FALSE(ack_frame.ecn_counters.has_value());
+    EXPECT_EQ(stats.num_ack_frames_sent_with_ecn, 0);
   }
+  EXPECT_EQ(stats.num_ecn_marks_received.ect0, 1);
+  EXPECT_EQ(stats.num_ecn_marks_received.ect1, 0);
+  EXPECT_EQ(stats.num_ecn_marks_received.ce, 0);
 }
 
 TEST_P(QuicConnectionTest, EcnMarksCoalescedPacket) {
@@ -16914,6 +16928,16 @@
       std::make_unique<TaggingEncrypter>(ENCRYPTION_HANDSHAKE));
   EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(2);
   ProcessCoalescedPacket(packets, ECN_ECT0);
+  // Send two PINGs so that the ACKs go too.
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  if (connection_.version().HasIetfQuicFrames()) {
+    EXPECT_CALL(visitor_, OnHandshakePacketSent()).Times(1);
+    connection_.SetDefaultEncryptionLevel(ENCRYPTION_HANDSHAKE);
+    QuicConnectionPeer::SendPing(&connection_);
+    connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+    QuicConnectionPeer::SendPing(&connection_);
+  }
+  QuicConnectionStats stats = connection_.GetStats();
   ack_frame =
       connection_.SupportsMultiplePacketNumberSpaces()
           ? connection_.received_packet_manager().GetAckFrame(HANDSHAKE_DATA)
@@ -16929,6 +16953,16 @@
     EXPECT_TRUE(ack_frame.ecn_counters.has_value());
     EXPECT_EQ(ack_frame.ecn_counters->ect0, 1);
   }
+  if (GetQuicRestartFlag(quic_receive_ecn)) {
+    EXPECT_EQ(stats.num_ecn_marks_received.ect0, 2);
+    EXPECT_EQ(stats.num_ack_frames_sent_with_ecn,
+              connection_.version().HasIetfQuicFrames() ? 2 : 0);
+  } else {
+    EXPECT_EQ(stats.num_ecn_marks_received.ect0, 0);
+    EXPECT_EQ(stats.num_ack_frames_sent_with_ecn, 0);
+  }
+  EXPECT_EQ(stats.num_ecn_marks_received.ect1, 0);
+  EXPECT_EQ(stats.num_ecn_marks_received.ce, 0);
 }
 
 TEST_P(QuicConnectionTest, EcnMarksUndecryptableCoalescedPacket) {
@@ -17047,6 +17081,12 @@
   // Should be recorded as ECT(0), not CE.
   EXPECT_EQ(ack_frame.ecn_counters->ect0,
             connection_.SupportsMultiplePacketNumberSpaces() ? 1 : 2);
+  QuicConnectionStats stats = connection_.GetStats();
+  EXPECT_EQ(stats.num_ecn_marks_received.ect0,
+            GetQuicRestartFlag(quic_receive_ecn) ? 2 : 0);
+  EXPECT_EQ(stats.num_ecn_marks_received.ect1, 0);
+  EXPECT_EQ(stats.num_ecn_marks_received.ce,
+            GetQuicRestartFlag(quic_receive_ecn) ? 1 : 0);
 }
 
 TEST_P(QuicConnectionTest, ReceivedPacketInfoDefaults) {
diff --git a/quiche/quic/core/quic_packet_creator.cc b/quiche/quic/core/quic_packet_creator.cc
index 2a0974f..5599f8f 100644
--- a/quiche/quic/core/quic_packet_creator.cc
+++ b/quiche/quic/core/quic_packet_creator.cc
@@ -492,6 +492,7 @@
 void QuicPacketCreator::ClearPacket() {
   packet_.has_ack = false;
   packet_.has_stop_waiting = false;
+  packet_.has_ack_ecn = false;
   packet_.has_crypto_handshake = NOT_HANDSHAKE;
   packet_.transmission_type = NOT_RETRANSMISSION;
   packet_.encrypted_buffer = nullptr;
@@ -1829,6 +1830,9 @@
   if (frame.type == ACK_FRAME) {
     packet_.has_ack = true;
     packet_.largest_acked = LargestAcked(*frame.ack_frame);
+    if (frame.ack_frame->ecn_counters.has_value()) {
+      packet_.has_ack_ecn = true;
+    }
   } else if (frame.type == STOP_WAITING_FRAME) {
     packet_.has_stop_waiting = true;
   } else if (frame.type == ACK_FREQUENCY_FRAME) {
diff --git a/quiche/quic/core/quic_packets.cc b/quiche/quic/core/quic_packets.cc
index 746737b..39e7eaf 100644
--- a/quiche/quic/core/quic_packets.cc
+++ b/quiche/quic/core/quic_packets.cc
@@ -441,6 +441,7 @@
       encryption_level(other.encryption_level),
       has_ack(other.has_ack),
       has_stop_waiting(other.has_stop_waiting),
+      has_ack_ecn(other.has_ack_ecn),
       transmission_type(other.transmission_type),
       largest_acked(other.largest_acked),
       has_ack_frame_copy(other.has_ack_frame_copy),
@@ -498,6 +499,7 @@
   copy->peer_address = serialized.peer_address;
   copy->bytes_not_retransmitted = serialized.bytes_not_retransmitted;
   copy->initial_header = serialized.initial_header;
+  copy->has_ack_ecn = serialized.has_ack_ecn;
 
   if (copy_buffer) {
     copy->encrypted_buffer = CopyBuffer(serialized);
diff --git a/quiche/quic/core/quic_packets.h b/quiche/quic/core/quic_packets.h
index c26a88a..d1eb52b 100644
--- a/quiche/quic/core/quic_packets.h
+++ b/quiche/quic/core/quic_packets.h
@@ -376,6 +376,7 @@
   // TODO(fayang): Remove has_ack and has_stop_waiting.
   bool has_ack;
   bool has_stop_waiting;
+  bool has_ack_ecn = false;  // ack frame contains ECN counts.
   TransmissionType transmission_type;
   // The largest acked of the AckFrame in this packet if has_ack is true,
   // 0 otherwise.