Record sent ECN marks correctly for coalesced and buffered packets.

Repro of flaky test failure now passes.

Protected by quic_reloadable_flag_quic_send_ect1.

PiperOrigin-RevId: 529557120
diff --git a/quiche/quic/core/quic_coalesced_packet.cc b/quiche/quic/core/quic_coalesced_packet.cc
index 802fac4..67091d6 100644
--- a/quiche/quic/core/quic_coalesced_packet.cc
+++ b/quiche/quic/core/quic_coalesced_packet.cc
@@ -11,7 +11,7 @@
 namespace quic {
 
 QuicCoalescedPacket::QuicCoalescedPacket()
-    : length_(0), max_packet_length_(0) {}
+    : length_(0), max_packet_length_(0), ecn_codepoint_(ECN_NOT_ECT) {}
 
 QuicCoalescedPacket::~QuicCoalescedPacket() { Clear(); }
 
@@ -19,7 +19,8 @@
     const SerializedPacket& packet, const QuicSocketAddress& self_address,
     const QuicSocketAddress& peer_address,
     quiche::QuicheBufferAllocator* allocator,
-    QuicPacketLength current_max_packet_length) {
+    QuicPacketLength current_max_packet_length,
+    QuicEcnCodepoint ecn_codepoint) {
   if (packet.encrypted_length == 0) {
     QUIC_BUG(quic_bug_10611_1) << "Trying to coalesce an empty packet";
     return true;
@@ -52,6 +53,10 @@
       // Do not coalesce packets of the same encryption level.
       return false;
     }
+    if (ecn_codepoint != ecn_codepoint_) {
+      // Do not coalesce packets with different ECN codepoints.
+      return false;
+    }
   }
 
   if (length_ + packet.encrypted_length > max_packet_length_) {
@@ -66,6 +71,7 @@
   if (length_ > 0) {
     QUIC_CODE_COUNT(QUIC_SUCCESSFULLY_COALESCED_MULTIPLE_PACKETS);
   }
+  ecn_codepoint_ = ecn_codepoint;
   length_ += packet.encrypted_length;
   transmission_types_[packet.encryption_level] = packet.transmission_type;
   if (packet.encryption_level == ENCRYPTION_INITIAL) {
diff --git a/quiche/quic/core/quic_coalesced_packet.h b/quiche/quic/core/quic_coalesced_packet.h
index 21a6e1e..b362937 100644
--- a/quiche/quic/core/quic_coalesced_packet.h
+++ b/quiche/quic/core/quic_coalesced_packet.h
@@ -26,7 +26,8 @@
                            const QuicSocketAddress& self_address,
                            const QuicSocketAddress& peer_address,
                            quiche::QuicheBufferAllocator* allocator,
-                           QuicPacketLength current_max_packet_length);
+                           QuicPacketLength current_max_packet_length,
+                           QuicEcnCodepoint ecn_codepoint);
 
   // Clears this coalesced packet.
   void Clear();
@@ -66,6 +67,8 @@
 
   std::vector<size_t> packet_lengths() const;
 
+  QuicEcnCodepoint ecn_codepoint() const { return ecn_codepoint_; }
+
  private:
   friend class test::QuicCoalescedPacketPeer;
 
@@ -89,6 +92,9 @@
   // frames are copied to allow it be re-serialized when this coalesced packet
   // gets sent.
   std::unique_ptr<SerializedPacket> initial_packet_;
+
+  // A coalesced packet shares an ECN codepoint.
+  QuicEcnCodepoint ecn_codepoint_;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/quic_coalesced_packet_test.cc b/quiche/quic/core/quic_coalesced_packet_test.cc
index eb69372..00ba7d8 100644
--- a/quiche/quic/core/quic_coalesced_packet_test.cc
+++ b/quiche/quic/core/quic_coalesced_packet_test.cc
@@ -31,7 +31,7 @@
   packet1.retransmittable_frames.push_back(
       QuicFrame(QuicStreamFrame(1, true, 0, 100)));
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet1, self_address, peer_address,
-                                            &allocator, 1500));
+                                            &allocator, 1500, ECN_NOT_ECT));
   EXPECT_EQ(PTO_RETRANSMISSION,
             coalesced.TransmissionTypeOfPacket(ENCRYPTION_INITIAL));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
@@ -40,12 +40,14 @@
   EXPECT_EQ(
       "total_length: 1500 padding_size: 1000 packets: {ENCRYPTION_INITIAL}",
       coalesced.ToString(1500));
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
 
   // Cannot coalesce packet of the same encryption level.
   SerializedPacket packet2(QuicPacketNumber(2), PACKET_4BYTE_PACKET_NUMBER,
                            buffer, 500, false, false);
-  EXPECT_FALSE(coalesced.MaybeCoalescePacket(packet2, self_address,
-                                             peer_address, &allocator, 1500));
+  EXPECT_FALSE(coalesced.MaybeCoalescePacket(
+      packet2, self_address, peer_address, &allocator, 1500, ECN_NOT_ECT));
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
 
   SerializedPacket packet3(QuicPacketNumber(3), PACKET_4BYTE_PACKET_NUMBER,
                            buffer, 500, false, false);
@@ -53,7 +55,7 @@
   packet3.encryption_level = ENCRYPTION_ZERO_RTT;
   packet3.transmission_type = LOSS_RETRANSMISSION;
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet3, self_address, peer_address,
-                                            &allocator, 1500));
+                                            &allocator, 1500, ECN_NOT_ECT));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1000u, coalesced.length());
   EXPECT_EQ(2u, coalesced.NumberOfPackets());
@@ -63,6 +65,7 @@
       "total_length: 1500 padding_size: 500 packets: {ENCRYPTION_INITIAL, "
       "ENCRYPTION_ZERO_RTT}",
       coalesced.ToString(1500));
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
 
   SerializedPacket packet4(QuicPacketNumber(4), PACKET_4BYTE_PACKET_NUMBER,
                            buffer, 500, false, false);
@@ -70,28 +73,31 @@
   // Cannot coalesce packet of changed self/peer address.
   EXPECT_FALSE(coalesced.MaybeCoalescePacket(
       packet4, QuicSocketAddress(QuicIpAddress::Loopback4(), 3), peer_address,
-      &allocator, 1500));
+      &allocator, 1500, ECN_NOT_ECT));
 
   // Packet does not fit.
   SerializedPacket packet5(QuicPacketNumber(5), PACKET_4BYTE_PACKET_NUMBER,
                            buffer, 501, false, false);
   packet5.encryption_level = ENCRYPTION_FORWARD_SECURE;
-  EXPECT_FALSE(coalesced.MaybeCoalescePacket(packet5, self_address,
-                                             peer_address, &allocator, 1500));
+  EXPECT_FALSE(coalesced.MaybeCoalescePacket(
+      packet5, self_address, peer_address, &allocator, 1500, ECN_NOT_ECT));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1000u, coalesced.length());
   EXPECT_EQ(2u, coalesced.NumberOfPackets());
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
 
   // Max packet number length changed.
   SerializedPacket packet6(QuicPacketNumber(6), PACKET_4BYTE_PACKET_NUMBER,
                            buffer, 100, false, false);
   packet6.encryption_level = ENCRYPTION_FORWARD_SECURE;
-  EXPECT_QUIC_BUG(coalesced.MaybeCoalescePacket(packet6, self_address,
-                                                peer_address, &allocator, 1000),
-                  "Max packet length changes in the middle of the write path");
+  EXPECT_QUIC_BUG(
+      coalesced.MaybeCoalescePacket(packet6, self_address, peer_address,
+                                    &allocator, 1000, ECN_NOT_ECT),
+      "Max packet length changes in the middle of the write path");
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1000u, coalesced.length());
   EXPECT_EQ(2u, coalesced.NumberOfPackets());
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
 }
 
 TEST(QuicCoalescedPacketTest, CopyEncryptedBuffers) {
@@ -111,10 +117,11 @@
   packet2.encryption_level = ENCRYPTION_FORWARD_SECURE;
 
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet1, self_address, peer_address,
-                                            &allocator, 1500));
+                                            &allocator, 1500, ECN_NOT_ECT));
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet2, self_address, peer_address,
-                                            &allocator, 1500));
+                                            &allocator, 1500, ECN_NOT_ECT));
   EXPECT_EQ(1000u, coalesced.length());
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
 
   char copy_buffer[1000];
   size_t length_copied = 0;
@@ -152,7 +159,7 @@
   packet1.retransmittable_frames.push_back(
       QuicFrame(QuicStreamFrame(1, true, 0, 100)));
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet1, self_address, peer_address,
-                                            &allocator, 1500));
+                                            &allocator, 1500, ECN_NOT_ECT));
   EXPECT_EQ(PTO_RETRANSMISSION,
             coalesced.TransmissionTypeOfPacket(ENCRYPTION_INITIAL));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
@@ -160,16 +167,17 @@
   EXPECT_EQ(
       "total_length: 1500 padding_size: 1000 packets: {ENCRYPTION_INITIAL}",
       coalesced.ToString(1500));
-  // Neuter initial packet.
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
   coalesced.NeuterInitialPacket();
   EXPECT_EQ(0u, coalesced.max_packet_length());
   EXPECT_EQ(0u, coalesced.length());
   EXPECT_EQ("total_length: 0 padding_size: 0 packets: {}",
             coalesced.ToString(0));
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
 
   // Coalesce initial packet again.
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet1, self_address, peer_address,
-                                            &allocator, 1500));
+                                            &allocator, 1500, ECN_NOT_ECT));
 
   SerializedPacket packet2(QuicPacketNumber(3), PACKET_4BYTE_PACKET_NUMBER,
                            buffer, 500, false, false);
@@ -177,7 +185,7 @@
   packet2.encryption_level = ENCRYPTION_ZERO_RTT;
   packet2.transmission_type = LOSS_RETRANSMISSION;
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet2, self_address, peer_address,
-                                            &allocator, 1500));
+                                            &allocator, 1500, ECN_NOT_ECT));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1000u, coalesced.length());
   EXPECT_EQ(LOSS_RETRANSMISSION,
@@ -186,6 +194,7 @@
       "total_length: 1500 padding_size: 500 packets: {ENCRYPTION_INITIAL, "
       "ENCRYPTION_ZERO_RTT}",
       coalesced.ToString(1500));
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
 
   // Neuter initial packet.
   coalesced.NeuterInitialPacket();
@@ -194,18 +203,52 @@
   EXPECT_EQ(
       "total_length: 1500 padding_size: 1000 packets: {ENCRYPTION_ZERO_RTT}",
       coalesced.ToString(1500));
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
 
   SerializedPacket packet3(QuicPacketNumber(5), PACKET_4BYTE_PACKET_NUMBER,
                            buffer, 501, false, false);
   packet3.encryption_level = ENCRYPTION_FORWARD_SECURE;
   EXPECT_TRUE(coalesced.MaybeCoalescePacket(packet3, self_address, peer_address,
-                                            &allocator, 1500));
+                                            &allocator, 1500, ECN_NOT_ECT));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1001u, coalesced.length());
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
   // Neuter initial packet.
   coalesced.NeuterInitialPacket();
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1001u, coalesced.length());
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
+}
+
+TEST(QuicCoalescedPacketTest, DoNotCoalesceDifferentEcn) {
+  QuicCoalescedPacket coalesced;
+  EXPECT_EQ("total_length: 0 padding_size: 0 packets: {}",
+            coalesced.ToString(0));
+  quiche::SimpleBufferAllocator allocator;
+  EXPECT_EQ(0u, coalesced.length());
+  EXPECT_EQ(0u, coalesced.NumberOfPackets());
+  char buffer[1000];
+  QuicSocketAddress self_address(QuicIpAddress::Loopback4(), 1);
+  QuicSocketAddress peer_address(QuicIpAddress::Loopback4(), 2);
+  SerializedPacket packet1(QuicPacketNumber(1), PACKET_4BYTE_PACKET_NUMBER,
+                           buffer, 500, false, false);
+  packet1.transmission_type = PTO_RETRANSMISSION;
+  QuicAckFrame ack_frame(InitAckFrame(1));
+  packet1.nonretransmittable_frames.push_back(QuicFrame(&ack_frame));
+  packet1.retransmittable_frames.push_back(
+      QuicFrame(QuicStreamFrame(1, true, 0, 100)));
+  ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet1, self_address, peer_address,
+                                            &allocator, 1500, ECN_ECT1));
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_ECT1);
+
+  SerializedPacket packet2(QuicPacketNumber(2), PACKET_4BYTE_PACKET_NUMBER,
+                           buffer, 500, false, false);
+  packet2.nonretransmittable_frames.push_back(QuicFrame(QuicPaddingFrame(100)));
+  packet2.encryption_level = ENCRYPTION_ZERO_RTT;
+  packet2.transmission_type = LOSS_RETRANSMISSION;
+  EXPECT_FALSE(coalesced.MaybeCoalescePacket(
+      packet2, self_address, peer_address, &allocator, 1500, ECN_NOT_ECT));
+  EXPECT_EQ(coalesced.ecn_codepoint(), ECN_ECT1);
 }
 
 }  // namespace
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 9fd036f..4ff1e85 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -3119,7 +3119,7 @@
     const BufferedPacket& packet = buffered_packets_.front();
     WriteResult result = SendPacketToWriter(
         packet.data.get(), packet.length, packet.self_address.host(),
-        packet.peer_address, per_packet_options_, writer_);
+        packet.peer_address, writer_, packet.ecn_codepoint);
     QUIC_DVLOG(1) << ENDPOINT << "Sending buffered packet, result: " << result;
     if (IsMsgTooBig(writer_, result) && packet.length > long_term_mtu_) {
       // When MSG_TOO_BIG is returned, the system typically knows what the
@@ -3438,7 +3438,8 @@
       if (!coalesced_packet_.MaybeCoalescePacket(
               *packet, send_from_address, send_to_address,
               helper_->GetStreamSendBufferAllocator(),
-              packet_creator_.max_packet_length())) {
+              packet_creator_.max_packet_length(),
+              GetEcnCodepointToSend(send_to_address))) {
         // Failed to coalesce packet, flush current coalesced packet.
         if (!FlushCoalescedPacket()) {
           QUIC_BUG_IF(quic_connection_connected_after_flush_coalesced_failure,
@@ -3451,7 +3452,8 @@
         if (!coalesced_packet_.MaybeCoalescePacket(
                 *packet, send_from_address, send_to_address,
                 helper_->GetStreamSendBufferAllocator(),
-                packet_creator_.max_packet_length())) {
+                packet_creator_.max_packet_length(),
+                GetEcnCodepointToSend(send_to_address))) {
           // Failed to coalesce packet even it is the only packet, raise a write
           // error.
           QUIC_DLOG(ERROR) << ENDPOINT << "Failed to coalesce packet";
@@ -3466,12 +3468,14 @@
         packet_creator_.SetSoftMaxPacketLength(
             coalesced_packet_.max_packet_length() - coalesced_packet_.length());
       }
+      last_ecn_codepoint_sent_ = coalesced_packet_.ecn_codepoint();
       break;
     case BUFFER:
       QUIC_DVLOG(1) << ENDPOINT << "Adding packet: " << packet->packet_number
                     << " to buffered packets";
+      last_ecn_codepoint_sent_ = GetEcnCodepointToSend(send_to_address);
       buffered_packets_.emplace_back(*packet, send_from_address,
-                                     send_to_address);
+                                     send_to_address, last_ecn_codepoint_sent_);
       break;
     case SEND_TO_WRITER:
       // Stop using coalescer from now on.
@@ -3484,9 +3488,9 @@
       //
       // writer_->WritePacket transfers buffer ownership back to the writer.
       packet->release_encrypted_buffer = nullptr;
-      result = SendPacketToWriter(packet->encrypted_buffer, encrypted_length,
-                                  send_from_address.host(), send_to_address,
-                                  per_packet_options_, writer_);
+      result = SendPacketToWriter(
+          packet->encrypted_buffer, encrypted_length, send_from_address.host(),
+          send_to_address, writer_, GetEcnCodepointToSend(send_to_address));
       // This is a work around for an issue with linux UDP GSO batch writers.
       // When sending a GSO packet with 2 segments, if the first segment is
       // larger than the path MTU, instead of EMSGSIZE, the linux kernel returns
@@ -3521,7 +3525,7 @@
       QUIC_DVLOG(1) << ENDPOINT << "Adding packet: " << packet->packet_number
                     << " to buffered packets";
       buffered_packets_.emplace_back(*packet, send_from_address,
-                                     send_to_address);
+                                     send_to_address, last_ecn_codepoint_sent_);
     }
   }
 
@@ -3946,7 +3950,8 @@
       first_serialized_one_rtt_packet_ == nullptr &&
       serialized_packet.encryption_level == ENCRYPTION_FORWARD_SECURE) {
     first_serialized_one_rtt_packet_ = std::make_unique<BufferedPacket>(
-        serialized_packet, self_address(), peer_address());
+        serialized_packet, self_address(), peer_address(),
+        GetEcnCodepointToSend(peer_address()));
   }
   SendOrQueuePacket(std::move(serialized_packet));
 }
@@ -4212,13 +4217,13 @@
 
 WriteResult QuicConnection::SendPacketToWriter(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
-    const QuicSocketAddress& destination_address, PerPacketOptions* options,
-    QuicPacketWriter* writer) {
+    const QuicSocketAddress& destination_address, QuicPacketWriter* writer,
+    const QuicEcnCodepoint ecn_codepoint) {
   QuicEcnCodepoint original_codepoint = GetNextEcnCodepoint();
-  last_ecn_codepoint_sent_ = GetEcnCodepointToSend(destination_address);
-  MaybeSetEcnCodepoint(last_ecn_codepoint_sent_);
-  WriteResult result = writer->WritePacket(buffer, buf_len, self_address,
-                                           destination_address, options);
+  last_ecn_codepoint_sent_ = ecn_codepoint;
+  MaybeSetEcnCodepoint(ecn_codepoint);
+  WriteResult result = writer->WritePacket(
+      buffer, buf_len, self_address, destination_address, per_packet_options_);
   MaybeSetEcnCodepoint(original_codepoint);
   return result;
 }
@@ -4970,17 +4975,18 @@
 
 QuicConnection::BufferedPacket::BufferedPacket(
     const SerializedPacket& packet, const QuicSocketAddress& self_address,
-    const QuicSocketAddress& peer_address)
+    const QuicSocketAddress& peer_address, const QuicEcnCodepoint ecn_codepoint)
     : BufferedPacket(packet.encrypted_buffer, packet.encrypted_length,
-                     self_address, peer_address) {}
+                     self_address, peer_address, ecn_codepoint) {}
 
 QuicConnection::BufferedPacket::BufferedPacket(
     const char* encrypted_buffer, QuicPacketLength encrypted_length,
     const QuicSocketAddress& self_address,
-    const QuicSocketAddress& peer_address)
+    const QuicSocketAddress& peer_address, const QuicEcnCodepoint ecn_codepoint)
     : length(encrypted_length),
       self_address(self_address),
-      peer_address(peer_address) {
+      peer_address(peer_address),
+      ecn_codepoint(ecn_codepoint) {
   data = std::make_unique<char[]>(encrypted_length);
   memcpy(data.get(), encrypted_buffer, encrypted_length);
 }
@@ -5159,7 +5165,7 @@
                        packet->encrypted_buffer, packet->encrypted_length));
   WriteResult result = SendPacketToWriter(
       packet->encrypted_buffer, packet->encrypted_length, self_address.host(),
-      peer_address, per_packet_options_, writer);
+      peer_address, writer, GetEcnCodepointToSend(peer_address));
 
   // If using a batch writer and the probing packet is buffered, flush it.
   if (writer->IsBatchMode() && result.status == WRITE_STATUS_OK &&
@@ -6057,11 +6063,13 @@
                   << "Buffering coalesced packet of len: " << length;
     buffered_packets_.emplace_back(
         buffer, static_cast<QuicPacketLength>(length),
-        coalesced_packet_.self_address(), coalesced_packet_.peer_address());
+        coalesced_packet_.self_address(), coalesced_packet_.peer_address(),
+        coalesced_packet_.ecn_codepoint());
   } else {
     WriteResult result = SendPacketToWriter(
         buffer, length, coalesced_packet_.self_address().host(),
-        coalesced_packet_.peer_address(), per_packet_options_, writer_);
+        coalesced_packet_.peer_address(), writer_,
+        coalesced_packet_.ecn_codepoint());
     if (IsWriteError(result.status)) {
       OnWriteError(result.error_code);
       return false;
@@ -6073,7 +6081,8 @@
                       << "Buffering coalesced packet of len: " << length;
         buffered_packets_.emplace_back(
             buffer, static_cast<QuicPacketLength>(length),
-            coalesced_packet_.self_address(), coalesced_packet_.peer_address());
+            coalesced_packet_.self_address(), coalesced_packet_.peer_address(),
+            coalesced_packet_.ecn_codepoint());
       }
     }
   }
@@ -6425,7 +6434,7 @@
         buffered_packets_.emplace_back(
             first_serialized_one_rtt_packet_->data.get(),
             first_serialized_one_rtt_packet_->length, self_address(),
-            peer_address());
+            peer_address(), first_serialized_one_rtt_packet_->ecn_codepoint);
         packet_buffered = true;
       }
       break;
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 09c1eed..5645e96 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -1480,11 +1480,13 @@
   struct QUIC_EXPORT_PRIVATE BufferedPacket {
     BufferedPacket(const SerializedPacket& packet,
                    const QuicSocketAddress& self_address,
-                   const QuicSocketAddress& peer_address);
+                   const QuicSocketAddress& peer_address,
+                   QuicEcnCodepoint ecn_codepoint);
     BufferedPacket(const char* encrypted_buffer,
                    QuicPacketLength encrypted_length,
                    const QuicSocketAddress& self_address,
-                   const QuicSocketAddress& peer_address);
+                   const QuicSocketAddress& peer_address,
+                   QuicEcnCodepoint ecn_codepoint);
     // Please note, this buffered packet contains random bytes (and is not
     // *actually* a QUIC packet).
     BufferedPacket(QuicRandom& random, QuicPacketLength encrypted_length,
@@ -1500,6 +1502,7 @@
     // Self and peer addresses when the packet is serialized.
     const QuicSocketAddress self_address;
     const QuicSocketAddress peer_address;
+    QuicEcnCodepoint ecn_codepoint = ECN_NOT_ECT;
   };
 
   // ReceivedPacketInfo comprises the received packet information.
@@ -1983,18 +1986,13 @@
   // Set the ECN codepoint, but only if set_per_packet_options has been called.
   void MaybeSetEcnCodepoint(QuicEcnCodepoint ecn_codepoint);
 
-  // Writes the packet to |writer| with the ECN mark specified in |options|. If
-  // by spec the connection should not send an ECN mark, or the packet is
-  // not on the default path, or it's PTO probe before an ECN packet has been
-  // successfully acked on the path, or QUIC reloadable flag quic_send_ect1 is
-  // false, then it sends Not-ECT instead. Will also set last_ecn_sent_
-  // appropriately. At the end, restores the original setting unless the flag
-  // is false.
+  // Writes the packet to |writer| with the ECN mark specified in
+  // |ecn_codepoint|. Will also set last_ecn_sent_ appropriately.
   WriteResult SendPacketToWriter(const char* buffer, size_t buf_len,
                                  const QuicIpAddress& self_address,
                                  const QuicSocketAddress& destination_address,
-                                 PerPacketOptions* options,
-                                 QuicPacketWriter* writer);
+                                 QuicPacketWriter* writer,
+                                 const QuicEcnCodepoint ecn_codepoint);
 
   QuicConnectionContext context_;
 
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 64a0358..6a0e9d0 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -17571,6 +17571,66 @@
   EXPECT_EQ(per_packet_options.ecn_codepoint, ECN_NOT_ECT);
 }
 
+TEST_P(QuicConnectionTest, StateMatchesSentEcn) {
+  SetQuicReloadableFlag(quic_send_ect1, true);
+  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
+  TestPerPacketOptions per_packet_options;
+  per_packet_options.ecn_codepoint = ECN_ECT1;
+  connection_.set_per_packet_options(&per_packet_options);
+  SendPing();
+  QuicSentPacketManager* sent_packet_manager =
+      QuicConnectionPeer::GetSentPacketManager(&connection_);
+  EXPECT_EQ(writer_->last_ecn_sent(), ECN_ECT1);
+  EXPECT_EQ(
+      QuicSentPacketManagerPeer::GetEct1Sent(sent_packet_manager, INITIAL_DATA),
+      1);
+}
+
+TEST_P(QuicConnectionTest, CoalescedPacketSplitsEcn) {
+  if (!connection_.version().CanSendCoalescedPackets()) {
+    return;
+  }
+  SetQuicReloadableFlag(quic_send_ect1, true);
+  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
+  TestPerPacketOptions per_packet_options;
+  per_packet_options.ecn_codepoint = ECN_ECT1;
+  connection_.set_per_packet_options(&per_packet_options);
+  // 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];
+  creator_->set_encryption_level(ENCRYPTION_INITIAL);
+  QuicFrames frames;
+  QuicPingFrame ping;
+  frames.emplace_back(QuicFrame(ping));
+  SerializedPacket packet1 = QuicPacketCreatorPeer::SerializeAllFrames(
+      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));
+  // If not for the line below, these packets would coalesce.
+  per_packet_options.ecn_codepoint = ECN_ECT0;
+  EXPECT_EQ(writer_->packets_write_attempts(), 0);
+  SendPing();
+  EXPECT_EQ(writer_->packets_write_attempts(), 2);
+  EXPECT_EQ(writer_->last_ecn_sent(), ECN_ECT0);
+}
+
+TEST_P(QuicConnectionTest, BufferedPacketRetainsOldEcn) {
+  SetQuicReloadableFlag(quic_send_ect1, true);
+  EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
+  TestPerPacketOptions per_packet_options;
+  per_packet_options.ecn_codepoint = ECN_ECT1;
+  connection_.set_per_packet_options(&per_packet_options);
+  writer_->SetWriteBlocked();
+  EXPECT_CALL(visitor_, OnWriteBlocked()).Times(2);
+  SendPing();
+  EXPECT_CALL(*send_algorithm_, SupportsECT0()).WillRepeatedly(Return(true));
+  per_packet_options.ecn_codepoint = ECN_ECT0;
+  writer_->SetWritable();
+  connection_.OnCanWrite();
+  EXPECT_EQ(writer_->last_ecn_sent(), ECN_ECT1);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/core/quic_packet_creator_test.cc b/quiche/quic/core/quic_packet_creator_test.cc
index ec8bcd7..b2fec7b 100644
--- a/quiche/quic/core/quic_packet_creator_test.cc
+++ b/quiche/quic/core/quic_packet_creator_test.cc
@@ -2276,9 +2276,9 @@
     SerializedPacket serialized = SerializeAllFrames(frames_);
     EXPECT_EQ(level, serialized.encryption_level);
     frames_.clear();
-    ASSERT_TRUE(coalesced.MaybeCoalescePacket(serialized, self_address,
-                                              peer_address, &allocator,
-                                              creator_.max_packet_length()));
+    ASSERT_TRUE(coalesced.MaybeCoalescePacket(
+        serialized, self_address, peer_address, &allocator,
+        creator_.max_packet_length(), ECN_NOT_ECT));
   }
   char buffer[kMaxOutgoingPacketSize];
   size_t coalesced_length = creator_.SerializeCoalescedPacket(
diff --git a/quiche/quic/test_tools/quic_sent_packet_manager_peer.cc b/quiche/quic/test_tools/quic_sent_packet_manager_peer.cc
index c895415..d6256fe 100644
--- a/quiche/quic/test_tools/quic_sent_packet_manager_peer.cc
+++ b/quiche/quic/test_tools/quic_sent_packet_manager_peer.cc
@@ -182,5 +182,17 @@
   return &(sent_packet_manager->peer_ack_ecn_counts_[space]);
 }
 
+// static
+QuicPacketCount QuicSentPacketManagerPeer::GetEct0Sent(
+    QuicSentPacketManager* sent_packet_manager, PacketNumberSpace space) {
+  return sent_packet_manager->ect0_packets_sent_[space];
+}
+
+// static
+QuicPacketCount QuicSentPacketManagerPeer::GetEct1Sent(
+    QuicSentPacketManager* sent_packet_manager, PacketNumberSpace space) {
+  return sent_packet_manager->ect1_packets_sent_[space];
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/test_tools/quic_sent_packet_manager_peer.h b/quiche/quic/test_tools/quic_sent_packet_manager_peer.h
index e960619..c0ea673 100644
--- a/quiche/quic/test_tools/quic_sent_packet_manager_peer.h
+++ b/quiche/quic/test_tools/quic_sent_packet_manager_peer.h
@@ -88,6 +88,12 @@
 
   static QuicEcnCounts* GetPeerEcnCounts(
       QuicSentPacketManager* sent_packet_manager, PacketNumberSpace space);
+
+  static QuicPacketCount GetEct0Sent(QuicSentPacketManager* sent_packet_manager,
+                                     PacketNumberSpace space);
+
+  static QuicPacketCount GetEct1Sent(QuicSentPacketManager* sent_packet_manager,
+                                     PacketNumberSpace space);
 };
 
 }  // namespace test