diff --git a/libwvdrmengine/cdm/core/include/usage_table_header.h b/libwvdrmengine/cdm/core/include/usage_table_header.h index 824bd936..b3363954 100644 --- a/libwvdrmengine/cdm/core/include/usage_table_header.h +++ b/libwvdrmengine/cdm/core/include/usage_table_header.h @@ -114,11 +114,10 @@ class UsageTableHeader { static bool DetermineLicenseToRemoveForTesting( const std::vector& usage_entry_info_list, - int64_t current_time, size_t unexpired_threshold, size_t removal_count, - std::vector* removal_candidates) { + int64_t current_time, size_t unexpired_threshold, + uint32_t* entry_to_remove) { return DetermineLicenseToRemove(usage_entry_info_list, current_time, - unexpired_threshold, removal_count, - removal_candidates); + unexpired_threshold, entry_to_remove); } private: @@ -156,7 +155,7 @@ class UsageTableHeader { // device file that had not yet been upgraded to use the LRU data. virtual bool LruUpgradeAllUsageEntries(); - virtual bool GetRemovalCandidates(std::vector* removal_candidates); + virtual bool GetRemovalCandidate(uint32_t* entry_to_remove); int64_t GetCurrentTime() { return clock_ref_->GetCurrentTime(); } @@ -166,7 +165,7 @@ class UsageTableHeader { uint64_t staleness, CdmUsageEntryStorageType storage_type); - // Uses an LRU-base algorithm to determine which licenses should be + // Uses an LRU-base algorithm to determine which license should be // removed. This is intended to be used if the usage table is full // and a new entry needs to be added. // @@ -181,8 +180,6 @@ class UsageTableHeader { // 2) Unexpired offline licenses will only be considered for // removal if the number of unexpired offline licenses exceeds // |unexpired_threshold|. - // The number of licenses to be considered will be less than or - // equal to the requested |removal_count|. // // Unknown storage types will be considered above all other entry // types. @@ -195,21 +192,17 @@ class UsageTableHeader { // [in] unexpired_threshold: The maximum number of unexpired // offline licenses that are present, before offline // licenses would be considered for removal. - // [in] removal_count: The desired number of removal candidate to - // find. Note that the actual number will be anywhere - // between 1 and |removal_count|. Must be greater than or - // equal to 1. - // [out] removal_candidates: List of usage entry numbers of the - // entries to be removed. Assume to be unaffected if the + // [out] entry_to_remove: Usage entry index of the entry selected + // to be removed. Assume to be unaffected if the // function returns |false|. // // Returns: - // |true| if at least one removal candidate can be determined. + // |true| if an entry has been determined to be removed. // Otherwise returns |false|. static bool DetermineLicenseToRemove( const std::vector& usage_entry_info_list, - int64_t current_time, size_t unexpired_threshold, size_t removal_count, - std::vector* removal_candidates); + int64_t current_time, size_t unexpired_threshold, + uint32_t* entry_to_remove); // This handle and file system is only to be used when accessing // usage_table_header. Usage entries should use the file system provided diff --git a/libwvdrmengine/cdm/core/src/usage_table_header.cpp b/libwvdrmengine/cdm/core/src/usage_table_header.cpp index 09ca4ab6..632ce1b8 100644 --- a/libwvdrmengine/cdm/core/src/usage_table_header.cpp +++ b/libwvdrmengine/cdm/core/src/usage_table_header.cpp @@ -5,6 +5,7 @@ #include "usage_table_header.h" #include +#include #include "cdm_random.h" #include "crypto_session.h" @@ -22,9 +23,6 @@ std::string kOldUsageEntryClientMacKey(wvcdm::MAC_KEY_SIZE, 0); std::string kOldUsageEntryPoviderSessionToken = "nahZ6achSheiqua3TohQuei0ahwohv"; constexpr int64_t kDefaultExpireDuration = 33 * 24 * 60 * 60; // 33 Days -// Number of elements to be considered for removal using the LRU algorithm. -// TODO(b/155230578): Remove this constant. -constexpr size_t kLruRemovalSetSize = 3; // Fraction of table capacity of number of unexpired offline licenses // before they are considered to be removed. This could occur if // there are not enough expired offline or streaming licenses to @@ -946,13 +944,11 @@ CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files, CdmResponseType UsageTableHeader::ReleaseOldestEntry( metrics::CryptoMetrics* metrics) { LOGV("Releasing oldest entry"); - std::vector removal_candidates; - if (!GetRemovalCandidates(&removal_candidates)) { + uint32_t entry_number_to_delete; + if (!GetRemovalCandidate(&entry_number_to_delete)) { LOGE("Could not determine which license to remove"); return UNKNOWN_ERROR; } - // Only release one entry. - const uint32_t entry_number_to_delete = removal_candidates.front(); const CdmUsageEntryInfo& usage_entry_info = usage_entry_info_[entry_number_to_delete]; @@ -1115,15 +1111,13 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { return true; } -bool UsageTableHeader::GetRemovalCandidates( - std::vector* removal_candidates) { +bool UsageTableHeader::GetRemovalCandidate(uint32_t* entry_to_remove) { LOGI("Locking to determine removal candidates"); std::unique_lock auto_lock(usage_table_header_lock_); const size_t lru_unexpired_threshold = kLruUnexpiredThresholdFraction * potential_table_capacity(); return DetermineLicenseToRemove(usage_entry_info_, GetCurrentTime(), - lru_unexpired_threshold, kLruRemovalSetSize, - removal_candidates); + lru_unexpired_threshold, entry_to_remove); } void UsageTableHeader::RecordLruEventMetrics( @@ -1139,125 +1133,105 @@ void UsageTableHeader::RecordLruEventMetrics( } // Static. -// TODO(b/155230578): Change this function to only return 1 entry. bool UsageTableHeader::DetermineLicenseToRemove( const std::vector& usage_entry_info_list, - int64_t current_time, size_t unexpired_threshold, size_t removal_count, - std::vector* removal_candidates) { - if (removal_candidates == nullptr) { - LOGE("Output parameter |removal_candidates| is null"); - return false; - } - if (removal_count == 0) { - LOGE("|removal_count| cannot be zero"); + int64_t current_time, size_t unexpired_threshold, + uint32_t* entry_to_remove) { + if (entry_to_remove == nullptr) { + LOGE("Output parameter |entry_to_remove| is null"); return false; } if (usage_entry_info_list.empty()) { return false; } - removal_candidates->clear(); - std::vector unknown_storage_entry_numbers; - // |entry_numbers| contains expired offline and streaming license. - std::vector entry_numbers; - std::vector unexpired_offline_license_entry_numbers; - - // Separate the entries based on their priority properties. - for (uint32_t entry_number = 0; entry_number < usage_entry_info_list.size(); - ++entry_number) { - const CdmUsageEntryInfo& usage_entry_info = - usage_entry_info_list[entry_number]; - if (usage_entry_info.storage_type == kStorageLicense) { - if (usage_entry_info.offline_license_expiry_time > current_time) { - // Unexpired offline. - unexpired_offline_license_entry_numbers.push_back(entry_number); - } else { - // Expired offline. - entry_numbers.push_back(entry_number); - } - } else if (usage_entry_info.storage_type == kStorageUsageInfo) { - // Streaming. - entry_numbers.push_back(entry_number); - } else { - // Unknown entries. - unknown_storage_entry_numbers.push_back(entry_number); - } - } - - // Select any entries of unknown storage type. - if (!unknown_storage_entry_numbers.empty()) { - if (unknown_storage_entry_numbers.size() >= removal_count) { - // Case: There are enough entries with unknown storage types to - // fill the removal set. - removal_candidates->insert( - removal_candidates->begin(), unknown_storage_entry_numbers.begin(), - unknown_storage_entry_numbers.begin() + removal_count); - return true; - } - // Fill whatever are available, and check for more. - *removal_candidates = std::move(unknown_storage_entry_numbers); - } - - // Sort licenses based on last used time. - const auto compare_last_used = [&](uint32_t i, uint32_t j) { + // Returns true if entry of first index is more stale than the + // entry of the second index. + const auto is_more_stale = [&](uint32_t i, uint32_t j) -> bool { return usage_entry_info_list[i].last_use_time < usage_entry_info_list[j].last_use_time; }; - // Check if unexpired licenses should be considered too. - if (unexpired_offline_license_entry_numbers.size() > unexpired_threshold) { - std::sort(unexpired_offline_license_entry_numbers.begin(), - unexpired_offline_license_entry_numbers.end(), compare_last_used); - if (unexpired_offline_license_entry_numbers.size() > removal_count) { - unexpired_offline_license_entry_numbers.resize(removal_count); + // Find the most stale expired offline / streaming license and the + // most stale unexpired offline entry. Count the number of unexpired + // entries. If any entry is of storage type unknown, then it should + // be removed. + constexpr uint32_t kNoEntry = std::numeric_limits::max(); + uint32_t stalest_expired_offline_license = kNoEntry; + uint32_t stalest_unexpired_offline_license = kNoEntry; + uint32_t stalest_streaming_license = kNoEntry; + size_t unexpired_license_count = 0; + + for (uint32_t entry_number = 0; entry_number < usage_entry_info_list.size(); + ++entry_number) { + const CdmUsageEntryInfo& usage_entry_info = + usage_entry_info_list[entry_number]; + + if (usage_entry_info.storage_type != kStorageLicense && + usage_entry_info.storage_type != kStorageUsageInfo) { + // Unknown storage type entries. Remove this entry. + *entry_to_remove = entry_number; + return true; + } + if (usage_entry_info.storage_type == kStorageLicense && + usage_entry_info.offline_license_expiry_time > current_time) { + // Unexpired offline. + ++unexpired_license_count; + if (stalest_unexpired_offline_license == kNoEntry || + is_more_stale(entry_number, stalest_unexpired_offline_license)) { + stalest_unexpired_offline_license = entry_number; + } + } else if (usage_entry_info.storage_type == kStorageLicense) { + // Expired offline. + if (stalest_expired_offline_license == kNoEntry || + is_more_stale(entry_number, stalest_expired_offline_license)) { + stalest_expired_offline_license = entry_number; + } + } else { + // Streaming. + if (stalest_streaming_license == kNoEntry || + is_more_stale(entry_number, stalest_streaming_license)) { + stalest_streaming_license = entry_number; + } } - // Merge the sets. - entry_numbers.insert(entry_numbers.end(), - unexpired_offline_license_entry_numbers.begin(), - unexpired_offline_license_entry_numbers.end()); } - // Sort expired offline and streaming license based on last used time. - std::sort(entry_numbers.begin(), entry_numbers.end(), compare_last_used); - - if ((entry_numbers.size() + removal_candidates->size()) <= removal_count) { - // Under testing conditions, it is possible for there to be fewer usage - // entries than there are being requested. - - // Move whatever values are available to the removal candidates. - removal_candidates->insert(removal_candidates->end(), entry_numbers.begin(), - entry_numbers.end()); - return removal_candidates->size() > 0; + if (stalest_expired_offline_license == kNoEntry && + stalest_streaming_license == kNoEntry && + unexpired_license_count <= unexpired_threshold) { + // Unexpected situation, could be an issue with the threshold. + LOGW( + "Table only contains unexpired offline licenses, " + "but threshold not met: size = %zu, count = %zu, threshold = %zu", + usage_entry_info_list.size(), unexpired_license_count, + unexpired_threshold); + *entry_to_remove = stalest_unexpired_offline_license; + return true; } - const size_t remaining_removal_count = - removal_count - removal_candidates->size(); - - // Based on the last use time the |remaining_removal_count|-th - // least recently used entry, filter out all elements which have - // been used more recently than it. This might result in a set - // which is larger than the desired size. - const int64_t cutoff_last_use_time = - usage_entry_info_list[entry_numbers[remaining_removal_count - 1]] - .last_use_time; - const auto equal_to_cutoff = [&](uint32_t entry_number) { - return usage_entry_info_list[entry_number].last_use_time == - cutoff_last_use_time; + const auto select_most_stale = [&](uint32_t a, uint32_t b) -> uint32_t { + if (a == kNoEntry) return b; + if (b == kNoEntry) return a; + return is_more_stale(a, b) ? a : b; }; - const auto first_cutoff_it = - std::find_if(entry_numbers.begin(), entry_numbers.end(), equal_to_cutoff); + // Only consider an unexpired entry if the threshold is reached. + if (unexpired_license_count > unexpired_threshold) { + const uint32_t temp = select_most_stale(stalest_unexpired_offline_license, + stalest_streaming_license); + *entry_to_remove = select_most_stale(temp, stalest_expired_offline_license); + } else { + *entry_to_remove = select_most_stale(stalest_streaming_license, + stalest_expired_offline_license); + } - const auto after_last_cutoff_it = - std::find_if_not(first_cutoff_it, entry_numbers.end(), equal_to_cutoff); - - // To avoid always selecting the greatest entry number (due to the - // sort & reverse), we randomize the set. - std::shuffle(first_cutoff_it, after_last_cutoff_it, - std::default_random_engine(CdmRandom::Rand())); - - removal_candidates->insert(removal_candidates->end(), entry_numbers.cbegin(), - entry_numbers.cbegin() + remaining_removal_count); + if (*entry_to_remove == kNoEntry) { + // Illegal state check. The loop above should have found at least + // one entry given that |usage_entry_info_list| is not empty. + LOGE("No entry could be used for removal: size = %zu", + usage_entry_info_list.size()); + return false; + } return true; } diff --git a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp index f70ae5f6..67f336c9 100644 --- a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp @@ -3541,38 +3541,16 @@ TEST_F(UsageTableHeaderTest, LruLastUsedTime_LoadEntry) { // operations. TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_InvalidInput) { constexpr size_t kUnexpiredThreshold = 50; // Arbitrary - constexpr size_t kRemovalCount = 3; // Also artbirary std::vector usage_entry_info_list; - std::vector removal_candidates; + uint32_t entry_to_remove = 0; // Empty list. EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount, - &removal_candidates)); + usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, + &entry_to_remove)); // Output is null. usage_entry_info_list = kUpgradedUsageEntryInfoList; EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount, - nullptr)); - // Zero size requests. - EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, 0, - &removal_candidates)); - // Request more than is available. Not invalid, but an unlikely use - // case. - EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, - /* removal_count = */ usage_entry_info_list.size() * 2, - &removal_candidates)); - // Only unexpired offline license, but threshold is not met. - usage_entry_info_list.resize(kRemovalCount); - for (size_t i = 0; i < kRemovalCount; ++i) { - usage_entry_info_list[i].storage_type = kStorageLicense; - usage_entry_info_list[i].last_use_time = kLruBaseTime; - usage_entry_info_list[i].offline_license_expiry_time = kLruBaseTime + 1; - } - EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount, - &removal_candidates)); + usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, nullptr)); } // Check that the major priority buckets are respected. @@ -3582,6 +3560,7 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_InvalidInput) { TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) { constexpr int64_t kOneDay = 24 * 60 * 60; constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay; + constexpr uint32_t kInvalidEntry = 9999; std::vector usage_entry_info_list; // Unexpired offline license. @@ -3615,56 +3594,98 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) { usage_entry_info_list.push_back(unknown_entry_info); constexpr uint32_t unknown_entry_number = 3; - std::vector removal_candidates; + // Case 1: If there is an entry with unknown storage type, it should + // be selected above any other entry. + uint32_t entry_to_remove = kInvalidEntry; // Expect the unknown entry. EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 3, - /* removal_count = */ 1, &removal_candidates)); - const std::vector unknown_entry_numbers = {unknown_entry_number}; - EXPECT_THAT(removal_candidates, ContainerEq(unknown_entry_numbers)); + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(unknown_entry_number, entry_to_remove); usage_entry_info_list.pop_back(); // Removing unknown. - // Expect both expired offline and streaming license. + // Case 2a: Threshold not met, all entries are equally stale. + // The expired entry should be selected over the streaming license. + entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 3, - /* removal_count = */ 3, &removal_candidates)); - const std::vector expired_and_streaming_entry_numbers = { - expired_entry_number, streaming_entry_number}; - EXPECT_THAT(removal_candidates, - ContainerEq(expired_and_streaming_entry_numbers)); + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(expired_entry_number, entry_to_remove); - // With threshold met, expect all three. + // Case 2b: Threshold not met, streaming license is most stale. + usage_entry_info_list[streaming_entry_number].last_use_time--; + entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 0, - /* removal_count = */ 3, &removal_candidates)); - const std::vector all_license_entry_numbers = { - expired_entry_number, streaming_entry_number, unexpired_entry_number}; - EXPECT_THAT(removal_candidates, ContainerEq(all_license_entry_numbers)); + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(streaming_entry_number, entry_to_remove); usage_entry_info_list.pop_back(); // Removing streaming. - usage_entry_info_list.pop_back(); // Removing expired offline. + // |usage_entry_info_list| only contains 1 expired and 1 unexpired offline + // license. - // Sanity check: while below threshold there will not be any possible - // candidates, expect failure. This is an unexpected case in normal - // operation. - EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 3, - /* removal_count = */ 1, &removal_candidates)); - - // Expect the unexpired license. + // Case 2c: Threshold met, equally stale entries. Expect the expired + // entry over the unexpired. + entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 0, - /* removal_count = */ 1, &removal_candidates)); - const std::vector unexpired_entry_numbers = { - unexpired_entry_number}; - EXPECT_THAT(removal_candidates, ContainerEq(unexpired_entry_numbers)); + /* unexpired_threshold = */ 0, &entry_to_remove)); + EXPECT_EQ(expired_entry_number, entry_to_remove); + + // Case 3a: Threshold not met, expired entry is the most stale. + entry_to_remove = kInvalidEntry; + usage_entry_info_list[expired_entry_number].last_use_time--; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(expired_entry_number, entry_to_remove); + + // Case 3b: Threshold not met, unexpired entry is the most stale. + entry_to_remove = kInvalidEntry; + usage_entry_info_list[expired_entry_number].last_use_time++; + usage_entry_info_list[unexpired_entry_number].last_use_time--; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(expired_entry_number, entry_to_remove); + + // Case 3c: Threshold met, unexpired entry is the most stale. + entry_to_remove = kInvalidEntry; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 0, &entry_to_remove)); + EXPECT_EQ(unexpired_entry_number, entry_to_remove); + + // Case 3d: Threshold met, expired entry is the most stale. + entry_to_remove = kInvalidEntry; + usage_entry_info_list[expired_entry_number].last_use_time--; + usage_entry_info_list[unexpired_entry_number].last_use_time++; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 0, &entry_to_remove)); + EXPECT_EQ(expired_entry_number, entry_to_remove); + + usage_entry_info_list.pop_back(); // Removing expired offline. + + // Case 4a: Threshold met, and only an unexpired offline license + // is available. + entry_to_remove = kInvalidEntry; // Invalidate value. + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 0, &entry_to_remove)); + EXPECT_EQ(unexpired_entry_number, entry_to_remove); + + // Case 4b (stability check): Threshold not met, and only an + // unexpired offline license is available. This is an unexpected + // condition in normal operation, but the algorithm should still + // return an entry. + entry_to_remove = kInvalidEntry; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(unexpired_entry_number, entry_to_remove); } // Testing algorithm with unexpired offline and streaming license. The @@ -3673,6 +3694,7 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) { TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_NoExpiredAndBelowThreshold) { constexpr int64_t kOneDay = 24 * 60 * 60; + constexpr uint32_t kInvalidEntry = 9999; std::vector usage_entry_info_list = kUpgradedUsageEntryInfoList; const size_t offline_threshold = usage_entry_info_list.size() + 1; @@ -3693,17 +3715,13 @@ TEST_F(UsageTableHeaderTest, // Must exist at least one streaming license for test to work. ASSERT_LT(0ull, usage_info_count); - std::vector removal_candidates; + uint32_t entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kLruBaseTime, offline_threshold, - /* removal_count = */ 3, &removal_candidates)); + &entry_to_remove)); - EXPECT_EQ(usage_info_count, removal_candidates.size()); - // Ensure that the proposed removal candidates are streaming. - for (uint32_t usage_entry_number : removal_candidates) { - EXPECT_EQ(kStorageUsageInfo, - usage_entry_info_list[usage_entry_number].storage_type); - } + EXPECT_EQ(kStorageUsageInfo, + usage_entry_info_list[entry_to_remove].storage_type); } // When the number of unexpired offline licenses are below the @@ -3718,6 +3736,7 @@ TEST_F(UsageTableHeaderTest, constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; // A threshold larger than the possible number of offline entries. constexpr size_t kUnexpiredThreshold = kSetSize + 1; + constexpr uint32_t kInvalidEntry = 9999; std::vector usage_entry_info_list; usage_entry_info_list.resize(kSetSize); @@ -3746,126 +3765,16 @@ TEST_F(UsageTableHeaderTest, expired_license_numbers.push_back(i); } - std::vector removal_candidates; + uint32_t entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kCurrentTime, kUnexpiredThreshold, 3, - &removal_candidates)); + usage_entry_info_list, kCurrentTime, kUnexpiredThreshold, + &entry_to_remove)); - // Sorting to ensure equality will work. - std::sort(removal_candidates.begin(), removal_candidates.end()); - std::sort(expired_license_numbers.begin(), expired_license_numbers.end()); - - EXPECT_EQ(expired_license_numbers, removal_candidates); -} - -// Test that if all of the license are unknown. For unknown licenses, -// the last use time has no effect on their selection. -TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_AllUnknown) { - constexpr int64_t kOneDay = 24 * 60 * 60; - constexpr size_t kSetSize = 10; - constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; - constexpr size_t kRemovalCount = 3; - - std::vector usage_entry_info_list; - usage_entry_info_list.resize(kSetSize); - - // Create a set of all unknown licenses. - for (uint32_t i = 0; i < kSetSize; ++i) { - CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - usage_entry_info.storage_type = kStorageTypeUnknown; - usage_entry_info.key_set_id = "unknown_key_set_id"; - usage_entry_info.last_use_time = - CdmRandom::RandomInRange(kLruBaseTime, kCurrentTime); - } - - std::vector removal_candidates; - EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kCurrentTime, 0, kRemovalCount, - &removal_candidates)); - // There should always be 3 (assuming kSetSize >= 3). - EXPECT_EQ(kRemovalCount, removal_candidates.size()); -} - -// Should there be two or more license which have the same -// |last_use_time| but only 1 of them are to be selected (due to the -// number being requested), then the algorithm should randomly select -// between the ones available. -TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_RandomnessOfCutoff) { - constexpr int64_t kOneDay = 24 * 60 * 60; - constexpr size_t kSetSize = 10; - constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; - constexpr size_t kTrials = 25; - std::vector usage_entry_info_list; - - // All will be streaming licenses. - usage_entry_info_list.resize(kSetSize); - for (size_t i = 0; i < kSetSize; ++i) { - CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - usage_entry_info.storage_type = kStorageUsageInfo; - usage_entry_info.last_use_time = kLruBaseTime + kOneDay; - usage_entry_info.key_set_id = "nothing_unusual"; - } - - std::vector expected_removals; - // Select two to be the most stale. - while (expected_removals.size() < 2) { - const uint32_t i = CdmRandom::RandomInRange(kSetSize - 1); - CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - if (usage_entry_info.key_set_id != "nothing_unusual") continue; - usage_entry_info_list[i].last_use_time = kLruBaseTime; - usage_entry_info.key_set_id = "most_stale"; - expected_removals.push_back(i); - } - - // Select another two to be slightly less stale. - std::vector random_removals; - while (random_removals.size() < 2) { - const uint32_t i = CdmRandom::RandomInRange(kSetSize - 1); - CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - if (usage_entry_info.key_set_id != "nothing_unusual") continue; - usage_entry_info_list[i].last_use_time = kLruBaseTime + 1; - usage_entry_info.key_set_id = "somewhat_stale"; - random_removals.push_back(i); - } - - // Create two sets for each possible outcome (one for each random - // removal). - std::vector possible_removal_1 = expected_removals; - possible_removal_1.push_back(random_removals[0]); - std::sort(possible_removal_1.begin(), possible_removal_1.end()); - std::vector possible_removal_2 = expected_removals; - possible_removal_2.push_back(random_removals[1]); - std::sort(possible_removal_2.begin(), possible_removal_2.end()); - std::vector possible_removal_3 = expected_removals; - - // Flags to check that both outcomes have occurred. - bool occurrence_1 = false; - bool occurrence_2 = false; - - // Each set should be equally likely. Possible false-negative every - // 2^kTrials time. - for (size_t i = 0; i < kTrials; ++i) { - // Remove 3 out of the 4 possible candidates. - std::vector removal_candidates; - EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kCurrentTime, 0, 3, &removal_candidates)); - - std::sort(removal_candidates.begin(), removal_candidates.end()); - if (removal_candidates == possible_removal_1) { - occurrence_1 = true; - } else if (removal_candidates == possible_removal_2) { - occurrence_2 = true; - } else { - EXPECT_TRUE(false) << "Unexpected removal set"; - } - } - - EXPECT_TRUE(occurrence_1); - EXPECT_TRUE(occurrence_2); + EXPECT_THAT(expired_license_numbers, Contains(entry_to_remove)); } // This test primarily tests the robustness of the algorithm for a full -// set of entries (200). Creates 3 stale streaming license and 3 +// set of entries (200). Creates 1 stale streaming license and 1 // offline licenses which are more stale than the streaming. // // First, with the stale offline licenses unexpired, checks that the @@ -3878,6 +3787,7 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_LargeMixedSet) { constexpr int64_t kOneDay = 24 * 60 * 60; constexpr size_t kLargeSetSize = 200; constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; + constexpr uint32_t kInvalidEntry = 9999; std::vector usage_entry_info_list; usage_entry_info_list.resize(kLargeSetSize); @@ -3896,72 +3806,57 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_LargeMixedSet) { } } - // Select 3 streaming license to be more stale than the rest. - std::vector modified_usage_info_numbers; - while (modified_usage_info_numbers.size() < 3) { + // Select a streaming license to be more stale than the rest. + uint32_t modified_usage_info_number = kInvalidEntry; + while (modified_usage_info_number == kInvalidEntry) { const uint32_t i = CdmRandom::RandomInRange(kLargeSetSize - 1); CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - // Skip streaming license that have already been modified and offline - // licenses. - if (usage_entry_info.storage_type != kStorageUsageInfo || - usage_entry_info.key_set_id != "nothing_unusual") - continue; - usage_entry_info.last_use_time = - kLruBaseTime + 10 + modified_usage_info_numbers.size(); + // Skip offline licenses. + if (usage_entry_info.storage_type != kStorageUsageInfo) continue; + usage_entry_info.last_use_time = kLruBaseTime + 10; usage_entry_info.key_set_id = "stale_streaming"; - modified_usage_info_numbers.push_back(i); + modified_usage_info_number = i; } - std::sort(modified_usage_info_numbers.begin(), - modified_usage_info_numbers.end()); - // Select 3 offline license to be even more stale, but unexpired. - std::vector modified_offline_license_numbers; - while (modified_offline_license_numbers.size() < 3) { + // Select a offline license to be even more stale, but unexpired. + uint32_t modified_offline_license_number = kInvalidEntry; + while (modified_offline_license_number == kInvalidEntry) { const uint32_t i = CdmRandom::RandomInRange(kLargeSetSize - 1); CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - // Skip offline license that have already been modified and streaming - // licenses. - if (usage_entry_info.storage_type != kStorageLicense || - usage_entry_info.key_set_id != "nothing_unusual") - continue; + // Skip streaming licenses. + if (usage_entry_info.storage_type != kStorageLicense) continue; usage_entry_info.last_use_time = kLruBaseTime; usage_entry_info.key_set_id = "stale_offline"; - modified_offline_license_numbers.push_back(i); + modified_offline_license_number = i; } - std::sort(modified_offline_license_numbers.begin(), - modified_offline_license_numbers.end()); // Test using only streaming and expired offline licenses // (which there are none). - std::vector removal_candidates; + uint32_t entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ kLargeSetSize, 3, &removal_candidates)); - EXPECT_THAT(removal_candidates, - UnorderedElementsAreArray(modified_usage_info_numbers)); + /* unexpired_threshold = */ kLargeSetSize, &entry_to_remove)); + EXPECT_EQ(modified_usage_info_number, entry_to_remove); - // Test where the equality threshold is met, now the 3 unexpired - // licenses should be selected. - removal_candidates.clear(); + // Test where the equality threshold is met, now the stale unexpired + // license should be selected. + entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 0, 3, &removal_candidates)); - EXPECT_THAT(removal_candidates, - UnorderedElementsAreArray(modified_offline_license_numbers)); + /* unexpired_threshold = */ 0, &entry_to_remove)); + EXPECT_EQ(modified_offline_license_number, entry_to_remove); - // Make the 3 offline licenses expired. - for (uint32_t i : modified_offline_license_numbers) { - CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - usage_entry_info.offline_license_expiry_time = kLruBaseTime; - } + // Make the stale offline license expired. + CdmUsageEntryInfo& offline_usage_entry_info = + usage_entry_info_list[modified_offline_license_number]; + offline_usage_entry_info.offline_license_expiry_time = kLruBaseTime; // Test again, expecting that the expired license should be considered. - removal_candidates.clear(); + entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ kLargeSetSize, 3, &removal_candidates)); - EXPECT_THAT(removal_candidates, - UnorderedElementsAreArray(modified_offline_license_numbers)); + /* unexpired_threshold = */ kLargeSetSize, &entry_to_remove)); + EXPECT_EQ(modified_offline_license_number, entry_to_remove); } TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Unavailable) {