// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/extras/sqlite/sqlite_persistent_shared_dictionary_store.h" #include "base/containers/span.h" #include "base/debug/dump_without_crashing.h" #include "base/files/file_path.h" #include "base/metrics/histogram_functions.h" #include "base/pickle.h" #include "base/strings/strcat.h" #include "base/task/sequenced_task_runner.h" #include "base/types/expected_macros.h" #include "net/base/network_isolation_key.h" #include "net/extras/shared_dictionary/shared_dictionary_isolation_key.h" #include "net/extras/sqlite/sqlite_persistent_store_backend_base.h" #include "sql/database.h" #include "sql/statement.h" #include "sql/transaction.h" namespace net { namespace { constexpr char kHistogramTag[] = "SharedDictionary"; constexpr char kHistogramPrefix[] = "Net.SharedDictionaryStore."; constexpr char kTableName[] = "dictionaries"; // The key for storing the total dictionary size in MetaTable. It is utilized // when determining whether cache eviction needs to be performed. We store it as // metadata because calculating the total size is an expensive operation. constexpr char kTotalDictSizeKey[] = "total_dict_size"; const int kCurrentVersionNumber = 1; const int kCompatibleVersionNumber = 1; bool CreateV1Schema(sql::Database* db, sql::MetaTable* meta_table) { CHECK(!db->DoesTableExist(kTableName)); static constexpr char kCreateTableQuery[] = // clang-format off "CREATE TABLE dictionaries(" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," "frame_origin TEXT NOT NULL," "top_frame_site TEXT NOT NULL," "host TEXT NOT NULL," "match TEXT NOT NULL," "url TEXT NOT NULL," "res_time INTEGER NOT NULL," "exp_time INTEGER NOT NULL," "last_used_time INTEGER NOT NULL," "size INTEGER NOT NULL," "sha256 BLOB NOT NULL," "token_high INTEGER NOT NULL," "token_low INTEGER NOT NULL)"; // clang-format on static constexpr char kCreateUniqueIndexQuery[] = // clang-format off "CREATE UNIQUE INDEX unique_index ON dictionaries(" "frame_origin," "top_frame_site," "host," "match)"; // clang-format on // This index is used for the size and count limitation per top_frame_site. static constexpr char kCreateTopFrameSiteIndexQuery[] = // clang-format off "CREATE INDEX top_frame_site_index ON dictionaries(" "top_frame_site)"; // clang-format on // This index is used for GetDictionaries(). static constexpr char kCreateIsolationIndexQuery[] = // clang-format off "CREATE INDEX isolation_index ON dictionaries(" "frame_origin," "top_frame_site)"; // clang-format on // This index will be used when implementing garbage collection logic of // SharedDictionaryDiskCache. static constexpr char kCreateTokenIndexQuery[] = // clang-format off "CREATE INDEX token_index ON dictionaries(" "token_high, token_low)"; // clang-format on // This index will be used when implementing clearing expired dictionary // logic. static constexpr char kCreateExpirationTimeIndexQuery[] = // clang-format off "CREATE INDEX exp_time_index ON dictionaries(" "exp_time)"; // clang-format on // This index will be used when implementing clearing dictionary logic which // will be called from BrowsingDataRemover. static constexpr char kCreateLastUsedTimeIndexQuery[] = // clang-format off "CREATE INDEX last_used_time_index ON dictionaries(" "last_used_time)"; // clang-format on if (!db->Execute(kCreateTableQuery) || !db->Execute(kCreateUniqueIndexQuery) || !db->Execute(kCreateTopFrameSiteIndexQuery) || !db->Execute(kCreateIsolationIndexQuery) || !db->Execute(kCreateTokenIndexQuery) || !db->Execute(kCreateExpirationTimeIndexQuery) || !db->Execute(kCreateLastUsedTimeIndexQuery) || !meta_table->SetValue(kTotalDictSizeKey, 0)) { return false; } return true; } absl::optional ToSHA256HashValue( base::span sha256_bytes) { SHA256HashValue sha256_hash; if (sha256_bytes.size() != sizeof(sha256_hash.data)) { return absl::nullopt; } memcpy(sha256_hash.data, sha256_bytes.data(), sha256_bytes.size()); return sha256_hash; } absl::optional ToUnguessableToken(int64_t token_high, int64_t token_low) { // There is no `sql::Statement::ColumnUint64()` method. So we cast to // uint64_t. return base::UnguessableToken::Deserialize(static_cast(token_high), static_cast(token_low)); } template base::OnceCallback WrapCallbackWithWeakPtrCheck( base::WeakPtr weak_ptr, base::OnceCallback callback) { return base::BindOnce( [](base::WeakPtr weak_ptr, base::OnceCallback callback, ResultType result) { if (!weak_ptr) { return; } std::move(callback).Run(std::move(result)); }, std::move(weak_ptr), std::move(callback)); } void RecordErrorHistogram(const char* method_name, SQLitePersistentSharedDictionaryStore::Error error) { base::UmaHistogramEnumeration( base::StrCat({kHistogramPrefix, method_name, ".Error"}), error); } template void RecordErrorHistogram( const char* method_name, base::expected result) { RecordErrorHistogram(method_name, result.has_value() ? SQLitePersistentSharedDictionaryStore::Error::kOk : result.error()); } } // namespace SQLitePersistentSharedDictionaryStore::RegisterDictionaryResult:: RegisterDictionaryResult( int64_t primary_key_in_database, absl::optional replaced_disk_cache_key_token, std::set evicted_disk_cache_key_tokens, uint64_t total_dictionary_size, uint64_t total_dictionary_count) : primary_key_in_database_(primary_key_in_database), replaced_disk_cache_key_token_(std::move(replaced_disk_cache_key_token)), evicted_disk_cache_key_tokens_(std::move(evicted_disk_cache_key_tokens)), total_dictionary_size_(total_dictionary_size), total_dictionary_count_(total_dictionary_count) {} SQLitePersistentSharedDictionaryStore::RegisterDictionaryResult:: ~RegisterDictionaryResult() = default; SQLitePersistentSharedDictionaryStore::RegisterDictionaryResult:: RegisterDictionaryResult(const RegisterDictionaryResult& other) = default; SQLitePersistentSharedDictionaryStore::RegisterDictionaryResult:: RegisterDictionaryResult(RegisterDictionaryResult&& other) = default; SQLitePersistentSharedDictionaryStore::RegisterDictionaryResult& SQLitePersistentSharedDictionaryStore::RegisterDictionaryResult::operator=( const RegisterDictionaryResult& other) = default; SQLitePersistentSharedDictionaryStore::RegisterDictionaryResult& SQLitePersistentSharedDictionaryStore::RegisterDictionaryResult::operator=( RegisterDictionaryResult&& other) = default; class SQLitePersistentSharedDictionaryStore::Backend : public SQLitePersistentStoreBackendBase { public: Backend( const base::FilePath& path, const scoped_refptr& client_task_runner, const scoped_refptr& background_task_runner) : SQLitePersistentStoreBackendBase(path, kHistogramTag, kCurrentVersionNumber, kCompatibleVersionNumber, background_task_runner, client_task_runner, /*enable_exclusive_access=*/false) {} Backend(const Backend&) = delete; Backend& operator=(const Backend&) = delete; #define DEFINE_CROSS_SEQUENCE_CALL_METHOD(Name) \ template \ void Name(base::OnceCallback callback, Args&&... args) { \ CHECK(client_task_runner()->RunsTasksInCurrentSequence()); \ PostBackgroundTask( \ FROM_HERE, \ base::BindOnce( \ [](scoped_refptr backend, \ base::OnceCallback callback, \ Args&&... args) { \ auto result = backend->Name##Impl(std::forward(args)...); \ RecordErrorHistogram(#Name, result); \ backend->PostClientTask( \ FROM_HERE, \ base::BindOnce(std::move(callback), std::move(result))); \ }, \ scoped_refptr(this), std::move(callback), \ std::forward(args)...)); \ } // The following methods call *Impl() method in the background task runner, // and call the passed `callback` with the result in the client task runner. DEFINE_CROSS_SEQUENCE_CALL_METHOD(GetTotalDictionarySize) DEFINE_CROSS_SEQUENCE_CALL_METHOD(RegisterDictionary) DEFINE_CROSS_SEQUENCE_CALL_METHOD(GetDictionaries) DEFINE_CROSS_SEQUENCE_CALL_METHOD(GetAllDictionaries) DEFINE_CROSS_SEQUENCE_CALL_METHOD(GetUsageInfo) DEFINE_CROSS_SEQUENCE_CALL_METHOD(GetOriginsBetween) DEFINE_CROSS_SEQUENCE_CALL_METHOD(ClearAllDictionaries) DEFINE_CROSS_SEQUENCE_CALL_METHOD(ClearDictionaries) DEFINE_CROSS_SEQUENCE_CALL_METHOD(ClearDictionariesForIsolationKey) DEFINE_CROSS_SEQUENCE_CALL_METHOD(DeleteExpiredDictionaries) DEFINE_CROSS_SEQUENCE_CALL_METHOD(ProcessEviction) DEFINE_CROSS_SEQUENCE_CALL_METHOD(GetAllDiskCacheKeyTokens) DEFINE_CROSS_SEQUENCE_CALL_METHOD(DeleteDictionariesByDiskCacheKeyTokens) #undef DEFINE_CROSS_SEQUENCE_CALL_METHOD void UpdateDictionaryLastUsedTime(int64_t primary_key_in_database, base::Time last_used_time); private: ~Backend() override = default; // Gets the total dictionary size in MetaTable. SizeOrError GetTotalDictionarySizeImpl(); RegisterDictionaryResultOrError RegisterDictionaryImpl( const SharedDictionaryIsolationKey& isolation_key, const SharedDictionaryInfo& dictionary_info, uint64_t max_size_per_site, uint64_t max_count_per_site); DictionaryListOrError GetDictionariesImpl( const SharedDictionaryIsolationKey& isolation_key); DictionaryMapOrError GetAllDictionariesImpl(); UsageInfoOrError GetUsageInfoImpl(); OriginListOrError GetOriginsBetweenImpl(const base::Time start_time, const base::Time end_time); UnguessableTokenSetOrError ClearAllDictionariesImpl(); UnguessableTokenSetOrError ClearDictionariesImpl( base::Time start_time, base::Time end_time, base::RepeatingCallback url_matcher); UnguessableTokenSetOrError ClearDictionariesForIsolationKeyImpl( const SharedDictionaryIsolationKey& isolation_key); UnguessableTokenSetOrError DeleteExpiredDictionariesImpl(base::Time now); UnguessableTokenSetOrError ProcessEvictionImpl(uint64_t cache_max_size, uint64_t size_low_watermark, uint64_t cache_max_count, uint64_t count_low_watermark); UnguessableTokenSetOrError GetAllDiskCacheKeyTokensImpl(); Error DeleteDictionariesByDiskCacheKeyTokensImpl( const std::set& disk_cache_key_tokens); // If a matching dictionary exists, populates 'size_out' and // 'disk_cache_key_out' with the dictionary's respective values and returns // true. Otherwise returns false. bool GetExistingDictionarySizeAndDiskCacheKeyToken( const SharedDictionaryIsolationKey& isolation_key, const url::SchemeHostPort& host, const std::string& match, int64_t* size_out, absl::optional* disk_cache_key_out); // Updates the total dictionary size in MetaTable by `size_delta` and returns // the updated total dictionary size. Error UpdateTotalDictionarySizeInMetaTable( int64_t size_delta, uint64_t* total_dictionary_size_out); // Gets the total dictionary count. SizeOrError GetTotalDictionaryCount(); // SQLitePersistentStoreBackendBase implementation bool CreateDatabaseSchema() override; absl::optional DoMigrateDatabaseSchema() override; void DoCommit() override; // Commits the last used time update. Error CommitDictionaryLastUsedTimeUpdate(int64_t primary_key_in_database, base::Time last_used_time); // Selects dictionaries which `res_time` is between `start_time` and // `end_time`. And fills their primary keys and tokens and total size. Error SelectMatchingDictionaries( base::Time start_time, base::Time end_time, std::vector* primary_keys_out, std::vector* tokens_out, int64_t* total_size_out); // Selects dictionaries which `res_time` is between `start_time` and // `end_time`, and which `frame_origin` or `top_frame_site` or `host` matches // with `url_matcher`. And fills their primary keys and tokens and total size. Error SelectMatchingDictionariesWithUrlMatcher( base::Time start_time, base::Time end_time, base::RepeatingCallback url_matcher, std::vector* primary_keys_out, std::vector* tokens_out, int64_t* total_size_out); // Selects dictionaries in order of `last_used_time` if the total size of all // dictionaries exceeds `cache_max_size` or the total dictionary count exceeds // `cache_max_count` until the total size reaches `size_low_watermark` and the // total count reaches `count_low_watermark`, and fills their primary keys and // tokens and total size. If `cache_max_size` is zero, the size limitation is // ignored. Error SelectEvictionCandidates( uint64_t cache_max_size, uint64_t size_low_watermark, uint64_t cache_max_count, uint64_t count_low_watermark, std::vector* primary_keys_out, std::vector* tokens_out, int64_t* total_size_after_eviction_out); // Deletes a dictionary with `primary_key`. Error DeleteDictionaryByPrimaryKey(int64_t primary_key); // Deletes a dictionary with `disk_cache_key_token` and returns the deleted // dictionarie's size. SizeOrError DeleteDictionaryByDiskCacheToken( const base::UnguessableToken& disk_cache_key_token); Error MaybeEvictDictionariesForPerSiteLimit( const SchemefulSite& top_frame_site, uint64_t max_size_per_site, uint64_t max_count_per_site, std::vector* evicted_disk_cache_key_tokens, uint64_t* total_dictionary_size_out); SizeOrError GetDictionaryCountPerSite(const SchemefulSite& top_frame_site); SizeOrError GetDictionarySizePerSite(const SchemefulSite& top_frame_site); Error SelectCandidatesForPerSiteEviction( const SchemefulSite& top_frame_site, uint64_t max_size_per_site, uint64_t max_count_per_site, std::vector* primary_keys_out, std::vector* tokens_out, int64_t* total_candidate_dictionary_size_out); // Total number of pending last used time update operations (may not match the // size of `pending_last_used_time_updates_`, due to operation coalescing). size_t num_pending_ GUARDED_BY(lock_) = 0; std::map pending_last_used_time_updates_ GUARDED_BY(lock_); // Protects `num_pending_`, and `pending_last_used_time_updates_`. mutable base::Lock lock_; }; bool SQLitePersistentSharedDictionaryStore::Backend::CreateDatabaseSchema() { if (!db()->DoesTableExist(kTableName) && !CreateV1Schema(db(), meta_table())) { return false; } return true; } absl::optional SQLitePersistentSharedDictionaryStore::Backend::DoMigrateDatabaseSchema() { int cur_version = meta_table()->GetVersionNumber(); if (cur_version != kCurrentVersionNumber) { return absl::nullopt; } // Future database upgrade statements go here. return absl::make_optional(cur_version); } void SQLitePersistentSharedDictionaryStore::Backend::DoCommit() { std::map pending_last_used_time_updates; { base::AutoLock locked(lock_); pending_last_used_time_updates_.swap(pending_last_used_time_updates); num_pending_ = 0; } if (!db() || pending_last_used_time_updates.empty()) { return; } sql::Transaction transaction(db()); if (!transaction.Begin()) { return; } for (const auto& it : pending_last_used_time_updates) { if (CommitDictionaryLastUsedTimeUpdate(it.first, it.second) != Error::kOk) { return; } } transaction.Commit(); } SQLitePersistentSharedDictionaryStore::Error SQLitePersistentSharedDictionaryStore::Backend:: CommitDictionaryLastUsedTimeUpdate(int64_t primary_key_in_database, base::Time last_used_time) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); if (!InitializeDatabase()) { return Error::kFailedToInitializeDatabase; } static constexpr char kQuery[] = "UPDATE dictionaries SET last_used_time=? WHERE id=?"; if (!db()->IsSQLValid(kQuery)) { return Error::kInvalidSql; } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindTime(0, last_used_time); statement.BindInt64(1, primary_key_in_database); if (!statement.Run()) { return Error::kFailedToExecuteSql; } return Error::kOk; } base::expected SQLitePersistentSharedDictionaryStore::Backend::GetTotalDictionarySizeImpl() { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } int64_t unsigned_total_dictionary_size = 0; if (!meta_table()->GetValue(kTotalDictSizeKey, &unsigned_total_dictionary_size)) { return base::unexpected(Error::kFailedToGetTotalDictSize); } // There is no `sql::Statement::ColumnUint64()` method. So we cast to // uint64_t. return base::ok(static_cast(unsigned_total_dictionary_size)); } SQLitePersistentSharedDictionaryStore::RegisterDictionaryResultOrError SQLitePersistentSharedDictionaryStore::Backend::RegisterDictionaryImpl( const SharedDictionaryIsolationKey& isolation_key, const SharedDictionaryInfo& dictionary_info, uint64_t max_size_per_site, uint64_t max_count_per_site) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); CHECK_NE(0u, max_count_per_site); if (max_size_per_site != 0 && dictionary_info.size() > max_size_per_site) { return base::unexpected(Error::kTooBigDictionary); } if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } // Commit `pending_last_used_time_updates_`. DoCommit(); sql::Transaction transaction(db()); if (!transaction.Begin()) { return base::unexpected(Error::kFailedToBeginTransaction); } int64_t size_of_removed_dict = 0; absl::optional replaced_disk_cache_key_token; int64_t size_delta = dictionary_info.size(); if (GetExistingDictionarySizeAndDiskCacheKeyToken( isolation_key, url::SchemeHostPort(dictionary_info.url()), dictionary_info.match(), &size_of_removed_dict, &replaced_disk_cache_key_token)) { size_delta -= size_of_removed_dict; } static constexpr char kQuery[] = // clang-format off "INSERT OR REPLACE INTO dictionaries(" "frame_origin," "top_frame_site," "host," "match," "url," "res_time," "exp_time," "last_used_time," "size," "sha256," "token_high," "token_low) " "VALUES(?,?,?,?,?,?,?,?,?,?,?,?)"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindString(0, isolation_key.frame_origin().Serialize()); statement.BindString(1, isolation_key.top_frame_site().Serialize()); statement.BindString(2, url::SchemeHostPort(dictionary_info.url()).Serialize()); statement.BindString(3, dictionary_info.match()); statement.BindString(4, dictionary_info.url().spec()); statement.BindTime(5, dictionary_info.response_time()); statement.BindTime(6, dictionary_info.GetExpirationTime()); statement.BindTime(7, dictionary_info.last_used_time()); statement.BindInt64(8, dictionary_info.size()); statement.BindBlob(9, base::make_span(dictionary_info.hash().data)); // There is no `sql::Statement::BindUint64()` method. So we cast to int64_t. int64_t token_high = static_cast( dictionary_info.disk_cache_key_token().GetHighForSerialization()); int64_t token_low = static_cast( dictionary_info.disk_cache_key_token().GetLowForSerialization()); statement.BindInt64(10, token_high); statement.BindInt64(11, token_low); if (!statement.Run()) { return base::unexpected(Error::kFailedToExecuteSql); } int64_t id = db()->GetLastInsertRowId(); uint64_t total_dictionary_size = 0; Error error = UpdateTotalDictionarySizeInMetaTable(size_delta, &total_dictionary_size); if (error != Error::kOk) { return base::unexpected(error); } std::vector evicted_disk_cache_key_tokens; error = MaybeEvictDictionariesForPerSiteLimit( isolation_key.top_frame_site(), max_size_per_site, max_count_per_site, &evicted_disk_cache_key_tokens, &total_dictionary_size); if (error != Error::kOk) { return base::unexpected(error); } ASSIGN_OR_RETURN(uint64_t total_dictionary_count, GetTotalDictionaryCount()); if (!transaction.Commit()) { return base::unexpected(Error::kFailedToCommitTransaction); } return base::ok(RegisterDictionaryResult{ id, replaced_disk_cache_key_token, std::set(evicted_disk_cache_key_tokens.begin(), evicted_disk_cache_key_tokens.end()), total_dictionary_size, total_dictionary_count}); } SQLitePersistentSharedDictionaryStore::Error SQLitePersistentSharedDictionaryStore::Backend:: MaybeEvictDictionariesForPerSiteLimit( const SchemefulSite& top_frame_site, uint64_t max_size_per_site, uint64_t max_count_per_site, std::vector* evicted_disk_cache_key_tokens, uint64_t* total_dictionary_size_out) { std::vector primary_keys; int64_t total_candidate_dictionary_size = 0; Error error = SelectCandidatesForPerSiteEviction( top_frame_site, max_size_per_site, max_count_per_site, &primary_keys, evicted_disk_cache_key_tokens, &total_candidate_dictionary_size); if (error != Error::kOk) { return error; } CHECK_EQ(primary_keys.size(), evicted_disk_cache_key_tokens->size()); if (primary_keys.empty()) { return Error::kOk; } for (int64_t primary_key : primary_keys) { error = DeleteDictionaryByPrimaryKey(primary_key); if (error != Error::kOk) { return error; } } error = UpdateTotalDictionarySizeInMetaTable(-total_candidate_dictionary_size, total_dictionary_size_out); if (error != Error::kOk) { return error; } return Error::kOk; } SQLitePersistentSharedDictionaryStore::Error SQLitePersistentSharedDictionaryStore::Backend:: SelectCandidatesForPerSiteEviction( const SchemefulSite& top_frame_site, uint64_t max_size_per_site, uint64_t max_count_per_site, std::vector* primary_keys_out, std::vector* tokens_out, int64_t* total_size_of_candidates_out) { CHECK(primary_keys_out->empty()); CHECK(tokens_out->empty()); CHECK_EQ(0, *total_size_of_candidates_out); ASSIGN_OR_RETURN(uint64_t size_per_site, GetDictionarySizePerSite(top_frame_site)); ASSIGN_OR_RETURN(uint64_t count_per_site, GetDictionaryCountPerSite(top_frame_site)); base::UmaHistogramMemoryKB( base::StrCat({kHistogramPrefix, "DictionarySizeKBPerSiteWhenAdded"}), size_per_site); base::UmaHistogramCounts1000( base::StrCat({kHistogramPrefix, "DictionaryCountPerSiteWhenAdded"}), count_per_site); if ((max_size_per_site == 0 || size_per_site <= max_size_per_site) && count_per_site <= max_count_per_site) { return Error::kOk; } uint64_t to_be_removed_count = 0; if (count_per_site > max_count_per_site) { to_be_removed_count = count_per_site - max_count_per_site; } int64_t to_be_removed_size = 0; if (max_size_per_site != 0 && size_per_site > max_size_per_site) { to_be_removed_size = size_per_site - max_size_per_site; } static constexpr char kQuery[] = // clang-format off "SELECT " "id," "size," "token_high," "token_low FROM dictionaries " "WHERE top_frame_site=? " "ORDER BY last_used_time"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return Error::kInvalidSql; } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindString(0, top_frame_site.Serialize()); base::CheckedNumeric checked_total_size_of_candidates; while (statement.Step()) { const int64_t primary_key_in_database = statement.ColumnInt64(0); const size_t size = statement.ColumnInt64(1); const int64_t token_high = statement.ColumnInt64(2); const int64_t token_low = statement.ColumnInt64(3); absl::optional disk_cache_key_token = ToUnguessableToken(token_high, token_low); if (!disk_cache_key_token) { LOG(WARNING) << "Invalid token"; continue; } checked_total_size_of_candidates += size; if (!checked_total_size_of_candidates.IsValid()) { base::debug::DumpWithoutCrashing(); return Error::kInvalidTotalDictSize; } *total_size_of_candidates_out = checked_total_size_of_candidates.ValueOrDie(); primary_keys_out->emplace_back(primary_key_in_database); tokens_out->emplace_back(*disk_cache_key_token); if (*total_size_of_candidates_out >= to_be_removed_size && tokens_out->size() >= to_be_removed_count) { break; } } return Error::kOk; } base::expected SQLitePersistentSharedDictionaryStore::Backend::GetDictionaryCountPerSite( const SchemefulSite& top_frame_site) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); static constexpr char kQuery[] = // clang-format off "SELECT " "COUNT(id) FROM dictionaries " "WHERE top_frame_site=?"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindString(0, top_frame_site.Serialize()); uint64_t count_per_site = 0; if (statement.Step()) { count_per_site = statement.ColumnInt64(0); } return base::ok(count_per_site); } base::expected SQLitePersistentSharedDictionaryStore::Backend::GetDictionarySizePerSite( const SchemefulSite& top_frame_site) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); static constexpr char kQuery[] = // clang-format off "SELECT " "SUM(size) FROM dictionaries " "WHERE top_frame_site=?"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindString(0, top_frame_site.Serialize()); uint64_t size_per_site = 0; if (statement.Step()) { size_per_site = statement.ColumnInt64(0); } return base::ok(size_per_site); } SQLitePersistentSharedDictionaryStore::DictionaryListOrError SQLitePersistentSharedDictionaryStore::Backend::GetDictionariesImpl( const SharedDictionaryIsolationKey& isolation_key) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); std::vector result; if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } // Commit `pending_last_used_time_updates_`. DoCommit(); static constexpr char kQuery[] = // clang-format off "SELECT " "id," "match," "url," "res_time," "exp_time," "last_used_time," "size," "sha256," "token_high," "token_low FROM dictionaries " "WHERE frame_origin=? AND top_frame_site=? " "ORDER BY id"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindString(0, isolation_key.frame_origin().Serialize()); statement.BindString(1, isolation_key.top_frame_site().Serialize()); while (statement.Step()) { const int64_t primary_key_in_database = statement.ColumnInt64(0); const std::string match = statement.ColumnString(1); const std::string url_string = statement.ColumnString(2); const base::Time response_time = statement.ColumnTime(3); const base::Time expiration_time = statement.ColumnTime(4); const base::Time last_used_time = statement.ColumnTime(5); const size_t size = statement.ColumnInt64(6); absl::optional sha256_hash = ToSHA256HashValue(statement.ColumnBlob(7)); if (!sha256_hash) { LOG(WARNING) << "Invalid hash"; continue; } absl::optional disk_cache_key_token = ToUnguessableToken(statement.ColumnInt64(8), statement.ColumnInt64(9)); if (!disk_cache_key_token) { LOG(WARNING) << "Invalid token"; continue; } result.emplace_back(GURL(url_string), response_time, expiration_time - response_time, match, last_used_time, size, *sha256_hash, *disk_cache_key_token, primary_key_in_database); } return base::ok(std::move(result)); } SQLitePersistentSharedDictionaryStore::DictionaryMapOrError SQLitePersistentSharedDictionaryStore::Backend::GetAllDictionariesImpl() { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } static constexpr char kQuery[] = // clang-format off "SELECT " "id," "frame_origin," "top_frame_site," "match," "url," "res_time," "exp_time," "last_used_time," "size," "sha256," "token_high," "token_low FROM dictionaries " "ORDER BY id"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } std::map> result; sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); while (statement.Step()) { const int64_t primary_key_in_database = statement.ColumnInt64(0); const std::string frame_origin_string = statement.ColumnString(1); const std::string top_frame_site_string = statement.ColumnString(2); const std::string match = statement.ColumnString(3); const std::string url_string = statement.ColumnString(4); const base::Time response_time = statement.ColumnTime(5); const base::Time expiration_time = statement.ColumnTime(6); const base::Time last_used_time = statement.ColumnTime(7); const size_t size = statement.ColumnInt64(8); absl::optional sha256_hash = ToSHA256HashValue(statement.ColumnBlob(9)); if (!sha256_hash) { LOG(WARNING) << "Invalid hash"; continue; } absl::optional disk_cache_key_token = ToUnguessableToken(statement.ColumnInt64(10), statement.ColumnInt64(11)); if (!disk_cache_key_token) { LOG(WARNING) << "Invalid token"; continue; } url::Origin frame_origin = url::Origin::Create(GURL(frame_origin_string)); SchemefulSite top_frame_site = SchemefulSite(GURL(top_frame_site_string)); result[SharedDictionaryIsolationKey(frame_origin, top_frame_site)] .emplace_back(GURL(url_string), response_time, expiration_time - response_time, match, last_used_time, size, *sha256_hash, *disk_cache_key_token, primary_key_in_database); } return base::ok(std::move(result)); } SQLitePersistentSharedDictionaryStore::UsageInfoOrError SQLitePersistentSharedDictionaryStore::Backend::GetUsageInfoImpl() { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } static constexpr char kQuery[] = // clang-format off "SELECT " "frame_origin," "top_frame_site," "size FROM dictionaries " "ORDER BY id"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } std::map result_map; sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); while (statement.Step()) { const std::string frame_origin_string = statement.ColumnString(0); const std::string top_frame_site_string = statement.ColumnString(1); const size_t size = statement.ColumnInt64(2); const SharedDictionaryIsolationKey key = SharedDictionaryIsolationKey( url::Origin::Create(GURL(frame_origin_string)), SchemefulSite(GURL(top_frame_site_string))); auto it = result_map.find(key); if (it != result_map.end()) { it->second.total_size_bytes += size; } else { result_map[key] = SharedDictionaryUsageInfo{.isolation_key = key, .total_size_bytes = size}; } } std::vector result; for (auto& it : result_map) { result.push_back(std::move(it.second)); } return base::ok(std::move(result)); } SQLitePersistentSharedDictionaryStore::OriginListOrError SQLitePersistentSharedDictionaryStore::Backend::GetOriginsBetweenImpl( const base::Time start_time, const base::Time end_time) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } static constexpr char kQuery[] = // clang-format off "SELECT " "frame_origin FROM dictionaries " "WHERE res_time>=? AND res_timeIsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindTime(0, start_time); statement.BindTime(1, end_time); std::set origins; while (statement.Step()) { const std::string frame_origin_string = statement.ColumnString(0); origins.insert(url::Origin::Create(GURL(frame_origin_string))); } return base::ok(std::vector(origins.begin(), origins.end())); } SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError SQLitePersistentSharedDictionaryStore::Backend::ClearAllDictionariesImpl() { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } sql::Transaction transaction(db()); if (!transaction.Begin()) { return base::unexpected(Error::kFailedToBeginTransaction); } static constexpr char kQuery[] = "DELETE FROM dictionaries RETURNING token_high, token_low"; if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); std::vector tokens; while (statement.Step()) { const int64_t token_high = statement.ColumnInt64(0); const int64_t token_low = statement.ColumnInt64(1); absl::optional disk_cache_key_token = ToUnguessableToken(token_high, token_low); if (!disk_cache_key_token) { continue; } tokens.emplace_back(*disk_cache_key_token); } if (!meta_table()->SetValue(kTotalDictSizeKey, 0)) { return base::unexpected(Error::kFailedToSetTotalDictSize); } if (!transaction.Commit()) { return base::unexpected(Error::kFailedToCommitTransaction); } return base::ok( std::set(tokens.begin(), tokens.end())); } SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError SQLitePersistentSharedDictionaryStore::Backend::ClearDictionariesImpl( base::Time start_time, base::Time end_time, base::RepeatingCallback url_matcher) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } // Commit `pending_last_used_time_updates_`. DoCommit(); sql::Transaction transaction(db()); if (!transaction.Begin()) { return base::unexpected(Error::kFailedToBeginTransaction); } std::vector primary_keys; std::vector tokens; int64_t total_size = 0; Error error = url_matcher ? SelectMatchingDictionariesWithUrlMatcher( start_time, end_time, std::move(url_matcher), &primary_keys, &tokens, &total_size) : SelectMatchingDictionaries(start_time, end_time, &primary_keys, &tokens, &total_size); if (error != Error::kOk) { return base::unexpected(error); } for (int64_t primary_key : primary_keys) { error = DeleteDictionaryByPrimaryKey(primary_key); if (error != Error::kOk) { return base::unexpected(error); } } if (total_size != 0) { uint64_t total_dictionary_size = 0; error = UpdateTotalDictionarySizeInMetaTable(-total_size, &total_dictionary_size); if (error != Error::kOk) { return base::unexpected(error); } } transaction.Commit(); return base::ok( std::set(tokens.begin(), tokens.end())); } SQLitePersistentSharedDictionaryStore::Error SQLitePersistentSharedDictionaryStore::Backend::SelectMatchingDictionaries( base::Time start_time, base::Time end_time, std::vector* primary_keys_out, std::vector* tokens_out, int64_t* total_size_out) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); static constexpr char kQuery[] = // clang-format off "SELECT " "id," "size," "token_high," "token_low FROM dictionaries " "WHERE res_time>=? AND res_timeIsSQLValid(kQuery)) { return Error::kInvalidSql; } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindTime(0, start_time); statement.BindTime(1, end_time.is_null() ? base::Time::Max() : end_time); base::CheckedNumeric checked_total_size; while (statement.Step()) { const int64_t primary_key_in_database = statement.ColumnInt64(0); const size_t size = statement.ColumnInt64(1); const int64_t token_high = statement.ColumnInt64(2); const int64_t token_low = statement.ColumnInt64(3); absl::optional disk_cache_key_token = ToUnguessableToken(token_high, token_low); if (!disk_cache_key_token) { LOG(WARNING) << "Invalid token"; continue; } primary_keys_out->emplace_back(primary_key_in_database); tokens_out->emplace_back(*disk_cache_key_token); checked_total_size += size; } *total_size_out = checked_total_size.ValueOrDie(); return Error::kOk; } SQLitePersistentSharedDictionaryStore::Error SQLitePersistentSharedDictionaryStore::Backend:: SelectMatchingDictionariesWithUrlMatcher( base::Time start_time, base::Time end_time, base::RepeatingCallback url_matcher, std::vector* primary_keys_out, std::vector* tokens_out, int64_t* total_size_out) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); static constexpr char kQuery[] = // clang-format off "SELECT " "id," "frame_origin," "top_frame_site," "host," "size," "token_high," "token_low FROM dictionaries " "WHERE res_time>=? AND res_timeIsSQLValid(kQuery)) { return Error::kInvalidSql; } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindTime(0, start_time); statement.BindTime(1, end_time.is_null() ? base::Time::Max() : end_time); base::CheckedNumeric checked_total_size; while (statement.Step()) { const int64_t primary_key_in_database = statement.ColumnInt64(0); const std::string frame_origin_string = statement.ColumnString(1); const std::string top_frame_site_string = statement.ColumnString(2); const std::string host = statement.ColumnString(3); const size_t size = statement.ColumnInt64(4); const int64_t token_high = statement.ColumnInt64(5); const int64_t token_low = statement.ColumnInt64(6); if (!url_matcher.Run(GURL(frame_origin_string)) && !url_matcher.Run(GURL(top_frame_site_string)) && !url_matcher.Run(GURL(host))) { continue; } absl::optional disk_cache_key_token = ToUnguessableToken(token_high, token_low); if (!disk_cache_key_token) { LOG(WARNING) << "Invalid token"; continue; } primary_keys_out->emplace_back(primary_key_in_database); tokens_out->emplace_back(*disk_cache_key_token); checked_total_size += size; } *total_size_out = checked_total_size.ValueOrDie(); return Error::kOk; } SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError SQLitePersistentSharedDictionaryStore::Backend:: ClearDictionariesForIsolationKeyImpl( const SharedDictionaryIsolationKey& isolation_key) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } sql::Transaction transaction(db()); if (!transaction.Begin()) { return base::unexpected(Error::kFailedToBeginTransaction); } static constexpr char kQuery[] = // clang-format off "DELETE FROM dictionaries " "WHERE frame_origin=? AND top_frame_site=? " "RETURNING size, token_high, token_low"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindString(0, isolation_key.frame_origin().Serialize()); statement.BindString(1, isolation_key.top_frame_site().Serialize()); std::vector tokens; base::CheckedNumeric checked_total_size = 0; while (statement.Step()) { const size_t size = statement.ColumnInt64(0); const int64_t token_high = statement.ColumnInt64(1); const int64_t token_low = statement.ColumnInt64(2); checked_total_size += size; absl::optional disk_cache_key_token = ToUnguessableToken(token_high, token_low); if (!disk_cache_key_token) { continue; } tokens.emplace_back(*disk_cache_key_token); } int64_t total_size = checked_total_size.ValueOrDie(); if (total_size != 0) { uint64_t total_dictionary_size = 0; Error error = UpdateTotalDictionarySizeInMetaTable(-total_size, &total_dictionary_size); if (error != Error::kOk) { return base::unexpected(error); } } transaction.Commit(); return base::ok( std::set(tokens.begin(), tokens.end())); } SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError SQLitePersistentSharedDictionaryStore::Backend::DeleteExpiredDictionariesImpl( base::Time now) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } sql::Transaction transaction(db()); if (!transaction.Begin()) { return base::unexpected(Error::kFailedToBeginTransaction); } static constexpr char kQuery[] = // clang-format off "DELETE FROM dictionaries " "WHERE exp_time<=? " "RETURNING size, token_high, token_low"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindTime(0, now); std::vector tokens; base::CheckedNumeric checked_total_size = 0; while (statement.Step()) { const size_t size = statement.ColumnInt64(0); const int64_t token_high = statement.ColumnInt64(1); const int64_t token_low = statement.ColumnInt64(2); checked_total_size += size; absl::optional disk_cache_key_token = ToUnguessableToken(token_high, token_low); if (!disk_cache_key_token) { LOG(WARNING) << "Invalid token"; continue; } tokens.emplace_back(*disk_cache_key_token); } int64_t total_size = checked_total_size.ValueOrDie(); if (total_size != 0) { uint64_t total_dictionary_size = 0; Error error = UpdateTotalDictionarySizeInMetaTable(-total_size, &total_dictionary_size); if (error != Error::kOk) { return base::unexpected(error); } } transaction.Commit(); return base::ok( std::set(tokens.begin(), tokens.end())); } SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError SQLitePersistentSharedDictionaryStore::Backend::ProcessEvictionImpl( uint64_t cache_max_size, uint64_t size_low_watermark, uint64_t cache_max_count, uint64_t count_low_watermark) { if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } // Commit `pending_last_used_time_updates_`. DoCommit(); sql::Transaction transaction(db()); if (!transaction.Begin()) { return base::unexpected(Error::kFailedToBeginTransaction); } std::vector primary_keys; std::vector tokens; int64_t total_size_after_eviction = 0; Error error = SelectEvictionCandidates( cache_max_size, size_low_watermark, cache_max_count, count_low_watermark, &primary_keys, &tokens, &total_size_after_eviction); if (error != Error::kOk) { return base::unexpected(error); } CHECK_EQ(primary_keys.size(), tokens.size()); if (primary_keys.empty()) { return base::ok(std::set()); } for (int64_t primary_key : primary_keys) { error = DeleteDictionaryByPrimaryKey(primary_key); if (error != Error::kOk) { return base::unexpected(error); } } if (!meta_table()->SetValue(kTotalDictSizeKey, total_size_after_eviction)) { return base::unexpected(Error::kFailedToSetTotalDictSize); } transaction.Commit(); return base::ok( std::set(tokens.begin(), tokens.end())); } SQLitePersistentSharedDictionaryStore::Error SQLitePersistentSharedDictionaryStore::Backend::SelectEvictionCandidates( uint64_t cache_max_size, uint64_t size_low_watermark, uint64_t cache_max_count, uint64_t count_low_watermark, std::vector* primary_keys_out, std::vector* tokens_out, int64_t* total_size_after_eviction_out) { ASSIGN_OR_RETURN(uint64_t total_dictionary_size, GetTotalDictionarySizeImpl()); ASSIGN_OR_RETURN(uint64_t total_dictionary_count, GetTotalDictionaryCount()); if ((cache_max_size == 0 || total_dictionary_size <= cache_max_size) && total_dictionary_count <= cache_max_count) { return Error::kOk; } uint64_t to_be_removed_count = 0; if (total_dictionary_count > count_low_watermark) { to_be_removed_count = total_dictionary_count - count_low_watermark; } base::CheckedNumeric checked_total_dictionary_size = total_dictionary_size; static constexpr char kQuery[] = // clang-format off "SELECT " "id," "size," "token_high," "token_low FROM dictionaries " "ORDER BY last_used_time"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return Error::kInvalidSql; } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); while (statement.Step()) { const int64_t primary_key_in_database = statement.ColumnInt64(0); const size_t size = statement.ColumnInt64(1); const int64_t token_high = statement.ColumnInt64(2); const int64_t token_low = statement.ColumnInt64(3); absl::optional disk_cache_key_token = ToUnguessableToken(token_high, token_low); if (!disk_cache_key_token) { LOG(WARNING) << "Invalid token"; continue; } checked_total_dictionary_size -= size; if (!checked_total_dictionary_size.IsValid()) { base::debug::DumpWithoutCrashing(); return Error::kInvalidTotalDictSize; } *total_size_after_eviction_out = base::checked_cast(checked_total_dictionary_size.ValueOrDie()); primary_keys_out->emplace_back(primary_key_in_database); tokens_out->emplace_back(*disk_cache_key_token); if ((cache_max_size == 0 || size_low_watermark >= checked_total_dictionary_size.ValueOrDie()) && tokens_out->size() >= to_be_removed_count) { break; } } return Error::kOk; } SQLitePersistentSharedDictionaryStore::Error SQLitePersistentSharedDictionaryStore::Backend::DeleteDictionaryByPrimaryKey( int64_t primary_key) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); static constexpr char kQuery[] = "DELETE FROM dictionaries WHERE id=?"; if (!db()->IsSQLValid(kQuery)) { return Error::kInvalidSql; } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindInt64(0, primary_key); if (!statement.Run()) { return Error::kFailedToExecuteSql; } return Error::kOk; } SQLitePersistentSharedDictionaryStore::Error SQLitePersistentSharedDictionaryStore::Backend:: DeleteDictionariesByDiskCacheKeyTokensImpl( const std::set& disk_cache_key_tokens) { if (!InitializeDatabase()) { return Error::kFailedToInitializeDatabase; } sql::Transaction transaction(db()); if (!transaction.Begin()) { return Error::kFailedToBeginTransaction; } base::CheckedNumeric checked_total_deleted_dictionary_size; for (const auto& token : disk_cache_key_tokens) { ASSIGN_OR_RETURN(uint64_t deleted_dictionary_size, DeleteDictionaryByDiskCacheToken(token)); checked_total_deleted_dictionary_size += deleted_dictionary_size; } int64_t total_deleted_dictionary_size = checked_total_deleted_dictionary_size.ValueOrDie(); if (total_deleted_dictionary_size != 0) { uint64_t total_dictionary_size = 0; Error error = UpdateTotalDictionarySizeInMetaTable( -total_deleted_dictionary_size, &total_dictionary_size); if (error != Error::kOk) { return error; } } if (!transaction.Commit()) { return Error::kFailedToCommitTransaction; } return Error::kOk; } base::expected SQLitePersistentSharedDictionaryStore::Backend:: DeleteDictionaryByDiskCacheToken( const base::UnguessableToken& disk_cache_key_token) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } static constexpr char kQuery[] = // clang-format off "DELETE FROM dictionaries " "WHERE token_high=? AND token_low=?" "RETURNING size"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); // There is no `sql::Statement::BindUint64()` method. So we cast to int64_t. int64_t token_high = static_cast(disk_cache_key_token.GetHighForSerialization()); int64_t token_low = static_cast(disk_cache_key_token.GetLowForSerialization()); statement.BindInt64(0, token_high); statement.BindInt64(1, token_low); base::CheckedNumeric checked_size = 0; while (statement.Step()) { const size_t size = statement.ColumnInt64(0); checked_size += size; } return base::ok(checked_size.ValueOrDie()); } SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError SQLitePersistentSharedDictionaryStore::Backend::GetAllDiskCacheKeyTokensImpl() { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); if (!InitializeDatabase()) { return base::unexpected(Error::kFailedToInitializeDatabase); } static constexpr char kQuery[] = // clang-format off "SELECT " "id," "token_high," "token_low FROM dictionaries " "ORDER BY id"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); std::vector tokens; while (statement.Step()) { absl::optional disk_cache_key_token = ToUnguessableToken(statement.ColumnInt64(1), statement.ColumnInt64(2)); if (!disk_cache_key_token) { LOG(WARNING) << "Invalid token"; continue; } tokens.emplace_back(*disk_cache_key_token); } return base::ok( std::set(tokens.begin(), tokens.end())); } void SQLitePersistentSharedDictionaryStore::Backend:: UpdateDictionaryLastUsedTime(int64_t primary_key_in_database, base::Time last_used_time) { CHECK(client_task_runner()->RunsTasksInCurrentSequence()); CHECK(!background_task_runner()->RunsTasksInCurrentSequence()); size_t num_pending; { base::AutoLock locked(lock_); pending_last_used_time_updates_[primary_key_in_database] = last_used_time; num_pending = ++num_pending_; } // Commit every 30 seconds. static const int kCommitIntervalMs = 30 * 1000; // Commit right away if we have more than 100 operations. static const size_t kCommitAfterBatchSize = 100; if (num_pending == 1) { // We've gotten our first entry for this batch, fire off the timer. if (!background_task_runner()->PostDelayedTask( FROM_HERE, base::BindOnce(&Backend::Commit, this), base::Milliseconds(kCommitIntervalMs))) { NOTREACHED() << "background_task_runner_ is not running."; } } else if (num_pending >= kCommitAfterBatchSize) { // We've reached a big enough batch, fire off a commit now. PostBackgroundTask(FROM_HERE, base::BindOnce(&Backend::Commit, this)); } } base::expected SQLitePersistentSharedDictionaryStore::Backend::GetTotalDictionaryCount() { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); static constexpr char kQuery[] = "SELECT COUNT(id) FROM dictionaries"; if (!db()->IsSQLValid(kQuery)) { return base::unexpected(Error::kInvalidSql); } uint64_t dictionary_count = 0; sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); if (statement.Step()) { dictionary_count = statement.ColumnInt64(0); } return base::ok(dictionary_count); } bool SQLitePersistentSharedDictionaryStore::Backend:: GetExistingDictionarySizeAndDiskCacheKeyToken( const SharedDictionaryIsolationKey& isolation_key, const url::SchemeHostPort& host, const std::string& match, int64_t* size_out, absl::optional* disk_cache_key_out) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); static constexpr char kQuery[] = // clang-format off "SELECT " "size," "token_high," "token_low FROM dictionaries " "WHERE frame_origin=? AND top_frame_site=? AND host=? AND match=? " "ORDER BY id"; // clang-format on if (!db()->IsSQLValid(kQuery)) { return false; } sql::Statement statement(db()->GetCachedStatement(SQL_FROM_HERE, kQuery)); statement.BindString(0, isolation_key.frame_origin().Serialize()); statement.BindString(1, isolation_key.top_frame_site().Serialize()); statement.BindString(2, host.Serialize()); statement.BindString(3, match); if (statement.Step()) { *size_out = statement.ColumnInt64(0); *disk_cache_key_out = ToUnguessableToken(statement.ColumnInt64(1), statement.ColumnInt64(2)); return true; } return false; } SQLitePersistentSharedDictionaryStore::Error SQLitePersistentSharedDictionaryStore::Backend:: UpdateTotalDictionarySizeInMetaTable(int64_t size_delta, uint64_t* total_dictionary_size_out) { CHECK(background_task_runner()->RunsTasksInCurrentSequence()); ASSIGN_OR_RETURN(uint64_t total_dictionary_size, GetTotalDictionarySizeImpl()); base::CheckedNumeric checked_total_dictionary_size = total_dictionary_size; checked_total_dictionary_size += size_delta; if (!checked_total_dictionary_size.IsValid()) { LOG(ERROR) << "Invalid total_dict_size detected."; base::debug::DumpWithoutCrashing(); return Error::kInvalidTotalDictSize; } *total_dictionary_size_out = checked_total_dictionary_size.ValueOrDie(); if (!meta_table()->SetValue(kTotalDictSizeKey, *total_dictionary_size_out)) { return Error::kFailedToSetTotalDictSize; } return Error::kOk; } SQLitePersistentSharedDictionaryStore::SQLitePersistentSharedDictionaryStore( const base::FilePath& path, const scoped_refptr& client_task_runner, const scoped_refptr& background_task_runner) : backend_(base::MakeRefCounted(path, client_task_runner, background_task_runner)) {} SQLitePersistentSharedDictionaryStore:: ~SQLitePersistentSharedDictionaryStore() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->Close(); } void SQLitePersistentSharedDictionaryStore::GetTotalDictionarySize( base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->GetTotalDictionarySize( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback))); } void SQLitePersistentSharedDictionaryStore::RegisterDictionary( const SharedDictionaryIsolationKey& isolation_key, SharedDictionaryInfo dictionary_info, const uint64_t max_size_per_site, const uint64_t max_count_per_site, base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->RegisterDictionary( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback)), isolation_key, std::move(dictionary_info), max_size_per_site, max_count_per_site); } void SQLitePersistentSharedDictionaryStore::GetDictionaries( const SharedDictionaryIsolationKey& isolation_key, base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->GetDictionaries( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback)), isolation_key); } void SQLitePersistentSharedDictionaryStore::GetAllDictionaries( base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->GetAllDictionaries( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback))); } void SQLitePersistentSharedDictionaryStore::GetUsageInfo( base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->GetUsageInfo( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback))); } void SQLitePersistentSharedDictionaryStore::GetOriginsBetween( const base::Time start_time, const base::Time end_time, base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->GetOriginsBetween( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback)), start_time, end_time); } void SQLitePersistentSharedDictionaryStore::ClearAllDictionaries( base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->ClearAllDictionaries( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback))); } void SQLitePersistentSharedDictionaryStore::ClearDictionaries( const base::Time start_time, const base::Time end_time, base::RepeatingCallback url_matcher, base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->ClearDictionaries( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback)), start_time, end_time, std::move(url_matcher)); } void SQLitePersistentSharedDictionaryStore::ClearDictionariesForIsolationKey( const SharedDictionaryIsolationKey& isolation_key, base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->ClearDictionariesForIsolationKey( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback)), isolation_key); } void SQLitePersistentSharedDictionaryStore::DeleteExpiredDictionaries( const base::Time now, base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->DeleteExpiredDictionaries( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback)), now); } void SQLitePersistentSharedDictionaryStore::ProcessEviction( const uint64_t cache_max_size, const uint64_t size_low_watermark, const uint64_t cache_max_count, const uint64_t count_low_watermark, base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->ProcessEviction( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback)), cache_max_size, size_low_watermark, cache_max_count, count_low_watermark); } void SQLitePersistentSharedDictionaryStore::GetAllDiskCacheKeyTokens( base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->GetAllDiskCacheKeyTokens( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback))); } void SQLitePersistentSharedDictionaryStore:: DeleteDictionariesByDiskCacheKeyTokens( std::set disk_cache_key_tokens, base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->DeleteDictionariesByDiskCacheKeyTokens( WrapCallbackWithWeakPtrCheck(GetWeakPtr(), std::move(callback)), std::move(disk_cache_key_tokens)); } void SQLitePersistentSharedDictionaryStore::UpdateDictionaryLastUsedTime( int64_t primary_key_in_database, base::Time last_used_time) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); backend_->UpdateDictionaryLastUsedTime(primary_key_in_database, last_used_time); } base::WeakPtr SQLitePersistentSharedDictionaryStore::GetWeakPtr() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return weak_factory_.GetWeakPtr(); } } // namespace net