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.