Do not reuse tokens received in NEW_TOKEN frames for different connection attempts by:
1) add source address token to QuicClientSessionCache,
2) Clear token after use.

Protected by FLAGS_quic_reloadable_flag_quic_tls_use_token_in_session_cache.

PiperOrigin-RevId: 411536820
diff --git a/quic/core/crypto/quic_client_session_cache.cc b/quic/core/crypto/quic_client_session_cache.cc
index 64c3ffd..97f7ee5 100644
--- a/quic/core/crypto/quic_client_session_cache.cc
+++ b/quic/core/crypto/quic_client_session_cache.cc
@@ -87,6 +87,12 @@
     state->application_state =
         std::make_unique<ApplicationState>(*iter->second->application_state);
   }
+  if (GetQuicReloadableFlag(quic_tls_use_token_in_session_cache) &&
+      !iter->second->token.empty()) {
+    state->token = iter->second->token;
+    // Clear token after use.
+    iter->second->token.clear();
+  }
 
   return state;
 }
@@ -102,6 +108,18 @@
   }
 }
 
+void QuicClientSessionCache::OnNewTokenReceived(const QuicServerId& server_id,
+                                                absl::string_view token) {
+  if (token.empty()) {
+    return;
+  }
+  auto iter = cache_.Lookup(server_id);
+  if (iter == cache_.end()) {
+    return;
+  }
+  iter->second->token = token;
+}
+
 void QuicClientSessionCache::RemoveExpiredEntries(QuicWallTime now) {
   auto iter = cache_.begin();
   while (iter != cache_.end()) {
diff --git a/quic/core/crypto/quic_client_session_cache.h b/quic/core/crypto/quic_client_session_cache.h
index 667594f..38678af 100644
--- a/quic/core/crypto/quic_client_session_cache.h
+++ b/quic/core/crypto/quic_client_session_cache.h
@@ -13,6 +13,10 @@
 
 namespace quic {
 
+namespace test {
+class QuicClientSessionCachePeer;
+}  // namespace test
+
 // QuicClientSessionCache maps from QuicServerId to information used to resume
 // TLS sessions for that server.
 class QUIC_EXPORT_PRIVATE QuicClientSessionCache : public SessionCache {
@@ -32,6 +36,9 @@
 
   void ClearEarlyData(const QuicServerId& server_id) override;
 
+  void OnNewTokenReceived(const QuicServerId& server_id,
+                          absl::string_view token) override;
+
   void RemoveExpiredEntries(QuicWallTime now) override;
 
   void Clear() override;
@@ -39,6 +46,8 @@
   size_t size() const { return cache_.Size(); }
 
  private:
+  friend class test::QuicClientSessionCachePeer;
+
   struct QUIC_EXPORT_PRIVATE Entry {
     Entry();
     Entry(Entry&&);
@@ -56,6 +65,7 @@
     bssl::UniquePtr<SSL_SESSION> sessions[2];
     std::unique_ptr<TransportParameters> params;
     std::unique_ptr<ApplicationState> application_state;
+    std::string token;  // An opaque string received in NEW_TOKEN frame.
   };
 
   // Creates a new entry and insert into |cache_|.
diff --git a/quic/core/crypto/quic_crypto_client_config.h b/quic/core/crypto/quic_crypto_client_config.h
index 694e068..1351729 100644
--- a/quic/core/crypto/quic_crypto_client_config.h
+++ b/quic/core/crypto/quic_crypto_client_config.h
@@ -49,6 +49,9 @@
   // client received from the server at the application layer that the client
   // needs to remember when performing a 0-RTT handshake.
   std::unique_ptr<ApplicationState> application_state = nullptr;
+
+  // Opaque token received in NEW_TOKEN frame if any.
+  std::string token;
 };
 
 // SessionCache is an interface for managing storing and retrieving
@@ -80,6 +83,10 @@
   // associated with |server_id|.
   virtual void ClearEarlyData(const QuicServerId& server_id) = 0;
 
+  // Called when NEW_TOKEN frame is received.
+  virtual void OnNewTokenReceived(const QuicServerId& server_id,
+                                  absl::string_view token) = 0;
+
   // Called to remove expired entries.
   virtual void RemoveExpiredEntries(QuicWallTime now) = 0;
 
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index ee891dc..5d2d884 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -47,6 +47,7 @@
 #include "quic/test_tools/qpack/qpack_encoder_test_utils.h"
 #include "quic/test_tools/qpack/qpack_test_utils.h"
 #include "quic/test_tools/quic_client_peer.h"
+#include "quic/test_tools/quic_client_session_cache_peer.h"
 #include "quic/test_tools/quic_config_peer.h"
 #include "quic/test_tools/quic_connection_peer.h"
 #include "quic/test_tools/quic_dispatcher_peer.h"
@@ -1702,6 +1703,61 @@
   server_thread_->Resume();
 
   client_->Disconnect();
+
+  // Regression test for b/206087883.
+  // Mock server crash.
+  StopServer();
+
+  // The handshake fails due to idle timeout.
+  client_->Connect();
+  ASSERT_FALSE(client_->client()->WaitForOneRttKeysAvailable());
+  client_->WaitForWriteToFlush();
+  client_->WaitForResponse();
+  ASSERT_FALSE(client_->client()->connected());
+  EXPECT_THAT(client_->connection_error(), IsError(QUIC_NETWORK_IDLE_TIMEOUT));
+
+  // Server restarts.
+  server_writer_ = new PacketDroppingTestWriter();
+  StartServer();
+
+  // Client re-connect.
+  client_->Connect();
+  ASSERT_TRUE(client_->client()->WaitForOneRttKeysAvailable());
+  client_->WaitForWriteToFlush();
+  client_->WaitForResponse();
+  ASSERT_TRUE(client_->client()->connected());
+  client_session = GetClientSession();
+  ASSERT_TRUE(client_session);
+  EXPECT_FALSE(client_session->EarlyDataAccepted());
+  EXPECT_FALSE(client_->client()->EarlyDataAccepted());
+  server_thread_->Pause();
+  server_session = GetServerSession();
+  server_connection = GetServerConnection();
+  if (!GetQuicReloadableFlag(quic_tls_use_token_in_session_cache)) {
+    // Address token is reused.
+    if (server_session != nullptr && server_connection != nullptr) {
+      // Verify address is validated via validating token received in INITIAL
+      // packet.
+      EXPECT_FALSE(server_connection->GetStats()
+                       .address_validated_via_decrypting_packet);
+      EXPECT_TRUE(server_connection->GetStats().address_validated_via_token);
+    } else {
+      ADD_FAILURE() << "Missing server connection";
+    }
+  } else {
+    // Verify address token is only used once.
+    if (server_session != nullptr && server_connection != nullptr) {
+      // Verify address is validated via decrypting packet.
+      EXPECT_TRUE(server_connection->GetStats()
+                      .address_validated_via_decrypting_packet);
+      EXPECT_FALSE(server_connection->GetStats().address_validated_via_token);
+    } else {
+      ADD_FAILURE() << "Missing server connection";
+    }
+  }
+  server_thread_->Resume();
+
+  client_->Disconnect();
 }
 
 TEST_P(EndToEndTest, AddressTokenRefreshedByServer) {
@@ -1721,8 +1777,16 @@
 
   client_->Disconnect();
 
-  std::string old_address_token =
-      client_crypto_config->LookupOrCreate(server_id)->source_address_token();
+  QuicClientSessionCache* session_cache = static_cast<QuicClientSessionCache*>(
+      client_crypto_config->mutable_session_cache());
+  std::string old_address_token;
+  if (GetQuicReloadableFlag(quic_tls_use_token_in_session_cache)) {
+    old_address_token =
+        QuicClientSessionCachePeer::GetToken(session_cache, server_id);
+  } else {
+    old_address_token =
+        client_crypto_config->LookupOrCreate(server_id)->source_address_token();
+  }
   ASSERT_TRUE(!old_address_token.empty());
 
   SetQuicReloadableFlag(quic_add_cached_network_parameters_to_address_token,
@@ -1750,8 +1814,14 @@
 
   client_->Disconnect();
 
-  std::string new_address_token =
-      client_crypto_config->LookupOrCreate(server_id)->source_address_token();
+  std::string new_address_token;
+  if (GetQuicReloadableFlag(quic_tls_use_token_in_session_cache)) {
+    new_address_token =
+        QuicClientSessionCachePeer::GetToken(session_cache, server_id);
+  } else {
+    new_address_token =
+        client_crypto_config->LookupOrCreate(server_id)->source_address_token();
+  }
   ASSERT_TRUE(!new_address_token.empty());
   ASSERT_NE(new_address_token, old_address_token);
 }
@@ -1772,8 +1842,16 @@
 
   client_->Disconnect();
 
-  std::string old_address_token =
-      client_crypto_config->LookupOrCreate(server_id)->source_address_token();
+  QuicClientSessionCache* session_cache = static_cast<QuicClientSessionCache*>(
+      client_crypto_config->mutable_session_cache());
+  std::string old_address_token;
+  if (GetQuicReloadableFlag(quic_tls_use_token_in_session_cache)) {
+    old_address_token =
+        QuicClientSessionCachePeer::GetToken(session_cache, server_id);
+  } else {
+    old_address_token =
+        client_crypto_config->LookupOrCreate(server_id)->source_address_token();
+  }
   ASSERT_TRUE(!old_address_token.empty());
 
   // Pause the server thread again to blackhole packets from client.
@@ -1782,10 +1860,17 @@
   EXPECT_FALSE(client_->client()->WaitForOneRttKeysAvailable());
   EXPECT_FALSE(client_->client()->connected());
 
-  std::string new_address_token =
-      client_crypto_config->LookupOrCreate(server_id)->source_address_token();
-  // TODO(b/206087883): This currently fails, fix the client and uncomment it.
-  // ASSERT_TRUE(new_address_token.empty());
+  std::string new_address_token;
+  if (GetQuicReloadableFlag(quic_tls_use_token_in_session_cache)) {
+    new_address_token =
+        QuicClientSessionCachePeer::GetToken(session_cache, server_id);
+    // Verify address token gets cleared.
+    ASSERT_TRUE(new_address_token.empty());
+  } else {
+    new_address_token =
+        client_crypto_config->LookupOrCreate(server_id)->source_address_token();
+    ASSERT_FALSE(new_address_token.empty());
+  }
   server_thread_->Resume();
 }
 
diff --git a/quic/core/quic_dispatcher_test.cc b/quic/core/quic_dispatcher_test.cc
index d4af035..199270e 100644
--- a/quic/core/quic_dispatcher_test.cc
+++ b/quic/core/quic_dispatcher_test.cc
@@ -454,7 +454,8 @@
     ParsedClientHello parsed_chlo;
     parsed_chlo.alpns = {ExpectedAlpn()};
     parsed_chlo.sni = TestHostname();
-    if (address_token_.has_value()) {
+    if (address_token_.has_value() &&
+        !GetQuicReloadableFlag(quic_tls_use_token_in_session_cache)) {
       parsed_chlo.retry_token = *address_token_;
     }
     return parsed_chlo;
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 4c3a5e8..518b7c3 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -115,6 +115,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false)
 // If true, validate that peer owns the new address once the server detects peer migration or is probed from that address, and also apply anti-amplification limit while sending to that address.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_server_reverse_validate_new_path3, true)
+// If true, when client attempts TLS resumption, use token in session_cache_ instead of cached_states_ in QuicCryptoClientConfig.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_tls_use_token_in_session_cache, true)
 // When receiving STOP_SENDING, send a RESET_STREAM with a matching error code.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_match_ietf_reset_code, true)
 // When the flag is true, exit STARTUP after the same number of loss events as PROBE_UP.
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc
index 211dfde..c8306a8 100644
--- a/quic/core/tls_client_handshaker.cc
+++ b/quic/core/tls_client_handshaker.cc
@@ -42,10 +42,12 @@
       has_application_state_(has_application_state),
       crypto_config_(crypto_config),
       tls_connection_(crypto_config->ssl_ctx(), this, session->GetSSLConfig()) {
-  std::string token =
-      crypto_config->LookupOrCreate(server_id)->source_address_token();
-  if (!token.empty()) {
-    session->SetSourceAddressTokenToSend(token);
+  if (!GetQuicReloadableFlag(quic_tls_use_token_in_session_cache)) {
+    std::string token =
+        crypto_config->LookupOrCreate(server_id)->source_address_token();
+    if (!token.empty()) {
+      session->SetSourceAddressTokenToSend(token);
+    }
   }
   if (crypto_config->tls_signature_algorithms().has_value()) {
     SSL_set1_sigalgs_list(ssl(),
@@ -125,6 +127,10 @@
   }
   if (cached_state_) {
     SSL_set_session(ssl(), cached_state_->tls_session.get());
+    if (GetQuicReloadableFlag(quic_tls_use_token_in_session_cache) &&
+        !cached_state_->token.empty()) {
+      session()->SetSourceAddressTokenToSend(cached_state_->token);
+    }
   }
 
   // Start the handshake.
@@ -434,9 +440,15 @@
   if (token.empty()) {
     return;
   }
-  QuicCryptoClientConfig::CachedState* cached =
-      crypto_config_->LookupOrCreate(server_id_);
-  cached->set_source_address_token(token);
+  if (GetQuicReloadableFlag(quic_tls_use_token_in_session_cache)) {
+    if (session_cache_ != nullptr) {
+      session_cache_->OnNewTokenReceived(server_id_, token);
+    }
+  } else {
+    QuicCryptoClientConfig::CachedState* cached =
+        crypto_config_->LookupOrCreate(server_id_);
+    cached->set_source_address_token(token);
+  }
 }
 
 void TlsClientHandshaker::SetWriteSecret(
diff --git a/quic/test_tools/quic_client_session_cache_peer.h b/quic/test_tools/quic_client_session_cache_peer.h
new file mode 100644
index 0000000..6f0c667
--- /dev/null
+++ b/quic/test_tools/quic_client_session_cache_peer.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2021 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_CLIENT_SESSION_CACHE_PEER_H_
+#define QUICHE_QUIC_TEST_TOOLS_QUIC_CLIENT_SESSION_CACHE_PEER_H_
+
+#include "quic/core/crypto/quic_client_session_cache.h"
+
+namespace quic {
+namespace test {
+
+class QuicClientSessionCachePeer {
+ public:
+  static std::string GetToken(QuicClientSessionCache* cache,
+                              const QuicServerId& server_id) {
+    auto iter = cache->cache_.Lookup(server_id);
+    if (iter == cache->cache_.end()) {
+      return {};
+    }
+    return iter->second->token;
+  }
+};
+
+}  // namespace test
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TEST_TOOLS_QUIC_CLIENT_SESSION_CACHE_PEER_H_
diff --git a/quic/test_tools/simple_session_cache.cc b/quic/test_tools/simple_session_cache.cc
index d2f5cf8..2d23673 100644
--- a/quic/test_tools/simple_session_cache.cc
+++ b/quic/test_tools/simple_session_cache.cc
@@ -48,6 +48,7 @@
   }
   state->transport_params =
       std::make_unique<TransportParameters>(*it->second.params);
+  state->token = it->second.token;
   return state;
 }
 
@@ -56,6 +57,15 @@
   // do anything here.
 }
 
+void SimpleSessionCache::OnNewTokenReceived(const QuicServerId& server_id,
+                                            absl::string_view token) {
+  auto it = cache_entries_.find(server_id);
+  if (it == cache_entries_.end()) {
+    return;
+  }
+  it->second.token = token;
+}
+
 void SimpleSessionCache::RemoveExpiredEntries(QuicWallTime /*now*/) {
   // The simple session cache does not support removing expired entries.
 }
diff --git a/quic/test_tools/simple_session_cache.h b/quic/test_tools/simple_session_cache.h
index 54bb0c7..6b4111e 100644
--- a/quic/test_tools/simple_session_cache.h
+++ b/quic/test_tools/simple_session_cache.h
@@ -31,6 +31,8 @@
                                               QuicWallTime now,
                                               const SSL_CTX* ctx) override;
   void ClearEarlyData(const QuicServerId& server_id) override;
+  void OnNewTokenReceived(const QuicServerId& server_id,
+                          absl::string_view token) override;
   void RemoveExpiredEntries(QuicWallTime now) override;
   void Clear() override;
 
@@ -39,6 +41,7 @@
     bssl::UniquePtr<SSL_SESSION> session;
     std::unique_ptr<TransportParameters> params;
     std::unique_ptr<ApplicationState> application_state;
+    std::string token;
   };
   std::map<QuicServerId, Entry> cache_entries_;
 };