Internal change

PiperOrigin-RevId: 498244260
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index 2b404a7..5b1fa1f 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -87,6 +87,8 @@
 QUIC_FLAG(quic_reloadable_flag_quic_priority_update_structured_headers_parser, true)
 // If true, uses conservative cwnd gain and pacing gain when cwnd gets bootstrapped.
 QUIC_FLAG(quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false)
+// If true, when TicketCrypter fails to encrypt a session ticket, quic::TlsServerHandshaker will send a placeholder ticket, instead of an empty one, to the client.
+QUIC_FLAG(quic_reloadable_flag_quic_send_placeholder_ticket_when_encrypt_ticket_fails, true)
 // When true, defaults to BBR congestion control instead of Cubic.
 QUIC_FLAG(quic_reloadable_flag_quic_default_to_bbr, false)
 // When true, support draft-ietf-quic-v2-08
diff --git a/quiche/quic/core/tls_server_handshaker.cc b/quiche/quic/core/tls_server_handshaker.cc
index b035c60..f31489a 100644
--- a/quiche/quic/core/tls_server_handshaker.cc
+++ b/quiche/quic/core/tls_server_handshaker.cc
@@ -735,6 +735,15 @@
   QUICHE_DCHECK(proof_source_->GetTicketCrypter());
   std::vector<uint8_t> ticket =
       proof_source_->GetTicketCrypter()->Encrypt(in, ticket_encryption_key_);
+  if (GetQuicReloadableFlag(
+          quic_send_placeholder_ticket_when_encrypt_ticket_fails) &&
+      ticket.empty()) {
+    QUIC_CODE_COUNT(quic_tls_server_handshaker_send_placeholder_ticket);
+    const absl::string_view kTicketFailurePlaceholder = "TICKET FAILURE";
+    const absl::string_view kTicketWithSizeLimit =
+        kTicketFailurePlaceholder.substr(0, max_out_len);
+    ticket.assign(kTicketWithSizeLimit.begin(), kTicketWithSizeLimit.end());
+  }
   if (max_out_len < ticket.size()) {
     QUIC_BUG(quic_bug_12423_2)
         << "TicketCrypter returned " << ticket.size()
diff --git a/quiche/quic/core/tls_server_handshaker_test.cc b/quiche/quic/core/tls_server_handshaker_test.cc
index fe3f982..e68ea65 100644
--- a/quiche/quic/core/tls_server_handshaker_test.cc
+++ b/quiche/quic/core/tls_server_handshaker_test.cc
@@ -786,6 +786,29 @@
   EXPECT_TRUE(server_stream()->ResumptionAttempted());
 }
 
+TEST_P(TlsServerHandshakerTest, ResumptionWithPlaceholderTicket) {
+  // Do the first handshake
+  InitializeFakeClient();
+
+  ticket_crypter_->set_fail_encrypt(true);
+  CompleteCryptoHandshake();
+  ExpectHandshakeSuccessful();
+  EXPECT_FALSE(client_stream()->IsResumption());
+  EXPECT_FALSE(server_stream()->IsResumption());
+  EXPECT_FALSE(server_stream()->ResumptionAttempted());
+
+  // Now do another handshake. It should end up with a full handshake because
+  // the placeholder ticket is undecryptable.
+  InitializeServer();
+  InitializeFakeClient();
+  CompleteCryptoHandshake();
+  ExpectHandshakeSuccessful();
+  EXPECT_FALSE(client_stream()->IsResumption());
+  EXPECT_FALSE(server_stream()->IsResumption());
+  EXPECT_NE(server_stream()->ResumptionAttempted(),
+            GetParam().disable_resumption);
+}
+
 TEST_P(TlsServerHandshakerTest, AdvanceHandshakeDuringAsyncDecryptCallback) {
   if (GetParam().disable_resumption) {
     return;
diff --git a/quiche/quic/test_tools/test_ticket_crypter.cc b/quiche/quic/test_tools/test_ticket_crypter.cc
index dbdb2a9..0a5ec49 100644
--- a/quiche/quic/test_tools/test_ticket_crypter.cc
+++ b/quiche/quic/test_tools/test_ticket_crypter.cc
@@ -37,6 +37,9 @@
 
 std::vector<uint8_t> TestTicketCrypter::Encrypt(
     absl::string_view in, absl::string_view /* encryption_key */) {
+  if (fail_encrypt_) {
+    return {};
+  }
   size_t prefix_len = ticket_prefix_.size();
   std::vector<uint8_t> out(prefix_len + in.size());
   memcpy(out.data(), ticket_prefix_.data(), prefix_len);
diff --git a/quiche/quic/test_tools/test_ticket_crypter.h b/quiche/quic/test_tools/test_ticket_crypter.h
index 888bdef..0efd0f9 100644
--- a/quiche/quic/test_tools/test_ticket_crypter.h
+++ b/quiche/quic/test_tools/test_ticket_crypter.h
@@ -30,6 +30,7 @@
 
   // Allows configuring this TestTicketCrypter to fail decryption.
   void set_fail_decrypt(bool fail_decrypt) { fail_decrypt_ = fail_decrypt; }
+  void set_fail_encrypt(bool fail_encrypt) { fail_encrypt_ = fail_encrypt; }
 
  private:
   // Performs the Decrypt operation synchronously.
@@ -41,6 +42,7 @@
   };
 
   bool fail_decrypt_ = false;
+  bool fail_encrypt_ = false;
   bool run_async_ = false;
   std::vector<PendingCallback> pending_callbacks_;
   std::vector<uint8_t> ticket_prefix_;