diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index 41ffac8c..377c9e0c 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -129,6 +129,8 @@ class DeviceFiles { const std::string& usage_info_file_name, std::vector* provider_session_tokens); + virtual bool DeleteAllUsageInfo(); + // Retrieve one usage info from the file. Subsequent calls will retrieve // subsequent entries in the table for this app_id. virtual bool RetrieveUsageInfo( @@ -186,6 +188,8 @@ class DeviceFiles { CdmUsageTableHeader* usage_table_header, std::vector* usage_entry_info); + virtual bool DeleteUsageTableInfo(); + private: // Extract serial number and system ID from DRM Device certificate bool ExtractDeviceInfo(const std::string& device_certificate, diff --git a/libwvdrmengine/cdm/core/include/usage_table_header.h b/libwvdrmengine/cdm/core/include/usage_table_header.h index 312123f7..f38a96ef 100644 --- a/libwvdrmengine/cdm/core/include/usage_table_header.h +++ b/libwvdrmengine/cdm/core/include/usage_table_header.h @@ -8,6 +8,7 @@ #include #include +#include "crypto_session.h" #include "device_files.h" #include "file_store.h" #include "lock.h" @@ -17,8 +18,6 @@ namespace wvcdm { -class CryptoSession; - // Offline licenses/secure stops may be securely tracked using usage // tables (OEMCrypto v9-12) or usage table headers+usage entries // (OEMCrypto v13+). This class assists with the latter, synchronizing @@ -117,6 +116,8 @@ class UsageTableHeader { // data-structures Lock usage_table_header_lock_; + metrics::CryptoMetrics alternate_crypto_metrics_; + // Test related declarations friend class UsageTableHeaderTest; diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index c382e18a..252bada6 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -2085,6 +2085,8 @@ CdmResponseType CryptoSession::LoadUsageEntry( return LOAD_USAGE_ENTRY_GENERATION_SKEW; case OEMCrypto_ERROR_SIGNATURE_FAILURE: return LOAD_USAGE_ENTRY_SIGNATURE_FAILURE; + case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: + return INSUFFICIENT_CRYPTO_RESOURCES_3; default: return LOAD_USAGE_ENTRY_UNKNOWN_ERROR; } diff --git a/libwvdrmengine/cdm/core/src/device_files.cpp b/libwvdrmengine/cdm/core/src/device_files.cpp index 599ea806..2b6f8ffb 100644 --- a/libwvdrmengine/cdm/core/src/device_files.cpp +++ b/libwvdrmengine/cdm/core/src/device_files.cpp @@ -611,6 +611,15 @@ bool DeviceFiles::DeleteAllUsageInfoForApp( return RemoveFile(usage_info_file_name); } +bool DeviceFiles::DeleteAllUsageInfo() { + if (!initialized_) { + LOGW("DeviceFiles::DeleteAllUsageInfo: not initialized"); + return false; + } + return RemoveFile(kUsageInfoFileNamePrefix + std::string(kWildcard) + + kUsageInfoFileNameExt); +} + bool DeviceFiles::RetrieveUsageInfo( const std::string& usage_info_file_name, std::vector >* usage_info) { @@ -1105,6 +1114,14 @@ bool DeviceFiles::RetrieveUsageTableInfo( return true; } +bool DeviceFiles::DeleteUsageTableInfo() { + if (!initialized_) { + LOGW("DeviceFiles::DeleteUsageTableInfo: not initialized"); + return false; + } + return RemoveFile(GetUsageTableFileName()); +} + bool DeviceFiles::StoreFileWithHash(const std::string& name, const std::string& serialized_file) { // calculate SHA hash diff --git a/libwvdrmengine/cdm/core/src/usage_table_header.cpp b/libwvdrmengine/cdm/core/src/usage_table_header.cpp index 4b252244..6e5076f1 100644 --- a/libwvdrmengine/cdm/core/src/usage_table_header.cpp +++ b/libwvdrmengine/cdm/core/src/usage_table_header.cpp @@ -11,6 +11,9 @@ namespace { std::string kEmptyString; +size_t kMaxCryptoRetries = 3; +size_t kMinUsageEntriesSupported = 200; +wvcdm::CdmKeySetId kDummyKeySetId = "DummyKsid"; uint64_t kOldUsageEntryTimeSinceLicenseReceived = 0; uint64_t kOldUsageEntryTimeSinceFirstDecrypt = 0; uint64_t kOldUsageEntryTimeSinceLastDecrypt = 0; @@ -58,14 +61,42 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, } CdmResponseType status = USAGE_INFO_NOT_FOUND; + metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics(); + if (metrics == NULL) metrics = &alternate_crypto_metrics_; + if (file_handle_->RetrieveUsageTableInfo(&usage_table_header_, &usage_entry_info_)) { status = crypto_session->LoadUsageTableHeader(usage_table_header_); + + // If the usage table header has been successfully loaded, and is at + // minimum capacity (>200), we need to make sure we can still add and + // remove entries. If not, clear files/data and recreate usage header table. + if (status == NO_ERROR) { + if (usage_entry_info_.size() > kMinUsageEntriesSupported) { + uint32_t temporary_usage_entry_number; + CdmResponseType result = AddEntry(crypto_session, true, + kDummyKeySetId, kEmptyString, + &temporary_usage_entry_number); + if (result == NO_ERROR) { + result = DeleteEntry(temporary_usage_entry_number, + file_handle_.get(), metrics); + } + if (result != NO_ERROR) { + LOGE("UsageTableHeader::Init: Unable to create/delete new entry. " + "Clear usage entries, security level: %d, usage entries: %d", + security_level, usage_entry_info_.size()); + status = result; + } + } + } + if (status != NO_ERROR) { LOGE( "UsageTableHeader::Init: load usage table failed, security level: %d", security_level); file_handle_->DeleteAllLicenses(); + file_handle_->DeleteAllUsageInfo(); + file_handle_->DeleteUsageTableInfo(); usage_entry_info_.clear(); usage_table_header_.clear(); status = crypto_session->CreateUsageTableHeader(&usage_table_header_); @@ -77,11 +108,6 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, if (status != NO_ERROR) return false; file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); - metrics::CryptoMetrics alternate_metrics; - metrics::CryptoMetrics* metrics = - crypto_session->GetCryptoMetrics() != NULL ? - crypto_session->GetCryptoMetrics() : &alternate_metrics; - UpgradeFromUsageTable(file_handle_.get(), metrics); file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); } @@ -95,11 +121,25 @@ CdmResponseType UsageTableHeader::AddEntry( const CdmKeySetId& key_set_id, const std::string& usage_info_file_name, uint32_t* usage_entry_number) { LOGV("UsageTableHeader::AddEntry: Lock"); - AutoLock auto_lock(usage_table_header_lock_); - CdmResponseType status = crypto_session->CreateUsageEntry(usage_entry_number); + + metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics(); + if (metrics == NULL) metrics = &alternate_crypto_metrics_; + + uint32_t retry_count = 0; + CdmResponseType status = NO_ERROR; + do { + { + AutoLock auto_lock(usage_table_header_lock_); + status = crypto_session->CreateUsageEntry(usage_entry_number); + } + if (status == INSUFFICIENT_CRYPTO_RESOURCES_3) + DeleteEntry(retry_count, file_handle_.get(), metrics); + } while (status == INSUFFICIENT_CRYPTO_RESOURCES_3 && + ++retry_count < kMaxCryptoRetries); if (status != NO_ERROR) return status; + AutoLock auto_lock(usage_table_header_lock_); if (*usage_entry_number < usage_entry_info_.size()) { LOGE("UsageTableHeader::AddEntry: new entry %d smaller than table size: %d", *usage_entry_number, usage_entry_info_.size()); @@ -134,17 +174,34 @@ CdmResponseType UsageTableHeader::AddEntry( CdmResponseType UsageTableHeader::LoadEntry(CryptoSession* crypto_session, const CdmUsageEntry& usage_entry, uint32_t usage_entry_number) { - LOGV("UsageTableHeader::LoadEntry: Lock"); - AutoLock auto_lock(usage_table_header_lock_); + { + LOGV("UsageTableHeader::LoadEntry: Lock"); + AutoLock auto_lock(usage_table_header_lock_); - if (usage_entry_number >= usage_entry_info_.size()) { - LOGE( - "UsageTableHeader::LoadEntry: usage entry number %d larger than table " - "size: %d", - usage_entry_number, usage_entry_info_.size()); - return USAGE_INVALID_LOAD_ENTRY; + if (usage_entry_number >= usage_entry_info_.size()) { + LOGE( + "UsageTableHeader::LoadEntry: usage entry number %d larger than " + "table size: %d", + usage_entry_number, usage_entry_info_.size()); + return USAGE_INVALID_LOAD_ENTRY; + } } - return crypto_session->LoadUsageEntry(usage_entry_number, usage_entry); + metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics(); + if (metrics == NULL) metrics = &alternate_crypto_metrics_; + + uint32_t retry_count = 0; + CdmResponseType status = NO_ERROR; + do { + { + AutoLock auto_lock(usage_table_header_lock_); + status = crypto_session->LoadUsageEntry(usage_entry_number, usage_entry); + } + if (status == INSUFFICIENT_CRYPTO_RESOURCES_3) + DeleteEntry(retry_count, file_handle_.get(), metrics); + } while (status == INSUFFICIENT_CRYPTO_RESOURCES_3 && + ++retry_count < kMaxCryptoRetries); + + return status; } CdmResponseType UsageTableHeader::UpdateEntry(CryptoSession* crypto_session, diff --git a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp index e6aa5856..ddd963fa 100644 --- a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp @@ -24,8 +24,17 @@ const CdmUsageTableHeader kEmptyUsageTableHeader; const CdmUsageTableHeader kUsageTableHeader = "some usage table header"; const CdmUsageTableHeader kAnotherUsageTableHeader = "another usage table header"; +const CdmUsageTableHeader kYetAnotherUsageTableHeader = + "yet another usage table header"; +const CdmUsageTableHeader kAndAnotherUsageTableHeader = + "and another usage table header"; +const CdmUsageTableHeader kOneMoreUsageTableHeader = + "one more usage table header"; const CdmUsageEntry kUsageEntry = "usage entry"; const CdmUsageEntry kAnotherUsageEntry = "another usage entry"; +const CdmUsageEntry kYetAnotherUsageEntry = "yet another usage entry"; +const CdmUsageEntry kAndAnotherUsageEntry = "and another usage entry"; +const CdmUsageEntry kOneMoreUsageEntry = "one more usage entry"; const CdmUsageEntryInfo kUsageEntryInfoOfflineLicense1 = { .storage_type = kStorageLicense, .key_set_id = "offline_key_set_1", @@ -54,6 +63,10 @@ const CdmUsageEntryInfo kUsageEntryInfoStorageTypeUnknown = { .storage_type = kStorageTypeUnknown, .key_set_id = "", .usage_info_file_name = ""}; +const CdmUsageEntryInfo kDummyUsageEntryInfo = { + .storage_type = kStorageLicense, + .key_set_id = "DummyKsid", + .usage_info_file_name = ""}; const std::vector kEmptyLicenseList; @@ -105,6 +118,8 @@ const std::vector kEmptyUsageInfoUsageDataList; const std::vector kEmptyUsageEntryInfoVector; std::vector kUsageEntryInfoVector; +std::vector k10UsageEntryInfoVector; +std::vector k201UsageEntryInfoVector; const DeviceFiles::LicenseState kActiveLicenseState = DeviceFiles::kLicenseStateActive; @@ -128,6 +143,34 @@ void InitVectorConstants() { kUsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop1); kUsageEntryInfoVector.push_back(kUsageEntryInfoStorageTypeUnknown); + k10UsageEntryInfoVector.clear(); + k201UsageEntryInfoVector.clear(); + const CdmUsageEntryInfo* usage_entry_info = + &kUsageEntryInfoStorageTypeUnknown; + for (size_t i = 0; i < 201; ++i) { + switch (i % 4) { + case 0: + usage_entry_info = &kUsageEntryInfoOfflineLicense1; + break; + case 1: + usage_entry_info = &kUsageEntryInfoSecureStop1; + break; + case 2: + usage_entry_info = &kUsageEntryInfoOfflineLicense2; + break; + case 3: + usage_entry_info = &kUsageEntryInfoSecureStop2; + break; + default: + usage_entry_info = &kUsageEntryInfoStorageTypeUnknown; + break; + } + if (i < 10) { + k10UsageEntryInfoVector.push_back(*usage_entry_info); + } + k201UsageEntryInfoVector.push_back(*usage_entry_info); + } + kUsageInfoFileList.clear(); for (size_t i = 0; i < kUsageInfoFileArraySize; i++) { kUsageInfoFileList.push_back(kUsageInfoFileArray[i]); @@ -165,6 +208,8 @@ class MockDeviceFiles : public DeviceFiles { CdmKeyMessage*, CdmKeyResponse*, CdmUsageEntry*, uint32_t*)); MOCK_METHOD0(DeleteAllLicenses, bool()); + MOCK_METHOD0(DeleteAllUsageInfo, bool()); + MOCK_METHOD0(DeleteUsageTableInfo, bool()); MOCK_METHOD7(StoreUsageInfo, bool(const std::string&, const CdmKeyMessage&, const CdmKeyResponse&, const std::string&, @@ -201,9 +246,11 @@ class MockCryptoSession : public CryptoSession { // gmock methods using ::testing::_; +using ::testing::ElementsAreArray; using ::testing::NotNull; using ::testing::Return; using ::testing::SetArgPointee; +using ::testing::SizeIs; using ::testing::StrEq; using ::testing::UnorderedElementsAre; using ::testing::UnorderedElementsAreArray; @@ -355,10 +402,133 @@ TEST_P(UsageTableHeaderInitializationTest, UsageTableHeaderExists) { EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); } +TEST_P(UsageTableHeaderInitializationTest, 200UsageEntries) { + std::vector usage_entries_200 = k201UsageEntryInfoVector; + usage_entries_200.resize(200); + EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), + SetArgPointee<1>(usage_entries_200), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + + EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); +} + +TEST_P(UsageTableHeaderInitializationTest, + 201UsageEntries_AddEntryFails_UsageTableHeaderRecreated) { + EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), + SetArgPointee<1>(k201UsageEntryInfoVector), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(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 + uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size(); + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number), + Return(CREATE_USAGE_ENTRY_UNKNOWN_ERROR))); + + EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); +} + +TEST_P(UsageTableHeaderInitializationTest, + 201UsageEntries_DeleteEntryFails_UsageTableHeaderRecreated) { + std::vector usage_entries_202 = k201UsageEntryInfoVector; + usage_entries_202.push_back(kDummyUsageEntryInfo); + + EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), + SetArgPointee<1>(k201UsageEntryInfoVector), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); + EXPECT_CALL(*device_files_, DeleteAllUsageInfo()).WillOnce(Return(true)); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, + kEmptyUsageEntryInfoVector)) + .WillOnce(Return(true)); + + // Expectations for AddEntry + 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 + SecurityLevel security_level = + (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; + EXPECT_CALL(*crypto_session_, + Open(security_level)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader(usage_entries_202.size() - 1, NotNull())) + .WillOnce(Return(SHRINK_USAGE_TABLER_HEADER_UNKNOWN_ERROR)); + + EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); +} + +TEST_P(UsageTableHeaderInitializationTest, + 201UsageEntries_AddDeleteEntrySucceeds) { + std::vector usage_entries_202 = k201UsageEntryInfoVector; + usage_entries_202.push_back(kDummyUsageEntryInfo); + + EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), + SetArgPointee<1>(k201UsageEntryInfoVector), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + + // Expectations for AddEntry + 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 + SecurityLevel security_level = + (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; + + EXPECT_CALL(*crypto_session_, + Open(security_level)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader(usage_entries_202.size() - 1, NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + SizeIs(k201UsageEntryInfoVector.size()))) + .WillOnce(Return(true)); + + EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); +} + INSTANTIATE_TEST_CASE_P(Cdm, UsageTableHeaderInitializationTest, ::testing::Values(kSecurityLevelL1, kSecurityLevelL3)); -TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailed) { +TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailed_UnknownError) { Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); uint32_t usage_entry_number; uint32_t expect_usage_entry_number = kUsageEntryInfoVector.size(); @@ -490,6 +660,592 @@ TEST_F(UsageTableHeaderTest, AddEntry_SkipUsageEntries) { EXPECT_EQ(expect_usage_entry_number, usage_entry_number); } +TEST_F(UsageTableHeaderTest, + AddEntry_CreateUsageEntryFailsOnce_SucceedsSecondTime) { + Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); + std::vector usage_entry_info_vector_at_start = + k10UsageEntryInfoVector; + + uint32_t usage_entry_number_to_be_deleted = 0; + CdmUsageEntryInfo usage_entry_to_be_deleted = + usage_entry_info_vector_at_start[usage_entry_number_to_be_deleted]; + uint32_t usage_entry_number_to_be_moved = + usage_entry_info_vector_at_start.size() - 1; + CdmUsageEntryInfo usage_entry_to_be_moved = + usage_entry_info_vector_at_start[usage_entry_number_to_be_moved]; + + // The last entry is moved to the entry to be deleted (0) + std::vector usage_entry_info_vector_after_move = + usage_entry_info_vector_at_start; + usage_entry_info_vector_after_move[usage_entry_number_to_be_deleted] = + usage_entry_info_vector_at_start[usage_entry_number_to_be_moved]; + + // The entries are then shrunk by 1 + std::vector shrunk_usage_entry_info_vector = + usage_entry_info_vector_after_move; + shrunk_usage_entry_info_vector.resize( + shrunk_usage_entry_info_vector.size() - 1); + + // The new entry is then added to the end + uint32_t expect_usage_entry_number = k10UsageEntryInfoVector.size() - 1; + std::vector expect_usage_entry_info_vector = + shrunk_usage_entry_info_vector; + expect_usage_entry_info_vector.push_back(kUsageEntryInfoOfflineLicense3); + + // Expectations for AddEntry + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) + .WillOnce( + DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); + + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(expect_usage_entry_info_vector))) + .WillOnce(Return(true)); + + // Expectations for StoreEntry (DeleteEntry->MoveEntry) + EXPECT_CALL(*device_files_, + DeleteUsageInfo(usage_entry_to_be_moved.usage_info_file_name, + kProviderSessionToken)) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, + usage_entry_to_be_moved.usage_info_file_name, + usage_entry_to_be_moved.key_set_id, kAnotherUsageEntry, + usage_entry_number_to_be_deleted)) + .WillOnce(Return(true)); + + // Expectations for Shrink (DeleteEntry) + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader(shrunk_usage_entry_info_vector.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(shrunk_usage_entry_info_vector))) + .WillOnce(Return(true)); + + // Expectations for MoveEntry (DeleteEntry) + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_to_be_moved, + 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(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_move))) + .WillOnce(Return(true)); + + // Expectations for GetEntry (DeleteEntry) + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + usage_entry_to_be_moved.usage_info_file_name, + usage_entry_to_be_moved.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), + SetArgPointee<6>(usage_entry_number_to_be_moved), Return(true))); + + uint32_t usage_entry_number; + EXPECT_EQ(NO_ERROR, + usage_table_header_->AddEntry( + crypto_session_, + kUsageEntryInfoOfflineLicense3.storage_type == kStorageLicense, + kUsageEntryInfoOfflineLicense3.key_set_id, + kUsageEntryInfoOfflineLicense3.usage_info_file_name, + &usage_entry_number)); + EXPECT_EQ(expect_usage_entry_number, usage_entry_number); +} + +TEST_F(UsageTableHeaderTest, + AddEntry_CreateUsageEntryFailsTwice_SucceedsThirdTime) { + Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); + + // Initial usage entry info + std::vector usage_entry_info_vector_at_start = + k10UsageEntryInfoVector; + + // usage entry info after first move + uint32_t usage_entry_number_first_to_be_deleted = 0; + uint32_t usage_entry_number_first_to_be_moved = + usage_entry_info_vector_at_start.size() - 1; + + // The last entry is moved to the first entry to be deleted + CdmUsageEntryInfo usage_entry_first_to_be_moved = + usage_entry_info_vector_at_start[usage_entry_number_first_to_be_moved]; + std::vector usage_entry_info_vector_after_first_move = + usage_entry_info_vector_at_start; + usage_entry_info_vector_after_first_move[ + usage_entry_number_first_to_be_deleted] = + usage_entry_first_to_be_moved; + + // The entries are then shrunk by 1 + std::vector usage_entry_info_vector_after_first_shrink = + usage_entry_info_vector_after_first_move; + usage_entry_info_vector_after_first_shrink.resize( + usage_entry_info_vector_after_first_shrink.size() - 1); + + // usage entry info after second move + uint32_t usage_entry_number_second_to_be_deleted = 1; + uint32_t usage_entry_number_second_to_be_moved = + usage_entry_info_vector_after_first_shrink.size() - 1; + + // The last entry is moved to the second entry to be deleted + CdmUsageEntryInfo usage_entry_second_to_be_moved = + usage_entry_info_vector_after_first_shrink[ + usage_entry_number_second_to_be_moved]; + std::vector usage_entry_info_vector_after_second_move = + usage_entry_info_vector_after_first_shrink; + usage_entry_info_vector_after_second_move[ + usage_entry_number_second_to_be_deleted] = usage_entry_second_to_be_moved; + + // The entries are then shrunk by 1 + std::vector usage_entry_info_vector_after_second_shrink = + usage_entry_info_vector_after_second_move; + usage_entry_info_vector_after_second_shrink.resize( + usage_entry_info_vector_after_second_shrink.size() - 1); + + // The new entry is then added to the end + uint32_t expect_usage_entry_number = + usage_entry_info_vector_after_second_shrink.size(); + std::vector expect_usage_entry_info_vector = + usage_entry_info_vector_after_second_shrink; + expect_usage_entry_info_vector.push_back(kUsageEntryInfoOfflineLicense3); + + // Expectations for AddEntry + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) + .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) + .WillOnce( + DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); + + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kYetAnotherUsageTableHeader, + ElementsAreArray(expect_usage_entry_info_vector))) + .WillOnce(Return(true)); + + // Expectations for StoreEntry (DeleteEntry->MoveEntry) + EXPECT_CALL(*device_files_, + DeleteUsageInfo(usage_entry_first_to_be_moved.usage_info_file_name, + kProviderSessionToken)) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, + usage_entry_first_to_be_moved.usage_info_file_name, + usage_entry_first_to_be_moved.key_set_id, + kAnotherUsageEntry, + usage_entry_number_first_to_be_deleted)) + .WillOnce(Return(true)); + + EXPECT_TRUE(device_files_->StoreLicense( + usage_entry_second_to_be_moved.key_set_id, + kActiveLicenseState, kPsshData, kKeyRequest, kKeyResponse, + kKeyRenewalRequest, kKeyRenewalResponse, kReleaseServerUrl, + kPlaybackStartTime, kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, kEmptyAppParameters, kYetAnotherUsageEntry, + usage_entry_number_second_to_be_moved)); + + DeviceFiles::LicenseState license_state = DeviceFiles::kLicenseStateUnknown; + CdmInitData pssh_data; + CdmKeyMessage key_request; + CdmKeyResponse key_response; + CdmKeyMessage key_renewal_request; + CdmKeyResponse key_renewal_response; + std::string release_server_url; + int64_t playback_start_time; + int64_t last_playback_time; + int64_t grace_period_end_time; + CdmAppParameterMap app_parameters; + CdmUsageEntry usage_entry; + uint32_t usage_entry_number = ~0; + + EXPECT_TRUE(device_files_->RetrieveLicense( + usage_entry_second_to_be_moved.key_set_id, &license_state, &pssh_data, + &key_request, &key_response, &key_renewal_request, &key_renewal_response, + &release_server_url, &playback_start_time, &last_playback_time, + &grace_period_end_time, &app_parameters, &usage_entry, + &usage_entry_number)); + EXPECT_EQ(kActiveLicenseState, license_state); + EXPECT_EQ(kPsshData, pssh_data); + EXPECT_EQ(kKeyRequest, key_request); + EXPECT_EQ(kKeyResponse, key_response); + EXPECT_EQ(kKeyRenewalRequest, key_renewal_request); + EXPECT_EQ(kKeyRenewalResponse, key_renewal_response); + EXPECT_EQ(kReleaseServerUrl, release_server_url); + EXPECT_EQ(kPlaybackStartTime, playback_start_time); + EXPECT_EQ(kPlaybackStartTime + kPlaybackDuration, last_playback_time); + EXPECT_EQ(kGracePeriodEndTime, grace_period_end_time); + EXPECT_EQ(kEmptyAppParameters.size(), app_parameters.size()); + EXPECT_EQ(kYetAnotherUsageEntry, usage_entry); + EXPECT_EQ(usage_entry_number_second_to_be_moved, usage_entry_number); + + // Expectations for Shrink (DeleteEntry) + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader( + usage_entry_info_vector_after_first_shrink.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader( + usage_entry_info_vector_after_second_shrink.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kYetAnotherUsageTableHeader), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_first_shrink))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kYetAnotherUsageTableHeader, + ElementsAreArray( + usage_entry_info_vector_after_second_shrink))) + .WillOnce(Return(true)); + + // Expectations for MoveEntry (DeleteEntry) + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(4) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_first_to_be_moved, + kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_second_to_be_moved, + kYetAnotherUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + MoveUsageEntry(usage_entry_number_first_to_be_deleted)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + MoveUsageEntry(usage_entry_number_second_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))) + .WillOnce(DoAll(SetArgPointee<0>(kYetAnotherUsageTableHeader), + SetArgPointee<1>(kYetAnotherUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_first_move))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kYetAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_second_move))) + .WillOnce(Return(true)); + + // Expectations for GetEntry (DeleteEntry) + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + usage_entry_first_to_be_moved.usage_info_file_name, + usage_entry_first_to_be_moved.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), + SetArgPointee<6>(usage_entry_number_first_to_be_moved), + Return(true))); + + EXPECT_EQ(NO_ERROR, + usage_table_header_->AddEntry( + crypto_session_, + kUsageEntryInfoOfflineLicense3.storage_type == kStorageLicense, + kUsageEntryInfoOfflineLicense3.key_set_id, + kUsageEntryInfoOfflineLicense3.usage_info_file_name, + &usage_entry_number)); + EXPECT_EQ(expect_usage_entry_number, usage_entry_number); +} + +TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailsThrice) { + Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); + + // Initial usage entry info + std::vector usage_entry_info_vector_at_start = + k10UsageEntryInfoVector; + + // usage entry info after first move + uint32_t usage_entry_number_first_to_be_deleted = 0; + uint32_t usage_entry_number_first_to_be_moved = + usage_entry_info_vector_at_start.size() - 1; + + // The last entry is moved to the first entry to be deleted + CdmUsageEntryInfo usage_entry_first_to_be_moved = + usage_entry_info_vector_at_start[usage_entry_number_first_to_be_moved]; + std::vector usage_entry_info_vector_after_first_move = + usage_entry_info_vector_at_start; + usage_entry_info_vector_after_first_move[ + usage_entry_number_first_to_be_deleted] = + usage_entry_first_to_be_moved; + + // The entries are then shrunk by 1 + std::vector usage_entry_info_vector_after_first_shrink = + usage_entry_info_vector_after_first_move; + usage_entry_info_vector_after_first_shrink.resize( + usage_entry_info_vector_after_first_shrink.size() - 1); + + // usage entry info after second move + uint32_t usage_entry_number_second_to_be_deleted = 1; + uint32_t usage_entry_number_second_to_be_moved = + usage_entry_info_vector_after_first_shrink.size() - 1; + + // The last entry is moved to the second entry to be deleted + CdmUsageEntryInfo usage_entry_second_to_be_moved = + usage_entry_info_vector_after_first_shrink[ + usage_entry_number_second_to_be_moved]; + std::vector usage_entry_info_vector_after_second_move = + usage_entry_info_vector_after_first_shrink; + usage_entry_info_vector_after_second_move[ + usage_entry_number_second_to_be_deleted] = usage_entry_second_to_be_moved; + + // The entries are then shrunk by 1 + std::vector usage_entry_info_vector_after_second_shrink = + usage_entry_info_vector_after_second_move; + usage_entry_info_vector_after_second_shrink.resize( + usage_entry_info_vector_after_second_shrink.size() - 1); + + // usage entry info after third move + uint32_t usage_entry_number_third_to_be_deleted = 2; + uint32_t usage_entry_number_third_to_be_moved = + usage_entry_info_vector_after_second_shrink.size() - 1; + + // The last entry is moved to the third entry to be deleted + CdmUsageEntryInfo usage_entry_third_to_be_moved = + usage_entry_info_vector_after_second_shrink[ + usage_entry_number_third_to_be_moved]; + std::vector usage_entry_info_vector_after_third_move = + usage_entry_info_vector_after_second_shrink; + usage_entry_info_vector_after_third_move[ + usage_entry_number_third_to_be_deleted] = usage_entry_third_to_be_moved; + + // The entries are then shrunk by 1 + std::vector usage_entry_info_vector_after_third_shrink = + usage_entry_info_vector_after_third_move; + usage_entry_info_vector_after_third_shrink.resize( + usage_entry_info_vector_after_third_shrink.size() - 1); + + // Expected results after the third failure + std::vector expect_usage_entry_info_vector = + usage_entry_info_vector_after_third_shrink; + + // Expectations for AddEntry + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .Times(3) + .WillRepeatedly(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)); + + // Expectations for StoreEntry (DeleteEntry->MoveEntry) + EXPECT_CALL(*device_files_, + DeleteUsageInfo(usage_entry_first_to_be_moved.usage_info_file_name, + kProviderSessionToken)) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + DeleteUsageInfo(usage_entry_third_to_be_moved.usage_info_file_name, + kProviderSessionToken)) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, + usage_entry_first_to_be_moved.usage_info_file_name, + usage_entry_first_to_be_moved.key_set_id, + kAnotherUsageEntry, + usage_entry_number_first_to_be_deleted)) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, + usage_entry_third_to_be_moved.usage_info_file_name, + usage_entry_third_to_be_moved.key_set_id, + kOneMoreUsageEntry, + usage_entry_number_third_to_be_deleted)) + .WillOnce(Return(true)); + + EXPECT_TRUE(device_files_->StoreLicense( + usage_entry_second_to_be_moved.key_set_id, + kActiveLicenseState, kPsshData, kKeyRequest, kKeyResponse, + kKeyRenewalRequest, kKeyRenewalResponse, kReleaseServerUrl, + kPlaybackStartTime, kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, kEmptyAppParameters, kYetAnotherUsageEntry, + usage_entry_number_second_to_be_moved)); + + DeviceFiles::LicenseState license_state = DeviceFiles::kLicenseStateUnknown; + CdmInitData pssh_data; + CdmKeyMessage key_request; + CdmKeyResponse key_response; + CdmKeyMessage key_renewal_request; + CdmKeyResponse key_renewal_response; + std::string release_server_url; + int64_t playback_start_time; + int64_t last_playback_time; + int64_t grace_period_end_time; + CdmAppParameterMap app_parameters; + CdmUsageEntry usage_entry; + uint32_t usage_entry_number = ~0; + + EXPECT_TRUE(device_files_->RetrieveLicense( + usage_entry_second_to_be_moved.key_set_id, &license_state, &pssh_data, + &key_request, &key_response, &key_renewal_request, &key_renewal_response, + &release_server_url, &playback_start_time, &last_playback_time, + &grace_period_end_time, &app_parameters, &usage_entry, + &usage_entry_number)); + EXPECT_EQ(kActiveLicenseState, license_state); + EXPECT_EQ(kPsshData, pssh_data); + EXPECT_EQ(kKeyRequest, key_request); + EXPECT_EQ(kKeyResponse, key_response); + EXPECT_EQ(kKeyRenewalRequest, key_renewal_request); + EXPECT_EQ(kKeyRenewalResponse, key_renewal_response); + EXPECT_EQ(kReleaseServerUrl, release_server_url); + EXPECT_EQ(kPlaybackStartTime, playback_start_time); + EXPECT_EQ(kPlaybackStartTime + kPlaybackDuration, last_playback_time); + EXPECT_EQ(kGracePeriodEndTime, grace_period_end_time); + EXPECT_EQ(kEmptyAppParameters.size(), app_parameters.size()); + EXPECT_EQ(kYetAnotherUsageEntry, usage_entry); + EXPECT_EQ(usage_entry_number_second_to_be_moved, usage_entry_number); + + // Expectations for Shrink (DeleteEntry) + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader( + usage_entry_info_vector_after_first_shrink.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader( + usage_entry_info_vector_after_second_shrink.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kYetAnotherUsageTableHeader), + Return(NO_ERROR))); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader( + usage_entry_info_vector_after_third_shrink.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kOneMoreUsageTableHeader), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_first_shrink))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kYetAnotherUsageTableHeader, + ElementsAreArray( + usage_entry_info_vector_after_second_shrink))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kOneMoreUsageTableHeader, + ElementsAreArray( + usage_entry_info_vector_after_third_shrink))) + .WillOnce(Return(true)); + + // Expectations for MoveEntry (DeleteEntry) + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(6) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_first_to_be_moved, + kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_second_to_be_moved, + kYetAnotherUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_third_to_be_moved, + kOneMoreUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + MoveUsageEntry(usage_entry_number_first_to_be_deleted)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + MoveUsageEntry(usage_entry_number_second_to_be_deleted)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + MoveUsageEntry(usage_entry_number_third_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))) + .WillOnce(DoAll(SetArgPointee<0>(kYetAnotherUsageTableHeader), + SetArgPointee<1>(kYetAnotherUsageEntry), + Return(NO_ERROR))) + .WillOnce(DoAll(SetArgPointee<0>(kOneMoreUsageTableHeader), + SetArgPointee<1>(kOneMoreUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_first_move))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kYetAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_second_move))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kOneMoreUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_third_move))) + .WillOnce(Return(true)); + + // Expectations for GetEntry (DeleteEntry) + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + usage_entry_first_to_be_moved.usage_info_file_name, + usage_entry_first_to_be_moved.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), + SetArgPointee<6>(usage_entry_number_first_to_be_moved), + Return(true))); + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + usage_entry_third_to_be_moved.usage_info_file_name, + usage_entry_third_to_be_moved.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kOneMoreUsageEntry), + SetArgPointee<6>(usage_entry_number_third_to_be_moved), + Return(true))); + + EXPECT_EQ(INSUFFICIENT_CRYPTO_RESOURCES_3, + usage_table_header_->AddEntry( + crypto_session_, + kUsageEntryInfoOfflineLicense3.storage_type == kStorageLicense, + kUsageEntryInfoOfflineLicense3.key_set_id, + kUsageEntryInfoOfflineLicense3.usage_info_file_name, + &usage_entry_number)); +} + TEST_F(UsageTableHeaderTest, LoadEntry_InvalidEntryNumber) { Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); uint32_t usage_entry_number = kUsageEntryInfoVector.size() + 3; @@ -551,6 +1307,570 @@ TEST_F(UsageTableHeaderTest, UpdateEntry) { usage_table_header_->UpdateEntry(crypto_session_, &usage_entry)); } +TEST_F(UsageTableHeaderTest, + LoadEntry_LoadUsageEntryFailsOnce_SucceedsSecondTime) { + Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); + std::vector usage_entry_info_vector_at_start = + k10UsageEntryInfoVector; + + uint32_t usage_entry_number_to_load = 5; + + uint32_t usage_entry_number_to_be_deleted = 0; + CdmUsageEntryInfo usage_entry_to_be_deleted = + usage_entry_info_vector_at_start[usage_entry_number_to_be_deleted]; + uint32_t usage_entry_number_to_be_moved = + usage_entry_info_vector_at_start.size() - 1; + CdmUsageEntryInfo usage_entry_to_be_moved = + usage_entry_info_vector_at_start[usage_entry_number_to_be_moved]; + + // The last entry is moved to the entry to be deleted (0) + std::vector usage_entry_info_vector_after_move = + usage_entry_info_vector_at_start; + usage_entry_info_vector_after_move[usage_entry_number_to_be_deleted] = + usage_entry_info_vector_at_start[usage_entry_number_to_be_moved]; + + // The entries are then shrunk by 1 + std::vector shrunk_usage_entry_info_vector = + usage_entry_info_vector_after_move; + shrunk_usage_entry_info_vector.resize( + shrunk_usage_entry_info_vector.size() - 1); + + // Expectations for LoadEntry + std::vector expect_usage_entry_info_vector = + shrunk_usage_entry_info_vector; + + EXPECT_CALL(*crypto_session_, LoadUsageEntry(usage_entry_number_to_load, + kAndAnotherUsageEntry)) + .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) + .WillOnce(Return(NO_ERROR)); + + // Expectations for StoreEntry (DeleteEntry->MoveEntry) + EXPECT_CALL(*device_files_, + DeleteUsageInfo(usage_entry_to_be_moved.usage_info_file_name, + kProviderSessionToken)) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, + usage_entry_to_be_moved.usage_info_file_name, + usage_entry_to_be_moved.key_set_id, kAnotherUsageEntry, + usage_entry_number_to_be_deleted)) + .WillOnce(Return(true)); + + // Expectations for Shrink (DeleteEntry) + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader(shrunk_usage_entry_info_vector.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(shrunk_usage_entry_info_vector))) + .WillOnce(Return(true)); + + // Expectations for MoveEntry (DeleteEntry) + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_to_be_moved, + 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(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_move))) + .WillOnce(Return(true)); + + // Expectations for GetEntry (DeleteEntry) + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + usage_entry_to_be_moved.usage_info_file_name, + usage_entry_to_be_moved.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), + SetArgPointee<6>(usage_entry_number_to_be_moved), Return(true))); + + EXPECT_EQ(NO_ERROR, + usage_table_header_->LoadEntry( + crypto_session_, + kAndAnotherUsageEntry, + usage_entry_number_to_load)); +} + +TEST_F(UsageTableHeaderTest, + LoadEntry_LoadUsageEntryFailsTwice_SucceedsThirdTime) { + Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); + + // Initial usage entry info + std::vector usage_entry_info_vector_at_start = + k10UsageEntryInfoVector; + + uint32_t usage_entry_number_to_load = 5; + + // usage entry info after first move + uint32_t usage_entry_number_first_to_be_deleted = 0; + uint32_t usage_entry_number_first_to_be_moved = + usage_entry_info_vector_at_start.size() - 1; + + // The last entry is moved to the first entry to be deleted + CdmUsageEntryInfo usage_entry_first_to_be_moved = + usage_entry_info_vector_at_start[usage_entry_number_first_to_be_moved]; + std::vector usage_entry_info_vector_after_first_move = + usage_entry_info_vector_at_start; + usage_entry_info_vector_after_first_move[ + usage_entry_number_first_to_be_deleted] = + usage_entry_first_to_be_moved; + + // The entries are then shrunk by 1 + std::vector usage_entry_info_vector_after_first_shrink = + usage_entry_info_vector_after_first_move; + usage_entry_info_vector_after_first_shrink.resize( + usage_entry_info_vector_after_first_shrink.size() - 1); + + // usage entry info after second move + uint32_t usage_entry_number_second_to_be_deleted = 1; + uint32_t usage_entry_number_second_to_be_moved = + usage_entry_info_vector_after_first_shrink.size() - 1; + + // The last entry is moved to the second entry to be deleted + CdmUsageEntryInfo usage_entry_second_to_be_moved = + usage_entry_info_vector_after_first_shrink[ + usage_entry_number_second_to_be_moved]; + std::vector usage_entry_info_vector_after_second_move = + usage_entry_info_vector_after_first_shrink; + usage_entry_info_vector_after_second_move[ + usage_entry_number_second_to_be_deleted] = usage_entry_second_to_be_moved; + + // The entries are then shrunk by 1 + std::vector usage_entry_info_vector_after_second_shrink = + usage_entry_info_vector_after_second_move; + usage_entry_info_vector_after_second_shrink.resize( + usage_entry_info_vector_after_second_shrink.size() - 1); + + // Expectations for LoadEntry + std::vector expect_usage_entry_info_vector = + usage_entry_info_vector_after_second_shrink; + + EXPECT_CALL(*crypto_session_, LoadUsageEntry(usage_entry_number_to_load, + kAndAnotherUsageEntry)) + .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) + .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) + .WillOnce(Return(NO_ERROR)); + + // Expectations for StoreEntry (DeleteEntry->MoveEntry) + EXPECT_CALL(*device_files_, + DeleteUsageInfo(usage_entry_first_to_be_moved.usage_info_file_name, + kProviderSessionToken)) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, + usage_entry_first_to_be_moved.usage_info_file_name, + usage_entry_first_to_be_moved.key_set_id, + kAnotherUsageEntry, + usage_entry_number_first_to_be_deleted)) + .WillOnce(Return(true)); + + EXPECT_TRUE(device_files_->StoreLicense( + usage_entry_second_to_be_moved.key_set_id, + kActiveLicenseState, kPsshData, kKeyRequest, kKeyResponse, + kKeyRenewalRequest, kKeyRenewalResponse, kReleaseServerUrl, + kPlaybackStartTime, kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, kEmptyAppParameters, kYetAnotherUsageEntry, + usage_entry_number_second_to_be_moved)); + + DeviceFiles::LicenseState license_state = DeviceFiles::kLicenseStateUnknown; + CdmInitData pssh_data; + CdmKeyMessage key_request; + CdmKeyResponse key_response; + CdmKeyMessage key_renewal_request; + CdmKeyResponse key_renewal_response; + std::string release_server_url; + int64_t playback_start_time; + int64_t last_playback_time; + int64_t grace_period_end_time; + CdmAppParameterMap app_parameters; + CdmUsageEntry usage_entry; + uint32_t usage_entry_number = ~0; + + EXPECT_TRUE(device_files_->RetrieveLicense( + usage_entry_second_to_be_moved.key_set_id, &license_state, &pssh_data, + &key_request, &key_response, &key_renewal_request, &key_renewal_response, + &release_server_url, &playback_start_time, &last_playback_time, + &grace_period_end_time, &app_parameters, &usage_entry, + &usage_entry_number)); + EXPECT_EQ(kActiveLicenseState, license_state); + EXPECT_EQ(kPsshData, pssh_data); + EXPECT_EQ(kKeyRequest, key_request); + EXPECT_EQ(kKeyResponse, key_response); + EXPECT_EQ(kKeyRenewalRequest, key_renewal_request); + EXPECT_EQ(kKeyRenewalResponse, key_renewal_response); + EXPECT_EQ(kReleaseServerUrl, release_server_url); + EXPECT_EQ(kPlaybackStartTime, playback_start_time); + EXPECT_EQ(kPlaybackStartTime + kPlaybackDuration, last_playback_time); + EXPECT_EQ(kGracePeriodEndTime, grace_period_end_time); + EXPECT_EQ(kEmptyAppParameters.size(), app_parameters.size()); + EXPECT_EQ(kYetAnotherUsageEntry, usage_entry); + EXPECT_EQ(usage_entry_number_second_to_be_moved, usage_entry_number); + + // Expectations for Shrink (DeleteEntry) + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader( + usage_entry_info_vector_after_first_shrink.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader( + usage_entry_info_vector_after_second_shrink.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kYetAnotherUsageTableHeader), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_first_shrink))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kYetAnotherUsageTableHeader, + ElementsAreArray( + usage_entry_info_vector_after_second_shrink))) + .WillOnce(Return(true)); + + // Expectations for MoveEntry (DeleteEntry) + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(4) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_first_to_be_moved, + kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_second_to_be_moved, + kYetAnotherUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + MoveUsageEntry(usage_entry_number_first_to_be_deleted)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + MoveUsageEntry(usage_entry_number_second_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))) + .WillOnce(DoAll(SetArgPointee<0>(kYetAnotherUsageTableHeader), + SetArgPointee<1>(kYetAnotherUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_first_move))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kYetAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_second_move))) + .WillOnce(Return(true)); + + // Expectations for GetEntry (DeleteEntry) + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + usage_entry_first_to_be_moved.usage_info_file_name, + usage_entry_first_to_be_moved.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), + SetArgPointee<6>(usage_entry_number_first_to_be_moved), + Return(true))); + + EXPECT_EQ(NO_ERROR, + usage_table_header_->LoadEntry( + crypto_session_, + kAndAnotherUsageEntry, + usage_entry_number_to_load)); +} + +TEST_F(UsageTableHeaderTest, LoadEntry_LoadUsageEntryFailsThrice) { + Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); + + // Initial usage entry info + std::vector usage_entry_info_vector_at_start = + k10UsageEntryInfoVector; + + uint32_t usage_entry_number_to_load = 5; + + // usage entry info after first move + uint32_t usage_entry_number_first_to_be_deleted = 0; + uint32_t usage_entry_number_first_to_be_moved = + usage_entry_info_vector_at_start.size() - 1; + + // The last entry is moved to the first entry to be deleted + CdmUsageEntryInfo usage_entry_first_to_be_moved = + usage_entry_info_vector_at_start[usage_entry_number_first_to_be_moved]; + std::vector usage_entry_info_vector_after_first_move = + usage_entry_info_vector_at_start; + usage_entry_info_vector_after_first_move[ + usage_entry_number_first_to_be_deleted] = + usage_entry_first_to_be_moved; + + // The entries are then shrunk by 1 + std::vector usage_entry_info_vector_after_first_shrink = + usage_entry_info_vector_after_first_move; + usage_entry_info_vector_after_first_shrink.resize( + usage_entry_info_vector_after_first_shrink.size() - 1); + + // usage entry info after second move + uint32_t usage_entry_number_second_to_be_deleted = 1; + uint32_t usage_entry_number_second_to_be_moved = + usage_entry_info_vector_after_first_shrink.size() - 1; + + // The last entry is moved to the second entry to be deleted + CdmUsageEntryInfo usage_entry_second_to_be_moved = + usage_entry_info_vector_after_first_shrink[ + usage_entry_number_second_to_be_moved]; + std::vector usage_entry_info_vector_after_second_move = + usage_entry_info_vector_after_first_shrink; + usage_entry_info_vector_after_second_move[ + usage_entry_number_second_to_be_deleted] = usage_entry_second_to_be_moved; + + // The entries are then shrunk by 1 + std::vector usage_entry_info_vector_after_second_shrink = + usage_entry_info_vector_after_second_move; + usage_entry_info_vector_after_second_shrink.resize( + usage_entry_info_vector_after_second_shrink.size() - 1); + + // usage entry info after third move + uint32_t usage_entry_number_third_to_be_deleted = 2; + uint32_t usage_entry_number_third_to_be_moved = + usage_entry_info_vector_after_second_shrink.size() - 1; + + // The last entry is moved to the third entry to be deleted + CdmUsageEntryInfo usage_entry_third_to_be_moved = + usage_entry_info_vector_after_second_shrink[ + usage_entry_number_third_to_be_moved]; + std::vector usage_entry_info_vector_after_third_move = + usage_entry_info_vector_after_second_shrink; + usage_entry_info_vector_after_third_move[ + usage_entry_number_third_to_be_deleted] = usage_entry_third_to_be_moved; + + // The entries are then shrunk by 1 + std::vector usage_entry_info_vector_after_third_shrink = + usage_entry_info_vector_after_third_move; + usage_entry_info_vector_after_third_shrink.resize( + usage_entry_info_vector_after_third_shrink.size() - 1); + + // Expectations for LoadEntry + std::vector expect_usage_entry_info_vector = + usage_entry_info_vector_after_third_shrink; + + EXPECT_CALL(*crypto_session_, LoadUsageEntry(usage_entry_number_to_load, + kAndAnotherUsageEntry)) + .Times(3) + .WillRepeatedly(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)); + + // Expectations for StoreEntry (DeleteEntry->MoveEntry) + EXPECT_CALL(*device_files_, + DeleteUsageInfo(usage_entry_first_to_be_moved.usage_info_file_name, + kProviderSessionToken)) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + DeleteUsageInfo(usage_entry_third_to_be_moved.usage_info_file_name, + kProviderSessionToken)) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, + usage_entry_first_to_be_moved.usage_info_file_name, + usage_entry_first_to_be_moved.key_set_id, + kAnotherUsageEntry, + usage_entry_number_first_to_be_deleted)) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, + usage_entry_third_to_be_moved.usage_info_file_name, + usage_entry_third_to_be_moved.key_set_id, + kOneMoreUsageEntry, + usage_entry_number_third_to_be_deleted)) + .WillOnce(Return(true)); + + EXPECT_TRUE(device_files_->StoreLicense( + usage_entry_second_to_be_moved.key_set_id, + kActiveLicenseState, kPsshData, kKeyRequest, kKeyResponse, + kKeyRenewalRequest, kKeyRenewalResponse, kReleaseServerUrl, + kPlaybackStartTime, kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, kEmptyAppParameters, kYetAnotherUsageEntry, + usage_entry_number_second_to_be_moved)); + + DeviceFiles::LicenseState license_state = DeviceFiles::kLicenseStateUnknown; + CdmInitData pssh_data; + CdmKeyMessage key_request; + CdmKeyResponse key_response; + CdmKeyMessage key_renewal_request; + CdmKeyResponse key_renewal_response; + std::string release_server_url; + int64_t playback_start_time; + int64_t last_playback_time; + int64_t grace_period_end_time; + CdmAppParameterMap app_parameters; + CdmUsageEntry usage_entry; + uint32_t usage_entry_number = ~0; + + EXPECT_TRUE(device_files_->RetrieveLicense( + usage_entry_second_to_be_moved.key_set_id, &license_state, &pssh_data, + &key_request, &key_response, &key_renewal_request, &key_renewal_response, + &release_server_url, &playback_start_time, &last_playback_time, + &grace_period_end_time, &app_parameters, &usage_entry, + &usage_entry_number)); + EXPECT_EQ(kActiveLicenseState, license_state); + EXPECT_EQ(kPsshData, pssh_data); + EXPECT_EQ(kKeyRequest, key_request); + EXPECT_EQ(kKeyResponse, key_response); + EXPECT_EQ(kKeyRenewalRequest, key_renewal_request); + EXPECT_EQ(kKeyRenewalResponse, key_renewal_response); + EXPECT_EQ(kReleaseServerUrl, release_server_url); + EXPECT_EQ(kPlaybackStartTime, playback_start_time); + EXPECT_EQ(kPlaybackStartTime + kPlaybackDuration, last_playback_time); + EXPECT_EQ(kGracePeriodEndTime, grace_period_end_time); + EXPECT_EQ(kEmptyAppParameters.size(), app_parameters.size()); + EXPECT_EQ(kYetAnotherUsageEntry, usage_entry); + EXPECT_EQ(usage_entry_number_second_to_be_moved, usage_entry_number); + + // Expectations for Shrink (DeleteEntry) + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader( + usage_entry_info_vector_after_first_shrink.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader( + usage_entry_info_vector_after_second_shrink.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kYetAnotherUsageTableHeader), + Return(NO_ERROR))); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader( + usage_entry_info_vector_after_third_shrink.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kOneMoreUsageTableHeader), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_first_shrink))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kYetAnotherUsageTableHeader, + ElementsAreArray( + usage_entry_info_vector_after_second_shrink))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kOneMoreUsageTableHeader, + ElementsAreArray( + usage_entry_info_vector_after_third_shrink))) + .WillOnce(Return(true)); + + // Expectations for MoveEntry (DeleteEntry) + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(6) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_first_to_be_moved, + kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_second_to_be_moved, + kYetAnotherUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + LoadUsageEntry(usage_entry_number_third_to_be_moved, + kOneMoreUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + MoveUsageEntry(usage_entry_number_first_to_be_deleted)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + MoveUsageEntry(usage_entry_number_second_to_be_deleted)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + MoveUsageEntry(usage_entry_number_third_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))) + .WillOnce(DoAll(SetArgPointee<0>(kYetAnotherUsageTableHeader), + SetArgPointee<1>(kYetAnotherUsageEntry), + Return(NO_ERROR))) + .WillOnce(DoAll(SetArgPointee<0>(kOneMoreUsageTableHeader), + SetArgPointee<1>(kOneMoreUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_first_move))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kYetAnotherUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_second_move))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo( + kOneMoreUsageTableHeader, + ElementsAreArray(usage_entry_info_vector_after_third_move))) + .WillOnce(Return(true)); + + // Expectations for GetEntry (DeleteEntry) + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + usage_entry_first_to_be_moved.usage_info_file_name, + usage_entry_first_to_be_moved.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), + SetArgPointee<6>(usage_entry_number_first_to_be_moved), + Return(true))); + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + usage_entry_third_to_be_moved.usage_info_file_name, + usage_entry_third_to_be_moved.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kOneMoreUsageEntry), + SetArgPointee<6>(usage_entry_number_third_to_be_moved), + Return(true))); + + EXPECT_EQ(INSUFFICIENT_CRYPTO_RESOURCES_3, + usage_table_header_->LoadEntry( + crypto_session_, + kAndAnotherUsageEntry, + usage_entry_number_to_load)); +} + TEST_F(UsageTableHeaderTest, DeleteEntry_InvalidUsageEntryNumber) { Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); uint32_t usage_entry_number = kUsageEntryInfoVector.size(); @@ -1783,6 +3103,7 @@ TEST_F(UsageTableHeaderTest, StaleHeader) { .WillOnce( DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); + EXPECT_CALL(*device_files_, DeleteAllUsageInfo()).WillOnce(Return(true)); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, kEmptyUsageEntryInfoVector)) .WillOnce(Return(true)); diff --git a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp index b65e471c..d2c7f1ca 100644 --- a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp +++ b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp @@ -268,16 +268,26 @@ class WvCdmExtendedDurationTest : public WvCdmTestBase { void GenerateKeyRequest(const std::string& init_data, CdmLicenseType license_type) { + CdmResponseType response; + GenerateKeyRequest(init_data, license_type, &response); + EXPECT_EQ(KEY_MESSAGE, response); + } + + void GenerateKeyRequest(const std::string& init_data, + CdmLicenseType license_type, + CdmResponseType *response) { CdmAppParameterMap app_parameters; CdmKeyRequest key_request; - EXPECT_EQ(KEY_MESSAGE, decryptor_.GenerateKeyRequest( - session_id_, key_set_id_, "video/mp4", init_data, - license_type, app_parameters, NULL, - kDefaultCdmIdentifier, &key_request)); - EXPECT_EQ(kKeyRequestTypeInitial, key_request.type); - key_msg_ = key_request.message; - EXPECT_EQ(0u, key_request.url.size()); + *response = decryptor_.GenerateKeyRequest( + session_id_, key_set_id_, "video/mp4", init_data, + license_type, app_parameters, NULL, + kDefaultCdmIdentifier, &key_request); + if (*response == KEY_MESSAGE) { + EXPECT_EQ(kKeyRequestTypeInitial, key_request.type); + key_msg_ = key_request.message; + EXPECT_EQ(0u, key_request.url.size()); + } } void GenerateRenewalRequest(CdmLicenseType license_type, @@ -406,14 +416,21 @@ class WvCdmExtendedDurationTest : public WvCdmTestBase { void VerifyKeyRequestResponse(const std::string& server_url, const std::string& client_auth, bool is_renewal) { - std::string resp = GetKeyRequestResponse(server_url, client_auth); + VerifyKeyRequestResponse(server_url, client_auth, is_renewal, NULL); + } - if (is_renewal) { - // TODO application makes a license request, CDM will renew the license - // when appropriate - EXPECT_EQ(KEY_ADDED, decryptor_.AddKey(session_id_, resp, &key_set_id_)); + void VerifyKeyRequestResponse(const std::string& server_url, + const std::string& client_auth, + bool /* is_renewal */, + CdmResponseType* status) { + std::string resp = GetKeyRequestResponse(server_url, client_auth); + CdmResponseType sts = + decryptor_.AddKey(session_id_, resp, &key_set_id_); + + if (status == NULL) { + EXPECT_EQ(KEY_ADDED, sts); } else { - EXPECT_EQ(KEY_ADDED, decryptor_.AddKey(session_id_, resp, &key_set_id_)); + *status = sts; } } @@ -1574,6 +1591,55 @@ TEST_P(WvCdmOfflineUsageReportTest, UsageTest) { INSTANTIATE_TEST_CASE_P(Cdm, WvCdmOfflineUsageReportTest, ::testing::Values(0, 1, 2)); + +// This test verifies that a device can still work if the maximum capacity +// of the usage entry table is reached. New usage entries, for offline +// licenses, can still be added and existing licenses can still be played back. +TEST_F(WvCdmExtendedDurationTest, MaxUsageEntryOfflineRecoveryTest) { + Unprovision(); + Provision(); + + // override default settings unless configured through the command line + std::string key_id; + std::string client_auth; + GetOfflineConfiguration(&key_id, &client_auth); + + // Download large number of offline licenses. If OEMCrypto returns + // OEMCrypto_ERROR_INSUFFICIENT_RESOURCES when usage table is at capacity, + // licenses will be deleted internally to make space and we might + // not encounter an error. + CdmResponseType response = NO_ERROR; + for (size_t i = 0; i < 2000; ++i) { + decryptor_.OpenSession(g_key_system, NULL, kDefaultCdmIdentifier, NULL, + &session_id_); + GenerateKeyRequest(key_id, kLicenseTypeOffline, &response); + if (response != KEY_MESSAGE) { + decryptor_.CloseSession(session_id_); + break; + } + VerifyKeyRequestResponse(kUatLicenseServer, client_auth, false, &response); + if (response != KEY_ADDED) { + decryptor_.CloseSession(session_id_); + break; + } + EXPECT_EQ(KEY_ADDED, response); + decryptor_.CloseSession(session_id_); + } + + // If we encountered an error, verify that on UsageTableHeader creation + // the usage entries will be deleted and that we can add new ones. + if (response != KEY_ADDED && response != KEY_MESSAGE) { + Provision(); + for (size_t i = 0; i < 10; ++i) { + decryptor_.OpenSession(g_key_system, NULL, kDefaultCdmIdentifier, NULL, + &session_id_); + GenerateKeyRequest(key_id, kLicenseTypeOffline); + VerifyKeyRequestResponse(kUatLicenseServer, client_auth, false); + decryptor_.CloseSession(session_id_); + } + } +} + } // namespace wvcdm void show_menu(char* prog_name) {