Receive ECN marks on inbound packets and report counts in ACK_ECN frames.
It does not support marking outbound packets except for a minimal capability for test.

QuicPacketReader receives ECN marks from the socket API via QuicUdpPacketInfo.. QuicPacketReader then stores this in a new member of QuicReceivedPacket. This arrives at QuicConnection, which adds to the last_received_packet_info_. When the framer calls QuicConnection::OnPacketHeader(), QuicConnection updates the pending ack frame with the new ECN count via QuicReceivedPacketManager, which updates the counts in the ack frame.

To enable an end-to-end test, this CL also includes minimal changes to mark outbound packets and report the contents of ACK_ECN frames.

QuicConnection::per_packet_options_ is extended to include an ECN codepoint, which QuicDefaultPacketWriter reads, and passes the instruction to the QuicUdpSocketApi via QuicUdpPacketInfo. The test explicitly sets per_packet_options_ to mark ECT(0) after the handshake.

When receiving ACK_ECN, the results are reported to QuicConnection in OnAckFrameEnd(), which then just stores them in a new data structure QuicEcnCounts.

Protected by FLAGS_quic_reloadable_flag_quic_receive_ecn.

PiperOrigin-RevId: 501596181
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index b567587..a3a4144 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -16497,6 +16497,61 @@
   EXPECT_TRUE(alt_path->validated);
 }
 
+TEST_P(QuicConnectionTest, EcnMarksCorrectlyRecorded) {
+  set_perspective(Perspective::IS_SERVER);
+  QuicPacketHeader header = ConstructPacketHeader(1, ENCRYPTION_FORWARD_SECURE);
+  QuicFrames frames;
+  QuicPingFrame ping_frame;
+  QuicPaddingFrame padding_frame;
+  frames.push_back(QuicFrame(ping_frame));
+  frames.push_back(QuicFrame(padding_frame));
+  std::unique_ptr<QuicPacket> packet =
+      BuildUnsizedDataPacket(&peer_framer_, header, frames);
+  char buffer[kMaxOutgoingPacketSize];
+  size_t encrypted_length = peer_framer_.EncryptPayload(
+      ENCRYPTION_FORWARD_SECURE, QuicPacketNumber(1), *packet, buffer,
+      kMaxOutgoingPacketSize);
+  QuicReceivedPacket received_packet(buffer, encrypted_length, clock_.Now(),
+                                     false, 0, true, nullptr, 0, false,
+                                     ECN_ECT0);
+  if (connection_.SupportsMultiplePacketNumberSpaces()) {
+    EXPECT_FALSE(connection_.received_packet_manager()
+                     .GetAckFrame(APPLICATION_DATA)
+                     .ecn_counters.has_value());
+    connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, received_packet);
+    if (GetQuicRestartFlag(quic_receive_ecn)) {
+      EXPECT_TRUE(connection_.received_packet_manager()
+                      .GetAckFrame(APPLICATION_DATA)
+                      .ecn_counters.has_value());
+      EXPECT_EQ(connection_.received_packet_manager()
+                    .GetAckFrame(APPLICATION_DATA)
+                    .ecn_counters->ect0,
+                1);
+    } else {
+      EXPECT_FALSE(connection_.received_packet_manager()
+                       .GetAckFrame(APPLICATION_DATA)
+                       .ecn_counters.has_value());
+    }
+  } else {
+    EXPECT_FALSE(connection_.received_packet_manager()
+                     .ack_frame()
+                     .ecn_counters.has_value());
+    connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, received_packet);
+    if (GetQuicRestartFlag(quic_receive_ecn)) {
+      EXPECT_TRUE(connection_.received_packet_manager()
+                      .ack_frame()
+                      .ecn_counters.has_value());
+      EXPECT_EQ(
+          connection_.received_packet_manager().ack_frame().ecn_counters->ect0,
+          1);
+    } else {
+      EXPECT_FALSE(connection_.received_packet_manager()
+                       .ack_frame()
+                       .ecn_counters.has_value());
+    }
+  }
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic