Close QUIC connection if it fails to serialize a coalesced packet.

Protected by FLAGS_quic_reloadable_flag_quic_close_connection_if_fail_to_serialzie_coalesced_packet.

PiperOrigin-RevId: 434638558
diff --git a/quic/core/quic_coalesced_packet.cc b/quic/core/quic_coalesced_packet.cc
index e8ce931..dcfcb09 100644
--- a/quic/core/quic_coalesced_packet.cc
+++ b/quic/core/quic_coalesced_packet.cc
@@ -181,4 +181,17 @@
   return info;
 }
 
+std::vector<size_t> QuicCoalescedPacket::packet_lengths() const {
+  std::vector<size_t> lengths;
+  for (const auto& packet : encrypted_buffers_) {
+    if (lengths.empty()) {
+      lengths.push_back(
+          initial_packet_ == nullptr ? 0 : initial_packet_->encrypted_length);
+    } else {
+      lengths.push_back(packet.length());
+    }
+  }
+  return lengths;
+}
+
 }  // namespace quic
diff --git a/quic/core/quic_coalesced_packet.h b/quic/core/quic_coalesced_packet.h
index 9b8295c..2e4165d 100644
--- a/quic/core/quic_coalesced_packet.h
+++ b/quic/core/quic_coalesced_packet.h
@@ -9,6 +9,10 @@
 
 namespace quic {
 
+namespace test {
+class QuicCoalescedPacketPeer;
+}
+
 // QuicCoalescedPacket is used to buffer multiple packets which can be coalesced
 // into the same UDP datagram.
 class QUIC_EXPORT_PRIVATE QuicCoalescedPacket {
@@ -61,7 +65,11 @@
 
   QuicPacketLength max_packet_length() const { return max_packet_length_; }
 
+  std::vector<size_t> packet_lengths() const;
+
  private:
+  friend class test::QuicCoalescedPacketPeer;
+
   // self/peer addresses are set when trying to coalesce the first packet.
   // Packets with different self/peer addresses cannot be coalesced.
   QuicSocketAddress self_address_;
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 4715925..0ceafd9 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -3391,6 +3391,10 @@
               packet_creator_.max_packet_length())) {
         // Failed to coalesce packet, flush current coalesced packet.
         if (!FlushCoalescedPacket()) {
+          QUIC_BUG_IF(quic_connection_connected_after_flush_coalesced_failure,
+                      connected_)
+              << "QUIC connection is still connected after failing to flush "
+                 "coalesced packet.";
           // Failed to flush coalesced packet, write error has been handled.
           return false;
         }
@@ -5905,6 +5909,15 @@
   const size_t length = packet_creator_.SerializeCoalescedPacket(
       coalesced_packet_, buffer, coalesced_packet_.max_packet_length());
   if (length == 0) {
+    if (GetQuicReloadableFlag(
+            quic_close_connection_if_fail_to_serialzie_coalesced_packet) &&
+        connected_) {
+      QUIC_RELOADABLE_FLAG_COUNT(
+          quic_close_connection_if_fail_to_serialzie_coalesced_packet);
+      CloseConnection(QUIC_FAILED_TO_SERIALIZE_PACKET,
+                      "Failed to serialize coalesced packet.",
+                      ConnectionCloseBehavior::SILENT_CLOSE);
+    }
     return false;
   }
   if (debug_visitor_ != nullptr) {
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index db7ae69..d0ee223 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -13,6 +13,7 @@
 
 #include "absl/base/macros.h"
 #include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
 #include "absl/strings/string_view.h"
 #include "quic/core/congestion_control/loss_detection_interface.h"
 #include "quic/core/congestion_control/send_algorithm_interface.h"
@@ -42,6 +43,7 @@
 #include "quic/platform/api/quic_test.h"
 #include "quic/test_tools/mock_clock.h"
 #include "quic/test_tools/mock_random.h"
+#include "quic/test_tools/quic_coalesced_packet_peer.h"
 #include "quic/test_tools/quic_config_peer.h"
 #include "quic/test_tools/quic_connection_peer.h"
 #include "quic/test_tools/quic_framer_peer.h"
@@ -10266,6 +10268,81 @@
   EXPECT_NE(nullptr, writer_->coalesced_packet());
 }
 
+TEST_P(QuicConnectionTest, FailToCoalescePacket) {
+  // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+  if (!IsDefaultTestConfiguration() ||
+      !connection_.version().CanSendCoalescedPackets()) {
+    return;
+  }
+
+  set_perspective(Perspective::IS_SERVER);
+  use_tagging_decrypter();
+
+  EXPECT_CALL(visitor_, OnHandshakePacketSent());
+
+  if (GetQuicReloadableFlag(
+          quic_close_connection_if_fail_to_serialzie_coalesced_packet)) {
+    EXPECT_CALL(visitor_,
+                OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF))
+        .WillOnce(Invoke(this, &QuicConnectionTest::SaveConnectionCloseFrame));
+  }
+
+  ProcessDataPacketAtLevel(1, !kHasStopWaiting, ENCRYPTION_INITIAL);
+  auto test_body = [&] {
+    QuicConnection::ScopedPacketFlusher flusher(&connection_);
+    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);
+
+    creator_->Flush();
+
+    auto& coalesced_packet =
+        QuicConnectionPeer::GetCoalescedPacket(&connection_);
+    QuicPacketLength coalesced_packet_max_length =
+        coalesced_packet.max_packet_length();
+    QuicCoalescedPacketPeer::SetMaxPacketLength(coalesced_packet,
+                                                coalesced_packet.length());
+
+    // Make the coalescer's FORWARD_SECURE packet longer.
+    *QuicCoalescedPacketPeer::GetMutableEncryptedBuffer(
+        coalesced_packet, ENCRYPTION_FORWARD_SECURE) += "!!! TEST !!!";
+
+    QUIC_LOG(INFO) << "Reduced coalesced_packet_max_length from "
+                   << coalesced_packet_max_length << " to "
+                   << coalesced_packet.max_packet_length()
+                   << ", coalesced_packet.length:" << coalesced_packet.length()
+                   << ", coalesced_packet.packet_lengths:"
+                   << absl::StrJoin(coalesced_packet.packet_lengths(), ":");
+  };
+
+  EXPECT_QUIC_BUG(test_body(), "SerializeCoalescedPacket failed.");
+
+  if (GetQuicReloadableFlag(
+          quic_close_connection_if_fail_to_serialzie_coalesced_packet)) {
+    EXPECT_FALSE(connection_.connected());
+    EXPECT_THAT(saved_connection_close_frame_.quic_error_code,
+                IsError(QUIC_FAILED_TO_SERIALIZE_PACKET));
+    EXPECT_EQ(saved_connection_close_frame_.error_details,
+              "Failed to serialize coalesced packet.");
+  } else {
+    EXPECT_TRUE(connection_.connected());
+  }
+}
+
 TEST_P(QuicConnectionTest, LegacyVersionEncapsulation) {
   connection_.EnableLegacyVersionEncapsulation("test.example.org");
 
diff --git a/quic/core/quic_crypto_stream_test.cc b/quic/core/quic_crypto_stream_test.cc
index fffcb09..3760c7d 100644
--- a/quic/core/quic_crypto_stream_test.cc
+++ b/quic/core/quic_crypto_stream_test.cc
@@ -107,6 +107,9 @@
                                            &alarm_factory_,
                                            Perspective::IS_CLIENT)),
         session_(connection_, /*create_mock_crypto_stream=*/false) {
+    EXPECT_CALL(*static_cast<MockPacketWriter*>(connection_->writer()),
+                WritePacket(_, _, _, _, _))
+        .WillRepeatedly(Return(WriteResult(WRITE_STATUS_OK, 0)));
     stream_ = new MockQuicCryptoStream(&session_);
     session_.SetCryptoStream(stream_);
     session_.Initialize();
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 33f9bc8..0fe1980 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -23,6 +23,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_cached_network_parameters_to_address_token2, true)
 // If true, 1) QUIC connections will use a lower minimum for trusted initial rtt, 2) When TRTT is received, QUIC server sessions will mark the initial rtt from CachedNetworkParameters as trusted.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_use_lower_min_for_trusted_irtt, false)
+// If true, QUIC connection will be closed if it fails to serialize a coalesced packet.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_close_connection_if_fail_to_serialzie_coalesced_packet, true)
 // If true, QUIC will default enable MTU discovery at server, with a target of 1450 bytes.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_mtu_discovery_at_server, false)
 // If true, QuicGsoBatchWriter will support release time if it is available and the process has the permission to do so.
diff --git a/quic/core/quic_packet_creator.cc b/quic/core/quic_packet_creator.cc
index 4065e7e..95149b1 100644
--- a/quic/core/quic_packet_creator.cc
+++ b/quic/core/quic_packet_creator.cc
@@ -14,6 +14,7 @@
 #include "absl/base/macros.h"
 #include "absl/base/optimization.h"
 #include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/optional.h"
 #include "quic/core/crypto/crypto_protocol.h"
@@ -1122,6 +1123,7 @@
   QUIC_BUG_IF(quic_bug_12398_15, coalesced.length() == 0)
       << ENDPOINT << "Attempt to serialize empty coalesced packet";
   size_t packet_length = 0;
+  size_t initial_length = 0;
   if (coalesced.initial_packet() != nullptr) {
     // Padding coalesced packet containing initial packet to full.
     size_t padding_size = coalesced.max_packet_length() - coalesced.length();
@@ -1132,7 +1134,7 @@
       // Do not pad server initial connection close packet.
       padding_size = 0;
     }
-    size_t initial_length = ReserializeInitialPacketInCoalescedPacket(
+    initial_length = ReserializeInitialPacketInCoalescedPacket(
         *coalesced.initial_packet(), padding_size, buffer, buffer_len);
     if (initial_length == 0) {
       QUIC_BUG(quic_bug_10752_19)
@@ -1147,6 +1149,14 @@
   }
   size_t length_copied = 0;
   if (!coalesced.CopyEncryptedBuffers(buffer, buffer_len, &length_copied)) {
+    QUIC_BUG(quic_serialize_coalesced_packet_copy_failure)
+        << "SerializeCoalescedPacket failed. buffer_len:" << buffer_len
+        << ", initial_length:" << initial_length
+        << ", length_copied:" << length_copied
+        << ", coalesced.length:" << coalesced.length()
+        << ", coalesced.max_packet_length:" << coalesced.max_packet_length()
+        << ", coalesced.packet_lengths:"
+        << absl::StrJoin(coalesced.packet_lengths(), ":");
     return 0;
   }
   packet_length += length_copied;
diff --git a/quic/test_tools/quic_coalesced_packet_peer.cc b/quic/test_tools/quic_coalesced_packet_peer.cc
new file mode 100644
index 0000000..efd1900
--- /dev/null
+++ b/quic/test_tools/quic_coalesced_packet_peer.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quic/test_tools/quic_coalesced_packet_peer.h"
+
+namespace quic {
+namespace test {
+
+//  static
+void QuicCoalescedPacketPeer::SetMaxPacketLength(
+    QuicCoalescedPacket& coalesced_packet, QuicPacketLength length) {
+  coalesced_packet.max_packet_length_ = length;
+}
+
+//  static
+std::string* QuicCoalescedPacketPeer::GetMutableEncryptedBuffer(
+    QuicCoalescedPacket& coalesced_packet, EncryptionLevel encryption_level) {
+  return &coalesced_packet.encrypted_buffers_[encryption_level];
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/test_tools/quic_coalesced_packet_peer.h b/quic/test_tools/quic_coalesced_packet_peer.h
new file mode 100644
index 0000000..908cf09
--- /dev/null
+++ b/quic/test_tools/quic_coalesced_packet_peer.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_TEST_TOOLS_QUIC_COALESCED_PACKET_PEER_H_
+#define QUICHE_QUIC_TEST_TOOLS_QUIC_COALESCED_PACKET_PEER_H_
+
+#include "quic/core/quic_coalesced_packet.h"
+#include "quic/core/quic_types.h"
+
+namespace quic {
+namespace test {
+
+class QuicCoalescedPacketPeer {
+ public:
+  static void SetMaxPacketLength(QuicCoalescedPacket& coalesced_packet,
+                                 QuicPacketLength length);
+
+  static std::string* GetMutableEncryptedBuffer(
+      QuicCoalescedPacket& coalesced_packet, EncryptionLevel encryption_level);
+};
+
+}  // namespace test
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TEST_TOOLS_QUIC_COALESCED_PACKET_PEER_H_
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc
index 207582f..5e60a84 100644
--- a/quic/test_tools/quic_connection_peer.cc
+++ b/quic/test_tools/quic_connection_peer.cc
@@ -535,5 +535,16 @@
   connection->last_decrypted_packet_level_ = level;
 }
 
+// static
+QuicCoalescedPacket& QuicConnectionPeer::GetCoalescedPacket(
+    QuicConnection* connection) {
+  return connection->coalesced_packet_;
+}
+
+// static
+void QuicConnectionPeer::FlushCoalescedPacket(QuicConnection* connection) {
+  connection->FlushCoalescedPacket();
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h
index 62ed60e..ff536c2 100644
--- a/quic/test_tools/quic_connection_peer.h
+++ b/quic/test_tools/quic_connection_peer.h
@@ -220,6 +220,10 @@
 
   static void SetLastDecryptedLevel(QuicConnection* connection,
                                     EncryptionLevel level);
+
+  static QuicCoalescedPacket& GetCoalescedPacket(QuicConnection* connection);
+
+  static void FlushCoalescedPacket(QuicConnection* connection);
 };
 
 }  // namespace test