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) { diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp index 75c461dc..6c546cf6 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp @@ -1096,9 +1096,14 @@ void Session::InstallRSASessionTestKey(const vector& wrapped_rsa_key) { GenerateDerivedKeysFromSessionKey(); } -void Session::CreateNewUsageEntry() { - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_CreateNewUsageEntry(session_id(), &usage_entry_number_)); +void Session::CreateNewUsageEntry(OEMCryptoResult* status) { + OEMCryptoResult result = + OEMCrypto_CreateNewUsageEntry(session_id(), &usage_entry_number_); + if (status) { + *status = result; + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, result); } void Session::UpdateUsageEntry(std::vector* header_buffer) { diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.h b/libwvdrmengine/oemcrypto/test/oec_session_util.h index cea7d8dc..60c84ecf 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.h +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.h @@ -290,7 +290,9 @@ class Session { // GenerateDerivedKeysFromSessionKey to install known encryption and mac keys. void InstallRSASessionTestKey(const vector& wrapped_rsa_key); // Creates a new usage entry, and keeps track of the index. - void CreateNewUsageEntry(); + // If status is null, we expect success, otherwise status is set to the + // return value. + void CreateNewUsageEntry(OEMCryptoResult *status = NULL); // Copy encrypted usage entry from other session, and then load it. // This session must already be open. void LoadUsageEntry(uint32_t index, const vector& buffer); diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 4bb6969f..e42348fe 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -4684,66 +4685,6 @@ TEST_F(UsageTableTest, OnlineMissingEntry) { ASSERT_NO_FATAL_FAILURE(s.close()); } -TEST_F(UsageTableTest, TwoHundredEntries) { - Session s1; - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); - std::string pst1 = "pst saved"; - ASSERT_NO_FATAL_FAILURE(s1.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s1.get_nonce(), pst1)); - ASSERT_NO_FATAL_FAILURE(s1.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s1.CreateNewUsageEntry()); - ASSERT_EQ(0u, s1.usage_entry_number()); - time_t start = time(NULL); - ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst1, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s1.close()); - - // API says should hold at least 200 entries. Subtract one for s1's entry. - const size_t ENTRY_COUNT = 200 - 1; - vector sessions(ENTRY_COUNT); - for (size_t i = 0; i < ENTRY_COUNT; i++) { - ASSERT_NO_FATAL_FAILURE(sessions[i].open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&sessions[i])); - std::string pst = "pst "; - char c1 = 'A' + (i/26); - char c2 = 'A' + (i%26); - pst = pst + c1 + c2; - ASSERT_NO_FATAL_FAILURE(sessions[i].FillSimpleMessage( - 0, wvoec_mock::kControlNonceOrEntry, sessions[i].get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(sessions[i].EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(sessions[i].CreateNewUsageEntry()); - ASSERT_EQ(sessions[i].usage_entry_number(), i + 1); - ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE( - sessions[i].UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(sessions[i].close()); - } - sleep(kShortSleep); - for (size_t i = 0; i < ENTRY_COUNT; i++) { - ASSERT_NO_FATAL_FAILURE(sessions[i].open()); - std::string pst = "pst "; - char c1 = 'A' + (i/26); - char c2 = 'A' + (i%26); - pst = pst + c1 + c2; - // Reuse license message created above. - ASSERT_NO_FATAL_FAILURE(sessions[i].ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&sessions[i])); - ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)) - << "Failed to reload license " << i << " with pst = " << pst; - ASSERT_NO_FATAL_FAILURE( - sessions[i].UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(sessions[i].close()); - } - // Make sure s1's entry is still in the table. - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s1.GenerateVerifyReport(pst1, kUnused, start)); - ASSERT_NO_FATAL_FAILURE(s1.close()); -} - TEST_P(UsageTableTestWithMAC, GenericCryptoEncrypt) { std::string pst = "A PST"; uint32_t nonce = session_.get_nonce(); @@ -5418,6 +5359,95 @@ TEST_F(UsageTableDefragTest, ReloadUsageEntryBadData) { &data[0], data.size())); } +static std::string MakePST(size_t n) { + std::stringstream stream; + stream << "pst-" << n; + return stream.str(); +} + +TEST_F(UsageTableDefragTest, TwoHundredEntries) { + // OEMCrypto is required to store at least 200 entries in the usage table + // header, but it is allowed to store more. This test verifies that if we keep + // adding entries, the error indicates a resource limit. It then verifies + // that all of the successful entries are still valid after we throw out the + // last invalid entry. + const size_t ENTRY_COUNT = 2000; + vector sessions(ENTRY_COUNT); + size_t successful_count = 0; + for (size_t i = 0; i < ENTRY_COUNT; i++) { + if (i % 50 == 0) LOGD("Creating license %zd", i); + ASSERT_NO_FATAL_FAILURE(sessions[i].open()); + ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&sessions[i])); + std::string pst = MakePST(i); + ASSERT_NO_FATAL_FAILURE(sessions[i].FillSimpleMessage( + 0, wvoec_mock::kControlNonceOrEntry, sessions[i].get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(sessions[i].EncryptAndSign()); + // We attempt to create a new usage table entry for this session. + OEMCryptoResult status; + ASSERT_NO_FATAL_FAILURE(sessions[i].CreateNewUsageEntry(&status)); + if (status == OEMCrypto_SUCCESS) { + ASSERT_EQ(sessions[i].usage_entry_number(), i); + ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)); + ASSERT_NO_FATAL_FAILURE( + sessions[i].UpdateUsageEntry(&encrypted_usage_header_)); + successful_count++; + } else { + // If we failed to create this many entries because of limited resources, + // then the error returned should be insufficient resources. + EXPECT_EQ(OEMCrypto_ERROR_INSUFFICIENT_RESOURCES, status) + << "Failed to create license " << i << " with pst = " << pst; + break; + } + ASSERT_NO_FATAL_FAILURE(sessions[i].close()); + } + LOGD("successful_count = %d", successful_count); + EXPECT_GE(successful_count, 200u); + sleep(kShortSleep); + // Now we will loop through each valid entry, and verify that we can still + // reload the license and perform a decrypt. + for (size_t i = 0; i < successful_count; i++) { + if (i % 50 == 0) LOGD("Reloading license %zd", i); + ASSERT_NO_FATAL_FAILURE(sessions[i].open()); + std::string pst = MakePST(i); + // Reuse license message created above. + ASSERT_NO_FATAL_FAILURE(sessions[i].ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&sessions[i])); + ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)) + << "Failed to reload license " << i << " with pst = " << pst; + ASSERT_NO_FATAL_FAILURE( + sessions[i].UpdateUsageEntry(&encrypted_usage_header_)) + << "Failed to update license " << i << " with pst = " << pst; + ASSERT_NO_FATAL_FAILURE(sessions[i].TestDecryptCTR()) + << "Failed to use license " << i << " with pst = " << pst; + ASSERT_NO_FATAL_FAILURE( + sessions[i].UpdateUsageEntry(&encrypted_usage_header_)) + << "Failed to update license " << i << " with pst = " << pst; + ASSERT_NO_FATAL_FAILURE(sessions[i].close()); + } + // We also need to verify that a full table can be shrunk, and the remaining + // licenses still work. + size_t smaller_size = 10u; // 10 is smaller than 200. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(smaller_size)); + for (size_t i = 0; i < smaller_size; i++) { + ASSERT_NO_FATAL_FAILURE(sessions[i].open()); + std::string pst = MakePST(i); + // Reuse license message created above. + ASSERT_NO_FATAL_FAILURE(sessions[i].ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&sessions[i])); + ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)) + << "Failed to reload license " << i << " with pst = " << pst; + ASSERT_NO_FATAL_FAILURE( + sessions[i].UpdateUsageEntry(&encrypted_usage_header_)) + << "Failed to update license " << i << " with pst = " << pst; + ASSERT_NO_FATAL_FAILURE(sessions[i].TestDecryptCTR()) + << "Failed to use license " << i << " with pst = " << pst; + ASSERT_NO_FATAL_FAILURE( + sessions[i].UpdateUsageEntry(&encrypted_usage_header_)) + << "Failed to update license " << i << " with pst = " << pst; + ASSERT_NO_FATAL_FAILURE(sessions[i].close()); + } +} + TEST_F(UsageTableTest, CopyOldEntries) { // First create three old entries. We open sessions first to force creation // of the mac keys.