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;