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_received_packet_manager.cc b/quiche/quic/core/quic_received_packet_manager.cc
index bb7b87d..0486618 100644
--- a/quiche/quic/core/quic_received_packet_manager.cc
+++ b/quiche/quic/core/quic_received_packet_manager.cc
@@ -10,7 +10,9 @@
 
 #include "quiche/quic/core/congestion_control/rtt_stats.h"
 #include "quiche/quic/core/crypto/crypto_protocol.h"
+#include "quiche/quic/core/quic_config.h"
 #include "quiche/quic/core/quic_connection_stats.h"
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.h"
 #include "quiche/quic/platform/api/quic_flags.h"
 #include "quiche/quic/platform/api/quic_logging.h"
@@ -70,7 +72,8 @@
 }
 
 void QuicReceivedPacketManager::RecordPacketReceived(
-    const QuicPacketHeader& header, QuicTime receipt_time) {
+    const QuicPacketHeader& header, QuicTime receipt_time,
+    const QuicEcnCodepoint ecn) {
   const QuicPacketNumber packet_number = header.packet_number;
   QUICHE_DCHECK(IsAwaitingPacket(packet_number))
       << " packet_number:" << packet_number;
@@ -119,6 +122,27 @@
     }
   }
 
+  if (GetQuicRestartFlag(quic_receive_ecn) && ecn != ECN_NOT_ECT) {
+    QUIC_RESTART_FLAG_COUNT_N(quic_receive_ecn, 1, 3);
+    if (!ack_frame_.ecn_counters.has_value()) {
+      ack_frame_.ecn_counters = QuicEcnCounts();
+    }
+    switch (ecn) {
+      case ECN_NOT_ECT:
+        QUICHE_NOTREACHED();
+        break;  // It's impossible to get here, but the compiler complains.
+      case ECN_ECT0:
+        ack_frame_.ecn_counters->ect0++;
+        break;
+      case ECN_ECT1:
+        ack_frame_.ecn_counters->ect1++;
+        break;
+      case ECN_CE:
+        ack_frame_.ecn_counters->ce++;
+        break;
+    }
+  }
+
   if (least_received_packet_number_.IsInitialized()) {
     least_received_packet_number_ =
         std::min(least_received_packet_number_, packet_number);