Usage table LRU only retrieves a single entry. am: 7ce8950c0f

Original change: https://googleplex-android-review.googlesource.com/c/platform/vendor/widevine/+/12089309

Change-Id: I2fc9a816ca0306e513ecacbe1a2a97fc6adc3f17
This commit is contained in:
Alex Dale
2020-07-15 23:13:34 +00:00
committed by Automerger Merge Worker
3 changed files with 214 additions and 352 deletions

View File

@@ -114,11 +114,10 @@ class UsageTableHeader {
static bool DetermineLicenseToRemoveForTesting(
const std::vector<CdmUsageEntryInfo>& usage_entry_info_list,
int64_t current_time, size_t unexpired_threshold, size_t removal_count,
std::vector<uint32_t>* 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<uint32_t>* 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<CdmUsageEntryInfo>& usage_entry_info_list,
int64_t current_time, size_t unexpired_threshold, size_t removal_count,
std::vector<uint32_t>* 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

View File

@@ -5,6 +5,7 @@
#include "usage_table_header.h"
#include <algorithm>
#include <limits>
#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<uint32_t> 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<uint32_t>* removal_candidates) {
bool UsageTableHeader::GetRemovalCandidate(uint32_t* entry_to_remove) {
LOGI("Locking to determine removal candidates");
std::unique_lock<std::mutex> 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<CdmUsageEntryInfo>& usage_entry_info_list,
int64_t current_time, size_t unexpired_threshold, size_t removal_count,
std::vector<uint32_t>* 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<uint32_t> unknown_storage_entry_numbers;
// |entry_numbers| contains expired offline and streaming license.
std::vector<uint32_t> entry_numbers;
std::vector<uint32_t> 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<uint32_t>::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;
}

View File

@@ -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<CdmUsageEntryInfo> usage_entry_info_list;
std::vector<uint32_t> 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<CdmUsageEntryInfo> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<CdmUsageEntryInfo> 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<uint32_t> 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<CdmUsageEntryInfo> 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<uint32_t> 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<CdmUsageEntryInfo> 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<uint32_t> 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<CdmUsageEntryInfo> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<CdmUsageEntryInfo> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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) {