From 0c353d6851cfd71e05a22dac9d54d612826735d6 Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Thu, 1 Dec 2022 23:50:05 +0000 Subject: [PATCH] Revert "Core CDM: Remove secure stop from LRU algorithm." This reverts commit 7a34c1748cc9866b1cf65e922f4fcb1c9d60e781. Reason for revert: Feature rejected by Android Bug: 242289743 Change-Id: I63a730a6e5fa16d0d2425d216fd7f0fff8c09a4c --- .../cdm/core/include/usage_table_header.h | 5 +- .../cdm/core/src/usage_table_header.cpp | 159 ++++-- .../core/test/usage_table_header_unittest.cpp | 465 ++++++++++++------ 3 files changed, 427 insertions(+), 202 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/usage_table_header.h b/libwvdrmengine/cdm/core/include/usage_table_header.h index 269a9ab5..66aef9b3 100644 --- a/libwvdrmengine/cdm/core/include/usage_table_header.h +++ b/libwvdrmengine/cdm/core/include/usage_table_header.h @@ -261,9 +261,10 @@ class UsageTableHeader { int64_t GetCurrentTime() { return clock_ref_->GetCurrentTime(); } // Sets LRU related metrics based on the provided |staleness| (in - // seconds). + // seconds) and |storage_type| of the entry removed. void RecordLruEventMetrics(metrics::CryptoMetrics* metrics, - uint64_t staleness); + uint64_t staleness, + CdmUsageEntryStorageType storage_type); // Uses an LRU-base algorithm to determine which license should be // removed. This is intended to be used if the usage table is full diff --git a/libwvdrmengine/cdm/core/src/usage_table_header.cpp b/libwvdrmengine/cdm/core/src/usage_table_header.cpp index 39ff2954..8f9f775d 100644 --- a/libwvdrmengine/cdm/core/src/usage_table_header.cpp +++ b/libwvdrmengine/cdm/core/src/usage_table_header.cpp @@ -96,6 +96,41 @@ bool RetrieveOfflineLicense(DeviceFiles* device_files, return true; } +bool RetrieveUsageInfoLicense(DeviceFiles* device_files, + const std::string& usage_info_file_name, + const std::string& key_set_id, + CdmKeyResponse* license_message, + uint32_t* usage_entry_number) { + if (device_files == nullptr) { + LOGE("DeviceFiles handle is null"); + return false; + } + if (license_message == nullptr) { + LOGE("Output parameter |license_message| is null"); + return false; + } + if (usage_entry_number == nullptr) { + LOGE("Output parameter |usage_entry_number| is null"); + return false; + } + CdmUsageEntry usage_entry; + std::string provider_session_token; + CdmKeyMessage license_request; + std::string drm_certificate; + CryptoWrappedKey wrapped_private_key; + if (!device_files->RetrieveUsageInfoByKeySetId( + usage_info_file_name, key_set_id, &provider_session_token, + &license_request, license_message, &usage_entry, usage_entry_number, + &drm_certificate, &wrapped_private_key)) { + LOGW( + "Failed to retrieve usage information: " + "key_set_id = %s, usage_info_file_name = %s", + IdToString(key_set_id), IdToString(usage_info_file_name)); + return false; + } + return true; +} + bool EntryIsUsageInfo(const CdmUsageEntryInfo& info) { // Used for stl filters. return info.storage_type == kStorageUsageInfo; @@ -1128,6 +1163,7 @@ CdmResponseType UsageTableHeader::ReleaseOldestEntry( // Capture metric values now, as the |usage_entry_info| reference will // change after the call to invalidate. const int64_t staleness = current_time - usage_entry_info.last_use_time; + const CdmUsageEntryStorageType storage_type = usage_entry_info.storage_type; const CdmResponseType status = InvalidateEntryInternal(entry_number_to_delete, /* defrag_table = */ true, @@ -1140,7 +1176,7 @@ CdmResponseType UsageTableHeader::ReleaseOldestEntry( } // Record metrics on success. - RecordLruEventMetrics(metrics, staleness); + RecordLruEventMetrics(metrics, staleness, storage_type); return NO_ERROR; } @@ -1173,16 +1209,34 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { usage_entry_number < usage_entry_info_.size(); ++usage_entry_number) { CdmUsageEntryInfo& usage_entry_info = usage_entry_info_[usage_entry_number]; - if (usage_entry_info.storage_type != kStorageLicense) { - bad_license_file_entries.push_back(usage_entry_number); - continue; - } - uint32_t retrieved_entry_number; CdmKeyResponse license_message; - const bool retrieve_response = - RetrieveOfflineLicense(device_files_.get(), usage_entry_info.key_set_id, - &license_message, &retrieved_entry_number); + bool retrieve_response = false; + switch (usage_entry_info.storage_type) { + case kStorageLicense: { + retrieve_response = RetrieveOfflineLicense( + device_files_.get(), usage_entry_info.key_set_id, &license_message, + &retrieved_entry_number); + break; + } + case kStorageUsageInfo: { + retrieve_response = RetrieveUsageInfoLicense( + device_files_.get(), usage_entry_info.usage_info_file_name, + usage_entry_info.key_set_id, &license_message, + &retrieved_entry_number); + break; + } + case kStorageTypeUnknown: + bad_license_file_entries.push_back(usage_entry_number); + continue; + default: { + LOGW("Unknown usage entry storage type: %d, usage_entry_number = %u", + static_cast(usage_entry_info.storage_type), + usage_entry_number); + bad_license_file_entries.push_back(usage_entry_number); + continue; + } + } if (!retrieve_response) { LOGW("Could not retrieve license message: usage_entry_number = %u", usage_entry_number); @@ -1211,18 +1265,22 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { // for replacement above all others. usage_entry_info.last_use_time = license.license_start_time(); - // Only offline licenses need |offline_license_expiry_time| set. - const video_widevine::License::Policy& policy = license.policy(); - // TODO(b/139372190): Change how these fields are set once feature is - // implemented. - if (policy.license_duration_seconds() == 0) { - // Zero implies unlimited license duration. - usage_entry_info.offline_license_expiry_time = - license.license_start_time() + policy.rental_duration_seconds() + - policy.playback_duration_seconds(); + if (usage_entry_info.storage_type == kStorageLicense) { + // Only offline licenses need |offline_license_expiry_time| set. + const video_widevine::License::Policy& policy = license.policy(); + // TODO(b/139372190): Change how these fields are set once feature is + // implemented. + if (policy.license_duration_seconds() == 0) { + // Zero implies unlimited license duration. + usage_entry_info.offline_license_expiry_time = + license.license_start_time() + policy.rental_duration_seconds() + + policy.playback_duration_seconds(); + } else { + usage_entry_info.offline_license_expiry_time = + license.license_start_time() + policy.license_duration_seconds(); + } } else { - usage_entry_info.offline_license_expiry_time = - license.license_start_time() + policy.license_duration_seconds(); + usage_entry_info.offline_license_expiry_time = 0; } } // End for loop. @@ -1232,14 +1290,31 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { return false; } + // Maps -> []. + std::map> usage_info_clean_up; for (size_t usage_entry_number : bad_license_file_entries) { CdmUsageEntryInfo& usage_entry_info = usage_entry_info_[usage_entry_number]; if (usage_entry_info.storage_type == kStorageLicense) { device_files_->DeleteLicense(usage_entry_info.key_set_id); - } + } else if (usage_entry_info.storage_type == kStorageUsageInfo) { + // To reduce write cycles, the deletion of usage info will be done + // in bulk. + auto it = usage_info_clean_up.find(usage_entry_info.usage_info_file_name); + if (it == usage_info_clean_up.end()) { + it = usage_info_clean_up + .emplace(usage_entry_info.usage_info_file_name, + std::vector()) + .first; + } + it->second.push_back(usage_entry_info.key_set_id); + } // else kStorageUnknown { Nothing special }. usage_entry_info.Clear(); } + for (const auto& p : usage_info_clean_up) { + device_files_->DeleteMultipleUsageInfoByKeySetIds(p.first, p.second); + } + return true; } @@ -1252,18 +1327,16 @@ bool UsageTableHeader::GetRemovalCandidate(uint32_t* entry_to_remove) { lru_unexpired_threshold, entry_to_remove); } -void UsageTableHeader::RecordLruEventMetrics(metrics::CryptoMetrics* metrics, - uint64_t staleness) { +void UsageTableHeader::RecordLruEventMetrics( + metrics::CryptoMetrics* metrics, uint64_t staleness, + CdmUsageEntryStorageType storage_type) { if (metrics == nullptr) return; - // Usage info are deprecated, always record 0. - metrics->usage_table_header_lru_usage_info_count_.Record(0); + metrics->usage_table_header_lru_usage_info_count_.Record(UsageInfoCount()); metrics->usage_table_header_lru_offline_license_count_.Record( OfflineEntryCount()); metrics->usage_table_header_lru_evicted_entry_staleness_.Record(staleness); - // Can be assumed that only offline licenses are removed. - constexpr int kDefaultEntryType = 0; metrics->usage_table_header_lru_evicted_entry_type_.Record( - static_cast(kDefaultEntryType)); + static_cast(storage_type)); } // Static. @@ -1286,13 +1359,14 @@ bool UsageTableHeader::DetermineLicenseToRemove( usage_entry_info_list[j].last_use_time; }; - // Find the most stale expired offline license and the + // 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 not of storage type license, then it should + // entries. If any entry is of storage type unknown, then it should // be removed. constexpr uint32_t kNoEntry = std::numeric_limits::max(); uint32_t stalest_expired_offline_license = kNoEntry; uint32_t stalest_unexpired_offline_license = kNoEntry; + uint32_t stalest_streaming_license = kNoEntry; size_t unexpired_license_count = 0; for (uint32_t entry_number = 0; entry_number < usage_entry_info_list.size(); @@ -1300,28 +1374,37 @@ bool UsageTableHeader::DetermineLicenseToRemove( const CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[entry_number]; - if (usage_entry_info.storage_type != kStorageLicense) { - // Non-license storage type entries. Remove this entry. + 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.offline_license_expiry_time > current_time) { + 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 { + } 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; + } } } 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( @@ -1341,10 +1424,12 @@ bool UsageTableHeader::DetermineLicenseToRemove( // Only consider an unexpired entry if the threshold is reached. if (unexpired_license_count > unexpired_threshold) { - *entry_to_remove = select_most_stale(stalest_unexpired_offline_license, - stalest_expired_offline_license); + 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 = stalest_expired_offline_license; + *entry_to_remove = select_most_stale(stalest_streaming_license, + stalest_expired_offline_license); } if (*entry_to_remove == kNoEntry) { diff --git a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp index fccce60e..a1070b48 100644 --- a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp @@ -229,9 +229,9 @@ const CdmKeyResponse kKeyRenewalResponse = "key renewal response"; const std::string kReleaseServerUrl = "some url"; const std::string kProviderSessionToken = "provider session token"; const CdmAppParameterMap kEmptyAppParameters; -constexpr int64_t kPlaybackStartTime = 1030005; -constexpr int64_t kPlaybackDuration = 300; -constexpr int64_t kGracePeriodEndTime = 60; +int64_t kPlaybackStartTime = 1030005; +int64_t kPlaybackDuration = 300; +int64_t kGracePeriodEndTime = 60; // ==== LRU Upgrade Data ==== const CdmUsageTableHeader kUpgradableUsageTableHeader = @@ -245,9 +245,9 @@ const CdmUsageEntryInfo kUpgradableUsageEntryInfo1 = { /* last_use_time = */ 0, /* offline_license_expiry_time = */ 0}; const CdmUsageEntryInfo kUpgradableUsageEntryInfo2 = { - /* storage_type = */ kStorageLicense, - /* key_set_id = */ "offline_key_set_2", - /* usage_info_file_name = */ "", + /* storage_type = */ kStorageUsageInfo, + /* key_set_id = */ "streaming_key_set_2", + /* usage_info_file_name = */ "streaming_license_file_2", /* last_use_time = */ 0, /* offline_license_expiry_time = */ 0}; const CdmUsageEntryInfo kUpgradableUsageEntryInfo3 = { @@ -256,11 +256,7 @@ const CdmUsageEntryInfo kUpgradableUsageEntryInfo3 = { /* usage_info_file_name = */ "", /* last_use_time = */ 0, /* offline_license_expiry_time = */ 0}; -const std::vector kUpgradableUsageEntryInfoList{ - kUpgradableUsageEntryInfo1, kUpgradableUsageEntryInfo2, - kUpgradableUsageEntryInfo3}; - -const int64_t kLruBaseTime = 1563399000; +std::vector kUpgradableUsageEntryInfoList; // Offline license 1. // license_start_time = 1563399000 @@ -270,14 +266,11 @@ const int64_t kLruBaseTime = 1563399000; const CdmKeyResponse kUpgradableLicenseInfo1 = wvutil::a2bs_hex( "08021214120C2080F5242880A3053080E90F20D8A6BEE9051A20D5F7ACE8D84A166C69BB" "27523C84C019464B90AA9BF06B8332004839119BFD14"); -// Offline license 2. +// Streaming license 2. // license_start_time = 1563399000 -// license_duration_seconds = 259200 (3 days) -// rental_duration_seconds = 0 (unlimited) -// playback_duration_seconds = 0 (unlimited) const CdmKeyResponse kUpgradableLicenseInfo2 = wvutil::a2bs_hex( - "080212101208200028003080E90F20D8A6BEE9051A20D0611C4AFEF3EC9C67143ED39B44" - "8FAAA67DB6C4DBFDC28A733AF9A2EABDF0B3"); + "0802120620D8A6BEE9051A201956F2FD69E5E96DA8C65FDD04A3C294E484F219F2B1A8DD" + "C2B0737F6EF5BD22"); // Offline license 3. // license_start_time = 1563399000 // license_duration_seconds = 0 (unlimited) @@ -286,56 +279,26 @@ const CdmKeyResponse kUpgradableLicenseInfo2 = wvutil::a2bs_hex( const CdmKeyResponse kUpgradableLicenseInfo3 = wvutil::a2bs_hex( "08021212120A2080F5242880A305300020D8A6BEE9051A207B09896F46C4EE443170E215" "B2D8D5F072951027B152F4758AC3A339D7C7B4CE"); -const std::vector kUpgradableLicenseInfoList = { - kUpgradableLicenseInfo1, kUpgradableLicenseInfo2, kUpgradableLicenseInfo3}; - +std::vector kUpgradableLicenseInfoList; +std::vector kUpgradableLicenseDataList; // Same as Offline license 1, but without signature. const CdmKeyResponse kUnsignedUpgradableLicenseInfo1 = wvutil::a2bs_hex("08021214120C2080F5242880A3053080E90F20D8A6BEE905"); +// Same as streaming license 2, but message type is certificate, not license. +const CdmKeyResponse kWrongTypedUpgradableLicenseInfo2 = wvutil::a2bs_hex( + "0805120620D8A6BEE9051A201956F2FD69E5E96DA8C65FDD04A3C294E484F219F2B1A8DD" + "C2B0737F6EF5BD22"); +const int64_t kLruBaseTime = 1563399000; const int64_t kUpgradedUsageEntryInfo1LastUsedTime = kLruBaseTime; const int64_t kUpgradedUsageEntryInfo1ExpireTime = kLruBaseTime + 259200; const int64_t kUpgradedUsageEntryInfo2LastUsedTime = kLruBaseTime; -const int64_t kUpgradedUsageEntryInfo2ExpireTime = kLruBaseTime + 259200; +const int64_t kUpgradedUsageEntryInfo2ExpireTime = 0; // Unset const int64_t kUpgradedUsageEntryInfo3LastUsedTime = kLruBaseTime; const int64_t kUpgradedUsageEntryInfo3ExpireTime = kLruBaseTime + 604800 + 86400; const CdmUsageTableHeader kUpgradedUsageTableHeader = "Upgraded Table Header"; -const CdmUsageEntryInfo kUpgradedUsageEntryInfo1 = { - /* storage_type = */ kStorageLicense, - /* key_set_id = */ "offline_key_set_1", - /* usage_info_file_name = */ "", - /* last_use_time = */ kUpgradedUsageEntryInfo1LastUsedTime, - /* offline_license_expiry_time = */ kUpgradedUsageEntryInfo1ExpireTime}; -const CdmUsageEntryInfo kUpgradedUsageEntryInfo2 = { - /* storage_type = */ kStorageLicense, - /* key_set_id = */ "offline_key_set_2", - /* usage_info_file_name = */ "", - /* last_use_time = */ kUpgradedUsageEntryInfo2LastUsedTime, - /* offline_license_expiry_time = */ kUpgradedUsageEntryInfo2ExpireTime}; -const CdmUsageEntryInfo kUpgradedUsageEntryInfo3 = { - /* storage_type = */ kStorageLicense, - /* key_set_id = */ "offline_key_set_3", - /* usage_info_file_name = */ "", - /* last_use_time = */ kUpgradedUsageEntryInfo3LastUsedTime, - /* offline_license_expiry_time = */ kUpgradedUsageEntryInfo3ExpireTime}; - -const std::vector kUpgradedUsageEntryInfoList = { - kUpgradedUsageEntryInfo1, kUpgradedUsageEntryInfo2, - kUpgradedUsageEntryInfo3}; - -std::vector CreateUpgradableLicenseInfoList() { - std::vector license_data_list; - for (size_t i = 0; i < kUpgradableLicenseInfoList.size(); ++i) { - DeviceFiles::CdmLicenseData license_data; - license_data.key_set_id = kUpgradableUsageEntryInfoList[i].key_set_id; - license_data.state = kActiveLicenseState; - license_data.license = kUpgradableLicenseInfoList[i]; - license_data.usage_entry_number = static_cast(i); - license_data_list.push_back(license_data); - } - return license_data_list; -} +std::vector kUpgradedUsageEntryInfoList; void InitVectorConstants() { kOverFullUsageEntryInfoVector.clear(); @@ -369,6 +332,40 @@ void InitVectorConstants() { for (size_t i = 0; i < kLicenseArraySize; i++) { kLicenseList.push_back(kLicenseArray[i]); } + + // LRU Data. + kUpgradableUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo1); + kUpgradableUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo2); + kUpgradableUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo3); + + kUpgradableLicenseInfoList.push_back(kUpgradableLicenseInfo1); + kUpgradableLicenseInfoList.push_back(kUpgradableLicenseInfo2); + kUpgradableLicenseInfoList.push_back(kUpgradableLicenseInfo3); + + kUpgradedUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo1); + kUpgradedUsageEntryInfoList.back().last_use_time = + kUpgradedUsageEntryInfo1LastUsedTime; + kUpgradedUsageEntryInfoList.back().offline_license_expiry_time = + kUpgradedUsageEntryInfo1ExpireTime; + kUpgradedUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo2); + kUpgradedUsageEntryInfoList.back().last_use_time = + kUpgradedUsageEntryInfo2LastUsedTime; + kUpgradedUsageEntryInfoList.back().offline_license_expiry_time = + kUpgradedUsageEntryInfo2ExpireTime; + kUpgradedUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo3); + kUpgradedUsageEntryInfoList.back().last_use_time = + kUpgradedUsageEntryInfo3LastUsedTime; + kUpgradedUsageEntryInfoList.back().offline_license_expiry_time = + kUpgradedUsageEntryInfo3ExpireTime; + + for (size_t i = 0; i < kUpgradableLicenseInfoList.size(); ++i) { + DeviceFiles::CdmLicenseData license_data; + license_data.key_set_id = kUpgradableUsageEntryInfoList[i].key_set_id; + license_data.state = kActiveLicenseState; + license_data.license = kUpgradableLicenseInfoList[i]; + license_data.usage_entry_number = static_cast(i); + kUpgradableLicenseDataList.push_back(license_data); + } } void ToVector(std::vector& vec, const CdmUsageEntryInfo* arr, @@ -405,6 +402,9 @@ class MockDeviceFiles : public DeviceFiles { (override)); MOCK_METHOD(bool, DeleteUsageInfo, (const std::string&, const std::string&), (override)); + MOCK_METHOD(bool, DeleteMultipleUsageInfoByKeySetIds, + (const std::string&, const std::vector&), + (override)); MOCK_METHOD(bool, RetrieveUsageInfoByKeySetId, (const std::string&, const std::string&, std::string*, CdmKeyMessage*, CdmKeyResponse*, CdmUsageEntry*, uint32_t*, @@ -3728,6 +3728,9 @@ TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_NoAction) { // These function are called specifically by the LRU upgrading system. EXPECT_CALL(*device_files_, RetrieveLicense(_, _, _)).Times(0); + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId(_, _, _, _, _, _, _, _, _)) + .Times(0); EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); EXPECT_EQ(usage_table_header_->usage_entry_info(), @@ -3737,8 +3740,8 @@ TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_NoAction) { // Initial Test state: // 1. Table info is load from device files; however, device files reports // that the table has not been configured for upgrade. -// 2. The usage table header will load offline license to determine -// appropriate expiry and last_used times. +// 2. The usage table header will load license or usage information to +// determine appropriate expiry and last_used times. TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_Succeed) { EXPECT_CALL(*crypto_session_, GetNumberOfOpenSessions(kLevelDefault, NotNull())) @@ -3753,18 +3756,24 @@ TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_Succeed) { LoadUsageTableHeader(kLevelDefault, kUpgradableUsageTableHeader)) .WillOnce(Return(NO_ERROR)); - const std::vector license_info_list = - CreateUpgradableLicenseInfoList(); - for (size_t i = 0; i < kUpgradableUsageEntryInfoList.size(); ++i) { const CdmUsageEntryInfo& info = kUpgradableUsageEntryInfoList[i]; if (info.storage_type == kStorageLicense) { - // Only offline licenses are supported. EXPECT_CALL(*device_files_, RetrieveLicense(info.key_set_id, NotNull(), NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(license_info_list[i]), Return(true))); + .WillOnce(DoAll(SetArgPointee<1>(kUpgradableLicenseDataList[i]), + Return(true))); + } else { + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId(info.usage_info_file_name, + info.key_set_id, NotNull(), + NotNull(), NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<4>(kUpgradableLicenseInfoList[i]), + SetArgPointee<6>(static_cast(i)), + SetArgPointee<7>(kDrmCertificate), + SetArgPointee<8>(kCryptoWrappedKey), Return(true))); } } @@ -3774,22 +3783,33 @@ TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_Succeed) { } // Initial Test state: -// Table info is load from device files and must be upgraded. -// A few entries have the unknown storage type value. +// 1. Table info is load from device files and must be upgraded. +// A few entries have the incorrect storage type value. +// 2. During the LRU upgrade process, any license that cannot be +// validated are cleared, and marked to be deleted. // // Entry# Storage type Result info // ====== ============ =========== // 0 Offline Upgraded // 1 Unknown Cleared -// 2 Unknown Cleared +// 2 Invalid (99) Cleared TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_PartialSucceedWithUnknownStorageTypes) { - const std::vector upgradable_usage_entry_info_list = { - kUpgradableUsageEntryInfo1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown}; - const std::vector upgraded_usage_entry_info_list = { - kUpgradedUsageEntryInfo1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown}; + std::vector upgradable_usage_entry_info_list = + kUpgradableUsageEntryInfoList; + std::vector upgraded_usage_entry_info_list = + kUpgradedUsageEntryInfoList; + + // Set the wrong storage type. + upgradable_usage_entry_info_list[1].storage_type = kStorageTypeUnknown; + upgradable_usage_entry_info_list[2].storage_type = + static_cast(99); + + // Set the expected output. + upgraded_usage_entry_info_list[1] = CdmUsageEntryInfo{}; + upgraded_usage_entry_info_list[1].storage_type = kStorageTypeUnknown; + upgraded_usage_entry_info_list[2] = CdmUsageEntryInfo{}; + upgraded_usage_entry_info_list[2].storage_type = kStorageTypeUnknown; // Load table expectations. EXPECT_CALL(*crypto_session_, @@ -3806,12 +3826,11 @@ TEST_F(UsageTableHeaderTest, .WillOnce(Return(NO_ERROR)); // Expectations of the one successful license. - const std::vector license_info_list = - CreateUpgradableLicenseInfoList(); EXPECT_CALL(*device_files_, RetrieveLicense(upgradable_usage_entry_info_list[0].key_set_id, NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<1>(license_info_list[0]), Return(true))); + .WillOnce( + DoAll(SetArgPointee<1>(kUpgradableLicenseDataList[0]), Return(true))); EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); EXPECT_EQ(upgraded_usage_entry_info_list, @@ -3827,14 +3846,12 @@ TEST_F(UsageTableHeaderTest, // Entry# Storage type License Issue Result info // ====== ============ ============= =========== // 0 Offline No signature Cleared -// 1 Unknown Clear entry Cleared +// 1 Streaming Wrong type Cleared // 2 Offline Upgraded TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_PartialSucceedWithLicenseParseIssues) { - std::vector upgradable_usage_entry_info_list = - kUpgradableUsageEntryInfoList; std::vector license_data_list = - CreateUpgradableLicenseInfoList(); + kUpgradableLicenseDataList; std::vector upgradable_license_info_list = kUpgradableLicenseInfoList; std::vector upgraded_usage_entry_info_list = @@ -3842,12 +3859,21 @@ TEST_F(UsageTableHeaderTest, // Modify entry 0 to be invalid. license_data_list[0].license = kUnsignedUpgradableLicenseInfo1; - upgraded_usage_entry_info_list[0].Clear(); + CdmUsageEntryInfo& unsigned_usage_entry_info = + upgraded_usage_entry_info_list[0]; + unsigned_usage_entry_info.storage_type = kStorageTypeUnknown; + unsigned_usage_entry_info.key_set_id.clear(); + unsigned_usage_entry_info.last_use_time = 0; + unsigned_usage_entry_info.offline_license_expiry_time = 0; - // Modify entry 1 to be clear. - upgradable_usage_entry_info_list[1].Clear(); - upgradable_license_info_list[1].clear(); - upgraded_usage_entry_info_list[1].Clear(); + // Modify entry 1 to be invalid. + upgradable_license_info_list[1] = kWrongTypedUpgradableLicenseInfo2; + CdmUsageEntryInfo& wrond_typed_usage_entry_info = + upgraded_usage_entry_info_list[1]; + wrond_typed_usage_entry_info.storage_type = kStorageTypeUnknown; + wrond_typed_usage_entry_info.key_set_id.clear(); + wrond_typed_usage_entry_info.last_use_time = 0; + wrond_typed_usage_entry_info.offline_license_expiry_time = 0; EXPECT_CALL(*crypto_session_, GetNumberOfOpenSessions(kLevelDefault, NotNull())) @@ -3855,27 +3881,46 @@ TEST_F(UsageTableHeaderTest, EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUpgradableUsageTableHeader), - SetArgPointee<1>(upgradable_usage_entry_info_list), + SetArgPointee<1>(kUpgradableUsageEntryInfoList), SetArgPointee<2>(/* lru_upgrade = */ true), SetArgPointee<3>(false), Return(true))); EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kLevelDefault, kUpgradableUsageTableHeader)) .WillOnce(Return(NO_ERROR)); - for (size_t i = 0; i < upgradable_usage_entry_info_list.size(); ++i) { - const CdmUsageEntryInfo& info = upgradable_usage_entry_info_list[i]; + for (size_t i = 0; i < kUpgradableUsageEntryInfoList.size(); ++i) { + const CdmUsageEntryInfo& info = kUpgradableUsageEntryInfoList[i]; if (info.storage_type == kStorageLicense) { EXPECT_CALL(*device_files_, RetrieveLicense(info.key_set_id, NotNull(), NotNull())) .WillOnce( DoAll(SetArgPointee<1>(license_data_list[i]), Return(true))); + } else { + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId(info.usage_info_file_name, + info.key_set_id, NotNull(), + NotNull(), NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<4>(upgradable_license_info_list[i]), + SetArgPointee<6>(static_cast(i)), + SetArgPointee<7>(kDrmCertificate), + SetArgPointee<8>(kCryptoWrappedKey), Return(true))); } } // For the entries which failed, there should be an attempt to delete the // files associated with them. + // Offline license. EXPECT_CALL(*device_files_, - DeleteLicense(upgradable_usage_entry_info_list[0].key_set_id)) + DeleteLicense(kUpgradableUsageEntryInfoList[0].key_set_id)) + .WillOnce(Return(true)); + // Streaming license. + const std::vector corrupted_usage_info_key_set_ids = { + kUpgradableUsageEntryInfoList[1].key_set_id}; + EXPECT_CALL(*device_files_, + DeleteMultipleUsageInfoByKeySetIds( + kUpgradableUsageEntryInfoList[1].usage_info_file_name, + ContainerEq(corrupted_usage_info_key_set_ids))) .WillOnce(Return(true)); EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); @@ -3908,6 +3953,13 @@ TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_AllFailure) { EXPECT_CALL(*device_files_, RetrieveLicense(info.key_set_id, NotNull(), NotNull())) .WillOnce(Return(false)); + } else { + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId(info.usage_info_file_name, + info.key_set_id, NotNull(), + NotNull(), NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .WillOnce(Return(false)); } } @@ -3955,7 +4007,46 @@ TEST_F(UsageTableHeaderTest, LruLastUsedTime_CreateLicenseEntry) { EXPECT_EQ(NO_ERROR, usage_table_header_->AddEntry( crypto_session_, true /* persistent_license */, expected_new_entry.key_set_id, - /* usage_info_file_name */ kEmptyString, kEmptyString, + expected_new_entry.usage_info_file_name, kEmptyString, + &usage_entry_number)); + + EXPECT_EQ(expected_usage_entry_number, usage_entry_number); + EXPECT_EQ(expected_usage_info_list, usage_table_header_->usage_entry_info()); +} + +TEST_F(UsageTableHeaderTest, LruLastUsedTime_CreateUsageInfoEntry) { + Init(kSecurityLevelL1, kUpgradedUsageTableHeader, + kUpgradedUsageEntryInfoList); + + // Expected values. + const uint32_t expected_usage_entry_number = + static_cast(kUpgradedUsageEntryInfoList.size()); + const CdmUsageEntryInfo expected_new_entry = { + kStorageUsageInfo, "secure_stop_key_set_5", "streaming_license_file_4", + kLruBaseTime, 0 /* No set for streaming license. */ + }; + std::vector expected_usage_info_list = + kUpgradedUsageEntryInfoList; + expected_usage_info_list.push_back(expected_new_entry); + + // AddKey expectations + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(expected_usage_entry_number), + Return(NO_ERROR))); + MockClock mock_clock; + usage_table_header_->SetClock(&mock_clock); + EXPECT_CALL(mock_clock, GetCurrentTime()).WillOnce(Return(kLruBaseTime)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)); + + // The Call. + uint32_t usage_entry_number = 0; + EXPECT_EQ(NO_ERROR, usage_table_header_->AddEntry( + crypto_session_, false /* persistent_license */, + expected_new_entry.key_set_id, + expected_new_entry.usage_info_file_name, kEmptyString, &usage_entry_number)); EXPECT_EQ(expected_usage_entry_number, usage_entry_number); @@ -4060,7 +4151,6 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) { // Unexpired offline license. CdmUsageEntryInfo unexpired_entry_info; unexpired_entry_info.storage_type = kStorageLicense; - unexpired_entry_info.key_set_id = "unexpired_key_set_id"; unexpired_entry_info.last_use_time = kLruBaseTime; unexpired_entry_info.offline_license_expiry_time = kLruBaseTime + 2 * kOneDay; usage_entry_info_list.push_back(unexpired_entry_info); @@ -4069,43 +4159,59 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) { // Expired offline license. CdmUsageEntryInfo expired_entry_info; expired_entry_info.storage_type = kStorageLicense; - expired_entry_info.key_set_id = "expired_key_set_id"; expired_entry_info.last_use_time = kLruBaseTime; expired_entry_info.offline_license_expiry_time = kLruBaseTime; usage_entry_info_list.push_back(expired_entry_info); constexpr uint32_t expired_entry_number = 1; + // Streaming license. + CdmUsageEntryInfo streaming_entry_info; + streaming_entry_info.storage_type = kStorageUsageInfo; + streaming_entry_info.last_use_time = kLruBaseTime; + usage_entry_info_list.push_back(streaming_entry_info); + constexpr uint32_t streaming_entry_number = 2; + // Unknown entry. CdmUsageEntryInfo unknown_entry_info; unknown_entry_info.storage_type = kStorageTypeUnknown; // Should be chosen regardless of |last_use_time|. unknown_entry_info.last_use_time = kCurrentTime; usage_entry_info_list.push_back(unknown_entry_info); - constexpr uint32_t unknown_entry_number = 2; + constexpr uint32_t unknown_entry_number = 3; - // Case 1: If there is a clear entry, it should be selected above - // any other entry. + // 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 clear. + // Expect the unknown entry. EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, /* unexpired_threshold = */ 3, &entry_to_remove)); EXPECT_EQ(unknown_entry_number, entry_to_remove); - usage_entry_info_list.pop_back(); // Removing clear entry. - // |usage_entry_info_list| only contains 1 expired and 1 unexpired offline - // license. + usage_entry_info_list.pop_back(); // Removing unknown. // Case 2a: Threshold not met, all entries are equally stale. - // The expired entry should be selected over the unexpired entry. + // 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, &entry_to_remove)); EXPECT_EQ(expired_entry_number, entry_to_remove); - // Case 2b: Threshold met, equally stale entries. Expect the expired + // 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 = */ 3, &entry_to_remove)); + EXPECT_EQ(streaming_entry_number, entry_to_remove); + + usage_entry_info_list.pop_back(); // Removing streaming. + // |usage_entry_info_list| only contains 1 expired and 1 unexpired offline + // license. + + // Case 2c: Threshold met, equally stale entries. Expect the expired // entry over the unexpired. entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( @@ -4167,8 +4273,45 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) { EXPECT_EQ(unexpired_entry_number, entry_to_remove); } +// Testing algorithm with unexpired offline and streaming license. The +// sum of offline licenses is below the threshold for consideration. +// Only the streaming license should be considered for removal. +TEST_F(UsageTableHeaderTest, + DetermineLicenseToRemove_NoExpiredAndBelowThreshold) { + constexpr int64_t kOneDay = 24 * 60 * 60; + constexpr uint32_t kInvalidEntry = 9999; + std::vector usage_entry_info_list = + kUpgradedUsageEntryInfoList; + const size_t offline_threshold = usage_entry_info_list.size() + 1; + + size_t usage_info_count = 0; + for (auto& usage_entry_info : usage_entry_info_list) { + if (usage_entry_info.storage_type == kStorageUsageInfo) { + // Make usage info entries less stale than offline. + usage_entry_info.last_use_time += 100; + ++usage_info_count; + } else { + // Make offline licenses unexpired. + usage_entry_info.offline_license_expiry_time = + kLruBaseTime + kOneDay * 30; + } + } + + // Must exist at least one streaming license for test to work. + ASSERT_LT(0ull, usage_info_count); + + uint32_t entry_to_remove = kInvalidEntry; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kLruBaseTime, offline_threshold, + &entry_to_remove)); + + EXPECT_EQ(kStorageUsageInfo, + usage_entry_info_list[entry_to_remove].storage_type); +} + // When the number of unexpired offline licenses are below the -// threshold, only expired offline licenses should be considered. +// threshold, only streaming licenses and expired offline licenses +// should be considered. // Providing only offline licenses, only the expired license should be // considered for removal. TEST_F(UsageTableHeaderTest, @@ -4187,20 +4330,20 @@ TEST_F(UsageTableHeaderTest, for (uint32_t i = 0; i < kSetSize; ++i) { CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; usage_entry_info.storage_type = kStorageLicense; - usage_entry_info.key_set_id = "unexpired_offline"; + usage_entry_info.key_set_id = "nothing_unusual"; usage_entry_info.last_use_time = kLruBaseTime; usage_entry_info.offline_license_expiry_time = kCurrentTime + kDefaultExpireDuration; } - // Mark three licenses as expired. + // Mark 3 as expired. std::vector expired_license_numbers; while (expired_license_numbers.size() < 3) { const uint32_t i = static_cast(wvutil::CdmRandom::RandomInRange(kSetSize - 1)); CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; // Skip already expired ones - if (usage_entry_info.key_set_id != "unexpired_offline") continue; + if (usage_entry_info.key_set_id != "nothing_unusual") continue; // Make these less stale than the unexpired licenses. usage_entry_info.last_use_time = kLruBaseTime + kOneDay; usage_entry_info.offline_license_expiry_time = kCurrentTime - kOneDay; @@ -4216,31 +4359,16 @@ TEST_F(UsageTableHeaderTest, EXPECT_THAT(expired_license_numbers, Contains(entry_to_remove)); } -// This primarily tests the robustness of the algorithm for a full -// set of entries (200). Creates 1 offline license which are more -// stale than the all other licenses. +// This test primarily tests the robustness of the algorithm for a full +// set of entries (200). Creates 1 stale streaming license and 1 +// offline licenses which are more stale than the streaming. // -// All cases: 1 unexpired license is most stale. +// First, with the stale offline licenses unexpired, checks that the +// streaming are always selected, so long as |unexpired_threshold| +// is not met. // -// Case 1: -// - Unexpired threshold met: No -// - Exists expired license: No -// - Expect most stale license to be selected -// -// Case 2: -// - Unexpired threshold met: Yes -// - Exists expired license: No -// - Expect most stale license to be selected -// -// Case 3: -// - Unexpired threshold met: No -// - Exists expired license: Yes -// - Expect expired license to be selected -// -// Case 4: -// - Unexpired threshold met: Yes -// - Exists expired license: Yes -// - Expect most stale license to be selected +// Second, with the stale offline license marked as expired, checks that +// offline licenses are selected over the streaming. TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_LargeMixedSet) { constexpr int64_t kOneDay = 24 * 60 * 60; constexpr size_t kLargeSetSize = 200; @@ -4249,63 +4377,74 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_LargeMixedSet) { std::vector usage_entry_info_list; usage_entry_info_list.resize(kLargeSetSize); - // Create a set of unexpired license entries. + // Create a set of usage entries. for (uint32_t i = 0; i < kLargeSetSize; ++i) { CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - usage_entry_info.storage_type = kStorageLicense; - usage_entry_info.key_set_id = "unexpired_offline"; + usage_entry_info.key_set_id = "nothing_unusual"; usage_entry_info.last_use_time = kLruBaseTime + kOneDay; - usage_entry_info.offline_license_expiry_time = - kCurrentTime + kDefaultExpireDuration; + if ((i % 4) == 0) { + // Roughly 25% are offline, the rest are streaming. + usage_entry_info.storage_type = kStorageLicense; + usage_entry_info.offline_license_expiry_time = + kCurrentTime + kDefaultExpireDuration; + } else { + usage_entry_info.storage_type = kStorageUsageInfo; + } + } + + // 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 = static_cast( + wvutil::CdmRandom::RandomInRange(kLargeSetSize - 1)); + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + // 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_number = i; } // Select a offline license to be even more stale, but unexpired. - const uint32_t most_stale_offline_license_number = static_cast( - wvutil::CdmRandom::RandomInRange(kLargeSetSize - 1)); - CdmUsageEntryInfo& usage_entry_info = - usage_entry_info_list[most_stale_offline_license_number]; - usage_entry_info.last_use_time = kLruBaseTime; - usage_entry_info.key_set_id = "stale_offline"; + uint32_t modified_offline_license_number = kInvalidEntry; + while (modified_offline_license_number == kInvalidEntry) { + const uint32_t i = static_cast( + wvutil::CdmRandom::RandomInRange(kLargeSetSize - 1)); + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + // 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_number = i; + } - // Case 1: Most stale license is selected. + // Test using only streaming and expired offline licenses + // (which there are none). uint32_t entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, /* unexpired_threshold = */ kLargeSetSize, &entry_to_remove)); - EXPECT_EQ(most_stale_offline_license_number, entry_to_remove); + EXPECT_EQ(modified_usage_info_number, entry_to_remove); - // Case 2: Most stale license is selected. + // 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, &entry_to_remove)); - EXPECT_EQ(most_stale_offline_license_number, entry_to_remove); + EXPECT_EQ(modified_offline_license_number, entry_to_remove); - // Select a offline license to be marked as expired but not the most - // stale. - uint32_t expired_offline_license_number = kInvalidEntry; - while (expired_offline_license_number == kInvalidEntry) { - const uint32_t i = static_cast( - wvutil::CdmRandom::RandomInRange(kLargeSetSize - 1)); - // Don't modify the stale offline license. - if (usage_entry_info_list[i].key_set_id != "unexpired_offline") continue; - usage_entry_info_list[i].offline_license_expiry_time = kLruBaseTime; - expired_offline_license_number = i; - } + // 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; - // Case 3: Expired license is selected. + // Test again, expecting that the expired license should be considered. entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, /* unexpired_threshold = */ kLargeSetSize, &entry_to_remove)); - EXPECT_EQ(expired_offline_license_number, entry_to_remove); - - // Case 4: Most stale license is selected. - entry_to_remove = kInvalidEntry; - EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 0, &entry_to_remove)); - EXPECT_EQ(most_stale_offline_license_number, entry_to_remove); + EXPECT_EQ(modified_offline_license_number, entry_to_remove); } TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Unavailable) {