From da48461ba2c51698a231af7b99c3dbe6a406d48a Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Mon, 9 Mar 2020 10:58:06 -0700 Subject: [PATCH] Reworked DeleteEntry() into InvalidateEntry(). [ Merge of http://go/wvgerrit/95406 ] There was an issue with DeleteEntry() where it would result in an invalid table state if shrinking the usage table when the number of sessions is at its max. This required changing how the usage table invalidates entries. Now, after invalidating an entry (marking an entry as kStorageTypeUnknown) the table is defragmented if specified to. Defragmentation involves: 1) Move valid entries near the end of the table to the position of invalid entries near the front of the table. 2) Shrinking the table to cut off trailing invalid entries. This change updates the existing tests to pass, but still needs new tests for some of the edge cases. Bug: 150887808 Bug: 149100568 Test: Linux unit tests and Android unit tests Change-Id: I70c7b296e5e4b367746fcdaabbf0f12dcfb39230 --- .../cdm/core/include/usage_table_header.h | 46 +- .../cdm/core/include/wv_cdm_types.h | 8 + libwvdrmengine/cdm/core/src/cdm_session.cpp | 20 +- .../cdm/core/src/usage_table_header.cpp | 431 ++++- .../core/test/usage_table_header_unittest.cpp | 1684 +++++++++-------- 5 files changed, 1292 insertions(+), 897 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/usage_table_header.h b/libwvdrmengine/cdm/core/include/usage_table_header.h index 931048a7..61e28f56 100644 --- a/libwvdrmengine/cdm/core/include/usage_table_header.h +++ b/libwvdrmengine/cdm/core/include/usage_table_header.h @@ -73,18 +73,21 @@ class UsageTableHeader { // The licenses or usage info records specified by |usage_entry_number| // should not be in use by any open CryptoSession objects when calls - // to DeleteEntry and MoveEntry are made. - virtual CdmResponseType DeleteEntry(uint32_t usage_entry_number, - DeviceFiles* handle, - metrics::CryptoMetrics* metrics); + // to InvalidateEntry and MoveEntry are made. + // If |defrag_table| is true, the table will be defragmented after + // the entry has been invalidated. + virtual CdmResponseType InvalidateEntry(uint32_t usage_entry_number, + bool defrag_table, + DeviceFiles* device_files, + metrics::CryptoMetrics* metrics); - // Test only method. This method emulates the behavior of DeleteEntry + // Test only method. This method emulates the behavior of InvalidateEntry // without actually invoking OEMCrypto (through CryptoSession) // or storage (through DeviceFiles). It modifies internal data structures - // when DeleteEntry is mocked. This allows one to test methods that are - // dependent on DeleteEntry without having to set expectations - // for the objects that DeleteEntry depends on. - void DeleteEntryForTest(uint32_t usage_entry_number); + // when InvalidateEntry is mocked. This allows one to test methods that are + // dependent on InvalidateEntry without having to set expectations + // for the objects that InvalidateEntry depends on. + void InvalidateEntryForTest(uint32_t usage_entry_number); size_t size() { return usage_entry_info_.size(); } @@ -115,17 +118,27 @@ class UsageTableHeader { CdmResponseType MoveEntry(uint32_t from /* usage entry number */, const CdmUsageEntry& from_usage_entry, uint32_t to /* usage entry number */, - DeviceFiles* handle, + DeviceFiles* device_files, metrics::CryptoMetrics* metrics); - CdmResponseType GetEntry(uint32_t usage_entry_number, DeviceFiles* handle, + CdmResponseType GetEntry(uint32_t usage_entry_number, + DeviceFiles* device_files, CdmUsageEntry* usage_entry); - CdmResponseType StoreEntry(uint32_t usage_entry_number, DeviceFiles* handle, + CdmResponseType StoreEntry(uint32_t usage_entry_number, + DeviceFiles* device_files, const CdmUsageEntry& usage_entry); + // Stores the usage table and it's info. This will increment + // |store_table_counter_| if successful. + bool StoreTable(DeviceFiles* device_files); + CdmResponseType Shrink(metrics::CryptoMetrics* metrics, uint32_t number_of_usage_entries_to_delete); + // Must lock table before calling. + CdmResponseType DefragTable(DeviceFiles* device_files, + metrics::CryptoMetrics* metrics); + virtual bool is_inited() { return is_inited_; } // Performs and LRU upgrade on all loaded CdmUsageEntryInfo from a @@ -184,7 +197,7 @@ class UsageTableHeader { // This handle and file system is only to be used when accessing // usage_table_header. Usage entries should use the file system provided // by CdmSession. - std::unique_ptr file_handle_; + std::unique_ptr device_files_; std::unique_ptr file_system_; CdmSecurityLevel security_level_; SecurityLevel requested_security_level_; @@ -217,6 +230,11 @@ class UsageTableHeader { // assumed to be |kMinimumUsageTableEntriesSupported|. size_t potential_table_capacity_ = 0u; + // Counts the number of successful calls to |StoreTable()|. Used + // to reduce the number of calls to device files for certain + // table operations. + uint32_t store_table_counter_ = 0u; + #if defined(UNIT_TEST) // Test related declarations friend class UsageTableHeaderTest; @@ -228,7 +246,7 @@ class UsageTableHeader { // These setters are for testing only. Takes ownership of the pointers. void SetDeviceFiles(DeviceFiles* device_files) { - file_handle_.reset(device_files); + device_files_.reset(device_files); } void SetCryptoSession(CryptoSession* crypto_session) { test_crypto_session_.reset(crypto_session); diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index c0205a70..7d87adfe 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -533,6 +533,14 @@ struct CdmUsageEntryInfo { // else storage_type == kStorageTypeUnknown return true; } + + void Clear() { + storage_type = kStorageTypeUnknown; + key_set_id.clear(); + usage_info_file_name.clear(); + last_use_time = 0; + offline_license_expiry_time = 0; + } }; enum CdmKeySecurityLevel { diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index ab32c568..6955ed52 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -532,16 +532,18 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { metrics_->license_sdk_version_.Record( version_info.license_service_version()); - // Update or delete entry if usage table header+entries are supported + // Update or invalidate entry if usage table header+entries are supported if (usage_support_type_ == kUsageEntrySupport && !provider_session_token.empty() && usage_table_header_ != nullptr) { if (sts != KEY_ADDED) { - CdmResponseType delete_sts = usage_table_header_->DeleteEntry( - usage_entry_number_, file_handle_.get(), crypto_metrics_); - crypto_metrics_->usage_table_header_delete_entry_.Increment(delete_sts); - if (delete_sts != NO_ERROR) { - LOGW("Delete usage entry failed: status = %d", - static_cast(delete_sts)); + const CdmResponseType invalidate_sts = + usage_table_header_->InvalidateEntry( + usage_entry_number_, true, file_handle_.get(), crypto_metrics_); + crypto_metrics_->usage_table_header_delete_entry_.Increment( + invalidate_sts); + if (invalidate_sts != NO_ERROR) { + LOGW("Invalidate usage entry failed: status = %d", + static_cast(invalidate_sts)); } } } @@ -827,8 +829,8 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) { return INCORRECT_USAGE_SUPPORT_TYPE_1; } - sts = usage_table_header_->DeleteEntry(usage_entry_number, file_handle_.get(), - crypto_metrics_); + sts = usage_table_header_->InvalidateEntry( + usage_entry_number, true, file_handle_.get(), crypto_metrics_); crypto_metrics_->usage_table_header_delete_entry_.Increment(sts); return sts; } diff --git a/libwvdrmengine/cdm/core/src/usage_table_header.cpp b/libwvdrmengine/cdm/core/src/usage_table_header.cpp index ac45ea77..0504d760 100644 --- a/libwvdrmengine/cdm/core/src/usage_table_header.cpp +++ b/libwvdrmengine/cdm/core/src/usage_table_header.cpp @@ -23,7 +23,7 @@ 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 consider for removal using the LRU algorithm. +// Number of elements to be considered for removal using the LRU algorithm. 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 @@ -34,6 +34,11 @@ constexpr size_t kLruRemovalSetSize = 3; // nears the capacity of the usage table). constexpr double kLruUnexpiredThresholdFraction = 0.75; +// Maximum number of entries to be moved during a defrag operation. +// This is to prevent the system from stalling too long if the defrag +// occurs during an active application session. +constexpr size_t kMaxDefragEntryMoves = 5; + // Convert |license_message| -> SignedMessage -> License. bool ParseLicenseFromLicenseMessage(const CdmKeyResponse& license_message, video_widevine::License* license) { @@ -136,7 +141,7 @@ UsageTableHeader::UsageTableHeader() is_inited_(false), clock_ref_(&clock_) { file_system_.reset(new FileSystem()); - file_handle_.reset(new DeviceFiles(file_system_.get())); + device_files_.reset(new DeviceFiles(file_system_.get())); } bool UsageTableHeader::Init(CdmSecurityLevel security_level, @@ -173,7 +178,7 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, potential_table_capacity_ = kMinimumUsageTableEntriesSupported; } - if (!file_handle_->Init(security_level)) { + if (!device_files_->Init(security_level)) { LOGE("Failed to initialize device files"); return false; } @@ -183,7 +188,7 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, if (metrics == nullptr) metrics = &alternate_crypto_metrics_; bool run_lru_upgrade = false; - if (file_handle_->RetrieveUsageTableInfo( + if (device_files_->RetrieveUsageTableInfo( &usage_table_header_, &usage_entry_info_, &run_lru_upgrade)) { LOGI("Number of usage entries: %zu", usage_entry_info_.size()); status = crypto_session->LoadUsageTableHeader(requested_security_level_, @@ -230,8 +235,9 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, } } if (result == NO_ERROR) { - result = DeleteEntry(temporary_usage_entry_number, file_handle_.get(), - metrics); + result = InvalidateEntry(temporary_usage_entry_number, + /* defrag_table = */ true, + device_files_.get(), metrics); } if (result != NO_ERROR) { LOGE( @@ -246,21 +252,21 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, if (status != NO_ERROR || !lru_success) { LOGE("Failed to load usage table: security_level = %d, status = %d", static_cast(security_level), static_cast(status)); - file_handle_->DeleteAllLicenses(); - file_handle_->DeleteAllUsageInfo(); - file_handle_->DeleteUsageTableInfo(); + device_files_->DeleteAllLicenses(); + device_files_->DeleteAllUsageInfo(); + device_files_->DeleteUsageTableInfo(); usage_entry_info_.clear(); usage_table_header_.clear(); status = crypto_session->CreateUsageTableHeader(requested_security_level_, &usage_table_header_); if (status != NO_ERROR) return false; - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + StoreTable(device_files_.get()); } } else { status = crypto_session->CreateUsageTableHeader(requested_security_level_, &usage_table_header_); if (status != NO_ERROR) return false; - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + StoreTable(device_files_.get()); } is_inited_ = true; @@ -305,23 +311,24 @@ CdmResponseType UsageTableHeader::AddEntry( for (size_t i = 0; i < removal_candidates.size() && status == INSUFFICIENT_CRYPTO_RESOURCES_3; ++i) { - const uint32_t entry_number_to_delete = removal_candidates[i]; + const uint32_t entry_number_to_invalidate = removal_candidates[i]; // Calculate metric values. staleness_of_removed = current_time - - usage_entry_info_[entry_number_to_delete].last_use_time; + usage_entry_info_[entry_number_to_invalidate].last_use_time; storage_type_of_removed = - usage_entry_info_[entry_number_to_delete].storage_type; + usage_entry_info_[entry_number_to_invalidate].storage_type; - if (DeleteEntry(entry_number_to_delete, file_handle_.get(), metrics) == - NO_ERROR) { + if (InvalidateEntry(entry_number_to_invalidate, + /* defrag_table = */ true, device_files_.get(), + metrics) == NO_ERROR) { // If the entry was deleted, it is still possible for the create new // entry to fail. If so, we must ensure that the previously last // entry was not in the |removal_candidates| as it has now been swapped // with the deleted entry. for (uint32_t& entry_number : removal_candidates) { if (entry_number == usage_entry_info_.size()) { - entry_number = entry_number_to_delete; + entry_number = entry_number_to_invalidate; } } } @@ -361,9 +368,7 @@ CdmResponseType UsageTableHeader::AddEntry( const size_t number_of_entries = usage_entry_info_.size(); usage_entry_info_.resize(*usage_entry_number + 1); for (size_t i = number_of_entries; i < usage_entry_info_.size() - 1; ++i) { - usage_entry_info_[i].storage_type = kStorageTypeUnknown; - usage_entry_info_[i].key_set_id.clear(); - usage_entry_info_[i].usage_info_file_name.clear(); + usage_entry_info_[i].Clear(); } } else /* *usage_entry_number == usage_entry_info_.size() */ { usage_entry_info_.resize(*usage_entry_number + 1); @@ -396,7 +401,7 @@ CdmResponseType UsageTableHeader::AddEntry( } LOGI("New usage entry: usage_entry_number = %u", *usage_entry_number); - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + StoreTable(device_files_.get()); return NO_ERROR; } @@ -429,13 +434,14 @@ CdmResponseType UsageTableHeader::LoadEntry(CryptoSession* crypto_session, ++retry_count) { if (usage_entry_info_.size() <= 1) break; // Get a random entry from the other entries. - uint32_t entry_number_to_delete = + uint32_t entry_number_to_invalidate = CdmRandom::RandomInRange(usage_entry_info_.size() - 2); - if (entry_number_to_delete >= usage_entry_number) { + if (entry_number_to_invalidate >= usage_entry_number) { // Exclude |usage_entry_number|. - ++entry_number_to_delete; + ++entry_number_to_invalidate; } - DeleteEntry(entry_number_to_delete, file_handle_.get(), metrics); + InvalidateEntry(entry_number_to_invalidate, /* defrag_table = */ true, + device_files_.get(), metrics); status = crypto_session->LoadUsageEntry(usage_entry_number, usage_entry); } @@ -463,15 +469,19 @@ CdmResponseType UsageTableHeader::UpdateEntry(uint32_t usage_entry_number, if (status != NO_ERROR) return status; usage_entry_info_[usage_entry_number].last_use_time = GetCurrentTime(); - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + StoreTable(device_files_.get()); return NO_ERROR; } -CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number, - DeviceFiles* handle, - metrics::CryptoMetrics* metrics) { - LOGI("Locking to delete entry: usage_entry_number = %u", usage_entry_number); +CdmResponseType UsageTableHeader::InvalidateEntry( + uint32_t usage_entry_number, bool defrag_table, DeviceFiles* device_files, + metrics::CryptoMetrics* metrics) { + LOGI("Locking to invalidate entry: usage_entry_number = %u", + usage_entry_number); std::unique_lock auto_lock(usage_table_header_lock_); + // OEMCrypto does not have any concept of "deleting" an entry. + // Instead, the CDM marks the entry's meta data as invalid (storage + // type unknown) and then performs a "defrag" of the OEMCrypto table. if (usage_entry_number >= usage_entry_info_.size()) { LOGE( "Usage entry number is larger than table size: " @@ -480,55 +490,38 @@ CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number, return USAGE_INVALID_PARAMETERS_1; } - // Find the last valid entry number, in order to swap - size_t swap_entry_number = usage_entry_info_.size() - 1; - CdmUsageEntry swap_usage_entry; - bool swap_usage_entry_valid = false; + usage_entry_info_[usage_entry_number].Clear(); - while (!swap_usage_entry_valid && swap_entry_number > usage_entry_number) { - switch (usage_entry_info_[swap_entry_number].storage_type) { - case kStorageLicense: - case kStorageUsageInfo: { - CdmResponseType status = - GetEntry(swap_entry_number, handle, &swap_usage_entry); - if (status == NO_ERROR) swap_usage_entry_valid = true; - break; - } - case kStorageTypeUnknown: - default: - break; + if (defrag_table) { + // The defrag operation calls many OEMCrypto functions that are + // unrelated to the caller, the only error that will be returned is + // a SYSTEM_INVALIDATED_ERROR. As long as the storage type is + // properly set to unknown, the operation is considered successful. + // SYSTEM_INVALIDATED_ERROR is a special type of error that must be + // sent back to the caller for the CDM as a whole to handle. + const uint32_t pre_defrag_store_counter = store_table_counter_; + const CdmResponseType status = DefragTable(device_files, metrics); + if (pre_defrag_store_counter == store_table_counter_) { + // It is possible that DefragTable() does not result in any + // changes to the table, and as a result, it will not store the + // invalidated entry. + LOGD("Table was not stored during defrag, storing now"); + StoreTable(device_files); } - if (!swap_usage_entry_valid) --swap_entry_number; + if (status == SYSTEM_INVALIDATED_ERROR) { + LOGE("Invalidate entry failed due to system invalidation error"); + return SYSTEM_INVALIDATED_ERROR; + } + } else { + StoreTable(device_files); } - uint32_t number_of_entries_to_be_deleted = - usage_entry_info_.size() - usage_entry_number; - - if (swap_usage_entry_valid) { - CdmResponseType status = MoveEntry(swap_entry_number, swap_usage_entry, - usage_entry_number, handle, metrics); - // If unable to move entry, unset storage type of entry to be deleted and - // resize |usage_entry_info_| so that swap usage entry is the last entry. - if (status != NO_ERROR) { - usage_entry_info_[usage_entry_number].storage_type = kStorageTypeUnknown; - usage_entry_info_[usage_entry_number].key_set_id.clear(); - if (usage_entry_info_.size() - 1 == swap_entry_number) { - file_handle_->StoreUsageTableInfo(usage_table_header_, - usage_entry_info_); - } else { - Shrink(metrics, usage_entry_info_.size() - swap_entry_number - 1); - } - return NO_ERROR; - } - number_of_entries_to_be_deleted = - usage_entry_info_.size() - swap_entry_number; - } - return Shrink(metrics, number_of_entries_to_be_deleted); + return NO_ERROR; } CdmResponseType UsageTableHeader::MoveEntry( uint32_t from_usage_entry_number, const CdmUsageEntry& from_usage_entry, - uint32_t to_usage_entry_number, DeviceFiles* handle, + uint32_t to_usage_entry_number, DeviceFiles* device_files, metrics::CryptoMetrics* metrics) { LOGI( "Moving usage entry: " @@ -544,9 +537,14 @@ CdmResponseType UsageTableHeader::MoveEntry( crypto_session = scoped_crypto_session.get(); } - crypto_session->Open(requested_security_level_); + CdmResponseType status = crypto_session->Open(requested_security_level_); + if (status != NO_ERROR) { + LOGE("Cannot open session for move: usage_entry_number = %u", + from_usage_entry_number); + return status; + } - CdmResponseType status = + status = crypto_session->LoadUsageEntry(from_usage_entry_number, from_usage_entry); if (status != NO_ERROR) { @@ -567,6 +565,7 @@ CdmResponseType UsageTableHeader::MoveEntry( usage_entry_info_[to_usage_entry_number] = usage_entry_info_[from_usage_entry_number]; + usage_entry_info_[from_usage_entry_number].Clear(); CdmUsageEntry usage_entry; status = crypto_session->UpdateUsageEntry(&usage_table_header_, &usage_entry); @@ -577,15 +576,15 @@ CdmResponseType UsageTableHeader::MoveEntry( return status; } - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); - - StoreEntry(to_usage_entry_number, handle, usage_entry); + // Store the usage table and usage entry after successful move. + StoreTable(device_files); + StoreEntry(to_usage_entry_number, device_files, usage_entry); return NO_ERROR; } CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, - DeviceFiles* handle, + DeviceFiles* device_files, CdmUsageEntry* usage_entry) { LOGI("Getting usage entry: usage_entry_number = %u, storage_type: %d", usage_entry_number, @@ -597,7 +596,7 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, case kStorageLicense: { DeviceFiles::CdmLicenseData license_data; DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; - if (!handle->RetrieveLicense( + if (!device_files->RetrieveLicense( usage_entry_info_[usage_entry_number].key_set_id, &license_data, &sub_error_code)) { LOGE("Failed to retrieve license: status = %d", @@ -614,7 +613,7 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, CdmKeyMessage license_request; CdmKeyResponse license; - if (!handle->RetrieveUsageInfoByKeySetId( + if (!device_files->RetrieveUsageInfoByKeySetId( usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, &provider_session_token, &license_request, &license, usage_entry, @@ -645,7 +644,7 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, } CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, - DeviceFiles* handle, + DeviceFiles* device_files, const CdmUsageEntry& usage_entry) { LOGI("Storing usage entry: usage_entry_number = %u, storage type: %d", usage_entry_number, @@ -658,7 +657,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, DeviceFiles::CdmLicenseData license_data; DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; - if (!handle->RetrieveLicense( + if (!device_files->RetrieveLicense( usage_entry_info_[usage_entry_number].key_set_id, &license_data, &sub_error_code)) { LOGE("Failed to retrieve license: status = %d", @@ -670,7 +669,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, license_data.usage_entry = usage_entry; license_data.usage_entry_number = usage_entry_number; - if (!handle->StoreLicense(license_data, &sub_error_code)) { + if (!device_files->StoreLicense(license_data, &sub_error_code)) { LOGE("Failed to store license: status = %d", static_cast(sub_error_code)); return USAGE_STORE_LICENSE_FAILED; @@ -682,7 +681,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, uint32_t entry_number; std::string provider_session_token, init_data, key_request, key_response, key_renewal_request; - if (!handle->RetrieveUsageInfoByKeySetId( + if (!device_files->RetrieveUsageInfoByKeySetId( usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, &provider_session_token, &key_request, &key_response, &entry, @@ -690,10 +689,10 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, LOGE("Failed to retrieve usage information"); return USAGE_STORE_ENTRY_RETRIEVE_USAGE_INFO_FAILED; } - handle->DeleteUsageInfo( + device_files->DeleteUsageInfo( usage_entry_info_[usage_entry_number].usage_info_file_name, provider_session_token); - if (!handle->StoreUsageInfo( + if (!device_files->StoreUsageInfo( provider_session_token, key_request, key_response, usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, usage_entry, @@ -714,6 +713,18 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, return NO_ERROR; } +bool UsageTableHeader::StoreTable(DeviceFiles* device_files) { + LOGV("Storing usage table information"); + const bool result = + device_files->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + if (result) { + ++store_table_counter_; + } else { + LOGW("Failed to store usage table info"); + } + return result; +} + CdmResponseType UsageTableHeader::Shrink( metrics::CryptoMetrics* metrics, uint32_t number_of_usage_entries_to_delete) { @@ -734,10 +745,6 @@ CdmResponseType UsageTableHeader::Shrink( if (number_of_usage_entries_to_delete == 0) return NO_ERROR; - // TODO(b/150887808): Only resize if the shrink operation is successful. - usage_entry_info_.resize(usage_entry_info_.size() - - number_of_usage_entries_to_delete); - // crypto_session points to an object whose scope is this method or a test // object whose scope is the lifetime of this class std::unique_ptr scoped_crypto_session; @@ -747,18 +754,241 @@ CdmResponseType UsageTableHeader::Shrink( crypto_session = scoped_crypto_session.get(); } + const size_t new_size = + usage_entry_info_.size() - number_of_usage_entries_to_delete; const CdmResponseType status = crypto_session->ShrinkUsageTableHeader( - requested_security_level_, usage_entry_info_.size(), - &usage_table_header_); + requested_security_level_, new_size, &usage_table_header_); if (status == NO_ERROR) { - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + usage_entry_info_.resize(new_size); + StoreTable(device_files_.get()); } return status; } +CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files, + metrics::CryptoMetrics* metrics) { + LOGV("Defragging table: current_size = %zu", usage_entry_info_.size()); + // Defragging the usage table involves moving valid entries near the + // end of the usage table to the position of invalid entries near the + // front of the table. After the entries are moved, the CDM shrinks + // the table to cut off all trailing invalid entries at the end of + // the table. + + // Special case 0: Empty table, do nothing. + if (usage_entry_info_.empty()) { + LOGD("Table empty, nothing to defrag"); + return NO_ERROR; + } + + // Step 1: Create a list of entries to be removed from the table. + // Priority is given to the entries near the beginning of the table. + // To avoid large delays from the swapping process, we limit the + // quantity of entries to remove to |kMaxDefragEntryMoves| or fewer. + std::vector entries_to_remove; + for (uint32_t i = 0; i < usage_entry_info_.size() && + entries_to_remove.size() < kMaxDefragEntryMoves; + ++i) { + if (usage_entry_info_[i].storage_type == kStorageTypeUnknown) { + entries_to_remove.push_back(i); + } + } + + // Special case 1: There are no entries that are invalid; nothing + // needs to be done. + if (entries_to_remove.empty()) { + LOGD("No entries are invalid"); + return NO_ERROR; + } + + // Step 2: Create a list of entries to be moved from the end of the + // table to the positions identified for removal. + std::vector entries_to_move; + for (uint32_t i = 0; i < usage_entry_info_.size() && + entries_to_move.size() < entries_to_remove.size(); + ++i) { + // Search from the end of the table. + const uint32_t entry_index = usage_entry_info_.size() - i - 1; + if (usage_entry_info_[entry_index].storage_type != kStorageTypeUnknown) { + entries_to_move.push_back(entry_index); + } + } + + // Special case 2: There are no valid entries in the table. In this case, + // the whole table can be removed. + if (entries_to_move.empty()) { + LOGD("No valid entries found, shrinking entire table: size = %zu", + usage_entry_info_.size()); + return Shrink(metrics, usage_entry_info_.size()); + } + + // Step 3: Ignore invalid entries that are after the last valid + // entry. No entry is to be moved to a greater index than it already + // has, and entries after the last valid entry will be removed when + // the shrink operation is applied to the table. + // Note: Special case 4 will handle any non-trivial cases related to + // interweaving of valid and invalid entries. + const uint32_t last_valid_entry = entries_to_move.front(); + while (!entries_to_remove.empty() && + entries_to_remove.back() > last_valid_entry) { + entries_to_remove.pop_back(); + } + + // Special case 3: All of the invalid entries are after the last valid + // entry. In this case, no movement is required and the table can just + // be shrunk to the last valid entry. + if (entries_to_remove.empty()) { + const size_t to_remove = usage_entry_info_.size() - last_valid_entry - 1; + LOGD("Removing all entries after the last valid entry: count = %zu", + to_remove); + return Shrink(metrics, to_remove); + } + + // Step 4: Move the valid entries to overwrite the invalid entries. + // Moving the highest numbered valid entry to the lowest numbered + // invalid entry. + + // Reversing vectors to make accessing and popping easier. This + // will put the lowest number invalid entry and the highest number + // valid entry at the back of their respective vectors. + std::reverse(entries_to_remove.begin(), entries_to_remove.end()); + std::reverse(entries_to_move.begin(), entries_to_move.end()); + while (!entries_to_remove.empty() && !entries_to_move.empty()) { + // Entries are popped after use only. + const uint32_t to_entry_number = entries_to_remove.back(); + const uint32_t from_entry_number = entries_to_move.back(); + + // Special case 4: We don't want to move any entries to a higher + // index than their current. Once this occurs, we can stop the + // loop. + if (to_entry_number > from_entry_number) { + LOGD("Entries will not be moved further down the table"); + break; + } + + CdmUsageEntry from_entry; + CdmResponseType status = + GetEntry(from_entry_number, device_files, &from_entry); + if (status != NO_ERROR) { + LOGW("Could not get entry: entry_number = %u", from_entry_number); + // It is unlikely that an unretrievable entry will suddenly + // become retrievable later on when it is needed. + usage_entry_info_[from_entry_number].Clear(); + entries_to_move.pop_back(); + continue; + } + + status = MoveEntry(from_entry_number, from_entry, to_entry_number, + device_files, metrics); + switch (status) { + case NO_ERROR: { + entries_to_remove.pop_back(); + entries_to_move.pop_back(); + break; + } + // Handle errors associated with the valid "from" entry. + case LOAD_USAGE_ENTRY_INVALID_SESSION: { + // This is a special error code when returned from LoadEntry() + // indicating that the entry is already in use in a different + // session. In this case, skip the entry and move on. + LOGD("From entry already in use: from_entry_number = %u", + from_entry_number); + entries_to_move.pop_back(); + break; + } + case LOAD_USAGE_ENTRY_GENERATION_SKEW: + case LOAD_USAGE_ENTRY_SIGNATURE_FAILURE: + case LOAD_USAGE_ENTRY_UNKNOWN_ERROR: { + // The entry (from the CDM's point of view) is invalid and + // can no longer be used. Safe to continue loop. + // TODO(b/152256186): Remove local files associated with this + // entry. + usage_entry_info_[from_entry_number].Clear(); + LOGW("From entry was corrupted: from_entry_number = %u", + from_entry_number); + entries_to_move.pop_back(); + break; + } + // Handle errors associated with the invalid "to" entry. + case MOVE_USAGE_ENTRY_DESTINATION_IN_USE: { + // The usage entry specified by |to_entry_number| is currently + // being used by another session. This is unlikely, but still + // possible. Given that this entry is already marked as unknown + // storage type, it will likely be removed at a later time. + LOGD("To entry already in use: to_entry_number = %u", to_entry_number); + entries_to_remove.pop_back(); + break; + } + case MOVE_USAGE_ENTRY_UNKNOWN_ERROR: { + // Something else wrong occurred when moving to the destination + // entry. This could be a problem with from entry or the to + // entry. Both should be skipped on the next iteration. + LOGW( + "Move failed, skipping both to entry and from entry: " + "to_entry_number = %u, from_entry_number = %u", + to_entry_number, from_entry_number); + entries_to_remove.pop_back(); + entries_to_move.pop_back(); + break; + } + // Handle other possible errors from the operations. + case INSUFFICIENT_CRYPTO_RESOURCES: { + // Cannot open any new sessions. The loop should end, but + // an attempt to shrink the table should still be made. + LOGW("Cannot open new session for table clean up"); + entries_to_remove.clear(); + entries_to_move.clear(); + break; + } + default: { + // For all other cases, it may not be safe to proceed, even to + // shrink the table. + LOGE("Unrecoverable error occurred while defragging table: status = %d", + static_cast(status)); + return status; + } + } // End switch case. + } // End while loop. + + // Step 5: Find the new last valid entry. + uint32_t new_last_valid_entry = usage_entry_info_.size(); + for (uint32_t i = 0; i < usage_entry_info_.size(); ++i) { + const uint32_t entry_index = usage_entry_info_.size() - i - 1; + if (usage_entry_info_[entry_index].storage_type != kStorageTypeUnknown) { + new_last_valid_entry = entry_index; + break; + } + } + + // Special case 5: No entries in the table are valid. This could + // have occurred if entries during the move process were found to be + // invalid. In this case, remove the whole table. + if (new_last_valid_entry == usage_entry_info_.size()) { + LOGD( + "All entries have been invalidated, shrinking entire table: size = %zu", + usage_entry_info_.size()); + return Shrink(metrics, usage_entry_info_.size()); + } + + const size_t to_remove = usage_entry_info_.size() - new_last_valid_entry - 1; + + // Special case 6: It is possible that the last entry in the table + // is valid and currently loaded in the table by another session. + // The loop above would have tried to move it but had failed. In + // this case, nothing more to do. + if (to_remove == 0) { + LOGD("Defrag completed without shrinking table"); + StoreTable(device_files); + return NO_ERROR; + } + + // Step 6: Shrink table to the new size. + LOGD("Clean up complete, shrinking table: count = %zu", to_remove); + return Shrink(metrics, to_remove); +} // End Defrag(). + // Test only method. -void UsageTableHeader::DeleteEntryForTest(uint32_t usage_entry_number) { +void UsageTableHeader::InvalidateEntryForTest(uint32_t usage_entry_number) { LOGV("Deleting entry for test: usage_entry_number = %u", usage_entry_number); if (usage_entry_number >= usage_entry_info_.size()) { LOGE( @@ -767,7 +997,8 @@ void UsageTableHeader::DeleteEntryForTest(uint32_t usage_entry_number) { usage_entry_number, usage_entry_info_.size()); return; } - // Move last entry into deleted location and shrink usage entries + // Move last entry into invalidated entry location and shrink usage + // entries. usage_entry_info_[usage_entry_number] = usage_entry_info_[usage_entry_info_.size() - 1]; usage_entry_info_.resize(usage_entry_info_.size() - 1); @@ -791,13 +1022,13 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { switch (usage_entry_info.storage_type) { case kStorageLicense: { retrieve_response = RetrieveOfflineLicense( - file_handle_.get(), usage_entry_info.key_set_id, &license_message, + device_files_.get(), usage_entry_info.key_set_id, &license_message, &retrieved_entry_number); break; } case kStorageUsageInfo: { retrieve_response = RetrieveUsageInfoLicense( - file_handle_.get(), usage_entry_info.usage_info_file_name, + device_files_.get(), usage_entry_info.usage_info_file_name, usage_entry_info.key_set_id, &license_message, &retrieved_entry_number); break; @@ -871,7 +1102,7 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { 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) { - file_handle_->DeleteLicense(usage_entry_info.key_set_id); + 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. @@ -884,13 +1115,11 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { } it->second.push_back(usage_entry_info.key_set_id); } // else kStorageUnknown { Nothing special }. - usage_entry_info.storage_type = kStorageTypeUnknown; - usage_entry_info.key_set_id.clear(); - usage_entry_info.usage_info_file_name.clear(); + usage_entry_info.Clear(); } for (const auto& p : usage_info_clean_up) { - file_handle_->DeleteMultipleUsageInfoByKeySetIds(p.first, p.second); + device_files_->DeleteMultipleUsageInfoByKeySetIds(p.first, p.second); } return true; diff --git a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp index e4a633ab..82117f94 100644 --- a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp @@ -189,7 +189,8 @@ int64_t kPlaybackDuration = 300; int64_t kGracePeriodEndTime = 60; // ==== LRU Upgrade Data ==== -const CdmUsageTableHeader kUpgradableUsageTableHeader = {0}; +const CdmUsageTableHeader kUpgradableUsageTableHeader = + "Upgradable Table Header"; // Usage entries. const CdmUsageEntryInfo kUpgradableUsageEntryInfo1 = { @@ -251,7 +252,7 @@ const int64_t kUpgradedUsageEntryInfo2ExpireTime = 0; // Unset const int64_t kUpgradedUsageEntryInfo3LastUsedTime = kLruBaseTime; const int64_t kUpgradedUsageEntryInfo3ExpireTime = kLruBaseTime + 604800 + 86400; -const CdmUsageTableHeader kUpgradedUsageTableHeader = {0}; +const CdmUsageTableHeader kUpgradedUsageTableHeader = "Upgraded Table Header"; std::vector kUpgradedUsageEntryInfoList; namespace { @@ -400,6 +401,7 @@ class MockDeviceFiles : public DeviceFiles { bool(const std::string&, const std::string&, std::string*, CdmKeyMessage*, CdmKeyResponse*, CdmUsageEntry*, uint32_t*)); + MOCK_METHOD2(StoreLicense, bool(const CdmLicenseData&, ResponseType*)); MOCK_METHOD1(DeleteLicense, bool(const std::string&)); MOCK_METHOD0(DeleteAllLicenses, bool()); MOCK_METHOD0(DeleteAllUsageInfo, bool()); @@ -465,8 +467,8 @@ class MockCryptoSession : public TestCryptoSession { class MockUsageTableHeader : public UsageTableHeader { public: MockUsageTableHeader() : UsageTableHeader() {} - MOCK_METHOD3(DeleteEntry, CdmResponseType(uint32_t, DeviceFiles*, - metrics::CryptoMetrics*)); + MOCK_METHOD4(InvalidateEntry, CdmResponseType(uint32_t, bool, DeviceFiles*, + metrics::CryptoMetrics*)); }; } // namespace @@ -478,6 +480,7 @@ using ::testing::AtMost; using ::testing::ContainerEq; using ::testing::Contains; using ::testing::DoAll; +using ::testing::ElementsAre; using ::testing::ElementsAreArray; using ::testing::Ge; using ::testing::Invoke; @@ -498,9 +501,9 @@ class UsageTableHeaderTest : public WvCdmTestBase { } // Useful when UsageTableHeader is mocked - void DeleteEntry(uint32_t usage_entry_number, DeviceFiles*, - metrics::CryptoMetrics*) { - usage_table_header_->DeleteEntryForTest(usage_entry_number); + void InvalidateEntry(uint32_t usage_entry_number, bool, DeviceFiles*, + metrics::CryptoMetrics*) { + usage_table_header_->InvalidateEntryForTest(usage_entry_number); } protected: @@ -723,53 +726,7 @@ TEST_P(UsageTableHeaderInitializationTest, } TEST_P(UsageTableHeaderInitializationTest, - 201UsageEntries_DeleteEntryFails_UsageTableHeaderRecreated) { - std::vector usage_entries_202 = k201UsageEntryInfoVector; - usage_entries_202.push_back(kDummyUsageEntryInfo); - const SecurityLevel security_level = - (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; - - EXPECT_CALL(*device_files_, - RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), - SetArgPointee<1>(k201UsageEntryInfoVector), - SetArgPointee<2>(false), Return(true))); - EXPECT_CALL(*crypto_session_, - LoadUsageTableHeader(security_level, kUsageTableHeader)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - CreateUsageTableHeader(security_level, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kEmptyUsageTableHeader), Return(NO_ERROR))); - EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); - EXPECT_CALL(*device_files_, DeleteAllUsageInfo()).WillOnce(Return(true)); - EXPECT_CALL(*device_files_, DeleteUsageTableInfo()).WillOnce(Return(true)); - EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, - kEmptyUsageEntryInfoVector)) - .WillOnce(Return(true)); - - // Expectations for AddEntry - 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))); - EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, - usage_entries_202)) - .WillOnce(Return(true)); - - // Expectations for DeleteEntry - EXPECT_CALL(*crypto_session_, Open(security_level)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader(security_level, - usage_entries_202.size() - 1, NotNull())) - .WillOnce(Return(SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR)); - - EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); -} - -TEST_P(UsageTableHeaderInitializationTest, - 201UsageEntries_AddDeleteEntrySucceeds) { + 201UsageEntries_AddInvalidateEntrySucceeds) { std::vector usage_entries_202 = k201UsageEntryInfoVector; usage_entries_202.push_back(kDummyUsageEntryInfo); @@ -794,7 +751,7 @@ TEST_P(UsageTableHeaderInitializationTest, usage_entries_202)) .WillOnce(Return(true)); - // Expectations for DeleteEntry + // Expectations for InvalidateEntry EXPECT_CALL(*crypto_session_, Open(security_level)) .WillOnce(Return(NO_ERROR)); EXPECT_CALL(*crypto_session_, @@ -961,9 +918,9 @@ TEST_F(UsageTableHeaderTest, // Setup expectations EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) + InvalidateEntry(_, true, device_files_, NotNull())) .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_first_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), + Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) @@ -1019,12 +976,12 @@ TEST_F(UsageTableHeaderTest, // Setup expectations EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) + InvalidateEntry(_, true, device_files_, NotNull())) .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_first_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), + Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))) .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_second_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), + Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) @@ -1084,15 +1041,15 @@ TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailsThrice) { // Setup expectations EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) + InvalidateEntry(_, true, device_files_, NotNull())) .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_first_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), + Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))) .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_second_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), + Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))) .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_third_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), + Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) @@ -1188,8 +1145,8 @@ TEST_F(UsageTableHeaderTest, MockUsageTableHeader* mock_usage_table_header = SetUpMock(); Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); - // We try to load a usage entry from the first 9 entries, since DeleteEntry - // can't delete an entry if the last one is in use. + // We try to load a usage entry from the first 9 entries, since + // InvalidateEntry can't delete an entry if the last one is in use. uint32_t usage_entry_number_to_load = CdmRandom::RandomInRange(k10UsageEntryInfoVector.size() - 2); @@ -1197,10 +1154,10 @@ TEST_F(UsageTableHeaderTest, // Setup expectations EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) + InvalidateEntry(_, true, device_files_, NotNull())) .Times(1) .WillRepeatedly( - DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), + DoAll(Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, @@ -1222,18 +1179,18 @@ TEST_F(UsageTableHeaderTest, MockUsageTableHeader* mock_usage_table_header = SetUpMock(); Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); - // We try to load a usage entry from the first 8 entries, since DeleteEntry - // can't delete an entry if the last one is in use. + // We try to load a usage entry from the first 8 entries, since + // InvalidateEntry can't delete an entry if the last one is in use. uint32_t usage_entry_number_to_load = CdmRandom::RandomInRange(k10UsageEntryInfoVector.size() - 3); CdmUsageEntry usage_entry_to_load = kUsageEntry; // Setup expectations EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) + InvalidateEntry(_, true, device_files_, NotNull())) .Times(2) .WillRepeatedly( - DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), + DoAll(Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, @@ -1255,18 +1212,18 @@ TEST_F(UsageTableHeaderTest, LoadEntry_LoadUsageEntryFailsThrice) { MockUsageTableHeader* mock_usage_table_header = SetUpMock(); Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); - // We try to load a usage entry from the first 7 entries, since DeleteEntry - // can't delete an entry if the last one is in use. + // We try to load a usage entry from the first 7 entries, since + // InvalidateEntry can't delete an entry if the last one is in use. uint32_t usage_entry_number_to_load = CdmRandom::RandomInRange(k10UsageEntryInfoVector.size() - 4); CdmUsageEntry usage_entry_to_load = kUsageEntry; // Setup expectations EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) + InvalidateEntry(_, true, device_files_, NotNull())) .Times(3) .WillRepeatedly( - DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), + DoAll(Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, @@ -1282,243 +1239,272 @@ TEST_F(UsageTableHeaderTest, LoadEntry_LoadUsageEntryFailsThrice) { usage_entry_number_to_load)); } -TEST_F(UsageTableHeaderTest, DeleteEntry_InvalidUsageEntryNumber) { +TEST_F(UsageTableHeaderTest, InvalidateEntry_InvalidUsageEntryNumber) { Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); uint32_t usage_entry_number = kUsageEntryInfoVector.size(); metrics::CryptoMetrics metrics; - EXPECT_NE(NO_ERROR, usage_table_header_->DeleteEntry( - usage_entry_number, device_files_, &metrics)); + EXPECT_NE(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number, true, device_files_, &metrics)); } // Initial Test state: -// 1. Entry to be delete is the last entry and is an Offline license. +// 1. Entry to be deleted is the last entry and is an Offline license. // When attempting to delete the entry a crypto session error // will occur. // // Attempting to delete the entry in (1) will result in: -// a. The usage entry requested to be deleted will not be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. The usage table will be requested to shrink both the last entry +// and the unknown entry before it. +// c. OEMCrypto error will cause the internal entries to remain the +// same. +// d. InvalidateEntry() will return NO_ERROR as the storage type is +// changed. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 // Secure Stop 1 1 1 -// Storage Type unknown 2 2 -// Offline License 2 3 3 +// Storage Type Unknown 2 2 +// Offline License 2 3 3 (Storage Type Unknown) // // # of usage entries 4 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_CryptoSessionError) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +TEST_F(UsageTableHeaderTest, InvalidateEntry_CryptoSessionError) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoOfflineLicense2 + const uint32_t usage_entry_number_to_be_deleted = + 3; // kUsageEntryInfoOfflineLicense2 metrics::CryptoMetrics metrics; EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader( - kLevelDefault, usage_entry_info_vector.size() - 1, NotNull())) + ShrinkUsageTableHeader(kLevelDefault, 2, NotNull())) .WillOnce(Return(SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR)); - EXPECT_NE(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Regardless, the usage table should be updated to reflect the changes + // to the usage entry marked as storage type unknown. + EXPECT_CALL( + *device_files_, + StoreUsageTableInfo(kUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop1, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check that the list is unchanged. + constexpr size_t expected_size = 4; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: -// 1. Entry to be delete is the last entry and is an Offline license. +// 1. Entry to be deleted is the last entry and is an offline license. // // Attempting to delete the entry in (1) will result in: -// a. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. Usage table will be resized to remove the last two entries. +// c. Updated table will be saved. +// d. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 // Secure Stop 1 1 1 -// Storage Type unknown 2 2 +// Storage Type Unknown 2 Deleted // Offline License 2 3 Deleted // -// # of usage entries 4 3 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastOfflineEntry) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +// # of usage entries 4 2 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntry_OfflineEntry) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoOfflineLicense2 + const uint32_t usage_entry_number_to_be_deleted = + 3; // kUsageEntryInfoOfflineLicense2 metrics::CryptoMetrics metrics; + // Expectations for call to shrink. EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader( - kLevelDefault, usage_entry_info_vector.size() - 1, NotNull())) + ShrinkUsageTableHeader(kLevelDefault, 2, NotNull())) .WillOnce( DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoSecureStop1, - kUsageEntryInfoStorageTypeUnknown))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop1))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 2; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: -// 1. Entry to be delete is the last entry and is a secure stop. +// 1. Entry to be deleted is the last entry and is a secure stop. // // Attempting to delete the entry in (1) will result in: -// a. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. Usage table will be resized to remove the last two entries. +// c. Updated table will be saved. +// d. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 // Secure Stop 1 1 1 -// Storage Type unknown 2 2 +// Storage Type Unknown 2 Deleted // Secure Stop 2 3 Deleted // -// # of usage entries 4 3 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastSecureStopEntry) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +// # of usage entries 4 2 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntry_SecureStopEntry) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop2}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoSecureStop2 + const uint32_t usage_entry_number_to_be_deleted = + 3; // kUsageEntryInfoSecureStop2 metrics::CryptoMetrics metrics; + // Expectation when shrinking table. EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader( - kLevelDefault, usage_entry_info_vector.size() - 1, NotNull())) + ShrinkUsageTableHeader(kLevelDefault, 2, NotNull())) .WillOnce( DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoSecureStop1, - kUsageEntryInfoStorageTypeUnknown))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop1))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 2; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Last few entries are offline licenses, but have license files // missing from persistent storage. -// 2. Usage entry to be deleted preceeds those in (1). +// 2. Usage entry to be deleted precedes those in (1). // // Attempting to delete the entry in (2) will result in: -// a. Offline entries in (1) will be deleted. -// b. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, the last two entries will be selected to +// move. +// c. Getting the usage entry for the selected entries will fail and +// result in them being set as kStorageTypeUnknown. +// d. No entries will be moved due to (c). +// e. Usage table will be resized to have only one entry. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Offline License 1 2 Deleted -// Offline License 2 3 Deleted -// Offline License 3 4 Deleted +// Offline License 2 3 Deleted (because missing) +// Offline License 3 4 Deleted (because missing) // -// # of usage entries 5 2 +// # of usage entries 5 1 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastOfflineEntriesHaveMissingLicenses) { - std::vector usage_entry_info_vector = { + InvalidateEntry_LastOfflineEntriesHaveMissingLicenses) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3}; Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; + // Offline license 2 and 3 cannot be retrieved. + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())) + .WillOnce(Return(false)); + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, + NotNull(), NotNull())) + .WillOnce(Return(false)); + + // Shrink to contain only the one valid entry. EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader( - kLevelDefault, usage_entry_number_to_be_deleted, NotNull())) + ShrinkUsageTableHeader(kLevelDefault, 1, NotNull())) .WillOnce( DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoSecureStop1, - kUsageEntryInfoStorageTypeUnknown))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoSecureStop1))) .WillOnce(Return(true)); - EXPECT_CALL(*device_files_, - RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, - NotNull(), NotNull())); - EXPECT_CALL(*device_files_, - RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, - NotNull(), NotNull())); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 1; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Last few entries are secure stops, but have entries // missing from usage info file in persistent storage. -// 2. Usage entry to be deleted preceeds those in (1). +// 2. Usage entry to be deleted precedes those in (1). // // Attempting to delete the entry in (2) will result in: -// a. Secure stops in (1) will be deleted. -// b. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, the last two entries will be selected to +// move. +// c. Getting the usage entry for the selected entries will fail and +// result in them being set as kStorageTypeUnknown. +// d. No entries will be moved due to (c). +// e. Usage table will be resized to have only one entry. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Secure stop 1 2 Deleted -// Secure stop 2 3 Deleted -// Secure stop 3 4 Deleted +// Secure stop 2 3 Deleted (because missing) +// Secure stop 3 4 Deleted (because missing) // -// # of usage entries 5 2 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastSecureStopEntriesAreMissing) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +// # of usage entries 5 1 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastSecureStopEntriesAreMissing) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoSecureStop3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoSecureStop1 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader( - kLevelDefault, usage_entry_number_to_be_deleted, NotNull())) - .WillOnce( - DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); - + // Streaming license 2 and 3 cannot be retrieved. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop2.usage_info_file_name, @@ -1532,51 +1518,66 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastSecureStopEntriesAreMissing) { NotNull(), NotNull(), NotNull())) .WillOnce(Return(false)); + // Shrink to contain only the one valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 1, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoStorageTypeUnknown))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 1; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Last few entries are offline licenses, but have incorrect usage // entry number stored in persistent file store. -// 2. Usage entry to be deleted preceeds those in (1). +// 2. Usage entry to be deleted precedes those in (1). // // Attempting to delete the entry in (2) will result in: -// a. Offline entries in (1) will be deleted. -// b. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, the last two entries will be selected to +// move. +// c. Getting the usage entry for the selected entries will fail due +// to a mismatch in usage entry number and result in them being set +// as kStorageTypeUnknown. +// d. No entries will be moved due to (c). +// e. Usage table will be resized to have only one entry. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Offline License 1 2 Deleted -// Offline License 2 3 Deleted -// Offline License 3 4 Deleted +// Offline License 2 3 Deleted (because incorrect #) +// Offline License 3 4 Deleted (because incorrect #) // -// # of usage entries 5 2 +// # of usage entries 5 1 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastOfflineEntriesHaveIncorrectUsageEntryNumber) { - std::vector usage_entry_info_vector = { + InvalidateEntry_LastOfflineEntriesHaveIncorrectUsageEntryNumber) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3}; Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); const uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1 + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - DeviceFiles::ResponseType sub_error_code; + // Set offline license file data with mismatched usage entry numbers. const DeviceFiles::CdmLicenseData offline_license_3_data{ - usage_entry_info_vector[usage_entry_info_vector.size() - 1].key_set_id, + kUsageEntryInfoOfflineLicense3.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -1589,13 +1590,14 @@ TEST_F(UsageTableHeaderTest, kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - static_cast(usage_entry_info_vector.size() - 2)}; - EXPECT_TRUE( - device_files_->StoreLicense(offline_license_3_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); + static_cast(3) /* Mismatch */}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_3_data), Return(true))); const DeviceFiles::CdmLicenseData offline_license_2_data{ - usage_entry_info_vector[usage_entry_info_vector.size() - 2].key_set_id, + kUsageEntryInfoOfflineLicense2.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -1608,84 +1610,80 @@ TEST_F(UsageTableHeaderTest, kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - static_cast(usage_entry_info_vector.size() - 3)}; - EXPECT_TRUE( - device_files_->StoreLicense(offline_license_2_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); + static_cast(2) /* Mismatch */}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_2_data), Return(true))); + // Shrink to contain only the one valid entry. EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader( - kLevelDefault, usage_entry_number_to_be_deleted, NotNull())) + ShrinkUsageTableHeader(kLevelDefault, 1, NotNull())) .WillOnce( DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoSecureStop1, - kUsageEntryInfoStorageTypeUnknown))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoSecureStop1))) .WillOnce(Return(true)); - EXPECT_CALL(*device_files_, RetrieveLicense(offline_license_3_data.key_set_id, - NotNull(), NotNull())); - EXPECT_CALL(*device_files_, RetrieveLicense(offline_license_2_data.key_set_id, - NotNull(), NotNull())); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 1; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Last few entries are secure stops, but have incorrect usage // entry number stored in persistent file store. -// 2. Usage entry to be deleted preceeds those in (1). +// 2. Usage entry to be deleted precedes those in (1). // // Attempting to delete the entry in (2) will result in: -// a. Secure stops entries in (1) will be deleted. -// b. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, the last two entries will be selected to +// move. +// c. Getting the usage entry for the selected entries will fail due +// to a mismatch in usage entry number and result in them being set +// as kStorageTypeUnknown. +// d. No entries will be moved due to (c). +// e. Usage table will be resized to have only one entry. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Secure stop 1 2 Deleted -// Secure stop 2 3 Deleted -// Secure stop 3 4 Deleted +// Secure stop 2 3 Deleted (because incorrect #) +// Secure stop 3 4 Deleted (because incorrect #) // -// # of usage entries 5 2 +// # of usage entries 5 1 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastSecureStopEntriesHaveIncorrectUsageEntryNumber) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { + InvalidateEntry_LastSecureStopEntriesHaveIncorrectUsageEntryNumber) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoSecureStop3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoSecureStop1 - uint32_t usage_entry_number_after_deleted_entry = - usage_entry_number_to_be_deleted + 1; + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader( - kLevelDefault, usage_entry_number_to_be_deleted, NotNull())) - .WillOnce( - DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); - + // Set streaming license file data with mismatched usage entry numbers. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop2.usage_info_file_name, kUsageEntryInfoSecureStop2.key_set_id, NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) - .WillOnce(DoAll( - SetArgPointee<2>(kProviderSessionToken), - SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), - SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(usage_entry_number_to_be_deleted), Return(true))); + .WillOnce(DoAll(SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), + SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), + SetArgPointee<6>(2) /* Mismatch */, Return(true))); EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( @@ -1696,72 +1694,122 @@ TEST_F(UsageTableHeaderTest, SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(usage_entry_number_after_deleted_entry), - Return(true))); + SetArgPointee<6>(3) /* Mismatch */, Return(true))); + + // Shrink to contain only the one valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 1, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoStorageTypeUnknown))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 1; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Last few entries are of storage type unknown. -// 2. Usage entry to be deleted preceeds those in (1). +// 2. Usage entry to be deleted precedes those in (1). // // Attempting to delete the entry in (2) will result in: -// a. Entries of storage type unknown at the end will be deleted. -// b. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, offline license 2 will be selected to be +// moved. +// c. The selected entry will have the usage entry loaded from device +// files. +// d. The selected license will be moved to the entry position near the +// front of the table. +// e. The moved entry will be updated in device files. +// f. Usage table will be resized to have only one entry. +// g. Updated table will be saved. +// h. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Offline License 1 2 2 -// Offline License 2 3 3 +// Offline License 2 3 1 (Moved) // Offline License 3 4 Deleted -// Storage Type unknown 5 Deleted -// Storage Type unknown 6 Deleted +// Storage Type Unknown 5 Deleted +// Storage Type Unknown 6 Deleted // -// # of usage entries 7 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreStorageTypeUnknown) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { - kUsageEntryInfoSecureStop1, kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3, - kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); +// # of usage entries 7 3 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntriesAreStorageTypeUnknown) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, + kUsageEntryInfoOfflineLicense3, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown}; Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 4; // kUsageEntryInfoOfflineLicense3 metrics::CryptoMetrics metrics; + // Expect calls for moving offline license 2 (position 3) to position 1. + const DeviceFiles::CdmLicenseData offline_license_2_data{ + kUsageEntryInfoOfflineLicense2.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(3)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())) + .Times(2) // First to get entry, then again to update. + .WillRepeatedly( + DoAll(SetArgPointee<1>(offline_license_2_data), Return(true))); + // Calls during Move(). + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(1)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kYetAnotherUsageEntry), + SetArgPointee<1>(kUsageEntry), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kYetAnotherUsageEntry, _)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*device_files_, StoreLicense(_, NotNull())) + .WillOnce(Return(true)); + + // Shrink to contain the remaining valid entry. EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader( - kLevelDefault, usage_entry_number_to_be_deleted, NotNull())) + ShrinkUsageTableHeader(kLevelDefault, 3, NotNull())) .WillOnce( DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo(kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoSecureStop1, - kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoOfflineLicense2))) + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoSecureStop1, + kUsageEntryInfoOfflineLicense2, + kUsageEntryInfoOfflineLicense1))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 3; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: @@ -1770,40 +1818,43 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreStorageTypeUnknown) { // OEMCrypto_MoveUsageEntry on it will fail. // // Attempting to delete the entry in (1) will result in: -// b. The last offline usage entry will not be deleted/moved if the -// OEMCrypto_MoveUsageEntry operation fails. -// c. The usage entry requested to be deleted will be marked as -// storage type unknown. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, offline license 3 will be selected to be +// moved. +// c. The selected entry will have the usage entry loaded from device +// files. +// d. The move process will fail due to the entry being busy, leaving +// the entry inplace. +// e. Usage table will not be resized. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 -// Offline License 1 2 Deleted/storage type unknown +// Secure Stop 2 1 1 +// Offline License 1 2 2 (storage type unknown) // Offline License 2 3 3 // Offline License 3 4 4 // // # of usage entries 5 5 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastEntryIsOffline_MoveOfflineEntryFailed) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, + InvalidateEntry_LastEntryIsOffline_MoveOfflineEntryFailed) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1 - uint32_t last_usage_entry_number = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - const DeviceFiles::CdmLicenseData license_data{ - usage_entry_info_vector[last_usage_entry_number].key_set_id, + // Expect calls for moving offline license 3 (position 4), but + // failure to move will not result in any calls for updating. + const DeviceFiles::CdmLicenseData offline_license_3_data{ + kUsageEntryInfoOfflineLicense3.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -1816,34 +1867,36 @@ TEST_F(UsageTableHeaderTest, kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - last_usage_entry_number}; - DeviceFiles::ResponseType sub_error_code; - EXPECT_TRUE(device_files_->StoreLicense(license_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); - - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(MOVE_USAGE_ENTRY_UNKNOWN_ERROR)); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2, - kUsageEntryInfoOfflineLicense3))) - .WillOnce(Return(true)); + /* usage_entry_number = */ 4}; EXPECT_CALL(*device_files_, - RetrieveLicense(license_data.key_set_id, NotNull(), NotNull())); + RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_3_data), Return(true))); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Calls during Move(). + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); + + // No calls to shrink are expected. + + // Regardless, the usage table should be updated to reflect the changes + // to the usage entry marked as storage type unknown. + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kUsageTableHeader, + ElementsAre(kUsageEntryInfoSecureStop1, + kUsageEntryInfoSecureStop2, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoOfflineLicense2, + kUsageEntryInfoOfflineLicense3))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check that the table has been updated as expected. + constexpr size_t expected_size = 5; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: @@ -1852,117 +1905,126 @@ TEST_F(UsageTableHeaderTest, // OEMCrypto_MoveUsageEntry on it will fail. // // Attempting to delete the entry in (1) will result in: -// b. The last secure stop usage entry will not be deleted/moved if the -// OEMCrypto_MoveUsageEntry operation fails. -// c. The usage entry requested to be deleted will be marked as -// storage type unknown. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, secure stop 3 will be selected to be moved. +// c. The selected entry will have the usage entry loaded from device +// files. +// d. The move process will fail due to the entry being busy, leaving +// the entry inplace. +// e. Usage table will not be resized. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 -// Storage Type unknown 1 1 -// Secure stop 1 2 Deleted/storage type unknown -// Secure stop 2 3 3 -// Secure stop 3 4 4 +// Offline License 2 1 1 +// Secure Stop 1 2 2 (storage type unknown) +// Secure Stop 2 3 3 +// Secure Stop 3 4 4 // // # of usage entries 5 5 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastEntryIsSecureStop_MoveSecureStopEntryFailed) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, + InvalidateEntry_LastEntryIsSecureStop_MoveSecureStopEntryFailed) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoSecureStop3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoSecureStop1 - uint32_t last_usage_entry_number = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoSecureStop3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(MOVE_USAGE_ENTRY_UNKNOWN_ERROR)); - + // Expect calls for moving secure stop 3 (position 4), but + // failure to move will not result in any calls for updating. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop3.usage_info_file_name, kUsageEntryInfoSecureStop3.key_set_id, NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<2>(kProviderSessionToken), - SetArgPointee<3>(kKeyRequest), - SetArgPointee<4>(kKeyResponse), - SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(last_usage_entry_number), Return(true))); + .WillOnce(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(4), Return(true))); - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop2, - kUsageEntryInfoSecureStop3))) + // Calls during Move(). + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); + + // No calls to shrink are expected. + + // Regardless, the usage table should be updated to reflect the changes + // to the usage entry marked as storage type unknown. + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoOfflineLicense2, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoSecureStop2, + kUsageEntryInfoSecureStop3))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + constexpr size_t expected_size = 5; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: -// 1. Usage entry to be deleted is not last +// 1. Usage entry to be deleted is not last (Offline license 1) // 2. Last few entries are of storage type unknown. -// 3. Entry that preceeds those in (2) is an offline license and calling -// OEMCrypto_MoveUsageEntry on it will fail. +// 3. Entry that precedes those in (2) are offline license and calling +// OEMCrypto_LoadUsageEntry on it will fail. // // Attempting to delete the entry in (1) will result in: -// a. Entries of storage type unknown at the end will be deleted. -// b. The offline usage entry that preceeds the entries in (a) will -// not be deleted/moved if the OEMCrypto_MoveUsageEntry operation fails. -// c. The usage entry requested to be deleted will be marked as -// storage type unknown. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, offline licenses 2 and 3 will be selected to be +// moved. +// c. The selected entry will have the usage entry loaded from device +// files. +// d. The move processes will fail due to the entries being busy, leaving +// the entries inplace. +// e. Usage table will be resized to remove the last two entries. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 -// Offline License 1 2 Deleted/storage type unknown +// Storage Type Unknown 1 1 +// Offline License 1 2 2 (storage type unknown) // Offline License 2 3 3 // Offline License 3 4 4 -// Storage Type unknown 5 Deleted -// Storage Type unknown 6 Deleted +// Storage Type Unknown 5 Deleted +// Storage Type Unknown 6 Deleted // // # of usage entries 7 5 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastEntriesAreOfflineAndUnknown_MoveOfflineEntryFailed) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { + InvalidateEntry_LastEntriesAreOfflineAndUnknown_MoveOfflineEntryFailed) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 5; // kUsageEntryInfoOfflineLicense1 - uint32_t last_valid_usage_entry_number = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - const DeviceFiles::CdmLicenseData license_data{ - usage_entry_info_vector[last_valid_usage_entry_number].key_set_id, + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + + // Expect calls for moving offline license 3 (position 4), but + // failure to move will not result in any calls for updating. + const DeviceFiles::CdmLicenseData offline_license_3_data{ + kUsageEntryInfoOfflineLicense3.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -1975,159 +2037,203 @@ TEST_F(UsageTableHeaderTest, kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - last_valid_usage_entry_number}; - DeviceFiles::ResponseType sub_error_code; - EXPECT_TRUE(device_files_->StoreLicense(license_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); + 4}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_3_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); + // Expect calls for moving offline license 2 (position 3), but + // failure to move will not result in any calls for updating. + const DeviceFiles::CdmLicenseData offline_license_2_data{ + kUsageEntryInfoOfflineLicense2.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + 3}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_2_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); + + // Excect a call to shrink table to cut off only the unknown entries + // at the end of the table. EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_valid_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(MOVE_USAGE_ENTRY_UNKNOWN_ERROR)); - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader( - kLevelDefault, last_valid_usage_entry_number + 1, NotNull())) + ShrinkUsageTableHeader(kLevelDefault, 5, NotNull())) .WillOnce( DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2, - kUsageEntryInfoOfflineLicense3))) - .WillOnce(Return(true)); + // Update table for the entry now marked storage type unknown and + // the entries that were cut off. EXPECT_CALL(*device_files_, - RetrieveLicense(license_data.key_set_id, NotNull(), NotNull())); + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoSecureStop1, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoOfflineLicense2, + kUsageEntryInfoOfflineLicense3))) + .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 5; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: -// 1. Usage entry to be deleted is not last +// 1. Usage entry to be deleted is not last (Secure stop 1) // 2. Last few entries are of storage type unknown. -// 3. Entry that preceeds those in (2) is an offline license and calling -// OEMCrypto_MoveUsageEntry on it will fail. +// 3. Entry that precedes those in (2) are secure stops and calling +// OEMCrypto_LoadUsageEntry on it will fail. // // Attempting to delete the entry in (1) will result in: -// a. Entries of storage type unknown at the end will be deleted. -// b. The offline usage entry that preceeds the entries in (a) will -// not be deleted/moved if the OEMCrypto_MoveUsageEntry operation fails. -// c. The usage entry requested to be deleted will be marked as -// storage type unknown. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, secure stop 2 and 3 will be selected to be +// moved. +// c. The selected entry will have the usage entry loaded from device +// files. +// d. The move processes will fail due to the entries being busy, leaving +// the entries inplace. +// e. Usage table will be resized to remove the last two entries. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 // Storage Type unknown 1 1 -// Secure stop 1 2 Deleted/storage type unknown +// Secure stop 1 2 2 (storage type unknown) // Secure stop 2 3 3 // Secure stop 3 4 4 // Storage Type unknown 5 Deleted // Storage Type unknown 6 Deleted // // # of usage entries 7 5 -TEST_F(UsageTableHeaderTest, - DeleteEntry_LastEntriesAreSecureStopAndUnknown_MoveOfflineEntryFailed) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +TEST_F( + UsageTableHeaderTest, + InvalidateEntry_LastEntriesAreSecureStopAndUnknown_MoveOfflineEntryFailed) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoSecureStop3, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 5; // kUsageEntryInfoOfflineLicense1 - uint32_t last_valid_usage_entry_number = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_valid_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(MOVE_USAGE_ENTRY_UNKNOWN_ERROR)); + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + // Expect calls for moving streaming license 3 (position 4), but + // failure to move will not result in any calls for updating. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop3.usage_info_file_name, kUsageEntryInfoSecureStop3.key_set_id, NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) - .WillOnce( - DoAll(SetArgPointee<2>(kProviderSessionToken), - SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), - SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(last_valid_usage_entry_number), Return(true))); + .WillOnce(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(4), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); + + // Expect calls for moving streaming license 2 (position 3), but + // failure to move will not result in any calls for updating. + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop2.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(3), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); + + // Excect a call to shrink table to cut off only the unknown entries + // at the end of the table. EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader( - kLevelDefault, last_valid_usage_entry_number + 1, NotNull())) + ShrinkUsageTableHeader(kLevelDefault, 5, NotNull())) .WillOnce( DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop2, - kUsageEntryInfoSecureStop3))) + // Update table for the entry now marked storage type unknown and + // the entries that were cut off. + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoSecureStop2, + kUsageEntryInfoSecureStop3))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 5; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Usage entry to be deleted is not last. -// 2. Last entry is an offline license. +// 2. Last entries are valid offline licenses. // // Attempting to delete the entry in (1) will result in: -// a. The usage entry requested to be deleted will be replaced with the last -// entry. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, offline licenses 2 and 3 will be selected to be +// moved. +// c. The selected entries will be moved. +// d. Usage table will be resized. +// e. Updated table will be saved. +// f. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 +// Storage Type unknown 1 Deleted // Offline License 1 2 Deleted -// Offline License 2 3 3 -// Offline License 3 4 2 +// Offline License 2 3 2 (moved) +// Offline License 3 4 1 (moved) // -// # of usage entries 5 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntryIsOffline) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +// # of usage entries 5 3 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntryIsOffline) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1 - uint32_t last_usage_entry_number = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - const DeviceFiles::CdmLicenseData stored_license_data{ - usage_entry_info_vector[last_usage_entry_number].key_set_id, + // Expect calls for moving offline license 3 (position 4) to position 1. + const DeviceFiles::CdmLicenseData offline_license_3_data{ + kUsageEntryInfoOfflineLicense3.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -2140,220 +2246,236 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntryIsOffline) { kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - last_usage_entry_number}; - DeviceFiles::ResponseType sub_error_code; - EXPECT_TRUE( - device_files_->StoreLicense(stored_license_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); - - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), - SetArgPointee<1>(kAnotherUsageEntry), Return(NO_ERROR))); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(kLevelDefault, last_usage_entry_number, NotNull())) - .WillOnce( - DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoOfflineLicense3, kUsageEntryInfoOfflineLicense2))) - .WillOnce(Return(true)); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoOfflineLicense3, kUsageEntryInfoOfflineLicense2, - kUsageEntryInfoOfflineLicense3))) - .WillOnce(Return(true)); - // Expecting three calls to RetrieveLicense(), twice by usage table when - // swapping (probing for swap, and the actual swap_, then by test case - // to verify data is still there. + static_cast(4)}; EXPECT_CALL(*device_files_, RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, NotNull(), NotNull())) - .Times(3); + .Times(2) // First to get entry, then again to update. + .WillRepeatedly( + DoAll(SetArgPointee<1>(offline_license_3_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(1)).WillOnce(Return(NO_ERROR)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Expect calls for moving offline license 2 (position 3) to position 2. + const DeviceFiles::CdmLicenseData offline_license_2_data{ + kUsageEntryInfoOfflineLicense2.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(3)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())) + .Times(2) // First to get entry, then again to update. + .WillRepeatedly( + DoAll(SetArgPointee<1>(offline_license_2_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(2)).WillOnce(Return(NO_ERROR)); - DeviceFiles::CdmLicenseData retrieved_license_data; - EXPECT_TRUE( - device_files_->RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, - &retrieved_license_data, &sub_error_code)); + // Common to both moves. + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*device_files_, StoreLicense(_, NotNull())) + .Times(2) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), + SetArgPointee<1>(kAnotherUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)) + .Times(2) + .WillRepeatedly(Return(true)); - EXPECT_EQ(kActiveLicenseState, retrieved_license_data.state); - EXPECT_EQ(kPsshData, retrieved_license_data.pssh_data); - EXPECT_EQ(kKeyRequest, retrieved_license_data.license_request); - EXPECT_EQ(kKeyResponse, retrieved_license_data.license); - EXPECT_EQ(kKeyRenewalRequest, retrieved_license_data.license_renewal_request); - EXPECT_EQ(kKeyRenewalResponse, retrieved_license_data.license_renewal); - EXPECT_EQ(kReleaseServerUrl, retrieved_license_data.release_server_url); - EXPECT_EQ(kPlaybackStartTime, retrieved_license_data.playback_start_time); - EXPECT_EQ(kPlaybackStartTime + kPlaybackDuration, - retrieved_license_data.last_playback_time); - EXPECT_EQ(kGracePeriodEndTime, retrieved_license_data.grace_period_end_time); - EXPECT_EQ(kEmptyAppParameters.size(), - retrieved_license_data.app_parameters.size()); - EXPECT_EQ(kAnotherUsageEntry, retrieved_license_data.usage_entry); - EXPECT_EQ(usage_entry_number_to_be_deleted, - retrieved_license_data.usage_entry_number); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); + // Shrink to contain the remaining valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 3, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kYetAnotherUsageEntry), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kYetAnotherUsageEntry, + ElementsAre(kUsageEntryInfoSecureStop1, + kUsageEntryInfoOfflineLicense3, + kUsageEntryInfoOfflineLicense2))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 3; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Usage entry to be deleted is not last. -// 2. Last entry is a secure stop. +// 2. Last entries are valid streaming licenses. // // Attempting to delete the entry in (1) will result in: -// a. The usage entry requested to be deleted will be replaced with the last -// entry. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, secure stop 2 and 3 will be selected to be +// moved. +// c. The selected entries will be moved. +// d. Usage table will be resized. +// e. Updated table will be saved. +// f. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 -// Storage Type unknown 1 1 +// Storage Type unknown 1 Deleted // Secure stop 1 2 Deleted -// Secure stop 2 3 3 -// Secure stop 3 4 2 +// Secure stop 2 3 2 (moved) +// Secure stop 3 4 1 (moved) // -// # of usage entries 5 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntryIsSecureStop) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +// # of usage entries 5 3 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntryIsSecureStop) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoSecureStop3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoSecureStop1 - uint32_t last_usage_entry_number = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoSecureStop3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), - SetArgPointee<1>(kAnotherUsageEntry), Return(NO_ERROR))); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(kLevelDefault, last_usage_entry_number, NotNull())) - .WillOnce( - DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); - + // Expect calls for moving streaming license 3 (position 4) to position 1. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop3.usage_info_file_name, kUsageEntryInfoSecureStop3.key_set_id, NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) - .WillRepeatedly( - DoAll(SetArgPointee<2>(kProviderSessionToken), - SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), - SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(last_usage_entry_number), Return(true))); + .Times(2) // First to get entry, second to update. + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(4), Return(true))); + EXPECT_CALL( + *device_files_, + DeleteUsageInfo(kUsageEntryInfoSecureStop3.usage_info_file_name, _)) + .WillOnce(Return(true)); + EXPECT_CALL( + *device_files_, + StoreUsageInfo(_, _, _, kUsageEntryInfoSecureStop3.usage_info_file_name, + kUsageEntryInfoSecureStop3.key_set_id, _, 1)) + .WillOnce(Return(true)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(1)); + // Expect calls for moving streaming license 2 (position 3) to position 2. EXPECT_CALL(*device_files_, - DeleteUsageInfo(kUsageEntryInfoSecureStop3.usage_info_file_name, - kProviderSessionToken)) - .WillOnce(Return(true)); - + RetrieveUsageInfoByKeySetId( + kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop2.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) // First to get entry, second to update. + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(3), Return(true))); EXPECT_CALL( *device_files_, - StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, - kUsageEntryInfoSecureStop3.usage_info_file_name, - kUsageEntryInfoSecureStop3.key_set_id, kAnotherUsageEntry, - usage_entry_number_to_be_deleted)) + DeleteUsageInfo(kUsageEntryInfoSecureStop2.usage_info_file_name, _)) .WillOnce(Return(true)); - EXPECT_CALL( *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoSecureStop3, kUsageEntryInfoSecureStop2))) + StoreUsageInfo(_, _, _, kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop2.key_set_id, _, 2)) + .WillOnce(Return(true)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(2)); + + // Common to both moves. + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), + SetArgPointee<1>(kAnotherUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)) + .Times(2) + .WillRepeatedly(Return(true)); + + // Shrink to contain the remaining valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 3, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kYetAnotherUsageEntry), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kYetAnotherUsageEntry, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop3, + kUsageEntryInfoSecureStop2))) .WillOnce(Return(true)); - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoSecureStop3, kUsageEntryInfoSecureStop2, - kUsageEntryInfoSecureStop3))) - .WillOnce(Return(true)); - - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 3; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Usage entry to be deleted is not last. // 2. Last few entries are of storage type unknown. -// 3. Entry that preceeds those in (2) is an offline license. +// 3. Entry that precedes those in (2) is an offline license. // // Attempting to delete the entry in (1) will result in: -// a. The entry being deleted and replaced with the offline entry in (3). -// b. The entries with unknown storage type in (2) will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, offline licenses 2 and 3 will be selected to be +// moved. +// c. The selected entries will be moved. +// d. Usage table will be resized. +// e. Updated table will be saved. +// f. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Offline License 1 2 Deleted -// Offline License 2 3 3 -// Offline License 3 4 2 -// Storage Type unknown 5 Deleted -// Storage Type unknown 6 Deleted +// Offline License 2 3 2 (moved) +// Offline License 3 4 1 (moved) +// Storage Type Unknown 5 Deleted +// Storage Type Unknown 6 Deleted // // # of usage entries 7 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreOfflineAndUnknknown) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +TEST_F(UsageTableHeaderTest, + InvalidateEntry_LastEntriesAreOfflineAndUnknknown) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 5; // kUsageEntryInfoOfflineLicense1 - uint32_t last_valid_usage_entry_number = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - const DeviceFiles::CdmLicenseData stored_license_data{ - usage_entry_info_vector[last_valid_usage_entry_number].key_set_id, + // Expect calls for moving offline license 3 (position 4) to position 1. + const DeviceFiles::CdmLicenseData offline_license_3_data{ + kUsageEntryInfoOfflineLicense3.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -2366,105 +2488,106 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreOfflineAndUnknknown) { kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - last_valid_usage_entry_number}; - DeviceFiles::ResponseType sub_error_code; - EXPECT_TRUE( - device_files_->StoreLicense(stored_license_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); - - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_valid_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), - SetArgPointee<1>(kAnotherUsageEntry), Return(NO_ERROR))); - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader(kLevelDefault, - last_valid_usage_entry_number, NotNull())) - .WillOnce( - DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoOfflineLicense3, kUsageEntryInfoOfflineLicense2))) - .WillOnce(Return(true)); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoOfflineLicense3, kUsageEntryInfoOfflineLicense2, - kUsageEntryInfoOfflineLicense3, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown))) - .WillOnce(Return(true)); - // Expecting three calls to RetrieveLicense(), twice by usage table when - // swapping (probing for swap, and the actual swap_, then by test case - // to verify data is still there. + static_cast(4)}; EXPECT_CALL(*device_files_, RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, NotNull(), NotNull())) - .Times(3); + .Times(2) // First to get entry, then again to update. + .WillRepeatedly( + DoAll(SetArgPointee<1>(offline_license_3_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(1)).WillOnce(Return(NO_ERROR)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Expect calls for moving offline license 2 (position 3) to position 2. + const DeviceFiles::CdmLicenseData offline_license_2_data{ + kUsageEntryInfoOfflineLicense2.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(3)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())) + .Times(2) // First to get entry, then again to update. + .WillRepeatedly( + DoAll(SetArgPointee<1>(offline_license_2_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(2)).WillOnce(Return(NO_ERROR)); - DeviceFiles::CdmLicenseData retrieved_license_data; - EXPECT_TRUE( - device_files_->RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, - &retrieved_license_data, &sub_error_code)); + // Common to both moves. + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*device_files_, StoreLicense(_, NotNull())) + .Times(2) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), + SetArgPointee<1>(kAnotherUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)) + .Times(2) + .WillRepeatedly(Return(true)); - EXPECT_EQ(kActiveLicenseState, retrieved_license_data.state); - EXPECT_EQ(kPsshData, retrieved_license_data.pssh_data); - EXPECT_EQ(kKeyRequest, retrieved_license_data.license_request); - EXPECT_EQ(kKeyResponse, retrieved_license_data.license); - EXPECT_EQ(kKeyRenewalRequest, retrieved_license_data.license_renewal_request); - EXPECT_EQ(kKeyRenewalResponse, retrieved_license_data.license_renewal); - EXPECT_EQ(kReleaseServerUrl, retrieved_license_data.release_server_url); - EXPECT_EQ(kPlaybackStartTime, retrieved_license_data.playback_start_time); - EXPECT_EQ(kPlaybackStartTime + kPlaybackDuration, - retrieved_license_data.last_playback_time); - EXPECT_EQ(kGracePeriodEndTime, retrieved_license_data.grace_period_end_time); - EXPECT_EQ(kEmptyAppParameters.size(), - retrieved_license_data.app_parameters.size()); - EXPECT_EQ(kAnotherUsageEntry, retrieved_license_data.usage_entry); - EXPECT_EQ(usage_entry_number_to_be_deleted, - retrieved_license_data.usage_entry_number); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); + // Shrink to contain the remaining valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 3, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kYetAnotherUsageEntry), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kYetAnotherUsageEntry, + ElementsAre(kUsageEntryInfoSecureStop1, + kUsageEntryInfoOfflineLicense3, + kUsageEntryInfoOfflineLicense2))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 3; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: -// 1. Usage entry to be deleted is not last. +// 1. Usage entry to be deleted is not last (Secure stop 1) // 2. Last few entries are of storage type unknown. -// 3. Entry that preceeds those in (2) is a secure stop. // // Attempting to delete the entry in (1) will result in: -// a. The entry being deleted and replaced with the secure stop entry in (3). -// b. The entries with unknown storage type in (2) will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, secure stop 2 and 3 will be selected to be +// moved. +// c. The selected entries will be moved. +// d. Usage table will be resized. +// e. Updated table will be saved. +// f. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 -// Storage Type unknown 1 1 +// Storage Type unknown 1 Deleted // Secure stop 1 2 Deleted -// Secure stop 2 3 3 -// Secure stop 3 4 2 +// Secure stop 2 3 2 (Moved) +// Secure stop 3 4 1 (Moved) // Storage Type unknown 5 Deleted // Storage Type unknown 6 Deleted // -// # of usage entries 7 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreSecureStopAndUnknknown) { +// # of usage entries 7 3 +TEST_F(UsageTableHeaderTest, + InvalidateEntry_LastEntriesAreSecureStopAndUnknknown) { std::vector usage_entry_info_vector; const CdmUsageEntryInfo usage_entry_info_array[] = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, @@ -2475,75 +2598,90 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreSecureStopAndUnknknown) { sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 5; // kUsageEntryInfoSecureStop1 - uint32_t last_valid_usage_entry_number = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoSecureStop3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_valid_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), - SetArgPointee<1>(kAnotherUsageEntry), Return(NO_ERROR))); - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader(kLevelDefault, - last_valid_usage_entry_number, NotNull())) - .WillOnce( - DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); - + // Expect calls for moving streaming license 3 (position 4) to position 1. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop3.usage_info_file_name, kUsageEntryInfoSecureStop3.key_set_id, NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) - .WillRepeatedly( - DoAll(SetArgPointee<2>(kProviderSessionToken), - SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), - SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(last_valid_usage_entry_number), Return(true))); + .Times(2) // First to get entry, second to update. + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(4), Return(true))); + EXPECT_CALL( + *device_files_, + DeleteUsageInfo(kUsageEntryInfoSecureStop3.usage_info_file_name, _)) + .WillOnce(Return(true)); + EXPECT_CALL( + *device_files_, + StoreUsageInfo(_, _, _, kUsageEntryInfoSecureStop3.usage_info_file_name, + kUsageEntryInfoSecureStop3.key_set_id, _, 1)) + .WillOnce(Return(true)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(1)); + + // Expect calls for moving streaming license 2 (position 3) to position 2. + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop2.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) // First to get entry, second to update. + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(3), Return(true))); + EXPECT_CALL( + *device_files_, + DeleteUsageInfo(kUsageEntryInfoSecureStop2.usage_info_file_name, _)) + .WillOnce(Return(true)); + EXPECT_CALL( + *device_files_, + StoreUsageInfo(_, _, _, kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop2.key_set_id, _, 2)) + .WillOnce(Return(true)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(2)); + + // Common to both moves. + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), + SetArgPointee<1>(kAnotherUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)) + .Times(2) + .WillRepeatedly(Return(true)); + + // Shrink to contain the remaining valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 3, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kYetAnotherUsageEntry), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - DeleteUsageInfo(kUsageEntryInfoSecureStop3.usage_info_file_name, - kProviderSessionToken)) + StoreUsageTableInfo(kYetAnotherUsageEntry, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop3, + kUsageEntryInfoSecureStop2))) .WillOnce(Return(true)); - EXPECT_CALL( - *device_files_, - StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, - kUsageEntryInfoSecureStop3.usage_info_file_name, - kUsageEntryInfoSecureStop3.key_set_id, kAnotherUsageEntry, - usage_entry_number_to_be_deleted)) - .WillOnce(Return(true)); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoSecureStop3, kUsageEntryInfoSecureStop2))) - .WillOnce(Return(true)); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoSecureStop3, kUsageEntryInfoSecureStop2, - kUsageEntryInfoSecureStop3, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown))) - .WillOnce(Return(true)); - - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 3; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // If the crypto session says the usage table header is stale, init should fail.