diff --git a/libwvdrmengine/cdm/core/include/usage_table_header.h b/libwvdrmengine/cdm/core/include/usage_table_header.h index 4fd992f5..269a9ab5 100644 --- a/libwvdrmengine/cdm/core/include/usage_table_header.h +++ b/libwvdrmengine/cdm/core/include/usage_table_header.h @@ -67,9 +67,15 @@ class UsageTableHeader { // the entry with the provided |crypto_session|. The index of the new // usage entry will be returned by |usage_entry_number|. // + // Type of entry depends on the value of |persistent_license|: + // false - usage info / secure stop record + // true - offline license + // // Threading: Method takes exclusive use of |usage_table_header_lock_|. virtual CdmResponseType AddEntry(CryptoSession* crypto_session, + bool persistent_license, const CdmKeySetId& key_set_id, + const std::string& usage_info_filename, const CdmKeyResponse& license_message, uint32_t* usage_entry_number); // Threading: Method takes exclusive use of |usage_table_header_lock_|. @@ -115,6 +121,9 @@ class UsageTableHeader { return potential_table_capacity_ == 0; } + // Returns the number of entries currently tracked by the CDM that + // are related to usage info (streaming licenses). + size_t UsageInfoCount() const; // Returns the number of entries currently tracked by the CDM that // are related to offline licenses. size_t OfflineEntryCount() const; @@ -196,12 +205,15 @@ class UsageTableHeader { // unoccupied (released). bool IsEntryUnoccupied(const uint32_t usage_entry_number) const; - // Populates the entry's meta-data with the required information based + // SetOfflineEntryInfo() and SetUsageInfoEntryInfo() populate the // entry meta-data with the required information based on the type - // on the license content. + // of entry. void SetOfflineEntryInfo(const uint32_t usage_entry_number, const std::string& key_set_id, const CdmKeyResponse& license_message); + void SetUsageInfoEntryInfo(const uint32_t usage_entry_number, + const std::string& key_set_id, + const std::string& usage_info_file_name); // Shrinks the table, removing all trailing unoccupied entries. // |usage_entry_info_| will be resized appropriately. diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 9c1295a7..a1bc5203 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -484,8 +484,9 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { LOGE("CDM does not support secure stop licenses"); return ADD_KEY_ERROR; } - sts = usage_table_header_->AddEntry(crypto_session_.get(), key_set_id_, - key_response, &usage_entry_number_); + sts = usage_table_header_->AddEntry( + crypto_session_.get(), /* is_persistent */ true, key_set_id_, + /* usage_info_filename */ "", key_response, &usage_entry_number_); crypto_metrics_->usage_table_header_add_entry_.Increment(sts); if (sts != NO_ERROR) return sts; provider_session_token_ = std::move(provider_session_token); diff --git a/libwvdrmengine/cdm/core/src/usage_table_header.cpp b/libwvdrmengine/cdm/core/src/usage_table_header.cpp index ba85f65e..39ff2954 100644 --- a/libwvdrmengine/cdm/core/src/usage_table_header.cpp +++ b/libwvdrmengine/cdm/core/src/usage_table_header.cpp @@ -96,12 +96,17 @@ bool RetrieveOfflineLicense(DeviceFiles* device_files, return true; } +bool EntryIsUsageInfo(const CdmUsageEntryInfo& info) { + // Used for stl filters. + return info.storage_type == kStorageUsageInfo; +} + bool EntryIsOfflineLicense(const CdmUsageEntryInfo& info) { // Used for stl filters. return info.storage_type == kStorageLicense; } -bool IsValidCdmSecurityLevelForUsageTable(CdmSecurityLevel security_level) { +bool IsValidCdmSecurityLevelForUsageInfo(CdmSecurityLevel security_level) { return security_level == kSecurityLevelL1 || security_level == kSecurityLevelL3; } @@ -129,7 +134,7 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, CdmSecurityLevelToString(security_level)); return false; } - if (!IsValidCdmSecurityLevelForUsageTable(security_level)) { + if (!IsValidCdmSecurityLevelForUsageInfo(security_level)) { LOGE("Invalid security level provided: security_level = %s", CdmSecurityLevelToString(security_level)); return false; @@ -221,9 +226,11 @@ bool UsageTableHeader::CreateNewTable(CryptoSession* const crypto_session) { } CdmResponseType UsageTableHeader::AddEntry( - CryptoSession* crypto_session, const CdmKeySetId& key_set_id, + CryptoSession* crypto_session, bool persistent_license, + const CdmKeySetId& key_set_id, const std::string& usage_info_file_name, const CdmKeyResponse& license_message, uint32_t* usage_entry_number) { - LOGD("key_set_id = %s, current_size = %zu", IdToString(key_set_id), + LOGD("key_set_id = %s, type = %s, current_size = %zu", IdToString(key_set_id), + persistent_license ? "OfflineLicense" : "Streaming", usage_entry_info_.size()); metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics(); @@ -244,11 +251,15 @@ CdmResponseType UsageTableHeader::AddEntry( status = RelocateNewEntry(crypto_session, usage_entry_number); if (status != NO_ERROR) return status; - SetOfflineEntryInfo(*usage_entry_number, key_set_id, license_message); + if (persistent_license) { + SetOfflineEntryInfo(*usage_entry_number, key_set_id, license_message); + } else { + SetUsageInfoEntryInfo(*usage_entry_number, key_set_id, + usage_info_file_name); + } status = RefitTable(crypto_session); if (status != NO_ERROR) { - // Clear new entry on failure. usage_entry_info_[*usage_entry_number].Clear(); return status; } @@ -369,6 +380,12 @@ CdmResponseType UsageTableHeader::InvalidateEntryInternal( return NO_ERROR; } +size_t UsageTableHeader::UsageInfoCount() const { + LOGV("Locking to count usage info (streaming license) entries"); + return std::count_if(usage_entry_info_.cbegin(), usage_entry_info_.cend(), + EntryIsUsageInfo); +} + size_t UsageTableHeader::OfflineEntryCount() const { LOGV("Locking to count offline license entries"); return std::count_if(usage_entry_info_.cbegin(), usage_entry_info_.cend(), @@ -424,8 +441,8 @@ bool UsageTableHeader::CapacityCheck(CryptoSession* const crypto_session) { } uint32_t temporary_usage_entry_number; - status = AddEntry(local_crypto_session, kDummyKeySetId, kEmptyString, - &temporary_usage_entry_number); + status = AddEntry(local_crypto_session, true, kDummyKeySetId, kEmptyString, + kEmptyString, &temporary_usage_entry_number); if (status != NO_ERROR) { LOGE("Failed to add entry for capacity test: sts = %d", static_cast(status)); @@ -579,6 +596,17 @@ void UsageTableHeader::SetOfflineEntryInfo( } } +void UsageTableHeader::SetUsageInfoEntryInfo( + const uint32_t usage_entry_number, const std::string& key_set_id, + const std::string& usage_info_file_name) { + CdmUsageEntryInfo& entry_info = usage_entry_info_[usage_entry_number]; + entry_info.Clear(); + entry_info.storage_type = kStorageUsageInfo; + entry_info.key_set_id = key_set_id; + entry_info.last_use_time = GetCurrentTime(); + entry_info.usage_info_file_name = usage_info_file_name; +} + CdmResponseType UsageTableHeader::RefitTable( CryptoSession* const crypto_session) { // Remove all unoccupied entries at end of the table. @@ -679,39 +707,57 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, usage_entry_number < usage_entry_info_.size() ? usage_entry_info_[usage_entry_number].storage_type : kStorageTypeUnknown)); - if (usage_entry_number >= usage_entry_info_.size()) { - LOGE("Entry out of bounds: usage_entry_number = %u, table_size = %zu", - usage_entry_number, usage_entry_info_.size()); - return USAGE_GET_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE; + uint32_t entry_number; + switch (usage_entry_info_[usage_entry_number].storage_type) { + case kStorageLicense: { + DeviceFiles::CdmLicenseData license_data; + DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; + 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", + static_cast(sub_error_code)); + return USAGE_GET_ENTRY_RETRIEVE_LICENSE_FAILED; + } + + entry_number = license_data.usage_entry_number; + *usage_entry = std::move(license_data.usage_entry); + break; + } + case kStorageUsageInfo: { + std::string provider_session_token; + CdmKeyMessage license_request; + CdmKeyResponse license; + std::string drm_certificate; + CryptoWrappedKey wrapped_private_key; + + 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, + &entry_number, &drm_certificate, &wrapped_private_key)) { + LOGE("Failed to retrieve usage information"); + return USAGE_GET_ENTRY_RETRIEVE_USAGE_INFO_FAILED; + } + break; + } + case kStorageTypeUnknown: + default: + LOGE( + "Cannot retrieve usage information with unknown storage type: " + "storage_type = %d", + static_cast(usage_entry_info_[usage_entry_number].storage_type)); + return USAGE_GET_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE; } - CdmUsageEntryInfo& entry_info = usage_entry_info_[usage_entry_number]; - if (entry_info.storage_type != kStorageLicense) { - LOGE( - "Cannot retrieve information not associated without a license: " - "usage_entry_number = %u", - usage_entry_number); - return USAGE_GET_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE; - } - // Retrieve license. - DeviceFiles::CdmLicenseData license_data; - DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; - 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", - static_cast(sub_error_code)); - return USAGE_GET_ENTRY_RETRIEVE_LICENSE_FAILED; - } - // Validate entry number. - if (usage_entry_number != license_data.usage_entry_number) { + + if (usage_entry_number != entry_number) { LOGE( "Usage entry number mismatch: expected_usage_entry_number = %u, " "retrieved_usage_entry_number = %u", - usage_entry_number, license_data.usage_entry_number); + usage_entry_number, entry_number); return USAGE_ENTRY_NUMBER_MISMATCH; } - *usage_entry = std::move(license_data.usage_entry); return NO_ERROR; } @@ -723,37 +769,66 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, usage_entry_number < usage_entry_info_.size() ? usage_entry_info_[usage_entry_number].storage_type : kStorageTypeUnknown)); - if (usage_entry_number >= usage_entry_info_.size()) { - LOGE("Entry out of bounds: usage_entry_number = %u, table_size = %zu", - usage_entry_number, usage_entry_info_.size()); - return USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE; - } - CdmUsageEntryInfo& entry_info = usage_entry_info_[usage_entry_number]; - if (entry_info.storage_type != kStorageLicense) { - LOGE( - "Cannot store information not associated without a license: " - "usage_entry_number = %u", - usage_entry_number); - return USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE; - } - // Retrieve existing. - DeviceFiles::CdmLicenseData license_data; - DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; - if (!device_files->RetrieveLicense( - usage_entry_info_[usage_entry_number].key_set_id, &license_data, - &sub_error_code)) { - LOGE("Failed to retrieve license: status = %s", - DeviceFiles::ResponseTypeToString(sub_error_code)); - return USAGE_STORE_ENTRY_RETRIEVE_LICENSE_FAILED; - } - // Update. - license_data.usage_entry = usage_entry; - license_data.usage_entry_number = usage_entry_number; - if (!device_files->StoreLicense(license_data, &sub_error_code)) { - LOGE("Failed to store license: status = %s", - DeviceFiles::ResponseTypeToString(sub_error_code)); - return USAGE_STORE_LICENSE_FAILED; + switch (usage_entry_info_[usage_entry_number].storage_type) { + case kStorageLicense: { + DeviceFiles::CdmLicenseData license_data; + DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; + + if (!device_files->RetrieveLicense( + usage_entry_info_[usage_entry_number].key_set_id, &license_data, + &sub_error_code)) { + LOGE("Failed to retrieve license: status = %s", + DeviceFiles::ResponseTypeToString(sub_error_code)); + return USAGE_STORE_ENTRY_RETRIEVE_LICENSE_FAILED; + } + + // Update. + license_data.usage_entry = usage_entry; + license_data.usage_entry_number = usage_entry_number; + + if (!device_files->StoreLicense(license_data, &sub_error_code)) { + LOGE("Failed to store license: status = %s", + DeviceFiles::ResponseTypeToString(sub_error_code)); + return USAGE_STORE_LICENSE_FAILED; + } + break; + } + case kStorageUsageInfo: { + CdmUsageEntry entry; + uint32_t entry_number; + std::string provider_session_token, init_data, key_request, key_response, + key_renewal_request; + std::string drm_certificate; + CryptoWrappedKey wrapped_private_key; + 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, + &entry_number, &drm_certificate, &wrapped_private_key)) { + LOGE("Failed to retrieve usage information"); + return USAGE_STORE_ENTRY_RETRIEVE_USAGE_INFO_FAILED; + } + device_files->DeleteUsageInfo( + usage_entry_info_[usage_entry_number].usage_info_file_name, + provider_session_token); + 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, + usage_entry_number, drm_certificate, wrapped_private_key)) { + LOGE("Failed to store usage information"); + return USAGE_STORE_USAGE_INFO_FAILED; + } + break; + } + case kStorageTypeUnknown: + default: + LOGE( + "Cannot retrieve usage information with unknown storage type: " + "storage_type = %d", + static_cast(usage_entry_info_[usage_entry_number].storage_type)); + return USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE; } return NO_ERROR; } diff --git a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp index da402682..fccce60e 100644 --- a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp @@ -403,14 +403,30 @@ class MockDeviceFiles : public DeviceFiles { (const CdmUsageTableHeader&, const std::vector&), (override)); + MOCK_METHOD(bool, DeleteUsageInfo, (const std::string&, const std::string&), + (override)); + MOCK_METHOD(bool, RetrieveUsageInfoByKeySetId, + (const std::string&, const std::string&, std::string*, + CdmKeyMessage*, CdmKeyResponse*, CdmUsageEntry*, uint32_t*, + std::string*, CryptoWrappedKey*), + (override)); MOCK_METHOD(bool, StoreLicense, (const CdmLicenseData&, ResponseType*), (override)); MOCK_METHOD(bool, DeleteLicense, (const std::string&), (override)); MOCK_METHOD(bool, DeleteAllLicenses, (), (override)); MOCK_METHOD(bool, DeleteAllUsageInfo, (), (override)); MOCK_METHOD(bool, DeleteUsageTableInfo, (), (override)); + MOCK_METHOD(bool, StoreUsageInfo, + (const std::string&, const CdmKeyMessage&, const CdmKeyResponse&, + const std::string&, const std::string&, const CdmUsageEntry&, + uint32_t, const std::string&, const CryptoWrappedKey&), + (override)); + MOCK_METHOD(bool, RetrieveUsageInfo, + (const std::string&, std::vector*), (override)); MOCK_METHOD(bool, ListLicenses, (std::vector * key_set_ids), (override)); + MOCK_METHOD(bool, ListUsageInfoFiles, + (std::vector * usage_info_files), (override)); private: wvutil::FileSystem file_system_; @@ -473,15 +489,18 @@ class MockUsageTableHeader : public UsageTableHeader { (uint32_t, bool, DeviceFiles*, metrics::CryptoMetrics*), (override)); MOCK_METHOD(CdmResponseType, AddEntry, - (CryptoSession*, const CdmKeySetId&, const CdmKeyResponse&, - uint32_t*), + (CryptoSession*, bool, const CdmKeySetId&, const std::string&, + const CdmKeyResponse&, uint32_t*), (override)); CdmResponseType SuperAddEntry(CryptoSession* crypto_session, + bool persistent_license, const CdmKeySetId& key_set_id, + const std::string& usage_info_filename, const CdmKeyResponse& license_message, uint32_t* usage_entry_number) { - return UsageTableHeader::AddEntry(crypto_session, key_set_id, + return UsageTableHeader::AddEntry(crypto_session, persistent_license, + key_set_id, usage_info_filename, license_message, usage_entry_number); } }; @@ -1043,7 +1062,10 @@ TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailed_UnknownError) { uint32_t usage_entry_number = 0; EXPECT_NE(NO_ERROR, usage_table_header_->AddEntry( - crypto_session_, kUsageEntryInfoOfflineLicense1.key_set_id, + crypto_session_, + kUsageEntryInfoOfflineLicense1.storage_type == kStorageLicense, + kUsageEntryInfoOfflineLicense1.key_set_id, + kUsageEntryInfoOfflineLicense1.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); } @@ -1058,7 +1080,10 @@ TEST_F(UsageTableHeaderTest, AddEntry_UsageEntryTooSmall) { uint32_t usage_entry_number = 0; EXPECT_NE(NO_ERROR, usage_table_header_->AddEntry( - crypto_session_, kUsageEntryInfoOfflineLicense1.key_set_id, + crypto_session_, + kUsageEntryInfoOfflineLicense1.storage_type == kStorageLicense, + kUsageEntryInfoOfflineLicense1.key_set_id, + kUsageEntryInfoOfflineLicense1.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); } @@ -1117,7 +1142,74 @@ TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveOfflineUsageEntry) { uint32_t usage_entry_number = 0; EXPECT_EQ(NO_ERROR, usage_table_header_->AddEntry( - crypto_session_, kUsageEntryInfoOfflineLicense2.key_set_id, + crypto_session_, + kUsageEntryInfoOfflineLicense2.storage_type == kStorageLicense, + kUsageEntryInfoOfflineLicense2.key_set_id, + kUsageEntryInfoOfflineLicense2.usage_info_file_name, + kEmptyString /* license */, &usage_entry_number)); + EXPECT_EQ(final_usage_entry_number, usage_entry_number); +} + +// Initial Test state: +// 1. Table has a few entries, one of which is unoccupied. +// 2. An entry-less session requires a new entry. +// +// Attempting to add an entry will result in: +// a. A successful call to OEMCrypto to create an entry +// at the end of the current table +// b. Moving the new entry to the unoccupied entry index +// c. Shrink table to remove the now empty entry slot created in (a) +// d. Storing the new updated usage table +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Offline License 1 0 0 +// Secure Stop 1 1 1 +// Storage Type Unknown 2 Replaced +// Secure Stop 2 DNE 2 +// +// DNE = Does Not Exist +// +// # of usage entries 3 3 +TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveSecureStopUsageEntry) { + Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); + const uint32_t initial_usage_entry_number = + static_cast(kUsageEntryInfoVector.size()); + const uint32_t final_usage_entry_number = + static_cast(kUsageEntryInfoVector.size()) - 1; + std::vector expect_usage_entry_info_vector = + kUsageEntryInfoVector; + expect_usage_entry_info_vector[final_usage_entry_number] = + kUsageEntryInfoSecureStop2; + + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(initial_usage_entry_number), + Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(final_usage_entry_number)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader( + kLevelDefault, + static_cast(expect_usage_entry_info_vector.size()), + NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kYetAnotherUsageTableHeader), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kYetAnotherUsageTableHeader, + expect_usage_entry_info_vector)) + .WillOnce(Return(true)); + + uint32_t usage_entry_number = 0; + EXPECT_EQ(NO_ERROR, + usage_table_header_->AddEntry( + crypto_session_, + kUsageEntryInfoSecureStop2.storage_type == kStorageLicense, + kUsageEntryInfoSecureStop2.key_set_id, + kUsageEntryInfoSecureStop2.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); EXPECT_EQ(final_usage_entry_number, usage_entry_number); } @@ -1145,7 +1237,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveOfflineUsageEntry) { // Storage Type Unknown DNE Removed (never stored) // Storage Type Unknown DNE Removed (never stored) // Storage Type Unknown DNE Removed (never stored) -// Offline License 2 DNE 2 +// Secure Stop 2 DNE 2 // // DNE = Does Not Exist // @@ -1162,7 +1254,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_SkipUsageEntries) { std::vector expect_usage_entry_info_vector = kUsageEntryInfoVector; expect_usage_entry_info_vector[final_usage_entry_number] = - kUsageEntryInfoOfflineLicense2; + kUsageEntryInfoSecureStop2; EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(initial_usage_entry_number), @@ -1187,7 +1279,10 @@ TEST_F(UsageTableHeaderTest, AddEntry_SkipUsageEntries) { uint32_t usage_entry_number = 0; EXPECT_EQ(NO_ERROR, usage_table_header_->AddEntry( - crypto_session_, kUsageEntryInfoOfflineLicense2.key_set_id, + crypto_session_, + kUsageEntryInfoSecureStop2.storage_type == kStorageLicense, + kUsageEntryInfoSecureStop2.key_set_id, + kUsageEntryInfoSecureStop2.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); EXPECT_EQ(final_usage_entry_number, usage_entry_number); } @@ -1209,7 +1304,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_SkipUsageEntries) { // Offline License 1 0 0 // Secure Stop 1 1 1 // Storage Type Unknown 2 2 -// Offline License 2 DNE 3 +// Secure Stop 2 DNE 3 // // DNE = Does Not Exist // @@ -1222,7 +1317,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_CannotMoveNewEntry) { static_cast(kUsageEntryInfoVector.size()) - 1; std::vector expect_usage_entry_info_vector = kUsageEntryInfoVector; - expect_usage_entry_info_vector.push_back(kUsageEntryInfoOfflineLicense2); + expect_usage_entry_info_vector.push_back(kUsageEntryInfoSecureStop2); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) .WillOnce( @@ -1240,7 +1335,10 @@ TEST_F(UsageTableHeaderTest, AddEntry_CannotMoveNewEntry) { uint32_t usage_entry_number = 0; EXPECT_EQ(NO_ERROR, usage_table_header_->AddEntry( - crypto_session_, kUsageEntryInfoOfflineLicense2.key_set_id, + crypto_session_, + kUsageEntryInfoSecureStop2.storage_type == kStorageLicense, + kUsageEntryInfoSecureStop2.key_set_id, + kUsageEntryInfoSecureStop2.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); EXPECT_EQ(final_usage_entry_number, usage_entry_number); } @@ -1262,7 +1360,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_CannotMoveNewEntry) { // Offline License 1 0 0 // Secure Stop 1 1 1 // Storage Type Unknown 2 Replaced -// Offline License 2 DNE 2 +// Secure Stop 2 DNE 2 // Storage Type Unknown DNE 3 (created when new entry moved) // // DNE = Does Not Exist @@ -1277,7 +1375,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_CannotShinkAfterMove) { std::vector expect_usage_entry_info_vector = kUsageEntryInfoVector; expect_usage_entry_info_vector[final_usage_entry_number] = - kUsageEntryInfoOfflineLicense2; + kUsageEntryInfoSecureStop2; expect_usage_entry_info_vector.push_back(kUsageEntryInfoStorageTypeUnknown); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) @@ -1303,7 +1401,10 @@ TEST_F(UsageTableHeaderTest, AddEntry_CannotShinkAfterMove) { uint32_t usage_entry_number = 0; EXPECT_EQ(NO_ERROR, usage_table_header_->AddEntry( - crypto_session_, kUsageEntryInfoOfflineLicense2.key_set_id, + crypto_session_, + kUsageEntryInfoSecureStop2.storage_type == kStorageLicense, + kUsageEntryInfoSecureStop2.key_set_id, + kUsageEntryInfoSecureStop2.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); EXPECT_EQ(final_usage_entry_number, usage_entry_number); } @@ -1357,7 +1458,10 @@ TEST_F(UsageTableHeaderTest, uint32_t usage_entry_number = 0; EXPECT_EQ(NO_ERROR, mock_usage_table_header->SuperAddEntry( - crypto_session_, kUsageEntryInfoOfflineLicense6.key_set_id, + crypto_session_, + kUsageEntryInfoOfflineLicense6.storage_type == kStorageLicense, + kUsageEntryInfoOfflineLicense6.key_set_id, + kUsageEntryInfoOfflineLicense6.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); // Verify added/deleted usage entry number and entries @@ -1396,7 +1500,9 @@ TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailsEveryTime) { uint32_t usage_entry_number; EXPECT_EQ(INSUFFICIENT_CRYPTO_RESOURCES, mock_usage_table_header->SuperAddEntry( - crypto_session_, kUsageEntryInfoOfflineLicense6.key_set_id, + crypto_session_, true /* persistent */, + kUsageEntryInfoOfflineLicense6.key_set_id, + kUsageEntryInfoOfflineLicense6.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); // Verify the number of entries deleted. @@ -1710,6 +1816,76 @@ TEST_F(UsageTableHeaderTest, 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 precedes those in (1). +// +// Attempting to delete the entry in (2) will result in: +// 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 Deleted +// Secure stop 1 2 Deleted +// Secure stop 2 3 Deleted (because missing) +// Secure stop 3 4 Deleted (because missing) +// +// # of usage entries 5 1 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastSecureStopEntriesAreMissing) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, + kUsageEntryInfoSecureStop3}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // Streaming license 2 and 3 cannot be retrieved. + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop2.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) + .WillOnce(Return(false)); + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + kUsageEntryInfoSecureStop3.usage_info_file_name, + kUsageEntryInfoSecureStop3.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), 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, + ElementsAre(kUsageEntryInfoOfflineLicense1))) + .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 = 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. @@ -1811,6 +1987,89 @@ TEST_F(UsageTableHeaderTest, 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 precedes those in (1). +// +// Attempting to delete the entry in (2) will result in: +// 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 Deleted +// Secure stop 1 2 Deleted +// Secure stop 2 3 Deleted (because incorrect #) +// Secure stop 3 4 Deleted (because incorrect #) +// +// # of usage entries 5 1 +TEST_F(UsageTableHeaderTest, + InvalidateEntry_LastSecureStopEntriesHaveIncorrectUsageEntryNumber) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, + kUsageEntryInfoSecureStop3}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // 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(), NotNull(), NotNull())) + .WillOnce(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(2) /* Mismatch */, + SetArgPointee<7>(kDrmCertificate), + SetArgPointee<8>(kCryptoWrappedKey), Return(true))); + + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + kUsageEntryInfoSecureStop3.usage_info_file_name, + kUsageEntryInfoSecureStop3.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(3) /* Mismatch */, + SetArgPointee<7>(kDrmCertificate), + SetArgPointee<8>(kCryptoWrappedKey), 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, + ElementsAre(kUsageEntryInfoOfflineLicense1))) + .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 = 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 precedes those in (1). @@ -1999,6 +2258,83 @@ TEST_F(UsageTableHeaderTest, 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 secure stop and calling +// OEMCrypto_MoveUsageEntry on it will fail. +// +// Attempting to delete the entry in (1) will result in: +// 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 +// 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, + InvalidateEntry_LastEntryIsSecureStop_MoveSecureStopEntryFailed) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, + kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, + kUsageEntryInfoSecureStop3}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // 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(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), + SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(4), + SetArgPointee<7>(kDrmCertificate), + SetArgPointee<8>(kCryptoWrappedKey), Return(true))); + + // 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_->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 (Offline license 1) // 2. Last few entries are of storage type unknown. @@ -2124,6 +2460,112 @@ TEST_F(UsageTableHeaderTest, EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } +// Initial Test state: +// 1. Usage entry to be deleted is not last (Secure stop 1) +// 2. Last few entries are of storage type unknown. +// 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. 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 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, + InvalidateEntry_LastEntriesAreSecureStopAndUnknown_MoveOfflineEntryFailed) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, + kUsageEntryInfoSecureStop3, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + 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(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), + SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(4), + SetArgPointee<7>(kDrmCertificate), + SetArgPointee<8>(kCryptoWrappedKey), 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(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), + SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(3), + SetArgPointee<7>(kDrmCertificate), + SetArgPointee<8>(kCryptoWrappedKey), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); + + // Expect a call to shrink table to cut off only the unknown entries + // at the end of the table. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 5, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + + // 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_->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 entries are valid offline licenses. @@ -2250,6 +2692,125 @@ TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntryIsOffline) { 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 entries are valid streaming licenses. +// +// Attempting to delete the entry in (1) will result in: +// 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 Deleted +// Secure stop 1 2 Deleted +// Secure stop 2 3 2 (moved) +// Secure stop 3 4 1 (moved) +// +// # of usage entries 5 3 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntryIsSecureStop) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, + kUsageEntryInfoSecureStop3}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // 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(), 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>(4), + SetArgPointee<7>(kDrmCertificate), + SetArgPointee<8>(kCryptoWrappedKey), 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(), 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), + SetArgPointee<7>(kDrmCertificate), + SetArgPointee<8>(kCryptoWrappedKey), 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_, + StoreUsageTableInfo(kYetAnotherUsageEntry, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop3, + kUsageEntryInfoSecureStop2))) + .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 few entries are of storage type unknown. @@ -2381,6 +2942,130 @@ TEST_F(UsageTableHeaderTest, EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } +// Initial Test state: +// 1. Usage entry to be deleted is not last (Secure stop 1) +// 2. Last few entries are of storage type unknown. +// +// Attempting to delete the entry in (1) will result in: +// 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 Deleted +// Secure stop 1 2 Deleted +// 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 3 +TEST_F(UsageTableHeaderTest, + InvalidateEntry_LastEntriesAreSecureStopAndUnknknown) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, + kUsageEntryInfoSecureStop3, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // 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(), 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>(4), + SetArgPointee<7>(kDrmCertificate), + SetArgPointee<8>(kCryptoWrappedKey), 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(), 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), + SetArgPointee<7>(kDrmCertificate), + SetArgPointee<8>(kCryptoWrappedKey), 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_, + StoreUsageTableInfo(kYetAnotherUsageEntry, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop3, + kUsageEntryInfoSecureStop2))) + .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 (Secure stop 1) // 2. All other entries are invalid. @@ -3268,8 +3953,10 @@ TEST_F(UsageTableHeaderTest, LruLastUsedTime_CreateLicenseEntry) { // The Call. uint32_t usage_entry_number = 0; EXPECT_EQ(NO_ERROR, usage_table_header_->AddEntry( - crypto_session_, expected_new_entry.key_set_id, - kEmptyString, &usage_entry_number)); + crypto_session_, true /* persistent_license */, + expected_new_entry.key_set_id, + /* usage_info_file_name */ kEmptyString, kEmptyString, + &usage_entry_number)); EXPECT_EQ(expected_usage_entry_number, usage_entry_number); EXPECT_EQ(expected_usage_info_list, usage_table_header_->usage_entry_info());