From d542cb91b519bafbd231717a7a2ce764cc68e30f Mon Sep 17 00:00:00 2001 From: Cong Lin Date: Thu, 20 May 2021 13:28:55 -0700 Subject: [PATCH 1/2] Sync L3 headers in sc-dev Merge of these CLs from Widevine sc-dev: modified: libwvdrmengine/level3/include/clear_cache_function.h Add cache flush assembly for arm64 L3 to Android header | http://go/wvgerrit/124828 Address compilation errors | http://go/wvgerrit/113083 modified: libwvdrmengine/level3/include/level3_file_system_android.h Update Widevine Copyright header for android | http://go/wvgerrit/108084 Bug: 184866351 Test: Header changes for clearing cache is verified by one of the partners on their arm64 target; https: //b.corp.google.com/issues/175432203#comment13 Change-Id: I0ac8f339f65d02abb3080020fbc715b9c0db85b2 --- .../level3/include/clear_cache_function.h | 52 ++++++++++++++----- .../include/level3_file_system_android.h | 4 +- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/libwvdrmengine/level3/include/clear_cache_function.h b/libwvdrmengine/level3/include/clear_cache_function.h index 2c6d1b97..70d38684 100644 --- a/libwvdrmengine/level3/include/clear_cache_function.h +++ b/libwvdrmengine/level3/include/clear_cache_function.h @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include @@ -71,17 +73,19 @@ void clear_cache_function(void *page, size_t len) { // So, instead, we will define USED_BUILTIN_CLEAR_CACHE if both conditions // are true, and use "#ifndef USED_BUILTIN_CLEAR_CACHE" instead of #else. #ifdef __has_builtin -#if __has_builtin(__builtin___clear_cache) -#pragma message "(info): clear_cache_function is using __builtin___clear_cache." -#define USED_BUILTIN_CLEAR_CACHE +# if __has_builtin(__builtin___clear_cache) +# pragma message "(info): clear_cache_function is using __builtin___clear_cache." +# define USED_BUILTIN_CLEAR_CACHE char *begin = static_cast(page); char *end = begin + len; __builtin___clear_cache(begin, end); +# endif #endif -#endif + #ifndef USED_BUILTIN_CLEAR_CACHE -#if __arm__ -#pragma message "(info): clear_cache_function is using arm asm." +# if defined(__arm__) +# pragma message "(info): clear_cache_function is using arm asm." +# define USED_ARM_ASM_CLEAR_CACHE // ARM Cache Flush System Call: char *begin = static_cast(page); char *end = begin + len; @@ -97,25 +101,49 @@ void clear_cache_function(void *page, size_t len) { : : "r"(begin), "r"(end), "r"(syscall) : "r0", "r1", "r7"); -#elif __mips__ -#pragma message "(info): clear_cache_function is using mips asm." +# elif defined(__aarch64__) +# pragma message "(info): clear_cache_function is using arm64 asm." +# define USED_ARM_ASM_CLEAR_CACHE + uint64_t begin = static_cast(reinterpret_cast(page)); + uint64_t end = begin + len; + register uint64_t ctr_el0; + __asm __volatile("mrs %0, ctr_el0" : "=r"(ctr_el0)); + // register CTR_EL0 [19:16] contains dcache line size + const size_t dcache_line_size = 4 << ((ctr_el0 >> 16) & 0xF); + register uint64_t addr; + for (addr = begin; addr < end; addr += dcache_line_size) { + __asm __volatile("dc cvau, %0" ::"r"(addr)); + } + __asm __volatile("dsb ish"); + // register CTR_EL0 [3:0] contains icache line size + const size_t icache_line_size = 4 << (ctr_el0 & 0xF); + for (addr = begin; addr < end; addr += icache_line_size) { + __asm __volatile("ic ivau, %0" ::"r"(addr)); + } + __asm __volatile("dsb ish"); + __asm __volatile("isb sy"); +# elif __mips__ +# pragma message "(info): clear_cache_function is using mips asm." int result = syscall(__NR_cacheflush, page, len, ICACHE); if (result) { fprintf(stderr, "cacheflush failed!: errno=%d %s\n", errno, strerror(errno)); exit(-1); // TODO(fredgc): figure out more graceful error handling. } -#else -#pragma message "(info): clear_cache_function is not doing anything." +# else +# pragma message "(info): clear_cache_function is not doing anything." // TODO(fredgc): silence warning about unused variables. -#endif +# endif #endif #if defined(__arm__) || defined(__aarch64__) +# if !defined(USED_BUILTIN_CLEAR_CACHE) && !defined(USED_ARM_ASM_CLEAR_CACHE) +# error "clear cache function unavailable, which is required for ARM." +# endif # pragma message "(info): inserting membarrier_function calls." membarrier_function(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE); membarrier_function(MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE); #endif // defined(__arm__) || defined(__aarch64__) } -} // namespace wvoec3 \ No newline at end of file +} // namespace wvoec3 diff --git a/libwvdrmengine/level3/include/level3_file_system_android.h b/libwvdrmengine/level3/include/level3_file_system_android.h index 87069a0e..ac00165c 100644 --- a/libwvdrmengine/level3/include/level3_file_system_android.h +++ b/libwvdrmengine/level3/include/level3_file_system_android.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. /********************************************************************* * level3_file_system_android.h From 884550333d52ed349a9db0cf6b66159d3e75357d Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Thu, 29 Apr 2021 11:00:51 -0700 Subject: [PATCH 2/2] 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)));