Move QuicClientSessionCache in chromium to shared code by making following changes:
1) Rename FlushInvalidEntries() and Flush() to RemoveExpiredEntries() and Clear(), respectively.
2) Remove clock_ and SetClockForTesting(), instead, pass in QuicWallTime (use ToUNIXSeconds to get seconds from UNIX epoch because SSL_SESSION_get_time returns seconds from UNIX epoch) to Lookup and RemoveExpiredEntries.
3) Remove memory_pressure_listener_ and OnMemoryPressure(). In chromium, memory_pressure_listener_ and OnMemoryPressure() will be moved to QuicStreamFactory::QuicCryptoClientConfigOwner.
4) Replace base::LRUCache with QuicLRUCache (and add hasher for QuicServerId).

Also let clients in e2e tests and QuicClient use QuicClientSessionCache instead of SimpleSessionCache.

PiperOrigin-RevId: 411153489
diff --git a/quic/core/crypto/quic_client_session_cache.cc b/quic/core/crypto/quic_client_session_cache.cc
new file mode 100644
index 0000000..64c3ffd
--- /dev/null
+++ b/quic/core/crypto/quic_client_session_cache.cc
@@ -0,0 +1,156 @@
+// 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.
+
+#include "quic/core/crypto/quic_client_session_cache.h"
+
+#include "quic/core/quic_clock.h"
+
+namespace quic {
+
+namespace {
+
+const size_t kDefaultMaxEntries = 1024;
+// Returns false if the SSL |session| doesn't exist or it is expired at |now|.
+bool IsValid(SSL_SESSION* session, uint64_t now) {
+  if (!session) return false;
+
+  // now_u64 may be slightly behind because of differences in how
+  // time is calculated at this layer versus BoringSSL.
+  // Add a second of wiggle room to account for this.
+  return !(now + 1 < SSL_SESSION_get_time(session) ||
+           now >= SSL_SESSION_get_time(session) +
+                      SSL_SESSION_get_timeout(session));
+}
+
+bool DoApplicationStatesMatch(const ApplicationState* state,
+                              ApplicationState* other) {
+  if ((state && !other) || (!state && other)) return false;
+  if ((!state && !other) || *state == *other) return true;
+  return false;
+}
+
+}  // namespace
+
+QuicClientSessionCache::QuicClientSessionCache()
+    : QuicClientSessionCache(kDefaultMaxEntries) {}
+
+QuicClientSessionCache::QuicClientSessionCache(size_t max_entries)
+    : cache_(max_entries) {}
+
+QuicClientSessionCache::~QuicClientSessionCache() { Clear(); }
+
+void QuicClientSessionCache::Insert(const QuicServerId& server_id,
+                                    bssl::UniquePtr<SSL_SESSION> session,
+                                    const TransportParameters& params,
+                                    const ApplicationState* application_state) {
+  QUICHE_DCHECK(session) << "TLS session is not inserted into client cache.";
+  auto iter = cache_.Lookup(server_id);
+  if (iter == cache_.end()) {
+    CreateAndInsertEntry(server_id, std::move(session), params,
+                         application_state);
+    return;
+  }
+
+  QUICHE_DCHECK(iter->second->params);
+  // The states are both the same, so only need to insert sessions.
+  if (params == *iter->second->params &&
+      DoApplicationStatesMatch(application_state,
+                               iter->second->application_state.get())) {
+    iter->second->PushSession(std::move(session));
+    return;
+  }
+  // Erase the existing entry because this Insert call must come from a
+  // different QUIC session.
+  cache_.Erase(iter);
+  CreateAndInsertEntry(server_id, std::move(session), params,
+                       application_state);
+}
+
+std::unique_ptr<QuicResumptionState> QuicClientSessionCache::Lookup(
+    const QuicServerId& server_id, QuicWallTime now, const SSL_CTX* /*ctx*/) {
+  auto iter = cache_.Lookup(server_id);
+  if (iter == cache_.end()) return nullptr;
+
+  if (!IsValid(iter->second->PeekSession(), now.ToUNIXSeconds())) {
+    QUIC_DLOG(INFO) << "TLS Session expired for host:" << server_id.host();
+    cache_.Erase(iter);
+    return nullptr;
+  }
+  auto state = std::make_unique<QuicResumptionState>();
+  state->tls_session = iter->second->PopSession();
+  if (iter->second->params != nullptr) {
+    state->transport_params =
+        std::make_unique<TransportParameters>(*iter->second->params);
+  }
+  if (iter->second->application_state != nullptr) {
+    state->application_state =
+        std::make_unique<ApplicationState>(*iter->second->application_state);
+  }
+
+  return state;
+}
+
+void QuicClientSessionCache::ClearEarlyData(const QuicServerId& server_id) {
+  auto iter = cache_.Lookup(server_id);
+  if (iter == cache_.end()) return;
+  for (auto& session : iter->second->sessions) {
+    if (session) {
+      QUIC_DLOG(INFO) << "Clear early data for for host: " << server_id.host();
+      session.reset(SSL_SESSION_copy_without_early_data(session.get()));
+    }
+  }
+}
+
+void QuicClientSessionCache::RemoveExpiredEntries(QuicWallTime now) {
+  auto iter = cache_.begin();
+  while (iter != cache_.end()) {
+    if (!IsValid(iter->second->PeekSession(), now.ToUNIXSeconds())) {
+      iter = cache_.Erase(iter);
+    } else {
+      ++iter;
+    }
+  }
+}
+
+void QuicClientSessionCache::Clear() { cache_.Clear(); }
+
+void QuicClientSessionCache::CreateAndInsertEntry(
+    const QuicServerId& server_id, bssl::UniquePtr<SSL_SESSION> session,
+    const TransportParameters& params,
+    const ApplicationState* application_state) {
+  auto entry = std::make_unique<Entry>();
+  entry->PushSession(std::move(session));
+  entry->params = std::make_unique<TransportParameters>(params);
+  if (application_state) {
+    entry->application_state =
+        std::make_unique<ApplicationState>(*application_state);
+  }
+  cache_.Insert(server_id, std::move(entry));
+}
+
+QuicClientSessionCache::Entry::Entry() = default;
+QuicClientSessionCache::Entry::Entry(Entry&&) = default;
+QuicClientSessionCache::Entry::~Entry() = default;
+
+void QuicClientSessionCache::Entry::PushSession(
+    bssl::UniquePtr<SSL_SESSION> session) {
+  if (sessions[0] != nullptr) {
+    sessions[1] = std::move(sessions[0]);
+  }
+  sessions[0] = std::move(session);
+}
+
+bssl::UniquePtr<SSL_SESSION> QuicClientSessionCache::Entry::PopSession() {
+  if (sessions[0] == nullptr) return nullptr;
+  bssl::UniquePtr<SSL_SESSION> session = std::move(sessions[0]);
+  sessions[0] = std::move(sessions[1]);
+  sessions[1] = nullptr;
+  return session;
+}
+
+SSL_SESSION* QuicClientSessionCache::Entry::PeekSession() {
+  return sessions[0].get();
+}
+
+}  // namespace quic
diff --git a/quic/core/crypto/quic_client_session_cache.h b/quic/core/crypto/quic_client_session_cache.h
new file mode 100644
index 0000000..667594f
--- /dev/null
+++ b/quic/core/crypto/quic_client_session_cache.h
@@ -0,0 +1,72 @@
+// 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_CORE_CRYPTO_QUIC_CLIENT_SESSION_CACHE_H_
+#define QUICHE_QUIC_CORE_CRYPTO_QUIC_CLIENT_SESSION_CACHE_H_
+
+#include <memory>
+
+#include "quic/core/crypto/quic_crypto_client_config.h"
+#include "quic/core/quic_lru_cache.h"
+#include "quic/core/quic_server_id.h"
+
+namespace quic {
+
+// QuicClientSessionCache maps from QuicServerId to information used to resume
+// TLS sessions for that server.
+class QUIC_EXPORT_PRIVATE QuicClientSessionCache : public SessionCache {
+ public:
+  QuicClientSessionCache();
+  explicit QuicClientSessionCache(size_t max_entries);
+  ~QuicClientSessionCache() override;
+
+  void Insert(const QuicServerId& server_id,
+              bssl::UniquePtr<SSL_SESSION> session,
+              const TransportParameters& params,
+              const ApplicationState* application_state) override;
+
+  std::unique_ptr<QuicResumptionState> Lookup(const QuicServerId& server_id,
+                                              QuicWallTime now,
+                                              const SSL_CTX* ctx) override;
+
+  void ClearEarlyData(const QuicServerId& server_id) override;
+
+  void RemoveExpiredEntries(QuicWallTime now) override;
+
+  void Clear() override;
+
+  size_t size() const { return cache_.Size(); }
+
+ private:
+  struct QUIC_EXPORT_PRIVATE Entry {
+    Entry();
+    Entry(Entry&&);
+    ~Entry();
+
+    // Adds a new |session| onto sessions, dropping the oldest one if two are
+    // already stored.
+    void PushSession(bssl::UniquePtr<SSL_SESSION> session);
+
+    // Retrieves the latest session from the entry, meanwhile removing it.
+    bssl::UniquePtr<SSL_SESSION> PopSession();
+
+    SSL_SESSION* PeekSession();
+
+    bssl::UniquePtr<SSL_SESSION> sessions[2];
+    std::unique_ptr<TransportParameters> params;
+    std::unique_ptr<ApplicationState> application_state;
+  };
+
+  // Creates a new entry and insert into |cache_|.
+  void CreateAndInsertEntry(const QuicServerId& server_id,
+                            bssl::UniquePtr<SSL_SESSION> session,
+                            const TransportParameters& params,
+                            const ApplicationState* application_state);
+
+  QuicLRUCache<QuicServerId, Entry, QuicServerIdHash> cache_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_CRYPTO_QUIC_CLIENT_SESSION_CACHE_H_
diff --git a/quic/core/crypto/quic_client_session_cache_test.cc b/quic/core/crypto/quic_client_session_cache_test.cc
new file mode 100644
index 0000000..8fd6d25
--- /dev/null
+++ b/quic/core/crypto/quic_client_session_cache_test.cc
@@ -0,0 +1,440 @@
+// 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.
+
+#include "quic/core/crypto/quic_client_session_cache.h"
+
+#include "quic/platform/api/quic_test.h"
+#include "quic/test_tools/mock_clock.h"
+#include "common/quiche_text_utils.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+const QuicTime::Delta kTimeout = QuicTime::Delta::FromSeconds(1000);
+const QuicVersionLabel kFakeVersionLabel = 0x01234567;
+const QuicVersionLabel kFakeVersionLabel2 = 0x89ABCDEF;
+const uint64_t kFakeIdleTimeoutMilliseconds = 12012;
+const uint8_t kFakeStatelessResetTokenData[16] = {
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+    0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F};
+const uint64_t kFakeMaxPacketSize = 9001;
+const uint64_t kFakeInitialMaxData = 101;
+const bool kFakeDisableMigration = true;
+const auto kCustomParameter1 =
+    static_cast<TransportParameters::TransportParameterId>(0xffcd);
+const char* kCustomParameter1Value = "foo";
+const auto kCustomParameter2 =
+    static_cast<TransportParameters::TransportParameterId>(0xff34);
+const char* kCustomParameter2Value = "bar";
+
+std::vector<uint8_t> CreateFakeStatelessResetToken() {
+  return std::vector<uint8_t>(
+      kFakeStatelessResetTokenData,
+      kFakeStatelessResetTokenData + sizeof(kFakeStatelessResetTokenData));
+}
+
+TransportParameters::LegacyVersionInformation
+CreateFakeLegacyVersionInformation() {
+  TransportParameters::LegacyVersionInformation legacy_version_information;
+  legacy_version_information.version = kFakeVersionLabel;
+  legacy_version_information.supported_versions.push_back(kFakeVersionLabel);
+  legacy_version_information.supported_versions.push_back(kFakeVersionLabel2);
+  return legacy_version_information;
+}
+
+TransportParameters::VersionInformation CreateFakeVersionInformation() {
+  TransportParameters::VersionInformation version_information;
+  version_information.chosen_version = kFakeVersionLabel;
+  version_information.other_versions.push_back(kFakeVersionLabel);
+  return version_information;
+}
+
+// Make a TransportParameters that has a few fields set to help test comparison.
+std::unique_ptr<TransportParameters> MakeFakeTransportParams() {
+  auto params = std::make_unique<TransportParameters>();
+  params->perspective = Perspective::IS_CLIENT;
+  params->legacy_version_information = CreateFakeLegacyVersionInformation();
+  params->version_information = CreateFakeVersionInformation();
+  params->max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds);
+  params->stateless_reset_token = CreateFakeStatelessResetToken();
+  params->max_udp_payload_size.set_value(kFakeMaxPacketSize);
+  params->initial_max_data.set_value(kFakeInitialMaxData);
+  params->disable_active_migration = kFakeDisableMigration;
+  params->custom_parameters[kCustomParameter1] = kCustomParameter1Value;
+  params->custom_parameters[kCustomParameter2] = kCustomParameter2Value;
+  return params;
+}
+
+// Generated by running TlsClientHandshakerTest.ZeroRttResumption and in
+// TlsClientHandshaker::InsertSession calling SSL_SESSION_to_bytes to serialize
+// the received 0-RTT capable ticket.
+static const char kCachedSession[] =
+    "30820ad7020101020203040402130104206594ce84e61a866b56163c4ba09079aebf1d4f"
+    "6cbcbd38dc9d7066a38a76c9cf0420ec9062063582a4cc0a44f9ff93256a195153ba6032"
+    "0cf3c9189990932d838adaa10602046196f7b9a205020302a300a382039f3082039b3082"
+    "0183a00302010202021001300d06092a864886f70d010105050030623111300f06035504"
+    "030c08426f677573204941310b300906035504080c024d41310b30090603550406130255"
+    "533121301f06092a864886f70d0109011612626f67757340626f6775732d69612e636f6d"
+    "3110300e060355040a0c07426f6775734941301e170d3231303132383136323030315a17"
+    "0d3331303132363136323030315a3069311d301b06035504030c14746573745f6563632e"
+    "6578616d706c652e636f6d310b300906035504080c024d41310b30090603550406130255"
+    "53311e301c06092a864886f70d010901160f626f67757340626f6775732e636f6d310e30"
+    "0c060355040a0c05426f6775733059301306072a8648ce3d020106082a8648ce3d030107"
+    "034200041ba5e2b6f24e64990b9f24ae6d23473d8c77fbcfb7f554f36559529a69a57170"
+    "a10a81b7fe4a36ebf37b0a8c5e467a8443d8b8c002892aa5c1194bd843f42c9aa31f301d"
+    "301b0603551d11041430128210746573742e6578616d706c652e636f6d300d06092a8648"
+    "86f70d0101050500038202010019921d54ac06948763d609215f64f5d6540e3da886c6c9"
+    "61bc737a437719b4621416ef1229f39282d7d3234e1a5d57535473066233bd246eec8e96"
+    "1e0633cf4fe014c800e62599981820ec33d92e74ded0fa2953db1d81e19cb6890b6305b6"
+    "3ede8d3e9fcf3c09f3f57283acf08aa57be4ee9a68d00bb3e2ded5920c619b5d83e5194a"
+    "adb77ae5d61ed3e0a5670f0ae61cc3197329f0e71e3364dcab0405e9e4a6646adef8f022"
+    "6415ec16c8046307b1769029fe780bd576114dde2fa9b4a32aa70bc436549a24ee4907a9"
+    "045f6457ce8dfd8d62cc65315afe798ae1a948eefd70b035d415e73569c48fb20085de1a"
+    "87de039e6b0b9a5fcb4069df27f3a7a1409e72d1ac739c72f29ef786134207e61c79855f"
+    "c22e3ee5f6ad59a7b1ff0f18d79776f1c95efaebbebe381664132a58a1e7ff689945b7e0"
+    "88634b0872feeefbf6be020884b994c6a7ff435f2b3f609077ff97cb509cfa17ff479b34"
+    "e633e4b5bc46b20c5f27c80a2e2943f795a928acd5a3fc43c3af8425ad600c048b41d87e"
+    "6361bc72fc4e5e44680a3d325674ba6ffa760d2fc7d9e4847a8e0dd9d35a543324e18b94"
+    "2d42af6391ed1dd54a39e3f4a4c6b32486eb4ba72815dbd89c56fc053743a0b0483ce676"
+    "15defce6800c629b99d0cbc56da162487f475b7c246099eaf1e6d10a022b2f49c6af1da3"
+    "e8ed66096f267c4a76976b9572db7456ef90278330a4020400aa81b60481b3494e534543"
+    "55524500f3439e548c21d2ad6e5634cc1cc0045730819702010102020304040213010400"
+    "0420ec9062063582a4cc0a44f9ff93256a195153ba60320cf3c9189990932d838adaa106"
+    "02046196f7b9a205020302a300a4020400b20302011db5060404130800cdb807020500ff"
+    "ffffffb9050203093a80ba0404026833bb030101ffbc23042100d27d985bfce04833f02d"
+    "38366b219f4def42bc4ba1b01844d1778db11731487dbd020400be020400b20302011db3"
+    "8205da308205d6308203bea00302010202021000300d06092a864886f70d010105050030"
+    "62310b3009060355040613025553310b300906035504080c024d413110300e060355040a"
+    "0c07426f67757343413111300f06035504030c08426f6775732043413121301f06092a86"
+    "4886f70d0109011612626f67757340626f6775732d63612e636f6d3020170d3231303132"
+    "383136313935385a180f32303730303531313136313935385a30623111300f0603550403"
+    "0c08426f677573204941310b300906035504080c024d41310b3009060355040613025553"
+    "3121301f06092a864886f70d0109011612626f67757340626f6775732d69612e636f6d31"
+    "10300e060355040a0c07426f677573494130820222300d06092a864886f70d0101010500"
+    "0382020f003082020a028202010096c03a0ffc61bcedcd5ec9bf6f848b8a066b43f08377"
+    "3af518a6a0044f22e666e24d2ae741954e344302c4be04612185bd53bcd848eb322bf900"
+    "724eb0848047d647033ffbddb00f01d1de7c1cdb684f83c9bf5fd18ff60afad5a53b0d7d"
+    "2c2a50abc38df019cd7f50194d05bc4597a1ef8570ea04069a2c36d74496af126573ca18"
+    "8e470009b56250fadf2a04e837ee3837b36b1f08b7a0cfe2533d05f26484ce4e30203d01"
+    "517fffd3da63d0341079ddce16e9ab4dbf9d4049e5cc52326031e645dd682fe6220d9e0e"
+    "95451f5a82f3e1720dc13e8499466426a0bdbea9f6a76b3c9228dd3c79ab4dcc4c145ef0"
+    "e78d1ee8bfd4650692d7e28a54bed809d8f7b37fe24c586be59cc46638531cb291c8c156"
+    "8f08d67e768e51563e95a639c1f138b275ffad6a6a2a042ba9e26ad63c2ce63b600013f0"
+    "a6f0703ee51c4f457f7bab0391c2fc4c5bb3213742c9cf9941bff68cc2e1cc96139d35ed"
+    "1885244ddde0bf658416c486701841b81f7b17503d08c59a4db08a2a80755e007aa3b6c7"
+    "eadcaa9e07c8325f3689f100de23970b12c9d9f6d0a8fb35ba0fd75c64410318db4a13ac"
+    "3972ad16cdf6408af37013c7bcd7c42f20d6d04c3e39436c7531e8dafa219dd04b784ef0"
+    "3c70ee5a4782b33cafa925aa3deca62a14aed704f179b932efabc2b0c5c15a8a99bfc9e6"
+    "189dce7da50ea303594b6af9c933dd54b6e9d17c472d0203010001a38193308190300f06"
+    "03551d130101ff040530030101ff301d0603551d0e041604141a98e80029a80992b7e5e0"
+    "068ab9b3486cd839d6301f0603551d23041830168014780beeefe2fa419c48a438bdb30b"
+    "e37ef0b7a94e300b0603551d0f0404030202a430130603551d25040c300a06082b060105"
+    "05070301301b0603551d11041430128207426f67757343418207426f6775734941300d06"
+    "092a864886f70d010105050003820201009e822ed8064b1aabaddf1340010ea147f68c06"
+    "5a5a599ea305349f1b0e545a00817d6e55c7bf85560fab429ca72186c4d520b52f5cc121"
+    "abd068b06f3111494431d2522efa54642f907059e7db80b73bb5ecf621377195b8700bba"
+    "df798cece8c67a9571548d0e6592e81ae5d934877cb170aef18d3b97f635600fe0890d98"
+    "f88b33fe3d1fd34c1c915beae4e5c0b133f476c40b21d220f16ce9cdd9e8f97a36a31723"
+    "68875f052c9271648d9cb54687c6fdc3ea96f2908003bc5e5e79de00a21da7b8429f8b08"
+    "af4c4d34641e386d72eabf5f01f106363f2ffd18969bf0bb9a4d17627c6427ff772c4308"
+    "83c276feef5fc6dba9582c22fdbe9df7e8dfca375695f028ed588df54f3c86462dbf4c07"
+    "91d80ca738988a1419c86bb4dd8d738b746921f01f39422e5ffd488b6f00195b996e6392"
+    "3a820a32cd78b5989f339c0fcf4f269103964a30a16347d0ffdc8df1f3653ddc1515fa09"
+    "22c7aef1af1fbcb23e93ae7622ab1ee11fcfa98319bad4c37c091cad46bd0337b3cc78b5"
+    "5b9f1ea7994acc1f89c49a0b4cb540d2137e266fd43e56a9b5b778217b6f77df530e1eaf"
+    "b3417262b5ddb86d3c6c5ac51e3f326c650dcc2434473973b7182c66220d1f3871bde7ee"
+    "47d3f359d3d4c5bdd61baa684c03db4c75f9d6690c9e6e3abe6eaf5fa2c33c4daf26b373"
+    "d85a1e8a7d671ac4a0a97b14e36e81280de4593bbb12da7695b5060404130800cdb60301"
+    "0100b70402020403b807020500ffffffffb9050203093a80ba0404026833bb030101ffbd"
+    "020400be020400";
+
+class QuicClientSessionCacheTest : public QuicTest {
+ public:
+  QuicClientSessionCacheTest() : ssl_ctx_(SSL_CTX_new(TLS_method())) {
+    clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+  }
+
+ protected:
+  bssl::UniquePtr<SSL_SESSION> NewSSLSession() {
+    std::string cached_session =
+        absl::HexStringToBytes(absl::string_view(kCachedSession));
+    SSL_SESSION* session = SSL_SESSION_from_bytes(
+        reinterpret_cast<const uint8_t*>(cached_session.data()),
+        cached_session.size(), ssl_ctx_.get());
+    QUICHE_DCHECK(session);
+    return bssl::UniquePtr<SSL_SESSION>(session);
+  }
+
+  bssl::UniquePtr<SSL_SESSION> MakeTestSession(
+      QuicTime::Delta timeout = kTimeout) {
+    bssl::UniquePtr<SSL_SESSION> session = NewSSLSession();
+    SSL_SESSION_set_time(session.get(), clock_.WallNow().ToUNIXSeconds());
+    SSL_SESSION_set_timeout(session.get(), timeout.ToSeconds());
+    return session;
+  }
+
+  bssl::UniquePtr<SSL_CTX> ssl_ctx_;
+  MockClock clock_;
+};
+
+// Tests that simple insertion and lookup work correctly.
+TEST_F(QuicClientSessionCacheTest, SingleSession) {
+  QuicClientSessionCache cache;
+
+  auto params = MakeFakeTransportParams();
+  auto session = MakeTestSession();
+  QuicServerId id1("a.com", 443);
+
+  auto params2 = MakeFakeTransportParams();
+  auto session2 = MakeTestSession();
+  SSL_SESSION* unowned2 = session2.get();
+  QuicServerId id2("b.com", 443);
+
+  EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()));
+  EXPECT_EQ(nullptr, cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get()));
+  EXPECT_EQ(0u, cache.size());
+
+  cache.Insert(id1, std::move(session), *params, nullptr);
+  EXPECT_EQ(1u, cache.size());
+  EXPECT_EQ(
+      *params,
+      *(cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())->transport_params));
+  EXPECT_EQ(nullptr, cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get()));
+  // No session is available for id1, even though the entry exists.
+  EXPECT_EQ(1u, cache.size());
+  EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()));
+  // Lookup() will trigger a deletion of invalid entry.
+  EXPECT_EQ(0u, cache.size());
+
+  auto session3 = MakeTestSession();
+  SSL_SESSION* unowned3 = session3.get();
+  QuicServerId id3("c.com", 443);
+  cache.Insert(id3, std::move(session3), *params, nullptr);
+  cache.Insert(id2, std::move(session2), *params2, nullptr);
+  EXPECT_EQ(2u, cache.size());
+  EXPECT_EQ(
+      unowned2,
+      cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get())->tls_session.get());
+  EXPECT_EQ(
+      unowned3,
+      cache.Lookup(id3, clock_.WallNow(), ssl_ctx_.get())->tls_session.get());
+
+  // Verify that the cache is cleared after Lookups.
+  EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()));
+  EXPECT_EQ(nullptr, cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get()));
+  EXPECT_EQ(nullptr, cache.Lookup(id3, clock_.WallNow(), ssl_ctx_.get()));
+  EXPECT_EQ(0u, cache.size());
+}
+
+TEST_F(QuicClientSessionCacheTest, MultipleSessions) {
+  QuicClientSessionCache cache;
+
+  auto params = MakeFakeTransportParams();
+  auto session = MakeTestSession();
+  QuicServerId id1("a.com", 443);
+  auto session2 = MakeTestSession();
+  SSL_SESSION* unowned2 = session2.get();
+  auto session3 = MakeTestSession();
+  SSL_SESSION* unowned3 = session3.get();
+
+  cache.Insert(id1, std::move(session), *params, nullptr);
+  cache.Insert(id1, std::move(session2), *params, nullptr);
+  cache.Insert(id1, std::move(session3), *params, nullptr);
+  // The latest session is popped first.
+  EXPECT_EQ(
+      unowned3,
+      cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())->tls_session.get());
+  EXPECT_EQ(
+      unowned2,
+      cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())->tls_session.get());
+  // Only two sessions are cached.
+  EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()));
+}
+
+// Test that when a different TransportParameter is inserted for
+// the same server id, the existing entry is removed.
+TEST_F(QuicClientSessionCacheTest, DifferentTransportParams) {
+  QuicClientSessionCache cache;
+
+  auto params = MakeFakeTransportParams();
+  auto session = MakeTestSession();
+  QuicServerId id1("a.com", 443);
+  auto session2 = MakeTestSession();
+  auto session3 = MakeTestSession();
+  SSL_SESSION* unowned3 = session3.get();
+
+  cache.Insert(id1, std::move(session), *params, nullptr);
+  cache.Insert(id1, std::move(session2), *params, nullptr);
+  // tweak the transport parameters a little bit.
+  params->perspective = Perspective::IS_SERVER;
+  cache.Insert(id1, std::move(session3), *params, nullptr);
+  auto resumption_state = cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get());
+  EXPECT_EQ(unowned3, resumption_state->tls_session.get());
+  EXPECT_EQ(*params.get(), *resumption_state->transport_params);
+  EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()));
+}
+
+TEST_F(QuicClientSessionCacheTest, DifferentApplicationState) {
+  QuicClientSessionCache cache;
+
+  auto params = MakeFakeTransportParams();
+  auto session = MakeTestSession();
+  QuicServerId id1("a.com", 443);
+  auto session2 = MakeTestSession();
+  auto session3 = MakeTestSession();
+  SSL_SESSION* unowned3 = session3.get();
+  ApplicationState state;
+  state.push_back('a');
+
+  cache.Insert(id1, std::move(session), *params, &state);
+  cache.Insert(id1, std::move(session2), *params, &state);
+  cache.Insert(id1, std::move(session3), *params, nullptr);
+  auto resumption_state = cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get());
+  EXPECT_EQ(unowned3, resumption_state->tls_session.get());
+  EXPECT_EQ(nullptr, resumption_state->application_state);
+  EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()));
+}
+
+TEST_F(QuicClientSessionCacheTest, BothStatesDifferent) {
+  QuicClientSessionCache cache;
+
+  auto params = MakeFakeTransportParams();
+  auto session = MakeTestSession();
+  QuicServerId id1("a.com", 443);
+  auto session2 = MakeTestSession();
+  auto session3 = MakeTestSession();
+  SSL_SESSION* unowned3 = session3.get();
+  ApplicationState state;
+  state.push_back('a');
+
+  cache.Insert(id1, std::move(session), *params, &state);
+  cache.Insert(id1, std::move(session2), *params, &state);
+  params->perspective = Perspective::IS_SERVER;
+  cache.Insert(id1, std::move(session3), *params, nullptr);
+  auto resumption_state = cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get());
+  EXPECT_EQ(unowned3, resumption_state->tls_session.get());
+  EXPECT_EQ(*params.get(), *resumption_state->transport_params);
+  EXPECT_EQ(nullptr, resumption_state->application_state);
+  EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()));
+}
+
+// When the size limit is exceeded, the oldest entry should be erased.
+TEST_F(QuicClientSessionCacheTest, SizeLimit) {
+  QuicClientSessionCache cache(2);
+
+  auto params = MakeFakeTransportParams();
+  auto session = MakeTestSession();
+  QuicServerId id1("a.com", 443);
+
+  auto session2 = MakeTestSession();
+  SSL_SESSION* unowned2 = session2.get();
+  QuicServerId id2("b.com", 443);
+
+  auto session3 = MakeTestSession();
+  SSL_SESSION* unowned3 = session3.get();
+  QuicServerId id3("c.com", 443);
+
+  cache.Insert(id1, std::move(session), *params, nullptr);
+  cache.Insert(id2, std::move(session2), *params, nullptr);
+  cache.Insert(id3, std::move(session3), *params, nullptr);
+
+  EXPECT_EQ(2u, cache.size());
+  EXPECT_EQ(
+      unowned2,
+      cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get())->tls_session.get());
+  EXPECT_EQ(
+      unowned3,
+      cache.Lookup(id3, clock_.WallNow(), ssl_ctx_.get())->tls_session.get());
+  EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()));
+}
+
+TEST_F(QuicClientSessionCacheTest, ClearEarlyData) {
+  QuicClientSessionCache cache;
+  SSL_CTX_set_early_data_enabled(ssl_ctx_.get(), 1);
+  auto params = MakeFakeTransportParams();
+  auto session = MakeTestSession();
+  QuicServerId id1("a.com", 443);
+  auto session2 = MakeTestSession();
+
+  EXPECT_TRUE(SSL_SESSION_early_data_capable(session.get()));
+  EXPECT_TRUE(SSL_SESSION_early_data_capable(session2.get()));
+
+  cache.Insert(id1, std::move(session), *params, nullptr);
+  cache.Insert(id1, std::move(session2), *params, nullptr);
+
+  cache.ClearEarlyData(id1);
+
+  auto resumption_state = cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get());
+  EXPECT_FALSE(
+      SSL_SESSION_early_data_capable(resumption_state->tls_session.get()));
+  resumption_state = cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get());
+  EXPECT_FALSE(
+      SSL_SESSION_early_data_capable(resumption_state->tls_session.get()));
+  EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()));
+}
+
+// Expired session isn't considered valid and nullptr will be returned upon
+// Lookup.
+TEST_F(QuicClientSessionCacheTest, Expiration) {
+  QuicClientSessionCache cache;
+
+  auto params = MakeFakeTransportParams();
+  auto session = MakeTestSession();
+  QuicServerId id1("a.com", 443);
+
+  auto session2 = MakeTestSession(3 * kTimeout);
+  SSL_SESSION* unowned2 = session2.get();
+  QuicServerId id2("b.com", 443);
+
+  cache.Insert(id1, std::move(session), *params, nullptr);
+  cache.Insert(id2, std::move(session2), *params, nullptr);
+
+  EXPECT_EQ(2u, cache.size());
+  // Expire the session.
+  clock_.AdvanceTime(kTimeout * 2);
+  // The entry has not been removed yet.
+  EXPECT_EQ(2u, cache.size());
+
+  EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()));
+  EXPECT_EQ(1u, cache.size());
+  EXPECT_EQ(
+      unowned2,
+      cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get())->tls_session.get());
+  EXPECT_EQ(1u, cache.size());
+}
+
+TEST_F(QuicClientSessionCacheTest, RemoveExpiredEntriesAndClear) {
+  QuicClientSessionCache cache;
+
+  auto params = MakeFakeTransportParams();
+  auto session = MakeTestSession();
+  quic::QuicServerId id1("a.com", 443);
+
+  auto session2 = MakeTestSession(3 * kTimeout);
+  quic::QuicServerId id2("b.com", 443);
+
+  cache.Insert(id1, std::move(session), *params, nullptr);
+  cache.Insert(id2, std::move(session2), *params, nullptr);
+
+  EXPECT_EQ(2u, cache.size());
+  // Expire the session.
+  clock_.AdvanceTime(kTimeout * 2);
+  // The entry has not been removed yet.
+  EXPECT_EQ(2u, cache.size());
+
+  // Flush expired sessions.
+  cache.RemoveExpiredEntries(clock_.WallNow());
+
+  // session is expired and should be flushed.
+  EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()));
+  EXPECT_EQ(1u, cache.size());
+
+  cache.Clear();
+  EXPECT_EQ(0u, cache.size());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/crypto/quic_crypto_client_config.h b/quic/core/crypto/quic_crypto_client_config.h
index e316f8f..9d183e9 100644
--- a/quic/core/crypto/quic_crypto_client_config.h
+++ b/quic/core/crypto/quic_crypto_client_config.h
@@ -74,12 +74,17 @@
   // delete cache entries after returning them in Lookup so that session tickets
   // are used only once.
   virtual std::unique_ptr<QuicResumptionState> Lookup(
-      const QuicServerId& server_id,
-      const SSL_CTX* ctx) = 0;
+      const QuicServerId& server_id, QuicWallTime now, const SSL_CTX* ctx) = 0;
 
   // Called when 0-RTT is rejected. Disables early data for all the TLS tickets
   // associated with |server_id|.
   virtual void ClearEarlyData(const QuicServerId& server_id) = 0;
+
+  // Called to remove expired entries.
+  virtual void RemoveExpiredEntries(QuicWallTime now) = 0;
+
+  // Clear the session cache.
+  virtual void Clear() = 0;
 };
 
 // QuicCryptoClientConfig contains crypto-related configuration settings for a
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index cd1513d..ee891dc 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -14,6 +14,7 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "quic/core/crypto/null_encrypter.h"
+#include "quic/core/crypto/quic_client_session_cache.h"
 #include "quic/core/http/http_constants.h"
 #include "quic/core/http/quic_spdy_client_stream.h"
 #include "quic/core/http/web_transport_http3.h"
@@ -64,7 +65,6 @@
 #include "quic/test_tools/quic_test_utils.h"
 #include "quic/test_tools/quic_transport_test_tools.h"
 #include "quic/test_tools/server_thread.h"
-#include "quic/test_tools/simple_session_cache.h"
 #include "quic/tools/quic_backend_response.h"
 #include "quic/tools/quic_client.h"
 #include "quic/tools/quic_memory_cache_backend.h"
@@ -220,7 +220,7 @@
         new QuicTestClient(server_address_, server_hostname_, client_config_,
                            client_supported_versions_,
                            crypto_test_utils::ProofVerifierForTesting(),
-                           std::make_unique<SimpleSessionCache>());
+                           std::make_unique<QuicClientSessionCache>());
     client->SetUserAgentID(kTestUserAgentId);
     client->UseWriter(writer);
     if (!pre_shared_key_client_.empty()) {
@@ -4142,7 +4142,7 @@
   client_.reset(new QuicTestClient(server_address_, server_hostname_,
                                    client_config_, client_supported_versions_,
                                    crypto_test_utils::ProofVerifierForTesting(),
-                                   std::make_unique<SimpleSessionCache>()));
+                                   std::make_unique<QuicClientSessionCache>()));
   delete client_writer_;
   client_writer_ = new DowngradePacketWriter(target_version, downgrade_versions,
                                              client_.get(), server_writer_,
@@ -6327,7 +6327,7 @@
     client_->Disconnect();
 
     if (early_data_reason != ssl_early_data_session_not_resumed) {
-      EXPECT_EQ(early_data_reason, ssl_early_data_no_session_offered);
+      EXPECT_EQ(early_data_reason, ssl_early_data_unsupported_for_session);
       return;
     }
   }
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index af17b54..2a62046 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -982,10 +982,10 @@
   ApplicationState expected(std::begin(application_state),
                             std::end(application_state));
   session_->OnSettingsFrame(settings);
-  EXPECT_EQ(expected,
-            *client_session_cache_
-                 ->Lookup(QuicServerId(kServerHostname, kPort, false), nullptr)
-                 ->application_state);
+  EXPECT_EQ(expected, *client_session_cache_
+                           ->Lookup(QuicServerId(kServerHostname, kPort, false),
+                                    session_->GetClock()->WallNow(), nullptr)
+                           ->application_state);
 }
 
 TEST_P(QuicSpdyClientSessionTest, IetfZeroRttSetup) {
diff --git a/quic/core/quic_server_id.h b/quic/core/quic_server_id.h
index fa6b3c3..17c30db 100644
--- a/quic/core/quic_server_id.h
+++ b/quic/core/quic_server_id.h
@@ -8,6 +8,7 @@
 #include <cstdint>
 #include <string>
 
+#include "absl/hash/hash.h"
 #include "quic/platform/api/quic_export.h"
 
 namespace quic {
@@ -41,6 +42,14 @@
   bool privacy_mode_enabled_;
 };
 
+class QUIC_EXPORT_PRIVATE QuicServerIdHash {
+ public:
+  size_t operator()(const quic::QuicServerId& server_id) const noexcept {
+    return absl::HashOf(server_id.host(), server_id.port(),
+                        server_id.privacy_mode_enabled());
+  }
+};
+
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_QUIC_SERVER_ID_H_
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc
index 7f8fca6..211dfde 100644
--- a/quic/core/tls_client_handshaker.cc
+++ b/quic/core/tls_client_handshaker.cc
@@ -120,7 +120,8 @@
 
   // Set a session to resume, if there is one.
   if (session_cache_) {
-    cached_state_ = session_cache_->Lookup(server_id_, SSL_get_SSL_CTX(ssl()));
+    cached_state_ = session_cache_->Lookup(
+        server_id_, session()->GetClock()->WallNow(), SSL_get_SSL_CTX(ssl()));
   }
   if (cached_state_) {
     SSL_set_session(ssl(), cached_state_->tls_session.get());
diff --git a/quic/test_tools/simple_session_cache.cc b/quic/test_tools/simple_session_cache.cc
index 3b30797..d2f5cf8 100644
--- a/quic/test_tools/simple_session_cache.cc
+++ b/quic/test_tools/simple_session_cache.cc
@@ -28,7 +28,7 @@
 }
 
 std::unique_ptr<QuicResumptionState> SimpleSessionCache::Lookup(
-    const QuicServerId& server_id,
+    const QuicServerId& server_id, QuicWallTime /*now*/,
     const SSL_CTX* /*ctx*/) {
   auto it = cache_entries_.find(server_id);
   if (it == cache_entries_.end()) {
@@ -56,5 +56,11 @@
   // do anything here.
 }
 
+void SimpleSessionCache::RemoveExpiredEntries(QuicWallTime /*now*/) {
+  // The simple session cache does not support removing expired entries.
+}
+
+void SimpleSessionCache::Clear() { cache_entries_.clear(); }
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/simple_session_cache.h b/quic/test_tools/simple_session_cache.h
index 6fc1f3d..54bb0c7 100644
--- a/quic/test_tools/simple_session_cache.h
+++ b/quic/test_tools/simple_session_cache.h
@@ -17,6 +17,7 @@
 // the total number of entries in the cache. When Lookup is called, if a cache
 // entry exists for the provided QuicServerId, the entry will be removed from
 // the cached when it is returned.
+// TODO(fayang): Remove SimpleSessionCache by using QuicClientSessionCache.
 class SimpleSessionCache : public SessionCache {
  public:
   SimpleSessionCache() = default;
@@ -27,8 +28,11 @@
               const TransportParameters& params,
               const ApplicationState* application_state) override;
   std::unique_ptr<QuicResumptionState> Lookup(const QuicServerId& server_id,
+                                              QuicWallTime now,
                                               const SSL_CTX* ctx) override;
   void ClearEarlyData(const QuicServerId& server_id) override;
+  void RemoveExpiredEntries(QuicWallTime now) override;
+  void Clear() override;
 
  private:
   struct Entry {
diff --git a/quic/tools/quic_client_interop_test_bin.cc b/quic/tools/quic_client_interop_test_bin.cc
index eee0ae7..c90c8dd 100644
--- a/quic/tools/quic_client_interop_test_bin.cc
+++ b/quic/tools/quic_client_interop_test_bin.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "absl/strings/str_cat.h"
+#include "quic/core/crypto/quic_client_session_cache.h"
 #include "quic/core/quic_types.h"
 #include "quic/core/quic_versions.h"
 #include "quic/platform/api/quic_epoll.h"
@@ -15,7 +16,6 @@
 #include "net/quic/platform/impl/quic_epoll_clock.h"
 #include "quic/test_tools/quic_connection_peer.h"
 #include "quic/test_tools/quic_session_peer.h"
-#include "quic/test_tools/simple_session_cache.h"
 #include "quic/tools/fake_proof_verifier.h"
 #include "quic/tools/quic_client.h"
 #include "quic/tools/quic_url.h"
@@ -210,7 +210,7 @@
   }
 
   auto proof_verifier = std::make_unique<FakeProofVerifier>();
-  auto session_cache = std::make_unique<test::SimpleSessionCache>();
+  auto session_cache = std::make_unique<QuicClientSessionCache>();
   QuicEpollServer epoll_server;
   QuicEpollClock epoll_clock(&epoll_server);
   QuicConfig config;
diff --git a/quic/tools/quic_toy_client.cc b/quic/tools/quic_toy_client.cc
index 83fd7c0..59fe4ec 100644
--- a/quic/tools/quic_toy_client.cc
+++ b/quic/tools/quic_toy_client.cc
@@ -52,6 +52,7 @@
 #include "absl/strings/escaping.h"
 #include "absl/strings/str_split.h"
 #include "absl/strings/string_view.h"
+#include "quic/core/crypto/quic_client_session_cache.h"
 #include "quic/core/quic_packets.h"
 #include "quic/core/quic_server_id.h"
 #include "quic/core/quic_utils.h"
@@ -60,7 +61,6 @@
 #include "quic/platform/api/quic_ip_address.h"
 #include "quic/platform/api/quic_socket_address.h"
 #include "quic/platform/api/quic_system_event_loop.h"
-#include "quic/test_tools/simple_session_cache.h"
 #include "quic/tools/fake_proof_verifier.h"
 #include "quic/tools/quic_url.h"
 #include "common/quiche_text_utils.h"
@@ -323,7 +323,7 @@
   }
   std::unique_ptr<quic::SessionCache> session_cache;
   if (num_requests > 1 && GetQuicFlag(FLAGS_one_connection_per_request)) {
-    session_cache = std::make_unique<test::SimpleSessionCache>();
+    session_cache = std::make_unique<QuicClientSessionCache>();
   }
 
   QuicConfig config;