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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user