diff --git a/libwvdrmengine/cdm/core/include/usage_table_header.h b/libwvdrmengine/cdm/core/include/usage_table_header.h index 78136f1b..931048a7 100644 --- a/libwvdrmengine/cdm/core/include/usage_table_header.h +++ b/libwvdrmengine/cdm/core/include/usage_table_header.h @@ -88,6 +88,8 @@ class UsageTableHeader { size_t size() { return usage_entry_info_.size(); } + size_t potential_table_capacity() const { return potential_table_capacity_; } + const std::vector& usage_entry_info() const { return usage_entry_info_; } @@ -209,6 +211,12 @@ class UsageTableHeader { // |clock_| variable, however, it can be overrided for testing purpose. 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) // Test related declarations friend class UsageTableHeaderTest; diff --git a/libwvdrmengine/cdm/core/src/usage_table_header.cpp b/libwvdrmengine/cdm/core/src/usage_table_header.cpp index 43e91629..74b5d08e 100644 --- a/libwvdrmengine/cdm/core/src/usage_table_header.cpp +++ b/libwvdrmengine/cdm/core/src/usage_table_header.cpp @@ -25,14 +25,14 @@ std::string kOldUsageEntryPoviderSessionToken = constexpr int64_t kDefaultExpireDuration = 33 * 24 * 60 * 60; // 33 Days // Number of elements to consider for removal using the LRU algorithm. constexpr size_t kLruRemovalSetSize = 3; -// Threshold for maximum 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 remove. -// This threshold is set to prevent thrashing in the case that there -// are a very large number of unexpired offline licenses and few -// expired / streaming licenses (ie, number of unexpired licenses nears -// the capacity of the usage table). -constexpr size_t kLruUnexpiredThreshold = 150; +// 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 +// remove. This threshold is set to prevent thrashing in the case that +// there are a very large number of unexpired offline licenses and few +// expired / streaming licenses (ie, number of unexpired licenses +// nears the capacity of the usage table). +constexpr double kLruUnexpiredThresholdFraction = 0.75; // Convert |license_message| -> SignedMessage -> License. bool ParseLicenseFromLicenseMessage(const CdmKeyResponse& license_message, @@ -162,6 +162,17 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, requested_security_level_ = 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)) { LOGE("Failed to initialize device files"); 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 // remove entries. If not, clear files/data and recreate usage header table. 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; // Create a new temporary usage entry, close the session and then @@ -886,8 +897,10 @@ bool UsageTableHeader::GetRemovalCandidates( std::vector* removal_candidates) { 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(), - kLruUnexpiredThreshold, kLruRemovalSetSize, + lru_unexpired_threshold, kLruRemovalSetSize, removal_candidates); } diff --git a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp index a9c65866..a31355c5 100644 --- a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp @@ -434,6 +434,27 @@ class MockCryptoSession : public TestCryptoSession { MOCK_METHOD1(MoveUsageEntry, CdmResponseType(uint32_t)); MOCK_METHOD2(ShrinkUsageTableHeader, 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 @@ -618,6 +639,7 @@ TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveUsageInfo) { EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, kEmptyUsageEntryInfoVector)) .WillOnce(Return(true)); + EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); } @@ -655,7 +677,7 @@ TEST_P(UsageTableHeaderInitializationTest, SetArgPointee<1>(k201UsageEntryInfoVector), SetArgPointee<2>(false), Return(true))); - SecurityLevel security_level = + const SecurityLevel security_level = (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; EXPECT_CALL(*crypto_session_, Open(security_level)).WillOnce(Return(NO_ERROR)); @@ -672,7 +694,7 @@ TEST_P(UsageTableHeaderInitializationTest, .WillOnce(Return(true)); // 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())) .WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(CREATE_USAGE_ENTRY_UNKNOWN_ERROR))); @@ -703,7 +725,7 @@ TEST_P(UsageTableHeaderInitializationTest, .WillOnce(Return(true)); // 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())) .WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); @@ -712,7 +734,7 @@ TEST_P(UsageTableHeaderInitializationTest, .WillOnce(Return(true)); // Expectations for DeleteEntry - SecurityLevel security_level = + const SecurityLevel security_level = (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; EXPECT_CALL(*crypto_session_, Open(security_level)) @@ -740,7 +762,7 @@ TEST_P(UsageTableHeaderInitializationTest, .WillOnce(Return(NO_ERROR)); // 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())) .WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); @@ -749,7 +771,7 @@ TEST_P(UsageTableHeaderInitializationTest, .WillOnce(Return(true)); // Expectations for DeleteEntry - SecurityLevel security_level = + const SecurityLevel security_level = (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; EXPECT_CALL(*crypto_session_, @@ -3467,4 +3489,37 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_LargeMixedSet) { 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