From 884550333d52ed349a9db0cf6b66159d3e75357d Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Thu, 29 Apr 2021 11:00:51 -0700 Subject: [PATCH] New usage entries are moved lower after creation. [ Merge of http://go/wvgerrit/124004 ] When the CDM creates a new usage entry for an offline or streaming license, the new entry is immediately moved to the lowest available entry index that has been marked as vacant (kStorageTypeUnknown). When a license is released, its meta data that is managed by the CDM is cleared; however, the usage entry's index is marked vacant, but it is not released. This creates wasted entry space in the usage table. Unfortunately, defragging the table is computationally expensive and may not be able to actually free up much space depending on when it is performed. For a typical user, this will likely not be an issue as the table can get quite large compared to the number of licenses an app uses and the table is partially cleaned on each boot. GTS tests, however, have reached a point where they fill the usage table before all tests are complete. This is causing many unexpected failures for devices. Most of these tests release their license, but the CDM never reaches a state where it can clean up the table. By moving newly created entries to the lowest available index directly after creating the entries, the table never needs to grow unless all entries are in use. Clean up is now almost never required. Bug: 180639135 Bug: 180638990 Bug: 180638530 Test: MediaDrmTest#testWidevineApi28 Change-Id: I1a68d90d51384094298b27037778747ce7435374 --- .../cdm/core/include/usage_table_header.h | 41 ++ .../cdm/core/src/usage_table_header.cpp | 236 ++++++++--- .../core/test/usage_table_header_unittest.cpp | 368 ++++++++++++++---- 3 files changed, 504 insertions(+), 141 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/usage_table_header.h b/libwvdrmengine/cdm/core/include/usage_table_header.h index 9f7dc34f..c0637e95 100644 --- a/libwvdrmengine/cdm/core/include/usage_table_header.h +++ b/libwvdrmengine/cdm/core/include/usage_table_header.h @@ -154,6 +154,46 @@ class UsageTableHeader { bool DetermineTableCapacity(CryptoSession* crypto_session); // == Table operation methods == + // NOTE: The following "Table operation methods" require + // |usage_table_header_lock_| to be taken before calling. + + // Creates a new entry for the provided crypto session. If the + // entry is created successfully in OEMCrypto, then a new entry + // info is added to the table's vector of entry info. + CdmResponseType CreateEntry(CryptoSession* const crypto_session, + uint32_t* usage_entry_number); + + // Attempts to relocate a newly created usage entry associated with + // the provided |crypto_session| to the lowest unoccupied position in + // the table. + // |usage_entry_number| is treated as both an input and output. + // Returns NO_ERROR so long as no internal operation fails, + // regardless of whether the entry was moved or not. + CdmResponseType RelocateNewEntry(CryptoSession* const crypto_session, + uint32_t* usage_entry_number); + + // Checks if the specified |usage_entry_number| is known to be + // unoccupied (released). + bool IsEntryUnoccupied(const uint32_t usage_entry_number) const; + + // SetOfflineEntryInfo() and SetUsageInfoEntryInfo() populate the + // entry meta-data with the required information based on the type + // of entry. + void SetOfflineEntryInfo(const uint32_t usage_entry_number, + const std::string& key_set_id, + const CdmKeyResponse& license_message); + void SetUsageInfoEntryInfo(const uint32_t usage_entry_number, + const std::string& key_set_id, + const std::string& usage_info_file_name); + + // Shrinks the table, removing all trailing unoccupied entries. + // |usage_entry_info_| will be resized appropriately. + // Caller must store the table after a successful call. + CdmResponseType RefitTable(CryptoSession* const crypto_session); + + virtual CdmResponseType InvalidateEntryInternal( + uint32_t usage_entry_number, bool defrag_table, DeviceFiles* device_files, + metrics::CryptoMetrics* metrics); CdmResponseType MoveEntry(uint32_t from /* usage entry number */, const CdmUsageEntry& from_usage_entry, @@ -279,6 +319,7 @@ class UsageTableHeader { #if defined(UNIT_TEST) // Test related declarations friend class UsageTableHeaderTest; + FRIEND_TEST(UsageTableHeaderTest, Shrink_NoneOfTable); FRIEND_TEST(UsageTableHeaderTest, Shrink_PartOfTable); FRIEND_TEST(UsageTableHeaderTest, Shrink_AllOfTable); diff --git a/libwvdrmengine/cdm/core/src/usage_table_header.cpp b/libwvdrmengine/cdm/core/src/usage_table_header.cpp index 7cd7fb95..ee1f58db 100644 --- a/libwvdrmengine/cdm/core/src/usage_table_header.cpp +++ b/libwvdrmengine/cdm/core/src/usage_table_header.cpp @@ -15,6 +15,8 @@ namespace wvcdm { namespace { +using TableLock = std::unique_lock; + const std::string kEmptyString; const wvcdm::CdmKeySetId kDummyKeySetId = "DummyKsid"; @@ -252,75 +254,39 @@ CdmResponseType UsageTableHeader::AddEntry( CryptoSession* crypto_session, bool persistent_license, const CdmKeySetId& key_set_id, const std::string& usage_info_file_name, const CdmKeyResponse& license_message, uint32_t* usage_entry_number) { - LOGD("oec_session_id = %u, type = %s, current_size = %zu", - crypto_session->oec_session_id(), + LOGD("key_set_id = %s, type = %s, current_size = %zu", IdToString(key_set_id), persistent_license ? "OfflineLicense" : "Streaming", usage_entry_info_.size()); metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics(); if (metrics == nullptr) metrics = &alternate_crypto_metrics_; + TableLock auto_lock(usage_table_header_lock_); - CdmResponseType status = crypto_session->CreateUsageEntry(usage_entry_number); - + CdmResponseType status = CreateEntry(crypto_session, usage_entry_number); if (status == INSUFFICIENT_CRYPTO_RESOURCES) { LOGW("Usage table may be full, releasing oldest entry: size = %zu", usage_entry_info_.size()); status = ReleaseOldestEntry(metrics); if (status == NO_ERROR) { - status = crypto_session->CreateUsageEntry(usage_entry_number); + status = CreateEntry(crypto_session, usage_entry_number); } } - if (status != NO_ERROR) return status; - LOGV("Locking to add entry"); - std::unique_lock auto_lock(usage_table_header_lock_); - if (*usage_entry_number < usage_entry_info_.size()) { - LOGE( - "New entry number is smaller than table size: " - "entry_info_number = %u, table_size = %zu", - *usage_entry_number, usage_entry_info_.size()); - return USAGE_INVALID_NEW_ENTRY; - } + status = RelocateNewEntry(crypto_session, usage_entry_number); + if (status != NO_ERROR) return status; - if (*usage_entry_number > usage_entry_info_.size()) { - LOGW( - "New entry number is larger than table size, resizing: " - "entry_info_number = %u, table_size = %zu", - *usage_entry_number, usage_entry_info_.size()); - const size_t number_of_entries = usage_entry_info_.size(); - usage_entry_info_.resize(*usage_entry_number + 1); - for (size_t i = number_of_entries; i < usage_entry_info_.size() - 1; ++i) { - usage_entry_info_[i].Clear(); - } - } else /* *usage_entry_number == usage_entry_info_.size() */ { - usage_entry_info_.resize(*usage_entry_number + 1); - } - - usage_entry_info_[*usage_entry_number].storage_type = - persistent_license ? kStorageLicense : kStorageUsageInfo; - usage_entry_info_[*usage_entry_number].key_set_id = key_set_id; - usage_entry_info_[*usage_entry_number].last_use_time = GetCurrentTime(); - if (!persistent_license) { - usage_entry_info_[*usage_entry_number].usage_info_file_name = - usage_info_file_name; - usage_entry_info_[*usage_entry_number].offline_license_expiry_time = 0; + if (persistent_license) { + SetOfflineEntryInfo(*usage_entry_number, key_set_id, license_message); } else { - // Need to determine the expire time for offline licenses. - video_widevine::License license; - if (license_message.size() > 0 && - ParseLicenseFromLicenseMessage(license_message, &license)) { - const video_widevine::License::Policy& policy = license.policy(); - usage_entry_info_[*usage_entry_number].offline_license_expiry_time = - license.license_start_time() + policy.rental_duration_seconds() + - policy.playback_duration_seconds(); - } else { - // If the license duration cannot be determined for any reason, it - // is assumed to last at most 33 days. - usage_entry_info_[*usage_entry_number].offline_license_expiry_time = - usage_entry_info_[*usage_entry_number].last_use_time + - kDefaultExpireDuration; - } + SetUsageInfoEntryInfo(*usage_entry_number, key_set_id, + usage_info_file_name); + } + + status = RefitTable(crypto_session); + if (status != NO_ERROR) { + usage_entry_info_[*usage_entry_number].Clear(); + return status; } // Call to update the usage table header, but don't store the usage @@ -334,8 +300,6 @@ CdmResponseType UsageTableHeader::AddEntry( usage_entry_info_[*usage_entry_number].Clear(); return status; } - - LOGI("usage_entry_number = %u", *usage_entry_number); StoreTable(device_files_.get()); return NO_ERROR; } @@ -393,7 +357,14 @@ CdmResponseType UsageTableHeader::InvalidateEntry( uint32_t usage_entry_number, bool defrag_table, DeviceFiles* device_files, metrics::CryptoMetrics* metrics) { LOGD("usage_entry_number = %u", usage_entry_number); - std::unique_lock auto_lock(usage_table_header_lock_); + TableLock auto_lock(usage_table_header_lock_); + return InvalidateEntryInternal(usage_entry_number, defrag_table, device_files, + metrics); +} + +CdmResponseType UsageTableHeader::InvalidateEntryInternal( + uint32_t usage_entry_number, bool defrag_table, DeviceFiles* device_files, + metrics::CryptoMetrics* metrics) { // OEMCrypto does not have any concept of "deleting" an entry. // Instead, the CDM marks the entry's meta data as invalid (storage // type unknown) and then performs a "defrag" of the OEMCrypto table. @@ -436,14 +407,12 @@ CdmResponseType UsageTableHeader::InvalidateEntry( size_t UsageTableHeader::UsageInfoCount() const { LOGV("Locking to count usage info (streaming license) entries"); - std::unique_lock auto_lock(usage_table_header_lock_); return std::count_if(usage_entry_info_.cbegin(), usage_entry_info_.cend(), EntryIsUsageInfo); } size_t UsageTableHeader::OfflineEntryCount() const { LOGV("Locking to count offline license entries"); - std::unique_lock auto_lock(usage_table_header_lock_); return std::count_if(usage_entry_info_.cbegin(), usage_entry_info_.cend(), EntryIsOfflineLicense); } @@ -547,6 +516,151 @@ bool UsageTableHeader::DetermineTableCapacity(CryptoSession* crypto_session) { return true; } +CdmResponseType UsageTableHeader::CreateEntry( + CryptoSession* const crypto_session, uint32_t* usage_entry_number) { + const CdmResponseType status = + crypto_session->CreateUsageEntry(usage_entry_number); + if (status != NO_ERROR) return status; + // If the new entry number is smaller than expected, then the usage + // table may be out of sync or OEMCrypto has been rolled back. + // Not safe to continue. + if (*usage_entry_number < usage_entry_info_.size()) { + LOGE( + "New entry number is smaller than table size: " + "entry_info_number = %u, table_size = %zu", + *usage_entry_number, usage_entry_info_.size()); + return USAGE_INVALID_NEW_ENTRY; + } + LOGI("usage_entry_number = %u", *usage_entry_number); + const size_t previous_size = usage_entry_info_.size(); + usage_entry_info_.resize(*usage_entry_number + 1); + if (*usage_entry_number > previous_size) { + LOGW( + "New entry number is larger than table size, resizing: " + "entry_info_number = %u, table_size = %zu", + *usage_entry_number, previous_size); + for (size_t i = previous_size; i < usage_entry_info_.size() - 1; ++i) { + usage_entry_info_[i].Clear(); + } + } + usage_entry_info_[*usage_entry_number].Clear(); + return NO_ERROR; +} + +CdmResponseType UsageTableHeader::RelocateNewEntry( + CryptoSession* const crypto_session, uint32_t* usage_entry_number) { + static constexpr uint32_t kMinimumEntryNumber = 0; + const uint32_t initial_entry_number = *usage_entry_number; + if (initial_entry_number == kMinimumEntryNumber) { + // First entry in the table. + return NO_ERROR; + } + uint32_t unoccupied_entry_number = initial_entry_number; + for (uint32_t i = kMinimumEntryNumber; i < initial_entry_number; i++) { + if (IsEntryUnoccupied(i)) { + unoccupied_entry_number = i; + break; + } + } + if (unoccupied_entry_number == initial_entry_number) { + // No open position. + return NO_ERROR; + } + const CdmResponseType status = + crypto_session->MoveUsageEntry(unoccupied_entry_number); + if (status == MOVE_USAGE_ENTRY_DESTINATION_IN_USE) { + // Not unexpected, there is a window of time between releasing the + // entry and closing the OEMCrypto session. + LOGD("Released entry still in use: index = %u", unoccupied_entry_number); + return NO_ERROR; + } + if (status != NO_ERROR) return status; + LOGI("Entry moved: from_index = %u, to_index = %u", initial_entry_number, + unoccupied_entry_number); + *usage_entry_number = unoccupied_entry_number; + usage_entry_info_[unoccupied_entry_number] = + std::move(usage_entry_info_[initial_entry_number]); + usage_entry_info_[initial_entry_number].Clear(); + return NO_ERROR; +} + +bool UsageTableHeader::IsEntryUnoccupied( + const uint32_t usage_entry_number) const { + if (usage_entry_info_[usage_entry_number].storage_type != + kStorageTypeUnknown) { + return false; + } + // TODO(sigquit): Check that entry is not in use by another session. + // NOTE: The |storage_type| check will protect the integrity of the + // entry. Attempting to use an entry index that is used by another + // session is recoverable and will not affect any opened sessions. + return true; +} + +void UsageTableHeader::SetOfflineEntryInfo( + const uint32_t usage_entry_number, const std::string& key_set_id, + const CdmKeyResponse& license_message) { + CdmUsageEntryInfo& entry_info = usage_entry_info_[usage_entry_number]; + entry_info.Clear(); + entry_info.storage_type = kStorageLicense; + entry_info.key_set_id = key_set_id; + entry_info.last_use_time = GetCurrentTime(); + // Need to determine the expire time for offline licenses. + video_widevine::License license; + if (!license_message.empty() && + ParseLicenseFromLicenseMessage(license_message, &license)) { + const video_widevine::License::Policy& policy = license.policy(); + entry_info.offline_license_expiry_time = license.license_start_time() + + policy.rental_duration_seconds() + + policy.playback_duration_seconds(); + } else { + // If the license duration cannot be determined for any reason, it + // is assumed to last at most 33 days. + entry_info.offline_license_expiry_time = + entry_info.last_use_time + kDefaultExpireDuration; + } +} + +void UsageTableHeader::SetUsageInfoEntryInfo( + const uint32_t usage_entry_number, const std::string& key_set_id, + const std::string& usage_info_file_name) { + CdmUsageEntryInfo& entry_info = usage_entry_info_[usage_entry_number]; + entry_info.Clear(); + entry_info.storage_type = kStorageUsageInfo; + entry_info.key_set_id = key_set_id; + entry_info.last_use_time = GetCurrentTime(); + entry_info.usage_info_file_name = usage_info_file_name; +} + +CdmResponseType UsageTableHeader::RefitTable( + CryptoSession* const crypto_session) { + // Remove all unoccupied entries at end of the table. + uint32_t entries_to_remove = 0; + for (uint32_t i = 0; i < usage_entry_info_.size(); i++) { + const uint32_t usage_entry_number = usage_entry_info_.size() - i - 1; + if (!IsEntryUnoccupied(usage_entry_number)) break; + ++entries_to_remove; + } + if (entries_to_remove == 0) return NO_ERROR; + const uint32_t new_size = usage_entry_info_.size() - entries_to_remove; + const CdmResponseType status = crypto_session->ShrinkUsageTableHeader( + requested_security_level_, new_size, &usage_table_header_); + if (status == SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE) { + // This error likely indicates that another session has released + // its entry via a call to InvalidateEntry(), but has yet to close + // its OEMCrypto session. + // Safe to assume table state is not invalidated. + LOGW("Unexpected entry in use: range = [%u, %zu]", new_size, + usage_entry_info_.size() - 1); + return NO_ERROR; + } + if (status != NO_ERROR) return status; + LOGD("Table shrunk: old_size = %zu, new_size = %u", usage_entry_info_.size(), + new_size); + usage_entry_info_.resize(new_size); + return NO_ERROR; +} + CdmResponseType UsageTableHeader::MoveEntry( uint32_t from_usage_entry_number, const CdmUsageEntry& from_usage_entry, uint32_t to_usage_entry_number, DeviceFiles* device_files, @@ -1035,8 +1149,8 @@ CdmResponseType UsageTableHeader::ReleaseOldestEntry( const CdmUsageEntryStorageType storage_type = usage_entry_info.storage_type; const CdmResponseType status = - InvalidateEntry(entry_number_to_delete, /* defrag_table = */ true, - device_files_.get(), metrics); + InvalidateEntryInternal(entry_number_to_delete, /* defrag_table = */ true, + device_files_.get(), metrics); if (status != NO_ERROR) { LOGE("Failed to invalidate oldest entry: status = %d", @@ -1051,7 +1165,7 @@ CdmResponseType UsageTableHeader::ReleaseOldestEntry( // Test only method. void UsageTableHeader::InvalidateEntryForTest(uint32_t usage_entry_number) { - LOGV("usage_entry_number = %u", usage_entry_number); + LOGD("usage_entry_number = %u", usage_entry_number); if (usage_entry_number >= usage_entry_info_.size()) { LOGE( "Requested usage entry number is larger than table size: " @@ -1188,8 +1302,6 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { } bool UsageTableHeader::GetRemovalCandidate(uint32_t* entry_to_remove) { - LOGV("Locking to determine removal candidates"); - std::unique_lock auto_lock(usage_table_header_lock_); const size_t lru_unexpired_threshold = HasUnlimitedTableCapacity() ? kLruUnexpiredThresholdFraction * size() diff --git a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp index fb0b145b..36fd2ea9 100644 --- a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp @@ -202,8 +202,16 @@ const DeviceFiles::CdmUsageData kCdmUsageData3 = { const std::vector kEmptyUsageInfoUsageDataList; const std::vector kEmptyUsageEntryInfoVector; -std::vector kUsageEntryInfoVector; -std::vector k10UsageEntryInfoVector; +const std::vector kUsageEntryInfoVector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, + kUsageEntryInfoStorageTypeUnknown}; +const std::vector k10UsageEntryInfoVector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, + kUsageEntryInfoOfflineLicense2, kUsageEntryInfoSecureStop2, + kUsageEntryInfoOfflineLicense3, kUsageEntryInfoSecureStop3, + kUsageEntryInfoOfflineLicense4, kUsageEntryInfoSecureStop4, + kUsageEntryInfoOfflineLicense5, kUsageEntryInfoSecureStop5, +}; std::vector kOverFullUsageEntryInfoVector; const CdmOfflineLicenseState kActiveLicenseState = kLicenseStateActive; @@ -289,23 +297,6 @@ std::vector kUpgradedUsageEntryInfoList; namespace { void InitVectorConstants() { - kUsageEntryInfoVector.clear(); - kUsageEntryInfoVector.push_back(kUsageEntryInfoOfflineLicense1); - kUsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop1); - kUsageEntryInfoVector.push_back(kUsageEntryInfoStorageTypeUnknown); - - k10UsageEntryInfoVector.clear(); - k10UsageEntryInfoVector.push_back(kUsageEntryInfoOfflineLicense1); - k10UsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop1); - k10UsageEntryInfoVector.push_back(kUsageEntryInfoOfflineLicense2); - k10UsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop2); - k10UsageEntryInfoVector.push_back(kUsageEntryInfoOfflineLicense3); - k10UsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop3); - k10UsageEntryInfoVector.push_back(kUsageEntryInfoOfflineLicense4); - k10UsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop4); - k10UsageEntryInfoVector.push_back(kUsageEntryInfoOfflineLicense5); - k10UsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop5); - kOverFullUsageEntryInfoVector.clear(); for (size_t i = 0; i < (kDefaultTableCapacity + 1); ++i) { switch (i % 4) { @@ -479,8 +470,9 @@ class MockCryptoSession : public TestCryptoSession { class MockUsageTableHeader : public UsageTableHeader { public: MockUsageTableHeader() : UsageTableHeader() {} - MOCK_METHOD4(InvalidateEntry, CdmResponseType(uint32_t, bool, DeviceFiles*, - metrics::CryptoMetrics*)); + MOCK_METHOD4(InvalidateEntryInternal, + CdmResponseType(uint32_t, bool, DeviceFiles*, + metrics::CryptoMetrics*)); MOCK_METHOD6(AddEntry, CdmResponseType(CryptoSession*, bool, const CdmKeySetId&, const std::string&, const CdmKeyResponse&, uint32_t*)); @@ -1041,25 +1033,53 @@ TEST_F(UsageTableHeaderTest, AddEntry_UsageEntryTooSmall) { kEmptyString /* license */, &usage_entry_number)); } +// Initial Test state: +// 1. Table has a few entries, one of which is unoccupied. +// 2. An entry-less session requires a new entry. +// +// Attempting to add an entry will result in: +// a. A successful call to OEMCrypto to create an entry +// at the end of the current table +// b. Moving the new entry to the unoccupied entry index +// c. Shrink table to remove the now empty entry slot created in (a) +// d. Storing the new updated usage table +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Offline License 1 0 0 +// Secure Stop 1 1 1 +// Storage Type Unknown 2 Replaced +// Offline License 2 DNE 2 +// +// DNE = Does Not Exist +// +// # of usage entries 3 3 TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveOfflineUsageEntry) { Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); - const uint32_t expect_usage_entry_number = kUsageEntryInfoVector.size(); + const uint32_t initial_usage_entry_number = kUsageEntryInfoVector.size(); + const uint32_t final_usage_entry_number = kUsageEntryInfoVector.size() - 1; std::vector expect_usage_entry_info_vector = kUsageEntryInfoVector; - expect_usage_entry_info_vector.resize(expect_usage_entry_number + 1); - expect_usage_entry_info_vector[expect_usage_entry_number] = + expect_usage_entry_info_vector[final_usage_entry_number] = kUsageEntryInfoOfflineLicense2; - EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(initial_usage_entry_number), + Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(final_usage_entry_number)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, + expect_usage_entry_info_vector.size(), NotNull())) .WillOnce( - DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) - .WillOnce( - DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); + .WillOnce(DoAll(SetArgPointee<0>(kYetAnotherUsageTableHeader), + Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAreArray(expect_usage_entry_info_vector))) + StoreUsageTableInfo(kYetAnotherUsageTableHeader, + expect_usage_entry_info_vector)) .WillOnce(Return(true)); uint32_t usage_entry_number = 0; @@ -1070,29 +1090,182 @@ TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveOfflineUsageEntry) { kUsageEntryInfoOfflineLicense2.key_set_id, kUsageEntryInfoOfflineLicense2.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); - EXPECT_EQ(expect_usage_entry_number, usage_entry_number); + EXPECT_EQ(final_usage_entry_number, usage_entry_number); } +// Initial Test state: +// 1. Table has a few entries, one of which is unoccupied. +// 2. An entry-less session requires a new entry. +// +// Attempting to add an entry will result in: +// a. A successful call to OEMCrypto to create an entry +// at the end of the current table +// b. Moving the new entry to the unoccupied entry index +// c. Shrink table to remove the now empty entry slot created in (a) +// d. Storing the new updated usage table +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Offline License 1 0 0 +// Secure Stop 1 1 1 +// Storage Type Unknown 2 Replaced +// Secure Stop 2 DNE 2 +// +// DNE = Does Not Exist +// +// # of usage entries 3 3 TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveSecureStopUsageEntry) { Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); - const uint32_t expect_usage_entry_number = kUsageEntryInfoVector.size(); + const uint32_t initial_usage_entry_number = kUsageEntryInfoVector.size(); + const uint32_t final_usage_entry_number = kUsageEntryInfoVector.size() - 1; std::vector expect_usage_entry_info_vector = kUsageEntryInfoVector; - - expect_usage_entry_info_vector.resize(expect_usage_entry_number + 1); - expect_usage_entry_info_vector[expect_usage_entry_number] = + expect_usage_entry_info_vector[final_usage_entry_number] = kUsageEntryInfoSecureStop2; EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(initial_usage_entry_number), + Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(final_usage_entry_number)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, + expect_usage_entry_info_vector.size(), NotNull())) .WillOnce( - DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kYetAnotherUsageTableHeader), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kYetAnotherUsageTableHeader, + expect_usage_entry_info_vector)) + .WillOnce(Return(true)); + + uint32_t usage_entry_number = 0; + EXPECT_EQ(NO_ERROR, + usage_table_header_->AddEntry( + crypto_session_, + kUsageEntryInfoSecureStop2.storage_type == kStorageLicense, + kUsageEntryInfoSecureStop2.key_set_id, + kUsageEntryInfoSecureStop2.usage_info_file_name, + kEmptyString /* license */, &usage_entry_number)); + EXPECT_EQ(final_usage_entry_number, usage_entry_number); +} + +// Initial Test state: +// 1. Table has a few entries, one of which is unoccupied. +// 2. An entry-less session requires a new entry. +// +// Attempting to add an entry will result in: +// a. An odd but successful call to OEMCrypto to create an entry +// beyond the end of the current table +// b. Empty entries will fill the gap between the original table +// and the new entry +// c. Move the new entry to the *lowest* unoccupied entry index +// d. Shrink table to remove the now empty entry slot created in (a) +// and the filler gap entries created in (b) +// e. Storing the new updated usage table +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Offline License 1 0 0 +// Secure Stop 1 1 1 +// Storage Type Unknown 2 Replaced +// Storage Type Unknown DNE Removed (never stored) +// Storage Type Unknown DNE Removed (never stored) +// Storage Type Unknown DNE Removed (never stored) +// Secure Stop 2 DNE 2 +// +// DNE = Does Not Exist +// +// # of usage entries 3 3 +TEST_F(UsageTableHeaderTest, AddEntry_SkipUsageEntries) { + Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); + const uint32_t next_usage_entry_number = kUsageEntryInfoVector.size(); + const size_t skip_usage_entries = 3; + const uint32_t initial_usage_entry_number = + next_usage_entry_number + skip_usage_entries; + const uint32_t final_usage_entry_number = kUsageEntryInfoVector.size() - 1; + std::vector expect_usage_entry_info_vector = + kUsageEntryInfoVector; + expect_usage_entry_info_vector[final_usage_entry_number] = + kUsageEntryInfoSecureStop2; + + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(initial_usage_entry_number), + Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(final_usage_entry_number)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, + expect_usage_entry_info_vector.size(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kYetAnotherUsageTableHeader), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kYetAnotherUsageTableHeader, + expect_usage_entry_info_vector)) + .WillOnce(Return(true)); + + uint32_t usage_entry_number = 0; + EXPECT_EQ(NO_ERROR, + usage_table_header_->AddEntry( + crypto_session_, + kUsageEntryInfoSecureStop2.storage_type == kStorageLicense, + kUsageEntryInfoSecureStop2.key_set_id, + kUsageEntryInfoSecureStop2.usage_info_file_name, + kEmptyString /* license */, &usage_entry_number)); + EXPECT_EQ(final_usage_entry_number, usage_entry_number); +} + +// Initial Test state: +// 1. Table has a few entries, one of which is unoccupied. +// 2. An entry-less session requires a new entry. +// +// Attempting to add an entry will result in: +// a. A successful call to OEMCrypto to create an entry +// at the end of the current table +// b. Cannot move the new entry to the unoccupied entry index +// due to entry being in use (according to OEMCrypto) +// c. Storing the new updated usage table +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Offline License 1 0 0 +// Secure Stop 1 1 1 +// Storage Type Unknown 2 2 +// Secure Stop 2 DNE 3 +// +// DNE = Does Not Exist +// +// # of usage entries 3 4 +TEST_F(UsageTableHeaderTest, AddEntry_CannotMoveNewEntry) { + Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); + const uint32_t final_usage_entry_number = kUsageEntryInfoVector.size(); + const uint32_t attempted_usage_entry_number = + kUsageEntryInfoVector.size() - 1; + std::vector expect_usage_entry_info_vector = + kUsageEntryInfoVector; + expect_usage_entry_info_vector.push_back(kUsageEntryInfoSecureStop2); + + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(final_usage_entry_number), Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(attempted_usage_entry_number)) + .WillOnce(Return(MOVE_USAGE_ENTRY_DESTINATION_IN_USE)); EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) .WillOnce( DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAreArray(expect_usage_entry_info_vector))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + expect_usage_entry_info_vector)) .WillOnce(Return(true)); uint32_t usage_entry_number = 0; @@ -1103,32 +1276,58 @@ TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveSecureStopUsageEntry) { kUsageEntryInfoSecureStop2.key_set_id, kUsageEntryInfoSecureStop2.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); - EXPECT_EQ(expect_usage_entry_number, usage_entry_number); + EXPECT_EQ(final_usage_entry_number, usage_entry_number); } -TEST_F(UsageTableHeaderTest, AddEntry_SkipUsageEntries) { +// Initial Test state: +// 1. Table has a few entries, one of which is unoccupied. +// 2. An entry-less session requires a new entry. +// +// Attempting to add an entry will result in: +// a. A successful call to OEMCrypto to create an entry +// at the end of the current table +// b. Moving the new entry to the unoccupied entry index +// c. Fail to shrink table due to occupied entry +// d. Storing the new updated usage table +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Offline License 1 0 0 +// Secure Stop 1 1 1 +// Storage Type Unknown 2 Replaced +// Secure Stop 2 DNE 2 +// Storage Type Unknown DNE 3 (created when new entry moved) +// +// DNE = Does Not Exist +// +// # of usage entries 3 4 +TEST_F(UsageTableHeaderTest, AddEntry_CannotShinkAfterMove) { Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); - const uint32_t next_usage_entry_number = kUsageEntryInfoVector.size(); - size_t skip_usage_entries = 3; - uint32_t expect_usage_entry_number = - next_usage_entry_number + skip_usage_entries; + const uint32_t initial_usage_entry_number = kUsageEntryInfoVector.size(); + const uint32_t final_usage_entry_number = kUsageEntryInfoVector.size() - 1; + std::vector expect_usage_entry_info_vector = + kUsageEntryInfoVector; + expect_usage_entry_info_vector[final_usage_entry_number] = + kUsageEntryInfoSecureStop2; + expect_usage_entry_info_vector.push_back(kUsageEntryInfoStorageTypeUnknown); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) - .WillOnce( - DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); + .WillOnce(DoAll(SetArgPointee<0>(initial_usage_entry_number), + Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(final_usage_entry_number)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader( + kLevelDefault, expect_usage_entry_info_vector.size() - 1, NotNull())) + .WillOnce(Return(SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE)); EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) .WillOnce( DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, - kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop2))) + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, + expect_usage_entry_info_vector)) .WillOnce(Return(true)); uint32_t usage_entry_number = 0; @@ -1139,9 +1338,21 @@ TEST_F(UsageTableHeaderTest, AddEntry_SkipUsageEntries) { kUsageEntryInfoSecureStop2.key_set_id, kUsageEntryInfoSecureStop2.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); - EXPECT_EQ(expect_usage_entry_number, usage_entry_number); + EXPECT_EQ(final_usage_entry_number, usage_entry_number); } +// Initial Test state: +// 1. Table is full with entries +// 2. An entry-less session requires a new entry. +// +// Attempting to add an entry will result in: +// a. First call to OEMCrypto to create an entry fails due to +// table being full +// b. One of the existing entries will be removed, shrinking table +// by one +// c. Table will be stored +// d. Second call to OEMCrypto to create an entry will succeed +// e. Storing the new updated usage table TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailsOnce_SucceedsSecondTime) { // Initialize and setup @@ -1150,32 +1361,33 @@ TEST_F(UsageTableHeaderTest, std::vector usage_entry_info_vector_at_start = k10UsageEntryInfoVector; - uint32_t usage_entry_number_first_to_be_deleted; // randomly chosen - std::vector final_usage_entries; + uint32_t invalidated_entry = 0; // Randomly chosen by UsageTableHeader const uint32_t expected_usage_entry_number = k10UsageEntryInfoVector.size() - 1; - // Setup expectations + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + // First call fails + .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES)) + // Second call succeeds + .WillOnce(DoAll(SetArgPointee<0>(expected_usage_entry_number), + Return(NO_ERROR))); + // Covers all other expectations. EXPECT_CALL(*mock_usage_table_header, - InvalidateEntry(_, true, device_files_, NotNull())) - .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_first_to_be_deleted), + InvalidateEntryInternal(_, true, device_files_, NotNull())) + .WillOnce(DoAll(SaveArg<0>(&invalidated_entry), Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))); - EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) - .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES)) - .WillOnce(DoAll(SetArgPointee<0>(expected_usage_entry_number), - Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) .WillOnce( DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); - + std::vector final_usage_entries; EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)) .WillOnce(DoAll(SaveArg<1>(&final_usage_entries), Return(true))); // Now invoke the method under test - uint32_t usage_entry_number; + uint32_t usage_entry_number = 0; EXPECT_EQ(NO_ERROR, mock_usage_table_header->SuperAddEntry( crypto_session_, @@ -1187,17 +1399,15 @@ TEST_F(UsageTableHeaderTest, // Verify added/deleted usage entry number and entries EXPECT_EQ(expected_usage_entry_number, usage_entry_number); - EXPECT_LE(0u, usage_entry_number_first_to_be_deleted); - EXPECT_LE(usage_entry_number_first_to_be_deleted, - usage_entry_info_vector_at_start.size() - 1); + EXPECT_LE(0u, invalidated_entry); + EXPECT_LE(invalidated_entry, k10UsageEntryInfoVector.size() - 1); std::vector expected_usage_entries = - usage_entry_info_vector_at_start; - expected_usage_entries[usage_entry_number_first_to_be_deleted] = - expected_usage_entries[expected_usage_entries.size() - 1]; - expected_usage_entries.resize(expected_usage_entries.size() - 1); - expected_usage_entries.push_back(kUsageEntryInfoOfflineLicense6); + k10UsageEntryInfoVector; + expected_usage_entries[invalidated_entry] = expected_usage_entries.back(); + expected_usage_entries.pop_back(); + expected_usage_entries.push_back(kUsageEntryInfoOfflineLicense6); EXPECT_EQ(expected_usage_entries, final_usage_entries); } @@ -1210,7 +1420,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailsEveryTime) { // Setup expectations EXPECT_CALL(*mock_usage_table_header, - InvalidateEntry(_, true, device_files_, NotNull())) + InvalidateEntryInternal(_, true, device_files_, NotNull())) .WillOnce(DoAll(Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR)));