From 299b100fc8b1eceb7c4eac86333e50adfd74d198 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Wed, 22 Aug 2018 11:55:41 -0700 Subject: [PATCH] Delete usage information on insufficient resources [ Merge of http://go/wvgerrit/58460 ] If OEMCrypto runs out of space in the usage table header+entries adding a new license or loading/using an existing one might fail. This CL makes two modifications to handle this scenario. * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES will be returned from OEMCrypto_CreateNewUsageEntry or OEMCrypto_LoadUsageEntry. An attempt will be made to release a LRU entry from the usage table and retry the operation. This may be retried 3 times unless success occurs earlier. * On initialization, the usage table header is loaded. If there are more than the minimum number of usage entries (200), an attempt is made to add a usage entry. If this fails, we are likely in an unrecoverable state. We then delete all offline licenses, usage information and recreate the usage table header. This will allow future playback attempts to succeed and offline licenses to be able to be downloaded but will lose all current offline licenses and secure stops. Bug: 112486006 Test: WV unit/integration tests, GtsMediaDrmTest Playback tests using Netflix and Play movies. Change-Id: I41a18d69a329f8a96c7b607d299ce73af3d56177 --- .../cdm/core/include/device_files.h | 4 + .../cdm/core/include/usage_table_header.h | 5 +- .../cdm/core/src/crypto_session.cpp | 2 + libwvdrmengine/cdm/core/src/device_files.cpp | 17 + .../cdm/core/src/usage_table_header.cpp | 89 +- .../core/test/usage_table_header_unittest.cpp | 1323 ++++++++++++++++- .../cdm/test/cdm_extended_duration_test.cpp | 92 +- 7 files changed, 1500 insertions(+), 32 deletions(-) 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) {