Merge "LRU algorithm adapts to usage table capacity."
This commit is contained in:
@@ -88,6 +88,8 @@ class UsageTableHeader {
|
|||||||
|
|
||||||
size_t size() { return usage_entry_info_.size(); }
|
size_t size() { return usage_entry_info_.size(); }
|
||||||
|
|
||||||
|
size_t potential_table_capacity() const { return potential_table_capacity_; }
|
||||||
|
|
||||||
const std::vector<CdmUsageEntryInfo>& usage_entry_info() const {
|
const std::vector<CdmUsageEntryInfo>& usage_entry_info() const {
|
||||||
return usage_entry_info_;
|
return usage_entry_info_;
|
||||||
}
|
}
|
||||||
@@ -209,6 +211,12 @@ class UsageTableHeader {
|
|||||||
// |clock_| variable, however, it can be overrided for testing purpose.
|
// |clock_| variable, however, it can be overrided for testing purpose.
|
||||||
Clock* clock_ref_;
|
Clock* clock_ref_;
|
||||||
|
|
||||||
|
// The maximum number of entries that the underlying OEMCrypto
|
||||||
|
// implementation can support. Some implementations might not
|
||||||
|
// support reporting the table capacity, if so, then this value is
|
||||||
|
// assumed to be |kMinimumUsageTableEntriesSupported|.
|
||||||
|
size_t potential_table_capacity_ = 0u;
|
||||||
|
|
||||||
#if defined(UNIT_TEST)
|
#if defined(UNIT_TEST)
|
||||||
// Test related declarations
|
// Test related declarations
|
||||||
friend class UsageTableHeaderTest;
|
friend class UsageTableHeaderTest;
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ std::string kOldUsageEntryPoviderSessionToken =
|
|||||||
constexpr int64_t kDefaultExpireDuration = 33 * 24 * 60 * 60; // 33 Days
|
constexpr int64_t kDefaultExpireDuration = 33 * 24 * 60 * 60; // 33 Days
|
||||||
// Number of elements to consider for removal using the LRU algorithm.
|
// Number of elements to consider for removal using the LRU algorithm.
|
||||||
constexpr size_t kLruRemovalSetSize = 3;
|
constexpr size_t kLruRemovalSetSize = 3;
|
||||||
// Threshold for maximum number of unexpired offline licenses before
|
// Fraction of table capacity of number of unexpired offline licenses
|
||||||
// they are considered to be removed. This could occur if there are
|
// before they are considered to be removed. This could occur if
|
||||||
// not enough expired offline or streaming licenses to remove.
|
// there are not enough expired offline or streaming licenses to
|
||||||
// This threshold is set to prevent thrashing in the case that there
|
// remove. This threshold is set to prevent thrashing in the case that
|
||||||
// are a very large number of unexpired offline licenses and few
|
// there are a very large number of unexpired offline licenses and few
|
||||||
// expired / streaming licenses (ie, number of unexpired licenses nears
|
// expired / streaming licenses (ie, number of unexpired licenses
|
||||||
// the capacity of the usage table).
|
// nears the capacity of the usage table).
|
||||||
constexpr size_t kLruUnexpiredThreshold = 150;
|
constexpr double kLruUnexpiredThresholdFraction = 0.75;
|
||||||
|
|
||||||
// Convert |license_message| -> SignedMessage -> License.
|
// Convert |license_message| -> SignedMessage -> License.
|
||||||
bool ParseLicenseFromLicenseMessage(const CdmKeyResponse& license_message,
|
bool ParseLicenseFromLicenseMessage(const CdmKeyResponse& license_message,
|
||||||
@@ -162,6 +162,17 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
|
|||||||
requested_security_level_ =
|
requested_security_level_ =
|
||||||
security_level_ == kSecurityLevelL3 ? kLevel3 : kLevelDefault;
|
security_level_ == kSecurityLevelL3 ? kLevel3 : kLevelDefault;
|
||||||
|
|
||||||
|
if (!crypto_session->GetMaximumUsageTableEntries(
|
||||||
|
requested_security_level_, &potential_table_capacity_)) {
|
||||||
|
potential_table_capacity_ = kMinimumUsageTableEntriesSupported;
|
||||||
|
} else if (potential_table_capacity_ < kMinimumUsageTableEntriesSupported) {
|
||||||
|
LOGW(
|
||||||
|
"Reported usage table capacity is smaller than minimally required: "
|
||||||
|
"capacity = %zu, minimum = %zu",
|
||||||
|
potential_table_capacity_, kMinimumUsageTableEntriesSupported);
|
||||||
|
potential_table_capacity_ = kMinimumUsageTableEntriesSupported;
|
||||||
|
}
|
||||||
|
|
||||||
if (!file_handle_->Init(security_level)) {
|
if (!file_handle_->Init(security_level)) {
|
||||||
LOGE("Failed to initialize device files");
|
LOGE("Failed to initialize device files");
|
||||||
return false;
|
return false;
|
||||||
@@ -193,7 +204,7 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
|
|||||||
// minimum capacity (>200), we need to make sure we can still add and
|
// minimum capacity (>200), we need to make sure we can still add and
|
||||||
// remove entries. If not, clear files/data and recreate usage header table.
|
// remove entries. If not, clear files/data and recreate usage header table.
|
||||||
if (status == NO_ERROR && lru_success) {
|
if (status == NO_ERROR && lru_success) {
|
||||||
if (usage_entry_info_.size() > kMinimumUsageTableEntriesSupported) {
|
if (usage_entry_info_.size() > potential_table_capacity()) {
|
||||||
uint32_t temporary_usage_entry_number;
|
uint32_t temporary_usage_entry_number;
|
||||||
|
|
||||||
// Create a new temporary usage entry, close the session and then
|
// Create a new temporary usage entry, close the session and then
|
||||||
@@ -886,8 +897,10 @@ bool UsageTableHeader::GetRemovalCandidates(
|
|||||||
std::vector<uint32_t>* removal_candidates) {
|
std::vector<uint32_t>* removal_candidates) {
|
||||||
LOGI("Locking to determine removal candidates");
|
LOGI("Locking to determine removal candidates");
|
||||||
std::unique_lock<std::mutex> auto_lock(usage_table_header_lock_);
|
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(),
|
return DetermineLicenseToRemove(usage_entry_info_, GetCurrentTime(),
|
||||||
kLruUnexpiredThreshold, kLruRemovalSetSize,
|
lru_unexpired_threshold, kLruRemovalSetSize,
|
||||||
removal_candidates);
|
removal_candidates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -434,6 +434,27 @@ class MockCryptoSession : public TestCryptoSession {
|
|||||||
MOCK_METHOD1(MoveUsageEntry, CdmResponseType(uint32_t));
|
MOCK_METHOD1(MoveUsageEntry, CdmResponseType(uint32_t));
|
||||||
MOCK_METHOD2(ShrinkUsageTableHeader,
|
MOCK_METHOD2(ShrinkUsageTableHeader,
|
||||||
CdmResponseType(uint32_t, CdmUsageTableHeader*));
|
CdmResponseType(uint32_t, CdmUsageTableHeader*));
|
||||||
|
|
||||||
|
// Fake method for testing. Having an EXPECT_CALL causes complexities
|
||||||
|
// for getting table capacity during initialization.
|
||||||
|
virtual bool GetMaximumUsageTableEntries(SecurityLevel security_level,
|
||||||
|
size_t* number_of_entries) {
|
||||||
|
if (number_of_entries == nullptr || !maximum_usage_table_entries_set_)
|
||||||
|
return false;
|
||||||
|
*number_of_entries = maximum_usage_table_entries_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void SetMaximumUsageTableEntries(size_t number_of_entries) {
|
||||||
|
maximum_usage_table_entries_ = number_of_entries;
|
||||||
|
maximum_usage_table_entries_set_ = true;
|
||||||
|
}
|
||||||
|
void UnsetMaximumUsageTableEntries() {
|
||||||
|
maximum_usage_table_entries_set_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t maximum_usage_table_entries_ = 0;
|
||||||
|
bool maximum_usage_table_entries_set_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Partial mock of the UsageTableHeader. This is to test when dependency
|
// Partial mock of the UsageTableHeader. This is to test when dependency
|
||||||
@@ -618,6 +639,7 @@ TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveUsageInfo) {
|
|||||||
EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader,
|
EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader,
|
||||||
kEmptyUsageEntryInfoVector))
|
kEmptyUsageEntryInfoVector))
|
||||||
.WillOnce(Return(true));
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_));
|
EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,7 +677,7 @@ TEST_P(UsageTableHeaderInitializationTest,
|
|||||||
SetArgPointee<1>(k201UsageEntryInfoVector),
|
SetArgPointee<1>(k201UsageEntryInfoVector),
|
||||||
SetArgPointee<2>(false), Return(true)));
|
SetArgPointee<2>(false), Return(true)));
|
||||||
|
|
||||||
SecurityLevel security_level =
|
const SecurityLevel security_level =
|
||||||
(GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault;
|
(GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault;
|
||||||
EXPECT_CALL(*crypto_session_,
|
EXPECT_CALL(*crypto_session_,
|
||||||
Open(security_level)).WillOnce(Return(NO_ERROR));
|
Open(security_level)).WillOnce(Return(NO_ERROR));
|
||||||
@@ -672,7 +694,7 @@ TEST_P(UsageTableHeaderInitializationTest,
|
|||||||
.WillOnce(Return(true));
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
// Expectations for AddEntry
|
// Expectations for AddEntry
|
||||||
uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size();
|
const uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size();
|
||||||
EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull()))
|
EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull()))
|
||||||
.WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number),
|
.WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number),
|
||||||
Return(CREATE_USAGE_ENTRY_UNKNOWN_ERROR)));
|
Return(CREATE_USAGE_ENTRY_UNKNOWN_ERROR)));
|
||||||
@@ -703,7 +725,7 @@ TEST_P(UsageTableHeaderInitializationTest,
|
|||||||
.WillOnce(Return(true));
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
// Expectations for AddEntry
|
// Expectations for AddEntry
|
||||||
uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size();
|
const uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size();
|
||||||
EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull()))
|
EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull()))
|
||||||
.WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number),
|
.WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number),
|
||||||
Return(NO_ERROR)));
|
Return(NO_ERROR)));
|
||||||
@@ -712,7 +734,7 @@ TEST_P(UsageTableHeaderInitializationTest,
|
|||||||
.WillOnce(Return(true));
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
// Expectations for DeleteEntry
|
// Expectations for DeleteEntry
|
||||||
SecurityLevel security_level =
|
const SecurityLevel security_level =
|
||||||
(GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault;
|
(GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault;
|
||||||
EXPECT_CALL(*crypto_session_,
|
EXPECT_CALL(*crypto_session_,
|
||||||
Open(security_level))
|
Open(security_level))
|
||||||
@@ -740,7 +762,7 @@ TEST_P(UsageTableHeaderInitializationTest,
|
|||||||
.WillOnce(Return(NO_ERROR));
|
.WillOnce(Return(NO_ERROR));
|
||||||
|
|
||||||
// Expectations for AddEntry
|
// Expectations for AddEntry
|
||||||
uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size();
|
const uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size();
|
||||||
EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull()))
|
EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull()))
|
||||||
.WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number),
|
.WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number),
|
||||||
Return(NO_ERROR)));
|
Return(NO_ERROR)));
|
||||||
@@ -749,7 +771,7 @@ TEST_P(UsageTableHeaderInitializationTest,
|
|||||||
.WillOnce(Return(true));
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
// Expectations for DeleteEntry
|
// Expectations for DeleteEntry
|
||||||
SecurityLevel security_level =
|
const SecurityLevel security_level =
|
||||||
(GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault;
|
(GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault;
|
||||||
|
|
||||||
EXPECT_CALL(*crypto_session_,
|
EXPECT_CALL(*crypto_session_,
|
||||||
@@ -3467,4 +3489,37 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_LargeMixedSet) {
|
|||||||
UnorderedElementsAreArray(modified_offline_license_numbers));
|
UnorderedElementsAreArray(modified_offline_license_numbers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Unavailable) {
|
||||||
|
crypto_session_->UnsetMaximumUsageTableEntries();
|
||||||
|
Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector);
|
||||||
|
EXPECT_EQ(usage_table_header_->potential_table_capacity(),
|
||||||
|
kMinimumUsageTableEntriesSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Zero) {
|
||||||
|
// This will issue a warning about the reported capacity is unexpected,
|
||||||
|
// and will default to the version's required minimum.
|
||||||
|
crypto_session_->SetMaximumUsageTableEntries(0u);
|
||||||
|
Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector);
|
||||||
|
EXPECT_EQ(usage_table_header_->potential_table_capacity(),
|
||||||
|
kMinimumUsageTableEntriesSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UsageTableHeaderTest, PotentialTableCapacity_TooSmall) {
|
||||||
|
// This will issue a warning about the reported capacity is unexpected,
|
||||||
|
// and will default to the version's required minimum.
|
||||||
|
crypto_session_->SetMaximumUsageTableEntries(
|
||||||
|
kMinimumUsageTableEntriesSupported / 2);
|
||||||
|
Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector);
|
||||||
|
EXPECT_EQ(usage_table_header_->potential_table_capacity(),
|
||||||
|
kMinimumUsageTableEntriesSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Available) {
|
||||||
|
constexpr size_t kTableCapacity = 2000u;
|
||||||
|
crypto_session_->SetMaximumUsageTableEntries(kTableCapacity);
|
||||||
|
Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector);
|
||||||
|
EXPECT_EQ(usage_table_header_->potential_table_capacity(), kTableCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace wvcdm
|
} // namespace wvcdm
|
||||||
|
|||||||
Reference in New Issue
Block a user