gfe-relnote: In QUIC T049+, support sending coalesced packets.

PiperOrigin-RevId: 278634718
Change-Id: If6eadcb1a165e0cf4993f60d3a4faeb108fe8ccb
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 2f54a3c..2507c7a 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -680,6 +680,26 @@
   EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
 }
 
+TEST_P(EndToEndTestWithTls, SendAndReceiveCoalescedPackets) {
+  ASSERT_TRUE(Initialize());
+  if (!GetClientConnection()->version().CanSendCoalescedPackets()) {
+    return;
+  }
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  // Verify both endpoints successfully process coalesced packets.
+  QuicConnectionStats client_stats = GetClientConnection()->GetStats();
+  EXPECT_LT(0u, client_stats.num_coalesced_packets_received);
+  EXPECT_EQ(client_stats.num_coalesced_packets_processed,
+            client_stats.num_coalesced_packets_received);
+  server_thread_->Pause();
+  QuicConnectionStats server_stats = GetServerConnection()->GetStats();
+  EXPECT_LT(0u, server_stats.num_coalesced_packets_received);
+  EXPECT_EQ(server_stats.num_coalesced_packets_processed,
+            server_stats.num_coalesced_packets_received);
+  server_thread_->Resume();
+}
+
 // Simple transaction, but set a non-default ack delay at the client
 // and ensure it gets to the server.
 TEST_P(EndToEndTest, SimpleRequestResponseWithAckDelayChange) {
diff --git a/quic/core/quic_coalesced_packet.cc b/quic/core/quic_coalesced_packet.cc
index 6d7f872..3dd9b24 100644
--- a/quic/core/quic_coalesced_packet.cc
+++ b/quic/core/quic_coalesced_packet.cc
@@ -46,8 +46,7 @@
       return false;
     }
     if (max_packet_length_ != current_max_packet_length) {
-      QUIC_DLOG(INFO)
-          << "Do not try to coalesce packet when max packet length changed.";
+      QUIC_BUG << "Max packet length changes in the middle of the write path";
       return false;
     }
     if (!encrypted_buffers_[packet.encryption_level].empty() ||
@@ -67,6 +66,9 @@
                 << ", encrypted_length: " << packet.encrypted_length
                 << ", current length: " << length_
                 << ", max_packet_length: " << max_packet_length_;
+  if (length_ > 0) {
+    QUIC_CODE_COUNT(QUIC_SUCCESSFULLY_COALESCED_MULTIPLE_PACKETS);
+  }
   length_ += packet.encrypted_length;
   if (packet.encryption_level == ENCRYPTION_INITIAL) {
     // Save a copy of ENCRYPTION_INITIAL packet (excluding encrypted buffer, as
diff --git a/quic/core/quic_coalesced_packet.h b/quic/core/quic_coalesced_packet.h
index cb4825a..f03f875 100644
--- a/quic/core/quic_coalesced_packet.h
+++ b/quic/core/quic_coalesced_packet.h
@@ -38,6 +38,10 @@
     return initial_packet_.get();
   }
 
+  const QuicSocketAddress& self_address() const { return self_address_; }
+
+  const QuicSocketAddress& peer_address() const { return peer_address_; }
+
   QuicPacketLength length() const { return length_; }
 
   QuicPacketLength max_packet_length() const { return max_packet_length_; }
diff --git a/quic/core/quic_coalesced_packet_test.cc b/quic/core/quic_coalesced_packet_test.cc
index 6eb5a61..8213480 100644
--- a/quic/core/quic_coalesced_packet_test.cc
+++ b/quic/core/quic_coalesced_packet_test.cc
@@ -4,6 +4,7 @@
 
 #include "net/third_party/quiche/src/quic/core/quic_coalesced_packet.h"
 
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
 
@@ -65,8 +66,9 @@
   SerializedPacket packet6(QuicPacketNumber(6), PACKET_4BYTE_PACKET_NUMBER,
                            buffer, 100, false, false);
   packet6.encryption_level = ENCRYPTION_FORWARD_SECURE;
-  EXPECT_FALSE(coalesced.MaybeCoalescePacket(packet6, self_address,
-                                             peer_address, &allocator, 1000));
+  EXPECT_QUIC_BUG(coalesced.MaybeCoalescePacket(packet6, self_address,
+                                                peer_address, &allocator, 1000),
+                  "Max packet length changes in the middle of the write path");
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1000u, coalesced.length());
 }
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 443ecbc..64f7eea 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -179,6 +179,17 @@
   QuicConnection* connection_;
 };
 
+// When the clearer goes out of scope, the coalesced packet gets cleared.
+class ScopedCoalescedPacketClearer {
+ public:
+  explicit ScopedCoalescedPacketClearer(QuicCoalescedPacket* coalesced)
+      : coalesced_(coalesced) {}
+  ~ScopedCoalescedPacketClearer() { coalesced_->Clear(); }
+
+ private:
+  QuicCoalescedPacket* coalesced_;  // Unowned.
+};
+
 // Whether this incoming packet is allowed to replace our connection ID.
 bool PacketCanReplaceConnectionId(const QuicPacketHeader& header,
                                   Perspective perspective) {
@@ -327,7 +338,8 @@
       bytes_sent_before_address_validation_(0),
       address_validated_(false),
       treat_queued_packets_as_sent_(
-          GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)),
+          GetQuicReloadableFlag(quic_treat_queued_packets_as_sent) ||
+          version().CanSendCoalescedPackets()),
       mtu_discovery_v2_(GetQuicReloadableFlag(quic_mtu_discovery_v2)) {
   QUIC_DLOG(INFO) << ENDPOINT << "Created connection with server connection ID "
                   << server_connection_id
@@ -2197,7 +2209,8 @@
                     ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
     return true;
   }
-  SerializedPacketFate fate = DeterminePacketFate();
+  SerializedPacketFate fate = DeterminePacketFate(
+      /*is_mtu_discovery=*/packet->encrypted_length > long_term_mtu_);
   // Termination packets are encrypted and saved, so don't exit early.
   const bool is_termination_packet = IsTerminationPacket(*packet);
   if (!treat_queued_packets_as_sent_ && HandleWriteBlocked() &&
@@ -2238,7 +2251,8 @@
                         : " ack only ")
                 << ", encryption level: "
                 << EncryptionLevelToString(packet->encryption_level)
-                << ", encrypted length:" << encrypted_length;
+                << ", encrypted length:" << encrypted_length
+                << ", fate: " << SerializedPacketFateToString(fate);
   QUIC_DVLOG(2) << ENDPOINT << "packet(" << packet_number << "): " << std::endl
                 << QuicTextUtils::HexDump(QuicStringPiece(
                        packet->encrypted_buffer, encrypted_length));
@@ -2261,7 +2275,34 @@
   WriteResult result(WRITE_STATUS_OK, encrypted_length);
   switch (fate) {
     case COALESCE:
-      DCHECK(false);
+      QUIC_BUG_IF(!version().CanSendCoalescedPackets());
+      if (!coalesced_packet_.MaybeCoalescePacket(
+              *packet, self_address(), peer_address(),
+              helper_->GetStreamSendBufferAllocator(),
+              packet_generator_.GetCurrentMaxPacketLength())) {
+        // Failed to coalesce packet, flush current coalesced packet.
+        if (!FlushCoalescedPacket()) {
+          // Failed to flush coalesced packet, write error has been handled.
+          return false;
+        }
+        if (!coalesced_packet_.MaybeCoalescePacket(
+                *packet, self_address(), peer_address(),
+                helper_->GetStreamSendBufferAllocator(),
+                packet_generator_.GetCurrentMaxPacketLength())) {
+          // Failed to coalesce packet even it is the only packet, raise a write
+          // error.
+          QUIC_DLOG(ERROR) << ENDPOINT << "Failed to coalesce packet";
+          result.error_code = WRITE_STATUS_FAILED_TO_COALESCE_PACKET;
+          break;
+        }
+      }
+      if (coalesced_packet_.length() < coalesced_packet_.max_packet_length()) {
+        QUIC_DVLOG(1) << ENDPOINT << "Trying to set soft max packet length to "
+                      << coalesced_packet_.max_packet_length() -
+                             coalesced_packet_.length();
+        packet_generator_.SetSoftMaxPacketLength(
+            coalesced_packet_.max_packet_length() - coalesced_packet_.length());
+      }
       break;
     case BUFFER:
       DCHECK(treat_queued_packets_as_sent_);
@@ -2275,6 +2316,11 @@
                                     self_address().host(), peer_address(),
                                     per_packet_options_);
       break;
+    case FAILED_TO_WRITE_COALESCED_PACKET:
+      // Failed to send existing coalesced packet when determining packet fate,
+      // write error has been handled.
+      QUIC_BUG_IF(!version().CanSendCoalescedPackets());
+      return false;
     default:
       DCHECK(false);
       break;
@@ -2473,6 +2519,12 @@
 }
 
 char* QuicConnection::GetPacketBuffer() {
+  if (version().CanSendCoalescedPackets() &&
+      !sent_packet_manager_.forward_secure_packet_acked()) {
+    // Do not use writer's packet buffer for coalesced packets which may contain
+    // multiple QUIC packets.
+    return nullptr;
+  }
   return writer_->GetNextWriteLocation(self_address().host(), peer_address());
 }
 
@@ -2838,12 +2890,13 @@
 
 void QuicConnection::QueueCoalescedPacket(const QuicEncryptedPacket& packet) {
   QUIC_DVLOG(1) << ENDPOINT << "Queueing coalesced packet.";
-  coalesced_packets_.push_back(packet.Clone());
+  received_coalesced_packets_.push_back(packet.Clone());
+  ++stats_.num_coalesced_packets_received;
 }
 
 void QuicConnection::MaybeProcessCoalescedPackets() {
   bool processed = false;
-  while (connected_ && !coalesced_packets_.empty()) {
+  while (connected_ && !received_coalesced_packets_.empty()) {
     // Making sure there are no pending frames when processing the next
     // coalesced packet because the queued ack frame may change.
     packet_generator_.FlushAllQueuedFrames();
@@ -2852,12 +2905,13 @@
     }
 
     std::unique_ptr<QuicEncryptedPacket> packet =
-        std::move(coalesced_packets_.front());
-    coalesced_packets_.pop_front();
+        std::move(received_coalesced_packets_.front());
+    received_coalesced_packets_.pop_front();
 
     QUIC_DVLOG(1) << ENDPOINT << "Processing coalesced packet";
     if (framer_.ProcessPacket(*packet)) {
       processed = true;
+      ++stats_.num_coalesced_packets_processed;
     } else {
       // If we are unable to decrypt this packet, it might be
       // because the CHLO or SHLO packet was lost.
@@ -2895,6 +2949,9 @@
   if (!GetQuicReloadableFlag(quic_close_all_encryptions_levels)) {
     QUIC_DLOG(INFO) << ENDPOINT << "Sending connection close packet.";
     SetDefaultEncryptionLevel(GetConnectionCloseEncryptionLevel());
+    if (version().CanSendCoalescedPackets()) {
+      coalesced_packet_.Clear();
+    }
     ClearQueuedPackets();
     // If there was a packet write error, write the smallest close possible.
     ScopedPacketFlusher flusher(this);
@@ -2911,6 +2968,9 @@
                                          framer_.current_received_frame_type());
     packet_generator_.ConsumeRetransmittableControlFrame(QuicFrame(frame));
     packet_generator_.FlushAllQueuedFrames();
+    if (version().CanSendCoalescedPackets()) {
+      FlushCoalescedPacket();
+    }
     ClearQueuedPackets();
     return;
   }
@@ -2926,6 +2986,9 @@
     QUIC_DLOG(INFO) << ENDPOINT << "Sending connection close packet at level: "
                     << EncryptionLevelToString(level);
     SetDefaultEncryptionLevel(level);
+    if (version().CanSendCoalescedPackets()) {
+      coalesced_packet_.Clear();
+    }
     ClearQueuedPackets();
     // If there was a packet write error, write the smallest close possible.
     // When multiple packet number spaces are supported, an ACK frame will
@@ -2941,6 +3004,12 @@
                                      framer_.current_received_frame_type());
     packet_generator_.ConsumeRetransmittableControlFrame(QuicFrame(frame));
     packet_generator_.FlushAllQueuedFrames();
+    if (!version().CanSendCoalescedPackets()) {
+      ClearQueuedPackets();
+    }
+  }
+  if (version().CanSendCoalescedPackets()) {
+    FlushCoalescedPacket();
     ClearQueuedPackets();
   }
   SetDefaultEncryptionLevel(current_encryption_level);
@@ -3269,6 +3338,9 @@
       }
     }
     connection_->packet_generator_.Flush();
+    if (connection_->version().CanSendCoalescedPackets()) {
+      connection_->FlushCoalescedPacket();
+    }
     connection_->FlushPackets();
     // Reset transmission type.
     connection_->SetTransmissionType(NOT_RETRANSMISSION);
@@ -3310,6 +3382,16 @@
       self_address(self_address),
       peer_address(peer_address) {}
 
+QuicConnection::BufferedPacket::BufferedPacket(
+    char* encrypted_buffer,
+    QuicPacketLength encrypted_length,
+    const QuicSocketAddress& self_address,
+    const QuicSocketAddress& peer_address)
+    : encrypted_buffer(CopyBuffer(encrypted_buffer, encrypted_length),
+                       encrypted_length),
+      self_address(self_address),
+      peer_address(peer_address) {}
+
 QuicConnection::BufferedPacket::~BufferedPacket() {
   delete[] encrypted_buffer.data();
 }
@@ -3906,6 +3988,65 @@
   visitor_->OnAckNeedsRetransmittableFrame();
 }
 
+bool QuicConnection::FlushCoalescedPacket() {
+  ScopedCoalescedPacketClearer clearer(&coalesced_packet_);
+  if (!version().CanSendCoalescedPackets()) {
+    QUIC_BUG_IF(coalesced_packet_.length() > 0);
+    return true;
+  }
+  if (coalesced_packet_.length() == 0) {
+    return true;
+  }
+  QUIC_DVLOG(1) << ENDPOINT << "Sending coalesced packet";
+  char buffer[kMaxOutgoingPacketSize];
+  const size_t length = packet_generator_.SerializeCoalescedPacket(
+      coalesced_packet_, buffer, coalesced_packet_.max_packet_length());
+  if (length == 0) {
+    return false;
+  }
+
+  if (!buffered_packets_.empty() || HandleWriteBlocked()) {
+    QUIC_DVLOG(1) << ENDPOINT
+                  << "Buffering coalesced packet of len: " << length;
+    buffered_packets_.emplace_back(buffer, length,
+                                   coalesced_packet_.self_address(),
+                                   coalesced_packet_.peer_address());
+    return true;
+  }
+
+  WriteResult result = writer_->WritePacket(
+      buffer, length, coalesced_packet_.self_address().host(),
+      coalesced_packet_.peer_address(), per_packet_options_);
+  if (IsWriteError(result.status)) {
+    OnWriteError(result.error_code);
+    return false;
+  }
+  if (IsWriteBlockedStatus(result.status)) {
+    visitor_->OnWriteBlocked();
+    if (result.status != WRITE_STATUS_BLOCKED_DATA_BUFFERED) {
+      QUIC_DVLOG(1) << ENDPOINT
+                    << "Buffering coalesced packet of len: " << length;
+      buffered_packets_.emplace_back(buffer, length,
+                                     coalesced_packet_.self_address(),
+                                     coalesced_packet_.peer_address());
+    }
+  }
+  // Account for added padding.
+  if (length > coalesced_packet_.length()) {
+    size_t padding_size = length - coalesced_packet_.length();
+    if (EnforceAntiAmplificationLimit()) {
+      bytes_sent_before_address_validation_ += padding_size;
+    }
+    stats_.bytes_sent += padding_size;
+    if (coalesced_packet_.initial_packet() != nullptr &&
+        coalesced_packet_.initial_packet()->transmission_type !=
+            NOT_RETRANSMISSION) {
+      stats_.bytes_retransmitted += padding_size;
+    }
+  }
+  return true;
+}
+
 void QuicConnection::MaybeEnableMultiplePacketNumberSpacesSupport() {
   if (version().handshake_protocol != PROTOCOL_TLS1_3) {
     return;
@@ -3972,9 +4113,23 @@
                  bytes_received_before_address_validation_;
 }
 
-QuicConnection::SerializedPacketFate QuicConnection::DeterminePacketFate() {
-  if (treat_queued_packets_as_sent_ &&
-      (!buffered_packets_.empty() || HandleWriteBlocked())) {
+SerializedPacketFate QuicConnection::DeterminePacketFate(
+    bool is_mtu_discovery) {
+  if (!treat_queued_packets_as_sent_) {
+    return SEND_TO_WRITER;
+  }
+  if (version().CanSendCoalescedPackets() &&
+      !sent_packet_manager_.forward_secure_packet_acked() &&
+      !is_mtu_discovery) {
+    // Before receiving ACK for any 1-RTT packets, always try to coalesce
+    // packet (except MTU discovery packet).
+    return COALESCE;
+  }
+  // Packet cannot be coalesced, flush existing coalesced packet.
+  if (version().CanSendCoalescedPackets() && !FlushCoalescedPacket()) {
+    return FAILED_TO_WRITE_COALESCED_PACKET;
+  }
+  if (!buffered_packets_.empty() || HandleWriteBlocked()) {
     return BUFFER;
   }
   return SEND_TO_WRITER;
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 9c57f86..7bbd931 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -887,6 +887,10 @@
   // or the one sent after an IETF Retry.
   void InstallInitialCrypters(QuicConnectionId connection_id);
 
+  bool treat_queued_packets_as_sent() const {
+    return treat_queued_packets_as_sent_;
+  }
+
  protected:
   // Calls cancel() on all the alarms owned by this connection.
   void CancelAllAlarms();
@@ -968,13 +972,6 @@
 
   typedef std::list<SerializedPacket> QueuedPacketList;
 
-  // Indicates the fate of a serialized packet in WritePacket().
-  enum SerializedPacketFate : uint8_t {
-    COALESCE,        // Try to coalesce packet.
-    BUFFER,          // Buffer packet in buffered_packets_.
-    SEND_TO_WRITER,  // Send packet to writer.
-  };
-
   // BufferedPacket stores necessary information (encrypted buffer and self/peer
   // addresses) of those packets which are serialized but failed to send because
   // socket is blocked. From unacked packet map and send algorithm's
@@ -983,6 +980,10 @@
     BufferedPacket(const SerializedPacket& packet,
                    const QuicSocketAddress& self_address,
                    const QuicSocketAddress& peer_address);
+    BufferedPacket(char* encrypted_buffer,
+                   QuicPacketLength encrypted_length,
+                   const QuicSocketAddress& self_address,
+                   const QuicSocketAddress& peer_address);
     BufferedPacket(const BufferedPacket& other) = delete;
     BufferedPacket(const BufferedPacket&& other) = delete;
 
@@ -1137,8 +1138,12 @@
   // and flags.
   void MaybeEnableMultiplePacketNumberSpacesSupport();
 
-  // Returns packet fate when trying to write a packet.
-  SerializedPacketFate DeterminePacketFate();
+  // Returns packet fate when trying to write a packet via WritePacket().
+  SerializedPacketFate DeterminePacketFate(bool is_mtu_discovery);
+
+  // Serialize and send coalesced_packet. Returns false if serialization fails
+  // or the write causes errors, otherwise, returns true.
+  bool FlushCoalescedPacket();
 
   // Returns the encryption level the connection close packet should be sent at,
   // which is the highest encryption level that peer can guarantee to process.
@@ -1250,7 +1255,7 @@
 
   // Collection of coalesced packets which were received while processing
   // the current packet.
-  QuicDeque<std::unique_ptr<QuicEncryptedPacket>> coalesced_packets_;
+  QuicDeque<std::unique_ptr<QuicEncryptedPacket>> received_coalesced_packets_;
 
   // Maximum number of undecryptable packets the connection will store.
   size_t max_undecryptable_packets_;
@@ -1503,6 +1508,11 @@
   // treat_queued_packets_as_sent_ is true.
   std::list<BufferedPacket> buffered_packets_;
 
+  // Used to coalesce packets of different encryption level into the same UDP
+  // datagram. Connection stops trying to coalesce packets if a forward secure
+  // packet gets acknowledged.
+  QuicCoalescedPacket coalesced_packet_;
+
   // Latched value of quic_treat_queued_packets_as_sent.
   const bool treat_queued_packets_as_sent_;
 
diff --git a/quic/core/quic_connection_stats.cc b/quic/core/quic_connection_stats.cc
index 128bc93..e3652b0 100644
--- a/quic/core/quic_connection_stats.cc
+++ b/quic/core/quic_connection_stats.cc
@@ -48,7 +48,9 @@
       blocked_frames_received(0),
       blocked_frames_sent(0),
       num_connectivity_probing_received(0),
-      retry_packet_processed(false) {}
+      retry_packet_processed(false),
+      num_coalesced_packets_received(0),
+      num_coalesced_packets_processed(0) {}
 
 QuicConnectionStats::QuicConnectionStats(const QuicConnectionStats& other) =
     default;
@@ -98,6 +100,9 @@
      << s.num_connectivity_probing_received;
   os << " retry_packet_processed: "
      << (s.retry_packet_processed ? "yes" : "no");
+  os << " num_coalesced_packets_received: " << s.num_coalesced_packets_received;
+  os << " num_coalesced_packets_processed: "
+     << s.num_coalesced_packets_processed;
   os << " }";
 
   return os;
diff --git a/quic/core/quic_connection_stats.h b/quic/core/quic_connection_stats.h
index 805afd1..a67d867 100644
--- a/quic/core/quic_connection_stats.h
+++ b/quic/core/quic_connection_stats.h
@@ -109,6 +109,11 @@
 
   // Whether a RETRY packet was successfully processed.
   bool retry_packet_processed;
+
+  // Number of received coalesced packets.
+  uint64_t num_coalesced_packets_received;
+  // Number of successfully processed coalesced packets.
+  uint64_t num_coalesced_packets_processed;
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index b6aa85e..5a2e0ef 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -514,6 +514,10 @@
     return framer_.path_response_frames();
   }
 
+  const QuicEncryptedPacket* coalesced_packet() const {
+    return framer_.coalesced_packet();
+  }
+
   size_t last_packet_size() { return last_packet_size_; }
 
   const QuicPacketHeader& last_packet_header() const {
@@ -631,6 +635,7 @@
                   HasRetransmittableData retransmittable,
                   bool has_ack,
                   bool has_pending_frames) {
+    ScopedPacketFlusher flusher(this);
     char buffer[kMaxOutgoingPacketSize];
     size_t encrypted_length =
         QuicConnectionPeer::GetFramer(this)->EncryptPayload(
@@ -641,7 +646,7 @@
         encrypted_length, has_ack, has_pending_frames);
     if (retransmittable == HAS_RETRANSMITTABLE_DATA) {
       serialized_packet.retransmittable_frames.push_back(
-          QuicFrame(QuicStreamFrame()));
+          QuicFrame(QuicPingFrame()));
     }
     OnSerializedPacket(&serialized_packet);
   }
@@ -2010,7 +2015,7 @@
   writer_->SetWritable();
   connection_.SendConnectivityProbingPacket(writer_.get(),
                                             connection_.peer_address());
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(visitor_, OnConnectionClosed(_, _)).Times(0);
     connection_.OnCanWrite();
     return;
@@ -2046,7 +2051,7 @@
   connection_.SendStreamDataWithString(/*id=*/2, "foo", 0, NO_FIN);
 
   EXPECT_FALSE(connection_.connected());
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     // No need to buffer packets.
     EXPECT_EQ(0u, connection_.NumQueuedPackets());
   } else {
@@ -3054,7 +3059,9 @@
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
     QuicConnection::ScopedPacketFlusher flusher(&connection_);
     connection_.SendStreamData3();
+    connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
     connection_.SendCryptoStreamData();
+    connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   }
   EXPECT_EQ(0u, connection_.NumQueuedPackets());
   EXPECT_FALSE(connection_.HasQueuedData());
@@ -3352,7 +3359,7 @@
   BlockOnNextWrite();
 
   QuicStreamId stream_id = 2;
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
@@ -3364,7 +3371,7 @@
 
   // Unblock the connection and verify that the RST_STREAM is sent and the data
   // packet is sent.
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
         .Times(AtLeast(1));
   } else {
@@ -3569,7 +3576,7 @@
   EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _))
       .WillOnce(SetArgPointee<5>(lost_packets));
   EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, QuicPacketNumber(4), _, _))
         .Times(1);
   } else {
@@ -3584,7 +3591,7 @@
   QuicAckFrame ack_all = InitAckFrame(3);
   ProcessAckPacket(&ack_all);
 
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, QuicPacketNumber(4), _, _))
         .Times(0);
   } else {
@@ -3643,7 +3650,7 @@
   // Block the writer and ensure they're queued.
   BlockOnNextWrite();
   clock_.AdvanceTime(DefaultRetransmissionTime());
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
@@ -3655,7 +3662,7 @@
   writer_->SetWritable();
   clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(
       2 * DefaultRetransmissionTime().ToMicroseconds()));
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
   } else {
     // 2 RTOs + 1 TLP, which is buggy.
@@ -3680,13 +3687,13 @@
 TEST_P(QuicConnectionTest, WriteBlockedThenSent) {
   EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
   BlockOnNextWrite();
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
   }
   connection_.SendStreamDataWithString(1, "foo", 0, NO_FIN);
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
   } else {
     EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
@@ -3696,7 +3703,7 @@
   // The second packet should also be queued, in order to ensure packets are
   // never sent out of order.
   writer_->SetWritable();
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
@@ -3705,7 +3712,7 @@
   EXPECT_EQ(2u, connection_.NumQueuedPackets());
 
   // Now both are sent in order when we unblock.
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
@@ -4230,9 +4237,12 @@
 
   // Manually mark both packets for retransmission.
   connection_.RetransmitUnackedPackets(ALL_UNACKED_RETRANSMISSION);
-
-  // Packet should have been sent with ENCRYPTION_INITIAL.
-  EXPECT_EQ(0x01010101u, writer_->final_bytes_of_previous_packet());
+  if (!connection_.version().CanSendCoalescedPackets()) {
+    // Packet should have been sent with ENCRYPTION_INITIAL.
+    // If connection can send coalesced packet, both retransmissions will be
+    // coalesced in the same UDP datagram.
+    EXPECT_EQ(0x01010101u, writer_->final_bytes_of_previous_packet());
+  }
 
   // Packet should have been sent with ENCRYPTION_ZERO_RTT.
   EXPECT_EQ(0x02020202u, writer_->final_bytes_of_last_packet());
@@ -4277,7 +4287,7 @@
   // Simulate the retransmission alarm firing and the socket blocking.
   BlockOnNextWrite();
   clock_.AdvanceTime(DefaultRetransmissionTime());
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
@@ -4431,13 +4441,13 @@
 
 TEST_P(QuicConnectionTest, SetRTOAfterWritingToSocket) {
   BlockOnNextWrite();
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
   }
   connection_.SendStreamDataWithString(1, "foo", 0, NO_FIN);
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
   } else {
     // Make sure that RTO is not started when the packet is queued.
@@ -4446,7 +4456,7 @@
 
   // Test that RTO is started once we write to the socket.
   writer_->SetWritable();
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
@@ -6492,7 +6502,7 @@
   }
   // Check that ack is sent and that delayed ack alarm is reset.
   if (GetParam().no_stop_waiting) {
-    EXPECT_EQ(1u, writer_->frame_count());
+    EXPECT_EQ(writer_->padding_frames().size() + 1u, writer_->frame_count());
     EXPECT_TRUE(writer_->stop_waiting_frames().empty());
   } else {
     EXPECT_EQ(2u, writer_->frame_count());
@@ -6509,7 +6519,7 @@
                            ENCRYPTION_ZERO_RTT);
   // Check that ack is sent and that delayed ack alarm is reset.
   if (GetParam().no_stop_waiting) {
-    EXPECT_EQ(1u, writer_->frame_count());
+    EXPECT_EQ(writer_->padding_frames().size() + 1u, writer_->frame_count());
     EXPECT_TRUE(writer_->stop_waiting_frames().empty());
   } else {
     EXPECT_EQ(2u, writer_->frame_count());
@@ -6645,7 +6655,7 @@
   }
   // Check that ack is sent and that delayed ack alarm is reset.
   if (GetParam().no_stop_waiting) {
-    EXPECT_EQ(1u, writer_->frame_count());
+    EXPECT_EQ(writer_->padding_frames().size() + 1u, writer_->frame_count());
     EXPECT_TRUE(writer_->stop_waiting_frames().empty());
   } else {
     EXPECT_EQ(2u, writer_->frame_count());
@@ -6662,7 +6672,7 @@
                            ENCRYPTION_ZERO_RTT);
   // Check that ack is sent and that delayed ack alarm is reset.
   if (GetParam().no_stop_waiting) {
-    EXPECT_EQ(1u, writer_->frame_count());
+    EXPECT_EQ(writer_->padding_frames().size() + 1u, writer_->frame_count());
     EXPECT_TRUE(writer_->stop_waiting_frames().empty());
   } else {
     EXPECT_EQ(2u, writer_->frame_count());
@@ -8049,7 +8059,7 @@
   EXPECT_CALL(visitor_, WillingAndAbleToWrite()).WillRepeatedly(Return(true));
   BlockOnNextWrite();
 
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
@@ -8061,7 +8071,7 @@
   writer_->SetWritable();
   CongestionBlockWrites();
   EXPECT_CALL(visitor_, WillingAndAbleToWrite()).WillRepeatedly(Return(false));
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
@@ -8199,8 +8209,15 @@
       .WillRepeatedly(Invoke([this, &sent_count](const SerializedPacket&,
                                                  TransmissionType, QuicTime) {
         ASSERT_EQ(1u, writer_->stream_frames().size());
-        // Identify the frames by stream offset (0, 3, 6, 0, 3...).
-        EXPECT_EQ(3 * (sent_count % 3), writer_->stream_frames()[0]->offset);
+        if (connection_.version().CanSendCoalescedPackets()) {
+          // There is a delay of sending coalesced packet, so (6, 0, 3, 6,
+          // 0...).
+          EXPECT_EQ(3 * ((sent_count + 2) % 3),
+                    writer_->stream_frames()[0]->offset);
+        } else {
+          // Identify the frames by stream offset (0, 3, 6, 0, 3...).
+          EXPECT_EQ(3 * (sent_count % 3), writer_->stream_frames()[0]->offset);
+        }
         sent_count++;
       }));
   EXPECT_CALL(*send_algorithm_, ShouldSendProbingPacket())
@@ -8681,14 +8698,14 @@
 
 TEST_P(QuicConnectionTest, WriteBlockedWithInvalidAck) {
   EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(visitor_, OnConnectionClosed(_, _)).Times(0);
   } else {
     EXPECT_CALL(visitor_, OnConnectionClosed(_, _))
         .WillOnce(Invoke(this, &QuicConnectionTest::SaveConnectionCloseFrame));
   }
   BlockOnNextWrite();
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
@@ -8696,11 +8713,11 @@
   connection_.SendStreamDataWithString(5, "foo", 0, FIN);
   // This causes connection to be closed because packet 1 has not been sent yet.
   QuicAckFrame frame = InitAckFrame(1);
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _));
   }
   ProcessAckPacket(1, &frame);
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_EQ(0, connection_close_frame_count_);
   } else {
     EXPECT_EQ(1, connection_close_frame_count_);
@@ -9359,7 +9376,7 @@
   EXPECT_CALL(visitor_, OnWriteBlocked()).Times(AtLeast(1));
   SendRstStream(stream_id, QUIC_ERROR_PROCESSING_STREAM, 3);
 
-  if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) {
+  if (connection_.treat_queued_packets_as_sent()) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
@@ -9731,6 +9748,44 @@
   EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
 }
 
+TEST_P(QuicConnectionTest, SendCoalescedPackets) {
+  if (!connection_.version().CanSendCoalescedPackets()) {
+    return;
+  }
+  {
+    QuicConnection::ScopedPacketFlusher flusher(&connection_);
+    use_tagging_decrypter();
+    connection_.SetEncrypter(ENCRYPTION_INITIAL,
+                             std::make_unique<TaggingEncrypter>(0x01));
+    connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+    connection_.SendCryptoDataWithString("foo", 0);
+    // Verify this packet is on hold.
+    EXPECT_EQ(0u, writer_->packets_write_attempts());
+
+    connection_.SetEncrypter(ENCRYPTION_HANDSHAKE,
+                             std::make_unique<TaggingEncrypter>(0x02));
+    connection_.SetDefaultEncryptionLevel(ENCRYPTION_HANDSHAKE);
+    connection_.SendCryptoDataWithString("bar", 3);
+    EXPECT_EQ(0u, writer_->packets_write_attempts());
+
+    connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                             std::make_unique<TaggingEncrypter>(0x03));
+    connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+    SendStreamDataToPeer(2, "baz", 3, NO_FIN, nullptr);
+  }
+  // Verify all 3 packets are coalesced in the same UDP datagram.
+  EXPECT_EQ(1u, writer_->packets_write_attempts());
+  EXPECT_EQ(0x03030303u, writer_->final_bytes_of_last_packet());
+  // Verify the packet is padded to full.
+  EXPECT_EQ(connection_.max_packet_length(), writer_->last_packet_size());
+
+  // Verify packet process.
+  EXPECT_EQ(1u, writer_->crypto_frames().size());
+  EXPECT_EQ(0u, writer_->stream_frames().size());
+  // Verify there is coalesced packet.
+  EXPECT_NE(nullptr, writer_->coalesced_packet());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_packet_creator.cc b/quic/core/quic_packet_creator.cc
index 9f7f160..ac467a2 100644
--- a/quic/core/quic_packet_creator.cc
+++ b/quic/core/quic_packet_creator.cc
@@ -963,6 +963,7 @@
     QUIC_BUG << "Try to serialize coalesced packet with pending frames";
     return 0;
   }
+  RemoveSoftMaxPacketLength();
   QUIC_BUG_IF(coalesced.length() == 0)
       << "Attempt to serialize empty coalesced packet";
   size_t packet_length = 0;
@@ -1512,7 +1513,8 @@
       packet_.has_crypto_handshake = IS_HANDSHAKE;
     }
   } else {
-    if (populate_nonretransmittable_frames_) {
+    if (populate_nonretransmittable_frames_ ||
+        framer_->version().CanSendCoalescedPackets()) {
       if (frame.type == PADDING_FRAME &&
           frame.padding_frame.num_padding_bytes == -1) {
         // Populate the actual length of full padding frame, such that one can
@@ -1603,6 +1605,25 @@
     needs_full_padding_ = true;
   }
 
+  // Packet coalescer pads INITIAL packets, so the creator should not.
+  if (framer_->version().CanSendCoalescedPackets() &&
+      (packet_.encryption_level == ENCRYPTION_INITIAL ||
+       packet_.encryption_level == ENCRYPTION_HANDSHAKE)) {
+    // TODO(fayang): MTU discovery packets should not ever be sent as
+    // ENCRYPTION_INITIAL or ENCRYPTION_HANDSHAKE.
+    bool is_mtu_discovery = false;
+    for (const auto& frame : packet_.nonretransmittable_frames) {
+      if (frame.type == MTU_DISCOVERY_FRAME) {
+        is_mtu_discovery = true;
+        break;
+      }
+    }
+    if (!is_mtu_discovery) {
+      // Do not add full padding if connection tries to coalesce packet.
+      needs_full_padding_ = false;
+    }
+  }
+
   // Header protection requires a minimum plaintext packet size.
   size_t extra_padding_bytes = 0;
   if (framer_->version().HasHeaderProtection()) {
diff --git a/quic/core/quic_packet_creator_test.cc b/quic/core/quic_packet_creator_test.cc
index 0ba7bd1..4efcbfe 100644
--- a/quic/core/quic_packet_creator_test.cc
+++ b/quic/core/quic_packet_creator_test.cc
@@ -539,8 +539,10 @@
     // If there is not enough space in the packet to fit a padding frame
     // (1 byte) and to expand the stream frame (another 2 bytes) the packet
     // will not be padded.
-    if (bytes_free < 3 &&
-        !QuicVersionUsesCryptoFrames(client_framer_.transport_version())) {
+    // Padding is skipped when we try to send coalesced packets.
+    if ((bytes_free < 3 &&
+         !QuicVersionUsesCryptoFrames(client_framer_.transport_version())) ||
+        client_framer_.version().CanSendCoalescedPackets()) {
       EXPECT_EQ(kDefaultMaxPacketSize - bytes_free,
                 serialized_packet_.encrypted_length);
     } else {
@@ -2072,7 +2074,8 @@
 }
 
 TEST_P(QuicPacketCreatorTest, SaveNonRetransmittableFrames) {
-  if (!GetQuicReloadableFlag(quic_populate_nonretransmittable_frames)) {
+  if (!GetQuicReloadableFlag(quic_populate_nonretransmittable_frames) &&
+      !client_framer_.version().CanSendCoalescedPackets()) {
     return;
   }
   QuicAckFrame ack_frame(InitAckFrame(1));
diff --git a/quic/core/quic_packet_generator.cc b/quic/core/quic_packet_generator.cc
index de548ac..f4a5773 100644
--- a/quic/core/quic_packet_generator.cc
+++ b/quic/core/quic_packet_generator.cc
@@ -522,6 +522,18 @@
   packet_creator_.SetClientConnectionId(client_connection_id);
 }
 
+size_t QuicPacketGenerator::SerializeCoalescedPacket(
+    const QuicCoalescedPacket& coalesced,
+    char* buffer,
+    size_t buffer_len) {
+  return packet_creator_.SerializeCoalescedPacket(coalesced, buffer,
+                                                  buffer_len);
+}
+
+void QuicPacketGenerator::SetSoftMaxPacketLength(QuicByteCount length) {
+  packet_creator_.SetSoftMaxPacketLength(length);
+}
+
 void QuicPacketGenerator::set_fully_pad_crypto_handshake_packets(
     bool new_value) {
   if (packet_creator_.combine_generator_and_creator()) {
diff --git a/quic/core/quic_packet_generator.h b/quic/core/quic_packet_generator.h
index 2a7f996..efeed75 100644
--- a/quic/core/quic_packet_generator.h
+++ b/quic/core/quic_packet_generator.h
@@ -211,6 +211,17 @@
   // Update the client connection ID used in outgoing packets.
   void SetClientConnectionId(QuicConnectionId client_connection_id);
 
+  // Serializes |coalesced| to provided |buffer|, returns coalesced packet
+  // length if serialization succeeds. Otherwise, returns 0.
+  size_t SerializeCoalescedPacket(const QuicCoalescedPacket& coalesced,
+                                  char* buffer,
+                                  size_t buffer_len);
+
+  // Set a soft maximum packet length in the creator. If a packet cannot be
+  // successfully created, creator will remove the soft limit and use the actual
+  // max packet length.
+  void SetSoftMaxPacketLength(QuicByteCount length);
+
   void set_debug_delegate(QuicPacketCreator::DebugDelegate* debug_delegate) {
     packet_creator_.set_debug_delegate(debug_delegate);
   }
diff --git a/quic/core/quic_packets.cc b/quic/core/quic_packets.cc
index 467d346..87b310e 100644
--- a/quic/core/quic_packets.cc
+++ b/quic/core/quic_packets.cc
@@ -526,6 +526,13 @@
   return dst_buffer;
 }
 
+char* CopyBuffer(const char* encrypted_buffer,
+                 QuicPacketLength encrypted_length) {
+  char* dst_buffer = new char[encrypted_length];
+  memcpy(dst_buffer, encrypted_buffer, encrypted_length);
+  return dst_buffer;
+}
+
 ReceivedPacketInfo::ReceivedPacketInfo(const QuicSocketAddress& self_address,
                                        const QuicSocketAddress& peer_address,
                                        const QuicReceivedPacket& packet)
diff --git a/quic/core/quic_packets.h b/quic/core/quic_packets.h
index dab0849..91553d8 100644
--- a/quic/core/quic_packets.h
+++ b/quic/core/quic_packets.h
@@ -406,6 +406,10 @@
 // Allocates a new char[] of size |packet.encrypted_length| and copies in
 // |packet.encrypted_buffer|.
 QUIC_EXPORT_PRIVATE char* CopyBuffer(const SerializedPacket& packet);
+// Allocates a new char[] of size |encrypted_length| and copies in
+// |encrypted_buffer|.
+QUIC_EXPORT_PRIVATE char* CopyBuffer(const char* encrypted_buffer,
+                                     QuicPacketLength encrypted_length);
 
 struct QUIC_EXPORT_PRIVATE SerializedPacketDeleter {
   void operator()(SerializedPacket* packet) {
diff --git a/quic/core/quic_types.cc b/quic/core/quic_types.cc
index 5e717de..1f51dc5 100644
--- a/quic/core/quic_types.cc
+++ b/quic/core/quic_types.cc
@@ -51,6 +51,8 @@
       return "ERROR";
     case WRITE_STATUS_MSG_TOO_BIG:
       return "MSG_TOO_BIG";
+    case WRITE_STATUS_FAILED_TO_COALESCE_PACKET:
+      return "WRITE_STATUS_FAILED_TO_COALESCE_PACKET";
     case WRITE_STATUS_NUM_VALUES:
       return "NUM_VALUES";
   }
@@ -524,6 +526,17 @@
   }
 }
 
+std::string SerializedPacketFateToString(SerializedPacketFate fate) {
+  switch (fate) {
+    RETURN_STRING_LITERAL(COALESCE);
+    RETURN_STRING_LITERAL(BUFFER);
+    RETURN_STRING_LITERAL(SEND_TO_WRITER);
+    RETURN_STRING_LITERAL(FAILED_TO_WRITE_COALESCED_PACKET);
+    default:
+      return QuicStrCat("Unknown(", static_cast<int>(fate), ")");
+  }
+}
+
 std::string EncryptionLevelToString(EncryptionLevel level) {
   switch (level) {
     RETURN_STRING_LITERAL(ENCRYPTION_INITIAL);
diff --git a/quic/core/quic_types.h b/quic/core/quic_types.h
index 60754c0..d7206d6 100644
--- a/quic/core/quic_types.h
+++ b/quic/core/quic_types.h
@@ -95,6 +95,7 @@
   // - Errors MUST be added after WRITE_STATUS_ERROR.
   WRITE_STATUS_ERROR,
   WRITE_STATUS_MSG_TOO_BIG,
+  WRITE_STATUS_FAILED_TO_COALESCE_PACKET,
   WRITE_STATUS_NUM_VALUES,
 };
 
@@ -667,6 +668,18 @@
   PACKETS_ACKED_IN_WRONG_PACKET_NUMBER_SPACE,
 };
 
+// Indicates the fate of a serialized packet in WritePacket().
+enum SerializedPacketFate : uint8_t {
+  COALESCE,                          // Try to coalesce packet.
+  BUFFER,                            // Buffer packet in buffered_packets_.
+  SEND_TO_WRITER,                    // Send packet to writer.
+  FAILED_TO_WRITE_COALESCED_PACKET,  // Packet cannot be coalesced, error occurs
+                                     // when sending existing coalesced packet.
+};
+
+QUIC_EXPORT_PRIVATE std::string SerializedPacketFateToString(
+    SerializedPacketFate fate);
+
 // There are three different forms of CONNECTION_CLOSE.
 typedef enum QuicConnectionCloseType {
   GOOGLE_QUIC_CONNECTION_CLOSE = 0,
diff --git a/quic/core/quic_versions.cc b/quic/core/quic_versions.cc
index 57bd3fa..8d666a3 100644
--- a/quic/core/quic_versions.cc
+++ b/quic/core/quic_versions.cc
@@ -84,6 +84,11 @@
          handshake_protocol == PROTOCOL_TLS1_3;
 }
 
+bool ParsedQuicVersion::CanSendCoalescedPackets() const {
+  return QuicVersionHasLongHeaderLengths(transport_version) &&
+         handshake_protocol == PROTOCOL_TLS1_3;
+}
+
 bool VersionHasLengthPrefixedConnectionIds(
     QuicTransportVersion transport_version) {
   return transport_version > QUIC_VERSION_48;
diff --git a/quic/core/quic_versions.h b/quic/core/quic_versions.h
index 777d74f..7fd1d01 100644
--- a/quic/core/quic_versions.h
+++ b/quic/core/quic_versions.h
@@ -193,6 +193,9 @@
   // i.e., server will send no more than FLAGS_quic_anti_amplification_factor
   // times received bytes until address can be validated.
   bool SupportsAntiAmplificationLimit() const;
+
+  // Returns true if this version can send coalesced packets.
+  bool CanSendCoalescedPackets() const;
 };
 
 QUIC_EXPORT_PRIVATE ParsedQuicVersion UnsupportedQuicVersion();
diff --git a/quic/test_tools/simple_quic_framer.cc b/quic/test_tools/simple_quic_framer.cc
index be03aa1..588f3b6 100644
--- a/quic/test_tools/simple_quic_framer.cc
+++ b/quic/test_tools/simple_quic_framer.cc
@@ -59,7 +59,9 @@
     return true;
   }
 
-  void OnCoalescedPacket(const QuicEncryptedPacket& /*packet*/) override {}
+  void OnCoalescedPacket(const QuicEncryptedPacket& packet) override {
+    coalesced_packet_ = packet.Clone();
+  }
 
   void OnUndecryptablePacket(const QuicEncryptedPacket& /*packet*/,
                              EncryptionLevel /*decryption_level*/,
@@ -253,6 +255,9 @@
     return version_negotiation_packet_.get();
   }
   EncryptionLevel last_decrypted_level() const { return last_decrypted_level_; }
+  const QuicEncryptedPacket* coalesced_packet() const {
+    return coalesced_packet_.get();
+  }
 
  private:
   QuicErrorCode error_;
@@ -284,6 +289,7 @@
   std::vector<std::unique_ptr<std::string>> stream_data_;
   std::vector<std::unique_ptr<std::string>> crypto_data_;
   EncryptionLevel last_decrypted_level_;
+  std::unique_ptr<QuicEncryptedPacket> coalesced_packet_;
 };
 
 SimpleQuicFramer::SimpleQuicFramer()
@@ -404,5 +410,9 @@
   return visitor_->padding_frames();
 }
 
+const QuicEncryptedPacket* SimpleQuicFramer::coalesced_packet() const {
+  return visitor_->coalesced_packet();
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/simple_quic_framer.h b/quic/test_tools/simple_quic_framer.h
index a254ce5..3e063d0 100644
--- a/quic/test_tools/simple_quic_framer.h
+++ b/quic/test_tools/simple_quic_framer.h
@@ -50,6 +50,7 @@
   const std::vector<QuicPaddingFrame>& padding_frames() const;
   const QuicVersionNegotiationPacket* version_negotiation_packet() const;
   EncryptionLevel last_decrypted_level() const;
+  const QuicEncryptedPacket* coalesced_packet() const;
 
   QuicFramer* framer();
 
diff --git a/quic/test_tools/simple_session_notifier_test.cc b/quic/test_tools/simple_session_notifier_test.cc
index 32dbce4..058f220 100644
--- a/quic/test_tools/simple_session_notifier_test.cc
+++ b/quic/test_tools/simple_session_notifier_test.cc
@@ -42,6 +42,7 @@
       : connection_(&helper_, &alarm_factory_, Perspective::IS_CLIENT),
         notifier_(&connection_) {
     connection_.set_visitor(&visitor_);
+    connection_.SetSessionNotifier(&notifier_);
     EXPECT_FALSE(notifier_.WillingToWrite());
     EXPECT_EQ(0u, notifier_.StreamBytesSent());
     EXPECT_FALSE(notifier_.HasBufferedStreamData());