Refactor ECN in QuicConnection.

Since ECN settings are no longer in PerPacketOptions, QuicConnection now owns the memory where settings are stored and last-minute checks against the current status are no longer necessary.

Protected by quic_restart_flag_quic_send_ect1.

PiperOrigin-RevId: 534592885
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index d4e442f..b2c92db 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -7113,7 +7113,7 @@
   EXPECT_EQ(ecn->ect0, 0);
   EXPECT_EQ(ecn->ect1, 0);
   EXPECT_EQ(ecn->ce, 0);
-  client_connection->set_ecn_codepoint(ECN_NOT_ECT);
+  EXPECT_TRUE(client_connection->set_ecn_codepoint(ECN_NOT_ECT));
   client_->SendSynchronousRequest("/foo");
   EXPECT_EQ(ecn->ect0, 0);
   EXPECT_EQ(ecn->ect1, 0);
@@ -7133,7 +7133,7 @@
   EXPECT_EQ(ecn->ect0, 0);
   EXPECT_EQ(ecn->ect1, 0);
   EXPECT_EQ(ecn->ce, 0);
-  client_connection->set_ecn_codepoint(ECN_ECT0);
+  EXPECT_TRUE(client_connection->set_ecn_codepoint(ECN_ECT0));
   client_->SendSynchronousRequest("/foo");
   if (!GetQuicRestartFlag(quic_receive_ecn) ||
       !GetQuicRestartFlag(quic_quiche_ecn_sockets) ||
@@ -7159,7 +7159,7 @@
   EXPECT_EQ(ecn->ect0, 0);
   EXPECT_EQ(ecn->ect1, 0);
   EXPECT_EQ(ecn->ce, 0);
-  client_connection->set_ecn_codepoint(ECN_ECT1);
+  EXPECT_TRUE(client_connection->set_ecn_codepoint(ECN_ECT1));
   client_->SendSynchronousRequest("/foo");
   if (!GetQuicRestartFlag(quic_receive_ecn) ||
       !GetQuicRestartFlag(quic_quiche_ecn_sockets) ||
@@ -7185,7 +7185,7 @@
   EXPECT_EQ(ecn->ect0, 0);
   EXPECT_EQ(ecn->ect1, 0);
   EXPECT_EQ(ecn->ce, 0);
-  client_connection->set_ecn_codepoint(ECN_CE);
+  EXPECT_TRUE(client_connection->set_ecn_codepoint(ECN_CE));
   client_->SendSynchronousRequest("/foo");
   if (!GetQuicRestartFlag(quic_receive_ecn) ||
       !GetQuicRestartFlag(quic_quiche_ecn_sockets) ||
@@ -7210,7 +7210,7 @@
   QuicEcnCounts* ecn = QuicSentPacketManagerPeer::GetPeerEcnCounts(
       QuicConnectionPeer::GetSentPacketManager(server_connection),
       APPLICATION_DATA);
-  server_connection->set_ecn_codepoint(ECN_ECT1);
+  EXPECT_TRUE(server_connection->set_ecn_codepoint(ECN_ECT1));
   client_->SendSynchronousRequest("/foo");
   // A second request provides a packet for the client ACKs to go with.
   client_->SendSynchronousRequest("/foo");
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 6fd85b7..f139005 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -3915,7 +3915,7 @@
   // Only packets on the default path are in-flight.
   if (!default_path_.ecn_marked_packet_acked) {
     QUIC_DVLOG(1) << ENDPOINT << "First ECT packet acked on active path.";
-    QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 2, 2);
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 2, 3);
     default_path_.ecn_marked_packet_acked = true;
   }
 }
@@ -4073,11 +4073,6 @@
 
 QuicEcnCodepoint QuicConnection::GetEcnCodepointToSend(
     const QuicSocketAddress& destination_address) const {
-  const QuicEcnCodepoint original_codepoint =
-      packet_writer_params_.ecn_codepoint;
-  if (disable_ecn_codepoint_validation_) {
-    return original_codepoint;
-  }
   // Don't send ECN marks on alternate paths. Sending ECN marks might
   // cause the connectivity check to fail on some networks.
   if (destination_address != peer_address()) {
@@ -4088,23 +4083,7 @@
   if (in_probe_time_out_ && !default_path_.ecn_marked_packet_acked) {
     return ECN_NOT_ECT;
   }
-  switch (original_codepoint) {
-    case ECN_NOT_ECT:
-      break;
-    case ECN_ECT0:
-      if (!sent_packet_manager_.GetSendAlgorithm()->SupportsECT0()) {
-        return ECN_NOT_ECT;
-      }
-      break;
-    case ECN_ECT1:
-      if (!sent_packet_manager_.GetSendAlgorithm()->SupportsECT1()) {
-        return ECN_NOT_ECT;
-      }
-      break;
-    case ECN_CE:
-      return ECN_NOT_ECT;
-  }
-  return original_codepoint;
+  return packet_writer_params_.ecn_codepoint;
 }
 
 WriteResult QuicConnection::SendPacketToWriter(
@@ -7371,5 +7350,35 @@
       << context.peer_address() << " after successful validation";
 }
 
+bool QuicConnection::set_ecn_codepoint(QuicEcnCodepoint ecn_codepoint) {
+  if (!GetQuicReloadableFlag(quic_send_ect1)) {
+    return false;
+  }
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 3, 3);
+  if (disable_ecn_codepoint_validation_ || ecn_codepoint == ECN_NOT_ECT) {
+    packet_writer_params_.ecn_codepoint = ecn_codepoint;
+    return true;
+  }
+  switch (ecn_codepoint) {
+    case ECN_NOT_ECT:
+      QUICHE_DCHECK(false);
+      break;
+    case ECN_ECT0:
+      if (!sent_packet_manager_.GetSendAlgorithm()->SupportsECT0()) {
+        return false;
+      }
+      break;
+    case ECN_ECT1:
+      if (!sent_packet_manager_.GetSendAlgorithm()->SupportsECT1()) {
+        return false;
+      }
+      break;
+    case ECN_CE:
+      return false;
+  }
+  packet_writer_params_.ecn_codepoint = ecn_codepoint;
+  return true;
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 }  // namespace quic
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index a89e915..37e558d 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -1322,12 +1322,9 @@
   // Sets the ECN marking for all outgoing packets, assuming that the congestion
   // control supports that codepoint. QuicConnection will revert to sending
   // ECN_NOT_ECT if there is evidence the path is dropping ECN-marked packets,
-  // or if the peer provides invalid ECN feedback.
-  void set_ecn_codepoint(QuicEcnCodepoint ecn_codepoint) {
-    if (GetQuicReloadableFlag(quic_send_ect1)) {
-      packet_writer_params_.ecn_codepoint = ecn_codepoint;
-    }
-  }
+  // or if the peer provides invalid ECN feedback. Returns false if the current
+  // configuration prevents setting the desired codepoint.
+  bool set_ecn_codepoint(QuicEcnCodepoint ecn_codepoint);
 
   QuicEcnCodepoint ecn_codepoint() const {
     return packet_writer_params_.ecn_codepoint;
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 4e326fd..58bb93a 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -17111,15 +17111,19 @@
 TEST_P(QuicConnectionTest, EcnCodepointsRejected) {
   SetQuicReloadableFlag(quic_send_ect1, true);
   for (QuicEcnCodepoint ecn : {ECN_NOT_ECT, ECN_ECT0, ECN_ECT1, ECN_CE}) {
-    connection_.set_ecn_codepoint(ecn);
     if (ecn == ECN_ECT0) {
       EXPECT_CALL(*send_algorithm_, SupportsECT0()).WillOnce(Return(false));
     } else if (ecn == ECN_ECT1) {
       EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillOnce(Return(false));
     }
+    if (ecn == ECN_NOT_ECT) {
+      EXPECT_TRUE(connection_.set_ecn_codepoint(ecn));
+    } else {
+      EXPECT_FALSE(connection_.set_ecn_codepoint(ecn));
+    }
+    EXPECT_EQ(connection_.ecn_codepoint(), ECN_NOT_ECT);
     EXPECT_CALL(connection_, OnSerializedPacket(_));
     SendPing();
-    EXPECT_EQ(connection_.ecn_codepoint(), ecn);
     EXPECT_EQ(writer_->last_ecn_sent(), ECN_NOT_ECT);
   }
 }
@@ -17127,19 +17131,23 @@
 TEST_P(QuicConnectionTest, EcnCodepointsAccepted) {
   SetQuicReloadableFlag(quic_send_ect1, true);
   for (QuicEcnCodepoint ecn : {ECN_NOT_ECT, ECN_ECT0, ECN_ECT1, ECN_CE}) {
-    connection_.set_ecn_codepoint(ecn);
     if (ecn == ECN_ECT0) {
       EXPECT_CALL(*send_algorithm_, SupportsECT0()).WillOnce(Return(true));
     } else if (ecn == ECN_ECT1) {
       EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillOnce(Return(true));
     }
+    if (ecn == ECN_CE) {
+      EXPECT_FALSE(connection_.set_ecn_codepoint(ecn));
+    } else {
+      EXPECT_TRUE(connection_.set_ecn_codepoint(ecn));
+    }
     EXPECT_CALL(connection_, OnSerializedPacket(_));
     SendPing();
     QuicEcnCodepoint expected_codepoint = ecn;
     if (ecn == ECN_CE) {
-      expected_codepoint = ECN_NOT_ECT;
+      expected_codepoint = ECN_ECT1;
     }
-    EXPECT_EQ(connection_.ecn_codepoint(), ecn);
+    EXPECT_EQ(connection_.ecn_codepoint(), expected_codepoint);
     EXPECT_EQ(writer_->last_ecn_sent(), expected_codepoint);
   }
 }
@@ -17147,7 +17155,7 @@
 TEST_P(QuicConnectionTest, EcnCodepointsRejectedIfFlagIsFalse) {
   SetQuicReloadableFlag(quic_send_ect1, false);
   for (QuicEcnCodepoint ecn : {ECN_NOT_ECT, ECN_ECT0, ECN_ECT1, ECN_CE}) {
-    connection_.set_ecn_codepoint(ecn);
+    EXPECT_FALSE(connection_.set_ecn_codepoint(ecn));
     EXPECT_CALL(connection_, OnSerializedPacket(_));
     SendPing();
     EXPECT_EQ(connection_.ecn_codepoint(), ECN_NOT_ECT);
@@ -17159,7 +17167,7 @@
   SetQuicReloadableFlag(quic_send_ect1, true);
   QuicConnectionPeer::DisableEcnCodepointValidation(&connection_);
   for (QuicEcnCodepoint ecn : {ECN_NOT_ECT, ECN_ECT0, ECN_ECT1, ECN_CE}) {
-    connection_.set_ecn_codepoint(ecn);
+    EXPECT_TRUE(connection_.set_ecn_codepoint(ecn));
     EXPECT_CALL(connection_, OnSerializedPacket(_));
     SendPing();
     EXPECT_EQ(connection_.ecn_codepoint(), ecn);
@@ -17169,8 +17177,8 @@
 
 TEST_P(QuicConnectionTest, RtoDisablesEcnMarking) {
   SetQuicReloadableFlag(quic_send_ect1, true);
-  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  connection_.set_ecn_codepoint(ECN_ECT1);
+  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillOnce(Return(true));
+  EXPECT_TRUE(connection_.set_ecn_codepoint(ECN_ECT1));
   QuicPacketCreatorPeer::SetPacketNumber(
       QuicConnectionPeer::GetPacketCreator(&connection_), 1);
   SendPing();
@@ -17184,8 +17192,9 @@
 }
 
 TEST_P(QuicConnectionTest, RtoDoesntDisableEcnMarkingIfEcnAcked) {
-  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  connection_.set_ecn_codepoint(ECN_ECT1);
+  SetQuicReloadableFlag(quic_send_ect1, true);
+  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillOnce(Return(true));
+  EXPECT_TRUE(connection_.set_ecn_codepoint(ECN_ECT1));
   QuicPacketCreatorPeer::SetPacketNumber(
       QuicConnectionPeer::GetPacketCreator(&connection_), 1);
   if (!GetQuicReloadableFlag(quic_send_ect1)) {
@@ -17209,8 +17218,8 @@
 
 TEST_P(QuicConnectionTest, InvalidFeedbackCancelsEcn) {
   SetQuicReloadableFlag(quic_send_ect1, true);
-  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  connection_.set_ecn_codepoint(ECN_ECT1);
+  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillOnce(Return(true));
+  EXPECT_TRUE(connection_.set_ecn_codepoint(ECN_ECT1));
   EXPECT_EQ(connection_.ecn_codepoint(), ECN_ECT1);
   if (!GetQuicReloadableFlag(quic_send_ect1)) {
     EXPECT_QUIC_BUG(connection_.OnInvalidEcnFeedback(),
@@ -17224,8 +17233,8 @@
 
 TEST_P(QuicConnectionTest, StateMatchesSentEcn) {
   SetQuicReloadableFlag(quic_send_ect1, true);
-  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  connection_.set_ecn_codepoint(ECN_ECT1);
+  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillOnce(Return(true));
+  EXPECT_TRUE(connection_.set_ecn_codepoint(ECN_ECT1));
   SendPing();
   QuicSentPacketManager* sent_packet_manager =
       QuicConnectionPeer::GetSentPacketManager(&connection_);
@@ -17240,8 +17249,8 @@
     return;
   }
   SetQuicReloadableFlag(quic_send_ect1, true);
-  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  connection_.set_ecn_codepoint(ECN_ECT1);
+  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillOnce(Return(true));
+  EXPECT_TRUE(connection_.set_ecn_codepoint(ECN_ECT1));
   // All these steps are necessary to send an INITIAL ping and save it to be
   // coalesced, instead of just calling SendPing() and sending it immediately.
   char buffer[1000];
@@ -17253,9 +17262,9 @@
       creator_, frames, buffer, sizeof(buffer));
   connection_.SendOrQueuePacket(std::move(packet1));
   creator_->set_encryption_level(ENCRYPTION_FORWARD_SECURE);
-  EXPECT_CALL(*send_algorithm_, SupportsECT0()).WillRepeatedly(Return(true));
+  EXPECT_CALL(*send_algorithm_, SupportsECT0()).WillOnce(Return(true));
   // If not for the line below, these packets would coalesce.
-  connection_.set_ecn_codepoint(ECN_ECT0);
+  EXPECT_TRUE(connection_.set_ecn_codepoint(ECN_ECT0));
   EXPECT_EQ(writer_->packets_write_attempts(), 0);
   SendPing();
   EXPECT_EQ(writer_->packets_write_attempts(), 2);
@@ -17264,13 +17273,13 @@
 
 TEST_P(QuicConnectionTest, BufferedPacketRetainsOldEcn) {
   SetQuicReloadableFlag(quic_send_ect1, true);
-  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  connection_.set_ecn_codepoint(ECN_ECT1);
+  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillOnce(Return(true));
+  EXPECT_TRUE(connection_.set_ecn_codepoint(ECN_ECT1));
   writer_->SetWriteBlocked();
   EXPECT_CALL(visitor_, OnWriteBlocked()).Times(2);
   SendPing();
-  EXPECT_CALL(*send_algorithm_, SupportsECT0()).WillRepeatedly(Return(true));
-  connection_.set_ecn_codepoint(ECN_ECT0);
+  EXPECT_CALL(*send_algorithm_, SupportsECT0()).WillOnce(Return(true));
+  EXPECT_TRUE(connection_.set_ecn_codepoint(ECN_ECT0));
   writer_->SetWritable();
   connection_.OnCanWrite();
   EXPECT_EQ(writer_->last_ecn_sent(), ECN_ECT1);
diff --git a/quiche/quic/core/quic_sent_packet_manager.cc b/quiche/quic/core/quic_sent_packet_manager.cc
index 6dc502e..2f030ec 100644
--- a/quiche/quic/core/quic_sent_packet_manager.cc
+++ b/quiche/quic/core/quic_sent_packet_manager.cc
@@ -1421,7 +1421,7 @@
   // Validate ECN feedback.
   absl::optional<QuicEcnCounts> valid_ecn_counts;
   if (GetQuicReloadableFlag(quic_send_ect1)) {
-    QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 1, 2);
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 1, 3);
     if (IsEcnFeedbackValid(acked_packet_number_space, ecn_counts,
                            newly_acked_ect0, newly_acked_ect1)) {
       valid_ecn_counts = ecn_counts;