In QUIC client with TLS versions, delay retransmission of 0-RTT packets until 1-RTT keys are available.

Protected by enabled gfe2_reloadable_flag_quic_do_not_retransmit_immediately_on_zero_rtt_reject as this only affects client side.

PiperOrigin-RevId: 317142127
Change-Id: I21a297e9e1f181558fc27406960d56e3327a2778
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index 26b40f9..c466a9f 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -21,6 +21,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_versions.h"
 #include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
@@ -237,8 +238,6 @@
   if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
     // This test relies on resumption and is QUIC crypto specific, so it is
     // disabled for TLS.
-    // TODO(nharper): Add support for resumption to the TLS handshake, and fix
-    // this test to not rely on QUIC crypto.
     return;
   }
   // Complete a handshake in order to prime the crypto config for 0-RTT.
@@ -247,7 +246,6 @@
   // Now create a second session using the same crypto config.
   Initialize();
 
-  EXPECT_CALL(*connection_, OnCanWrite());
   // Starting the handshake should move immediately to encryption
   // established and will allow streams to be created.
   session_->CryptoConnect();
@@ -1075,7 +1073,10 @@
   }
 }
 
+// Regression test for b/159168475
 TEST_P(QuicSpdyClientSessionTest, RetransmitDataOnZeroRttReject) {
+  SetQuicReloadableFlag(quic_do_not_retransmit_immediately_on_zero_rtt_reject,
+                        true);
   // This feature is TLS-only.
   if (session_->version().UsesQuicCrypto()) {
     return;
@@ -1085,6 +1086,11 @@
 
   // Create a second connection, but disable 0-RTT on the server.
   CreateConnection();
+  ON_CALL(*connection_, OnCanWrite())
+      .WillByDefault(
+          testing::Invoke(connection_, &MockQuicConnection::ReallyOnCanWrite));
+  EXPECT_CALL(*connection_, OnCanWrite()).Times(0);
+
   QuicConfig config = DefaultQuicConfig();
   config.SetMaxUnidirectionalStreamsToSend(kDefaultMaxStreamsPerConnection);
   config.SetMaxBidirectionalStreamsToSend(kDefaultMaxStreamsPerConnection);
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index b30165d..38e117b 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -2338,7 +2338,10 @@
 void QuicConnection::RetransmitZeroRttPackets() {
   sent_packet_manager_.RetransmitZeroRttPackets();
 
-  WriteIfNotBlocked();
+  if (!GetQuicReloadableFlag(
+          quic_do_not_retransmit_immediately_on_zero_rtt_reject)) {
+    WriteIfNotBlocked();
+  }
 }
 
 void QuicConnection::NeuterUnencryptedPackets() {
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index b1409bf..d273bbe 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -4543,6 +4543,8 @@
 }
 
 TEST_P(QuicConnectionTest, RetransmitPacketsWithInitialEncryption) {
+  SetQuicReloadableFlag(quic_do_not_retransmit_immediately_on_zero_rtt_reject,
+                        true);
   use_tagging_decrypter();
   connection_.SetEncrypter(ENCRYPTION_INITIAL,
                            std::make_unique<TaggingEncrypter>(0x01));
@@ -4555,9 +4557,9 @@
   connection_.SetDefaultEncryptionLevel(ENCRYPTION_ZERO_RTT);
 
   SendStreamDataToPeer(2, "bar", 0, NO_FIN, nullptr);
-  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
-
+  EXPECT_FALSE(notifier_.HasLostStreamData());
   connection_.RetransmitZeroRttPackets();
+  EXPECT_TRUE(notifier_.HasLostStreamData());
 }
 
 TEST_P(QuicConnectionTest, BufferNonDecryptablePackets) {
diff --git a/quic/core/quic_crypto_client_stream_test.cc b/quic/core/quic_crypto_client_stream_test.cc
index 6542382..c1bcf17 100644
--- a/quic/core/quic_crypto_client_stream_test.cc
+++ b/quic/core/quic_crypto_client_stream_test.cc
@@ -299,7 +299,6 @@
   // Recreate connection with the new config and verify a 0-RTT attempt.
   CreateConnection();
 
-  EXPECT_CALL(*connection_, OnCanWrite());
   EXPECT_CALL(*session_, OnProofValid(testing::_));
   EXPECT_CALL(*session_, OnProofVerifyDetailsAvailable(testing::_))
       .Times(testing::AnyNumber());
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc
index cf754ec..23a74e4 100644
--- a/quic/core/tls_client_handshaker.cc
+++ b/quic/core/tls_client_handshaker.cc
@@ -525,6 +525,8 @@
 void TlsClientHandshaker::HandleZeroRttReject() {
   QUIC_LOG(INFO) << "0-RTT handshake attempted but was rejected by the server";
   DCHECK(session_cache_);
+  // Disable encrytion to block outgoing data until 1-RTT keys are available.
+  encryption_established_ = false;
   handshaker_delegate()->OnZeroRttRejected();
   SSL_reset_early_data_reject(ssl());
   session_cache_->ClearEarlyData(server_id_);
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index 686ea3a..c205b95 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -676,6 +676,8 @@
     QuicConnection::OnError(framer);
   }
 
+  void ReallyOnCanWrite() { QuicConnection::OnCanWrite(); }
+
   void ReallyCloseConnection(
       QuicErrorCode error,
       const std::string& details,
diff --git a/quic/test_tools/simple_session_notifier.h b/quic/test_tools/simple_session_notifier.h
index 1237f42..65aface 100644
--- a/quic/test_tools/simple_session_notifier.h
+++ b/quic/test_tools/simple_session_notifier.h
@@ -78,6 +78,7 @@
   bool IsFrameOutstanding(const QuicFrame& frame) const override;
   bool HasUnackedCryptoData() const override;
   bool HasUnackedStreamData() const override;
+  bool HasLostStreamData() const;
 
  private:
   struct StreamState {
@@ -124,8 +125,6 @@
 
   bool HasBufferedControlFrames() const;
 
-  bool HasLostStreamData() const;
-
   bool StreamHasBufferedData(QuicStreamId id) const;
 
   QuicCircularDeque<QuicFrame> control_frames_;