For IETF QUIC client, Retransmit 0-RTT packets with 1-RTT key when 0-RTT is rejected.

Client side change only. not protected.

PiperOrigin-RevId: 314632009
Change-Id: Ib6fc91aa28a5ea292a8063e7902a6c2ef0d4876d
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index e68199c..e7d4421 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -1035,6 +1035,57 @@
             control_stream->flow_controller()->send_window_offset());
 }
 
+TEST_P(QuicSpdyClientSessionTest, RetransmitDataOnZeroRttReject) {
+  // This feature is HTTP/3 only
+  if (!VersionUsesHttp3(session_->transport_version())) {
+    return;
+  }
+
+  CompleteCryptoHandshake();
+  EXPECT_FALSE(session_->GetCryptoStream()->IsResumption());
+  SettingsFrame settings;
+  settings.values[SETTINGS_QPACK_MAX_TABLE_CAPACITY] = 2;
+  settings.values[SETTINGS_MAX_HEADER_LIST_SIZE] = 5;
+  settings.values[256] = 4;  // unknown setting
+  session_->OnSettingsFrame(settings);
+
+  // Create a second connection, but disable 0-RTT on the server.
+  CreateConnection();
+  QuicCryptoClientStream* crypto_stream =
+      static_cast<QuicCryptoClientStream*>(session_->GetMutableCryptoStream());
+  std::unique_ptr<QuicCryptoServerConfig> crypto_config =
+      crypto_test_utils::CryptoServerConfigForTesting();
+  QuicConfig config = DefaultQuicConfig();
+  config.SetMaxUnidirectionalStreamsToSend(kDefaultMaxStreamsPerConnection);
+  config.SetMaxBidirectionalStreamsToSend(kDefaultMaxStreamsPerConnection);
+  SSL_CTX_set_early_data_enabled(crypto_config->ssl_ctx(), false);
+
+  // 3 packets will be written: CHLO, HTTP/3 SETTINGS, and request data.
+  EXPECT_CALL(*connection_,
+              OnPacketSent(ENCRYPTION_INITIAL, NOT_RETRANSMISSION));
+  EXPECT_CALL(*connection_,
+              OnPacketSent(ENCRYPTION_ZERO_RTT, NOT_RETRANSMISSION))
+      .Times(2);
+  session_->CryptoConnect();
+  EXPECT_TRUE(session_->IsEncryptionEstablished());
+  EXPECT_EQ(ENCRYPTION_ZERO_RTT, session_->connection()->encryption_level());
+  QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+  ASSERT_TRUE(stream);
+  stream->WriteOrBufferData("hello", true, nullptr);
+
+  // When handshake is done, the client sends 2 packet: HANDSHAKE FINISHED, and
+  // coalesced retransmission of HTTP/3 SETTINGS and request data.
+  EXPECT_CALL(*connection_,
+              OnPacketSent(ENCRYPTION_HANDSHAKE, NOT_RETRANSMISSION));
+  // TODO(b/158027651): change transmission type to ALL_ZERO_RTT_RETRANSMISSION.
+  EXPECT_CALL(*connection_,
+              OnPacketSent(ENCRYPTION_FORWARD_SECURE, LOSS_RETRANSMISSION));
+  crypto_test_utils::HandshakeWithFakeServer(
+      &config, crypto_config.get(), &helper_, &alarm_factory_, connection_,
+      crypto_stream, AlpnForVersion(connection_->version()));
+  EXPECT_TRUE(session_->GetCryptoStream()->IsResumption());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 1eb69e9..e88d109 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -1386,6 +1386,11 @@
     QUIC_DVLOG(1) << ENDPOINT << "Set default encryption level to " << level;
     QUIC_RELOADABLE_FLAG_COUNT(quic_change_default_encryption_level);
     connection()->SetDefaultEncryptionLevel(level);
+    if (perspective() == Perspective::IS_CLIENT &&
+        level == ENCRYPTION_FORWARD_SECURE) {
+      // 1-RTT write key is available. Retransmit 0-RTT data if there is any.
+      OnCanWrite();
+    }
     return;
   }
 
@@ -1395,6 +1400,9 @@
     // available.
     QUIC_DVLOG(1) << ENDPOINT << "Set default encryption level to " << level;
     connection()->SetDefaultEncryptionLevel(level);
+    if (perspective() == Perspective::IS_CLIENT) {
+      OnCanWrite();
+    }
   }
 }
 
@@ -1480,7 +1488,13 @@
 }
 
 void QuicSession::OnZeroRttRejected() {
-  // TODO(b/153726130): Handle early data rejection.
+  // TODO(b/153726130): Read stream limit and flow control limit from server
+  // transport params, and close the connection proactively if client has used
+  // too many.
+  connection_->RetransmitZeroRttPackets();
+  if (connection_->encryption_level() == ENCRYPTION_FORWARD_SECURE) {
+    OnCanWrite();
+  }
 }
 
 bool QuicSession::FillTransportParameters(TransportParameters* params) {
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc
index 6ab93e3..a6c713e 100644
--- a/quic/core/tls_client_handshaker.cc
+++ b/quic/core/tls_client_handshaker.cc
@@ -12,6 +12,7 @@
 #include "net/third_party/quiche/src/quic/core/crypto/quic_encrypter.h"
 #include "net/third_party/quiche/src/quic/core/crypto/transport_parameters.h"
 #include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_hostname_utils.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
@@ -388,6 +389,9 @@
   if (level == ENCRYPTION_FORWARD_SECURE || level == ENCRYPTION_ZERO_RTT) {
     encryption_established_ = true;
   }
+  if (level == ENCRYPTION_FORWARD_SECURE) {
+    handshaker_delegate()->DiscardOldEncryptionKey(ENCRYPTION_ZERO_RTT);
+  }
   TlsHandshaker::SetWriteSecret(level, cipher, write_secret);
 }
 
diff --git a/quic/core/tls_client_handshaker_test.cc b/quic/core/tls_client_handshaker_test.cc
index fff5135..a59e6b8 100644
--- a/quic/core/tls_client_handshaker_test.cc
+++ b/quic/core/tls_client_handshaker_test.cc
@@ -10,11 +10,15 @@
 #include "net/third_party/quiche/src/quic/core/crypto/quic_encrypter.h"
 #include "net/third_party/quiche/src/quic/core/quic_packets.h"
 #include "net/third_party/quiche/src/quic/core/quic_server_id.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.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_test.h"
 #include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_framer_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
 #include "net/third_party/quiche/src/quic/test_tools/simple_session_cache.h"
@@ -381,9 +385,34 @@
   // Create a second connection, but disable 0-RTT on the server.
   SSL_CTX_set_early_data_enabled(server_crypto_config_->ssl_ctx(), false);
   CreateConnection();
-  EXPECT_CALL(*session_, OnZeroRttRejected());
+
+  EXPECT_CALL(*session_, OnZeroRttRejected())
+      .WillOnce(testing::Invoke(
+          session_.get(), &TestQuicSpdyClientSession::ReallyOnZeroRttRejected));
+
+  // 4 packets will be sent in this connection: initial handshake packet, 0-RTT
+  // packet containing SETTINGS, handshake packet upon 0-RTT rejection, 0-RTT
+  // packet retransmission.
+  EXPECT_CALL(*connection_,
+              OnPacketSent(ENCRYPTION_INITIAL, NOT_RETRANSMISSION));
+  if (VersionUsesHttp3(session_->transport_version())) {
+    EXPECT_CALL(*connection_,
+                OnPacketSent(ENCRYPTION_ZERO_RTT, NOT_RETRANSMISSION));
+  }
+  EXPECT_CALL(*connection_,
+              OnPacketSent(ENCRYPTION_HANDSHAKE, NOT_RETRANSMISSION));
+  if (VersionUsesHttp3(session_->transport_version())) {
+    // TODO(b/158027651): change transmission type to
+    // ALL_ZERO_RTT_RETRANSMISSION.
+    EXPECT_CALL(*connection_,
+                OnPacketSent(ENCRYPTION_FORWARD_SECURE, LOSS_RETRANSMISSION));
+  }
+
   CompleteCryptoHandshake();
 
+  QuicFramer* framer = QuicConnectionPeer::GetFramer(connection_);
+  EXPECT_EQ(nullptr, QuicFramerPeer::GetEncrypter(framer, ENCRYPTION_ZERO_RTT));
+
   EXPECT_EQ(PROTOCOL_TLS1_3, stream()->handshake_protocol());
   EXPECT_TRUE(stream()->encryption_established());
   EXPECT_TRUE(stream()->one_rtt_keys_available());
diff --git a/quic/test_tools/quic_test_utils.cc b/quic/test_tools/quic_test_utils.cc
index fec4cc7..ed668d7 100644
--- a/quic/test_tools/quic_test_utils.cc
+++ b/quic/test_tools/quic_test_utils.cc
@@ -566,6 +566,7 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10));
   // Transfer ownership of the packet to the SentPacketManager and the
   // ack notifier to the AckNotifierManager.
+  OnPacketSent(packet.encryption_level, packet.transmission_type);
   QuicConnectionPeer::GetSentPacketManager(this)->OnPacketSent(
       &packet, clock_.ApproximateNow(), NOT_RETRANSMISSION,
       HAS_RETRANSMITTABLE_DATA);
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index 51cc7bf..11bf3c6 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -742,6 +742,8 @@
 
   void SendOrQueuePacket(SerializedPacket packet) override;
 
+  MOCK_METHOD(void, OnPacketSent, (EncryptionLevel, TransmissionType));
+
   std::vector<std::unique_ptr<QuicEncryptedPacket>> encrypted_packets_;
   MockClock clock_;
 };
@@ -1143,6 +1145,8 @@
   MOCK_METHOD(void, OnAlpnSelected, (quiche::QuicheStringPiece), (override));
   MOCK_METHOD(void, OnZeroRttRejected, (), (override));
 
+  void ReallyOnZeroRttRejected() { QuicSession::OnZeroRttRejected(); }
+
   QuicCryptoClientStream* GetMutableCryptoStream() override;
   const QuicCryptoClientStream* GetCryptoStream() const override;