Merge "New usage entries are moved lower after creation." into sc-dev

This commit is contained in:
Alex Dale
2021-05-26 22:31:03 +00:00
committed by Android (Google) Code Review
3 changed files with 504 additions and 141 deletions

View File

@@ -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);

View File

@@ -15,6 +15,8 @@
namespace wvcdm {
namespace {
using TableLock = std::unique_lock<std::mutex>;
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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> auto_lock(usage_table_header_lock_);
const size_t lru_unexpired_threshold =
HasUnlimitedTableCapacity()
? kLruUnexpiredThresholdFraction * size()

View File

@@ -202,8 +202,16 @@ const DeviceFiles::CdmUsageData kCdmUsageData3 = {
const std::vector<DeviceFiles::CdmUsageData> kEmptyUsageInfoUsageDataList;
const std::vector<CdmUsageEntryInfo> kEmptyUsageEntryInfoVector;
std::vector<CdmUsageEntryInfo> kUsageEntryInfoVector;
std::vector<CdmUsageEntryInfo> k10UsageEntryInfoVector;
const std::vector<CdmUsageEntryInfo> kUsageEntryInfoVector = {
kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1,
kUsageEntryInfoStorageTypeUnknown};
const std::vector<CdmUsageEntryInfo> k10UsageEntryInfoVector = {
kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1,
kUsageEntryInfoOfflineLicense2, kUsageEntryInfoSecureStop2,
kUsageEntryInfoOfflineLicense3, kUsageEntryInfoSecureStop3,
kUsageEntryInfoOfflineLicense4, kUsageEntryInfoSecureStop4,
kUsageEntryInfoOfflineLicense5, kUsageEntryInfoSecureStop5,
};
std::vector<CdmUsageEntryInfo> kOverFullUsageEntryInfoVector;
const CdmOfflineLicenseState kActiveLicenseState = kLicenseStateActive;
@@ -289,23 +297,6 @@ std::vector<CdmUsageEntryInfo> 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<CdmUsageEntryInfo> 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<CdmUsageEntryInfo> 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<CdmUsageEntryInfo> 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<CdmUsageEntryInfo> 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<CdmUsageEntryInfo> 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<CdmUsageEntryInfo> usage_entry_info_vector_at_start =
k10UsageEntryInfoVector;
uint32_t usage_entry_number_first_to_be_deleted; // randomly chosen
std::vector<CdmUsageEntryInfo> 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<CdmUsageEntryInfo> 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<CdmUsageEntryInfo> 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)));