Reworked DeleteEntry() into InvalidateEntry().

[ Merge of http://go/wvgerrit/95406 ]

There was an issue with DeleteEntry() where it would result in an
invalid table state if shrinking the usage table when the number of
sessions is at its max.

This required changing how the usage table invalidates entries.  Now,
after invalidating an entry (marking an entry as kStorageTypeUnknown)
the table is defragmented if specified to.

Defragmentation involves:
1)  Move valid entries near the end of the table to the position of
    invalid entries near the front of the table.
2)  Shrinking the table to cut off trailing invalid entries.

This change updates the existing tests to pass, but still needs new
tests for some of the edge cases.

Bug: 150887808
Bug: 149100568
Test: Linux unit tests and Android unit tests
Change-Id: I70c7b296e5e4b367746fcdaabbf0f12dcfb39230
This commit is contained in:
Alex Dale
2020-03-09 10:58:06 -07:00
parent 792e17a8c4
commit da48461ba2
5 changed files with 1292 additions and 897 deletions

View File

@@ -73,18 +73,21 @@ class UsageTableHeader {
// The licenses or usage info records specified by |usage_entry_number|
// should not be in use by any open CryptoSession objects when calls
// to DeleteEntry and MoveEntry are made.
virtual CdmResponseType DeleteEntry(uint32_t usage_entry_number,
DeviceFiles* handle,
metrics::CryptoMetrics* metrics);
// to InvalidateEntry and MoveEntry are made.
// If |defrag_table| is true, the table will be defragmented after
// the entry has been invalidated.
virtual CdmResponseType InvalidateEntry(uint32_t usage_entry_number,
bool defrag_table,
DeviceFiles* device_files,
metrics::CryptoMetrics* metrics);
// Test only method. This method emulates the behavior of DeleteEntry
// Test only method. This method emulates the behavior of InvalidateEntry
// without actually invoking OEMCrypto (through CryptoSession)
// or storage (through DeviceFiles). It modifies internal data structures
// when DeleteEntry is mocked. This allows one to test methods that are
// dependent on DeleteEntry without having to set expectations
// for the objects that DeleteEntry depends on.
void DeleteEntryForTest(uint32_t usage_entry_number);
// when InvalidateEntry is mocked. This allows one to test methods that are
// dependent on InvalidateEntry without having to set expectations
// for the objects that InvalidateEntry depends on.
void InvalidateEntryForTest(uint32_t usage_entry_number);
size_t size() { return usage_entry_info_.size(); }
@@ -115,17 +118,27 @@ class UsageTableHeader {
CdmResponseType MoveEntry(uint32_t from /* usage entry number */,
const CdmUsageEntry& from_usage_entry,
uint32_t to /* usage entry number */,
DeviceFiles* handle,
DeviceFiles* device_files,
metrics::CryptoMetrics* metrics);
CdmResponseType GetEntry(uint32_t usage_entry_number, DeviceFiles* handle,
CdmResponseType GetEntry(uint32_t usage_entry_number,
DeviceFiles* device_files,
CdmUsageEntry* usage_entry);
CdmResponseType StoreEntry(uint32_t usage_entry_number, DeviceFiles* handle,
CdmResponseType StoreEntry(uint32_t usage_entry_number,
DeviceFiles* device_files,
const CdmUsageEntry& usage_entry);
// Stores the usage table and it's info. This will increment
// |store_table_counter_| if successful.
bool StoreTable(DeviceFiles* device_files);
CdmResponseType Shrink(metrics::CryptoMetrics* metrics,
uint32_t number_of_usage_entries_to_delete);
// Must lock table before calling.
CdmResponseType DefragTable(DeviceFiles* device_files,
metrics::CryptoMetrics* metrics);
virtual bool is_inited() { return is_inited_; }
// Performs and LRU upgrade on all loaded CdmUsageEntryInfo from a
@@ -184,7 +197,7 @@ class UsageTableHeader {
// This handle and file system is only to be used when accessing
// usage_table_header. Usage entries should use the file system provided
// by CdmSession.
std::unique_ptr<DeviceFiles> file_handle_;
std::unique_ptr<DeviceFiles> device_files_;
std::unique_ptr<FileSystem> file_system_;
CdmSecurityLevel security_level_;
SecurityLevel requested_security_level_;
@@ -217,6 +230,11 @@ class UsageTableHeader {
// assumed to be |kMinimumUsageTableEntriesSupported|.
size_t potential_table_capacity_ = 0u;
// Counts the number of successful calls to |StoreTable()|. Used
// to reduce the number of calls to device files for certain
// table operations.
uint32_t store_table_counter_ = 0u;
#if defined(UNIT_TEST)
// Test related declarations
friend class UsageTableHeaderTest;
@@ -228,7 +246,7 @@ class UsageTableHeader {
// These setters are for testing only. Takes ownership of the pointers.
void SetDeviceFiles(DeviceFiles* device_files) {
file_handle_.reset(device_files);
device_files_.reset(device_files);
}
void SetCryptoSession(CryptoSession* crypto_session) {
test_crypto_session_.reset(crypto_session);

View File

@@ -533,6 +533,14 @@ struct CdmUsageEntryInfo {
// else storage_type == kStorageTypeUnknown
return true;
}
void Clear() {
storage_type = kStorageTypeUnknown;
key_set_id.clear();
usage_info_file_name.clear();
last_use_time = 0;
offline_license_expiry_time = 0;
}
};
enum CdmKeySecurityLevel {

View File

@@ -532,16 +532,18 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) {
metrics_->license_sdk_version_.Record(
version_info.license_service_version());
// Update or delete entry if usage table header+entries are supported
// Update or invalidate entry if usage table header+entries are supported
if (usage_support_type_ == kUsageEntrySupport &&
!provider_session_token.empty() && usage_table_header_ != nullptr) {
if (sts != KEY_ADDED) {
CdmResponseType delete_sts = usage_table_header_->DeleteEntry(
usage_entry_number_, file_handle_.get(), crypto_metrics_);
crypto_metrics_->usage_table_header_delete_entry_.Increment(delete_sts);
if (delete_sts != NO_ERROR) {
LOGW("Delete usage entry failed: status = %d",
static_cast<int>(delete_sts));
const CdmResponseType invalidate_sts =
usage_table_header_->InvalidateEntry(
usage_entry_number_, true, file_handle_.get(), crypto_metrics_);
crypto_metrics_->usage_table_header_delete_entry_.Increment(
invalidate_sts);
if (invalidate_sts != NO_ERROR) {
LOGW("Invalidate usage entry failed: status = %d",
static_cast<int>(invalidate_sts));
}
}
}
@@ -827,8 +829,8 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) {
return INCORRECT_USAGE_SUPPORT_TYPE_1;
}
sts = usage_table_header_->DeleteEntry(usage_entry_number, file_handle_.get(),
crypto_metrics_);
sts = usage_table_header_->InvalidateEntry(
usage_entry_number, true, file_handle_.get(), crypto_metrics_);
crypto_metrics_->usage_table_header_delete_entry_.Increment(sts);
return sts;
}

View File

@@ -23,7 +23,7 @@ std::string kOldUsageEntryClientMacKey(wvcdm::MAC_KEY_SIZE, 0);
std::string kOldUsageEntryPoviderSessionToken =
"nahZ6achSheiqua3TohQuei0ahwohv";
constexpr int64_t kDefaultExpireDuration = 33 * 24 * 60 * 60; // 33 Days
// Number of elements to consider for removal using the LRU algorithm.
// Number of elements to be considered for removal using the LRU algorithm.
constexpr size_t kLruRemovalSetSize = 3;
// Fraction of table capacity of number of unexpired offline licenses
// before they are considered to be removed. This could occur if
@@ -34,6 +34,11 @@ constexpr size_t kLruRemovalSetSize = 3;
// nears the capacity of the usage table).
constexpr double kLruUnexpiredThresholdFraction = 0.75;
// Maximum number of entries to be moved during a defrag operation.
// This is to prevent the system from stalling too long if the defrag
// occurs during an active application session.
constexpr size_t kMaxDefragEntryMoves = 5;
// Convert |license_message| -> SignedMessage -> License.
bool ParseLicenseFromLicenseMessage(const CdmKeyResponse& license_message,
video_widevine::License* license) {
@@ -136,7 +141,7 @@ UsageTableHeader::UsageTableHeader()
is_inited_(false),
clock_ref_(&clock_) {
file_system_.reset(new FileSystem());
file_handle_.reset(new DeviceFiles(file_system_.get()));
device_files_.reset(new DeviceFiles(file_system_.get()));
}
bool UsageTableHeader::Init(CdmSecurityLevel security_level,
@@ -173,7 +178,7 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
potential_table_capacity_ = kMinimumUsageTableEntriesSupported;
}
if (!file_handle_->Init(security_level)) {
if (!device_files_->Init(security_level)) {
LOGE("Failed to initialize device files");
return false;
}
@@ -183,7 +188,7 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
if (metrics == nullptr) metrics = &alternate_crypto_metrics_;
bool run_lru_upgrade = false;
if (file_handle_->RetrieveUsageTableInfo(
if (device_files_->RetrieveUsageTableInfo(
&usage_table_header_, &usage_entry_info_, &run_lru_upgrade)) {
LOGI("Number of usage entries: %zu", usage_entry_info_.size());
status = crypto_session->LoadUsageTableHeader(requested_security_level_,
@@ -230,8 +235,9 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
}
}
if (result == NO_ERROR) {
result = DeleteEntry(temporary_usage_entry_number, file_handle_.get(),
metrics);
result = InvalidateEntry(temporary_usage_entry_number,
/* defrag_table = */ true,
device_files_.get(), metrics);
}
if (result != NO_ERROR) {
LOGE(
@@ -246,21 +252,21 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
if (status != NO_ERROR || !lru_success) {
LOGE("Failed to load usage table: security_level = %d, status = %d",
static_cast<int>(security_level), static_cast<int>(status));
file_handle_->DeleteAllLicenses();
file_handle_->DeleteAllUsageInfo();
file_handle_->DeleteUsageTableInfo();
device_files_->DeleteAllLicenses();
device_files_->DeleteAllUsageInfo();
device_files_->DeleteUsageTableInfo();
usage_entry_info_.clear();
usage_table_header_.clear();
status = crypto_session->CreateUsageTableHeader(requested_security_level_,
&usage_table_header_);
if (status != NO_ERROR) return false;
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
StoreTable(device_files_.get());
}
} else {
status = crypto_session->CreateUsageTableHeader(requested_security_level_,
&usage_table_header_);
if (status != NO_ERROR) return false;
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
StoreTable(device_files_.get());
}
is_inited_ = true;
@@ -305,23 +311,24 @@ CdmResponseType UsageTableHeader::AddEntry(
for (size_t i = 0; i < removal_candidates.size() &&
status == INSUFFICIENT_CRYPTO_RESOURCES_3;
++i) {
const uint32_t entry_number_to_delete = removal_candidates[i];
const uint32_t entry_number_to_invalidate = removal_candidates[i];
// Calculate metric values.
staleness_of_removed =
current_time -
usage_entry_info_[entry_number_to_delete].last_use_time;
usage_entry_info_[entry_number_to_invalidate].last_use_time;
storage_type_of_removed =
usage_entry_info_[entry_number_to_delete].storage_type;
usage_entry_info_[entry_number_to_invalidate].storage_type;
if (DeleteEntry(entry_number_to_delete, file_handle_.get(), metrics) ==
NO_ERROR) {
if (InvalidateEntry(entry_number_to_invalidate,
/* defrag_table = */ true, device_files_.get(),
metrics) == NO_ERROR) {
// If the entry was deleted, it is still possible for the create new
// entry to fail. If so, we must ensure that the previously last
// entry was not in the |removal_candidates| as it has now been swapped
// with the deleted entry.
for (uint32_t& entry_number : removal_candidates) {
if (entry_number == usage_entry_info_.size()) {
entry_number = entry_number_to_delete;
entry_number = entry_number_to_invalidate;
}
}
}
@@ -361,9 +368,7 @@ CdmResponseType UsageTableHeader::AddEntry(
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].storage_type = kStorageTypeUnknown;
usage_entry_info_[i].key_set_id.clear();
usage_entry_info_[i].usage_info_file_name.clear();
usage_entry_info_[i].Clear();
}
} else /* *usage_entry_number == usage_entry_info_.size() */ {
usage_entry_info_.resize(*usage_entry_number + 1);
@@ -396,7 +401,7 @@ CdmResponseType UsageTableHeader::AddEntry(
}
LOGI("New usage entry: usage_entry_number = %u", *usage_entry_number);
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
StoreTable(device_files_.get());
return NO_ERROR;
}
@@ -429,13 +434,14 @@ CdmResponseType UsageTableHeader::LoadEntry(CryptoSession* crypto_session,
++retry_count) {
if (usage_entry_info_.size() <= 1) break;
// Get a random entry from the other entries.
uint32_t entry_number_to_delete =
uint32_t entry_number_to_invalidate =
CdmRandom::RandomInRange(usage_entry_info_.size() - 2);
if (entry_number_to_delete >= usage_entry_number) {
if (entry_number_to_invalidate >= usage_entry_number) {
// Exclude |usage_entry_number|.
++entry_number_to_delete;
++entry_number_to_invalidate;
}
DeleteEntry(entry_number_to_delete, file_handle_.get(), metrics);
InvalidateEntry(entry_number_to_invalidate, /* defrag_table = */ true,
device_files_.get(), metrics);
status = crypto_session->LoadUsageEntry(usage_entry_number, usage_entry);
}
@@ -463,15 +469,19 @@ CdmResponseType UsageTableHeader::UpdateEntry(uint32_t usage_entry_number,
if (status != NO_ERROR) return status;
usage_entry_info_[usage_entry_number].last_use_time = GetCurrentTime();
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
StoreTable(device_files_.get());
return NO_ERROR;
}
CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number,
DeviceFiles* handle,
metrics::CryptoMetrics* metrics) {
LOGI("Locking to delete entry: usage_entry_number = %u", usage_entry_number);
CdmResponseType UsageTableHeader::InvalidateEntry(
uint32_t usage_entry_number, bool defrag_table, DeviceFiles* device_files,
metrics::CryptoMetrics* metrics) {
LOGI("Locking to invalidate entry: usage_entry_number = %u",
usage_entry_number);
std::unique_lock<std::mutex> auto_lock(usage_table_header_lock_);
// 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.
if (usage_entry_number >= usage_entry_info_.size()) {
LOGE(
"Usage entry number is larger than table size: "
@@ -480,55 +490,38 @@ CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number,
return USAGE_INVALID_PARAMETERS_1;
}
// Find the last valid entry number, in order to swap
size_t swap_entry_number = usage_entry_info_.size() - 1;
CdmUsageEntry swap_usage_entry;
bool swap_usage_entry_valid = false;
usage_entry_info_[usage_entry_number].Clear();
while (!swap_usage_entry_valid && swap_entry_number > usage_entry_number) {
switch (usage_entry_info_[swap_entry_number].storage_type) {
case kStorageLicense:
case kStorageUsageInfo: {
CdmResponseType status =
GetEntry(swap_entry_number, handle, &swap_usage_entry);
if (status == NO_ERROR) swap_usage_entry_valid = true;
break;
}
case kStorageTypeUnknown:
default:
break;
if (defrag_table) {
// The defrag operation calls many OEMCrypto functions that are
// unrelated to the caller, the only error that will be returned is
// a SYSTEM_INVALIDATED_ERROR. As long as the storage type is
// properly set to unknown, the operation is considered successful.
// SYSTEM_INVALIDATED_ERROR is a special type of error that must be
// sent back to the caller for the CDM as a whole to handle.
const uint32_t pre_defrag_store_counter = store_table_counter_;
const CdmResponseType status = DefragTable(device_files, metrics);
if (pre_defrag_store_counter == store_table_counter_) {
// It is possible that DefragTable() does not result in any
// changes to the table, and as a result, it will not store the
// invalidated entry.
LOGD("Table was not stored during defrag, storing now");
StoreTable(device_files);
}
if (!swap_usage_entry_valid) --swap_entry_number;
if (status == SYSTEM_INVALIDATED_ERROR) {
LOGE("Invalidate entry failed due to system invalidation error");
return SYSTEM_INVALIDATED_ERROR;
}
} else {
StoreTable(device_files);
}
uint32_t number_of_entries_to_be_deleted =
usage_entry_info_.size() - usage_entry_number;
if (swap_usage_entry_valid) {
CdmResponseType status = MoveEntry(swap_entry_number, swap_usage_entry,
usage_entry_number, handle, metrics);
// If unable to move entry, unset storage type of entry to be deleted and
// resize |usage_entry_info_| so that swap usage entry is the last entry.
if (status != NO_ERROR) {
usage_entry_info_[usage_entry_number].storage_type = kStorageTypeUnknown;
usage_entry_info_[usage_entry_number].key_set_id.clear();
if (usage_entry_info_.size() - 1 == swap_entry_number) {
file_handle_->StoreUsageTableInfo(usage_table_header_,
usage_entry_info_);
} else {
Shrink(metrics, usage_entry_info_.size() - swap_entry_number - 1);
}
return NO_ERROR;
}
number_of_entries_to_be_deleted =
usage_entry_info_.size() - swap_entry_number;
}
return Shrink(metrics, number_of_entries_to_be_deleted);
return NO_ERROR;
}
CdmResponseType UsageTableHeader::MoveEntry(
uint32_t from_usage_entry_number, const CdmUsageEntry& from_usage_entry,
uint32_t to_usage_entry_number, DeviceFiles* handle,
uint32_t to_usage_entry_number, DeviceFiles* device_files,
metrics::CryptoMetrics* metrics) {
LOGI(
"Moving usage entry: "
@@ -544,9 +537,14 @@ CdmResponseType UsageTableHeader::MoveEntry(
crypto_session = scoped_crypto_session.get();
}
crypto_session->Open(requested_security_level_);
CdmResponseType status = crypto_session->Open(requested_security_level_);
if (status != NO_ERROR) {
LOGE("Cannot open session for move: usage_entry_number = %u",
from_usage_entry_number);
return status;
}
CdmResponseType status =
status =
crypto_session->LoadUsageEntry(from_usage_entry_number, from_usage_entry);
if (status != NO_ERROR) {
@@ -567,6 +565,7 @@ CdmResponseType UsageTableHeader::MoveEntry(
usage_entry_info_[to_usage_entry_number] =
usage_entry_info_[from_usage_entry_number];
usage_entry_info_[from_usage_entry_number].Clear();
CdmUsageEntry usage_entry;
status = crypto_session->UpdateUsageEntry(&usage_table_header_, &usage_entry);
@@ -577,15 +576,15 @@ CdmResponseType UsageTableHeader::MoveEntry(
return status;
}
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
StoreEntry(to_usage_entry_number, handle, usage_entry);
// Store the usage table and usage entry after successful move.
StoreTable(device_files);
StoreEntry(to_usage_entry_number, device_files, usage_entry);
return NO_ERROR;
}
CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number,
DeviceFiles* handle,
DeviceFiles* device_files,
CdmUsageEntry* usage_entry) {
LOGI("Getting usage entry: usage_entry_number = %u, storage_type: %d",
usage_entry_number,
@@ -597,7 +596,7 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number,
case kStorageLicense: {
DeviceFiles::CdmLicenseData license_data;
DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError;
if (!handle->RetrieveLicense(
if (!device_files->RetrieveLicense(
usage_entry_info_[usage_entry_number].key_set_id, &license_data,
&sub_error_code)) {
LOGE("Failed to retrieve license: status = %d",
@@ -614,7 +613,7 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number,
CdmKeyMessage license_request;
CdmKeyResponse license;
if (!handle->RetrieveUsageInfoByKeySetId(
if (!device_files->RetrieveUsageInfoByKeySetId(
usage_entry_info_[usage_entry_number].usage_info_file_name,
usage_entry_info_[usage_entry_number].key_set_id,
&provider_session_token, &license_request, &license, usage_entry,
@@ -645,7 +644,7 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number,
}
CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number,
DeviceFiles* handle,
DeviceFiles* device_files,
const CdmUsageEntry& usage_entry) {
LOGI("Storing usage entry: usage_entry_number = %u, storage type: %d",
usage_entry_number,
@@ -658,7 +657,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number,
DeviceFiles::CdmLicenseData license_data;
DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError;
if (!handle->RetrieveLicense(
if (!device_files->RetrieveLicense(
usage_entry_info_[usage_entry_number].key_set_id, &license_data,
&sub_error_code)) {
LOGE("Failed to retrieve license: status = %d",
@@ -670,7 +669,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number,
license_data.usage_entry = usage_entry;
license_data.usage_entry_number = usage_entry_number;
if (!handle->StoreLicense(license_data, &sub_error_code)) {
if (!device_files->StoreLicense(license_data, &sub_error_code)) {
LOGE("Failed to store license: status = %d",
static_cast<int>(sub_error_code));
return USAGE_STORE_LICENSE_FAILED;
@@ -682,7 +681,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number,
uint32_t entry_number;
std::string provider_session_token, init_data, key_request, key_response,
key_renewal_request;
if (!handle->RetrieveUsageInfoByKeySetId(
if (!device_files->RetrieveUsageInfoByKeySetId(
usage_entry_info_[usage_entry_number].usage_info_file_name,
usage_entry_info_[usage_entry_number].key_set_id,
&provider_session_token, &key_request, &key_response, &entry,
@@ -690,10 +689,10 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number,
LOGE("Failed to retrieve usage information");
return USAGE_STORE_ENTRY_RETRIEVE_USAGE_INFO_FAILED;
}
handle->DeleteUsageInfo(
device_files->DeleteUsageInfo(
usage_entry_info_[usage_entry_number].usage_info_file_name,
provider_session_token);
if (!handle->StoreUsageInfo(
if (!device_files->StoreUsageInfo(
provider_session_token, key_request, key_response,
usage_entry_info_[usage_entry_number].usage_info_file_name,
usage_entry_info_[usage_entry_number].key_set_id, usage_entry,
@@ -714,6 +713,18 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number,
return NO_ERROR;
}
bool UsageTableHeader::StoreTable(DeviceFiles* device_files) {
LOGV("Storing usage table information");
const bool result =
device_files->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
if (result) {
++store_table_counter_;
} else {
LOGW("Failed to store usage table info");
}
return result;
}
CdmResponseType UsageTableHeader::Shrink(
metrics::CryptoMetrics* metrics,
uint32_t number_of_usage_entries_to_delete) {
@@ -734,10 +745,6 @@ CdmResponseType UsageTableHeader::Shrink(
if (number_of_usage_entries_to_delete == 0) return NO_ERROR;
// TODO(b/150887808): Only resize if the shrink operation is successful.
usage_entry_info_.resize(usage_entry_info_.size() -
number_of_usage_entries_to_delete);
// crypto_session points to an object whose scope is this method or a test
// object whose scope is the lifetime of this class
std::unique_ptr<CryptoSession> scoped_crypto_session;
@@ -747,18 +754,241 @@ CdmResponseType UsageTableHeader::Shrink(
crypto_session = scoped_crypto_session.get();
}
const size_t new_size =
usage_entry_info_.size() - number_of_usage_entries_to_delete;
const CdmResponseType status = crypto_session->ShrinkUsageTableHeader(
requested_security_level_, usage_entry_info_.size(),
&usage_table_header_);
requested_security_level_, new_size, &usage_table_header_);
if (status == NO_ERROR) {
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
usage_entry_info_.resize(new_size);
StoreTable(device_files_.get());
}
return status;
}
CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files,
metrics::CryptoMetrics* metrics) {
LOGV("Defragging table: current_size = %zu", usage_entry_info_.size());
// Defragging the usage table involves moving valid entries near the
// end of the usage table to the position of invalid entries near the
// front of the table. After the entries are moved, the CDM shrinks
// the table to cut off all trailing invalid entries at the end of
// the table.
// Special case 0: Empty table, do nothing.
if (usage_entry_info_.empty()) {
LOGD("Table empty, nothing to defrag");
return NO_ERROR;
}
// Step 1: Create a list of entries to be removed from the table.
// Priority is given to the entries near the beginning of the table.
// To avoid large delays from the swapping process, we limit the
// quantity of entries to remove to |kMaxDefragEntryMoves| or fewer.
std::vector<uint32_t> entries_to_remove;
for (uint32_t i = 0; i < usage_entry_info_.size() &&
entries_to_remove.size() < kMaxDefragEntryMoves;
++i) {
if (usage_entry_info_[i].storage_type == kStorageTypeUnknown) {
entries_to_remove.push_back(i);
}
}
// Special case 1: There are no entries that are invalid; nothing
// needs to be done.
if (entries_to_remove.empty()) {
LOGD("No entries are invalid");
return NO_ERROR;
}
// Step 2: Create a list of entries to be moved from the end of the
// table to the positions identified for removal.
std::vector<uint32_t> entries_to_move;
for (uint32_t i = 0; i < usage_entry_info_.size() &&
entries_to_move.size() < entries_to_remove.size();
++i) {
// Search from the end of the table.
const uint32_t entry_index = usage_entry_info_.size() - i - 1;
if (usage_entry_info_[entry_index].storage_type != kStorageTypeUnknown) {
entries_to_move.push_back(entry_index);
}
}
// Special case 2: There are no valid entries in the table. In this case,
// the whole table can be removed.
if (entries_to_move.empty()) {
LOGD("No valid entries found, shrinking entire table: size = %zu",
usage_entry_info_.size());
return Shrink(metrics, usage_entry_info_.size());
}
// Step 3: Ignore invalid entries that are after the last valid
// entry. No entry is to be moved to a greater index than it already
// has, and entries after the last valid entry will be removed when
// the shrink operation is applied to the table.
// Note: Special case 4 will handle any non-trivial cases related to
// interweaving of valid and invalid entries.
const uint32_t last_valid_entry = entries_to_move.front();
while (!entries_to_remove.empty() &&
entries_to_remove.back() > last_valid_entry) {
entries_to_remove.pop_back();
}
// Special case 3: All of the invalid entries are after the last valid
// entry. In this case, no movement is required and the table can just
// be shrunk to the last valid entry.
if (entries_to_remove.empty()) {
const size_t to_remove = usage_entry_info_.size() - last_valid_entry - 1;
LOGD("Removing all entries after the last valid entry: count = %zu",
to_remove);
return Shrink(metrics, to_remove);
}
// Step 4: Move the valid entries to overwrite the invalid entries.
// Moving the highest numbered valid entry to the lowest numbered
// invalid entry.
// Reversing vectors to make accessing and popping easier. This
// will put the lowest number invalid entry and the highest number
// valid entry at the back of their respective vectors.
std::reverse(entries_to_remove.begin(), entries_to_remove.end());
std::reverse(entries_to_move.begin(), entries_to_move.end());
while (!entries_to_remove.empty() && !entries_to_move.empty()) {
// Entries are popped after use only.
const uint32_t to_entry_number = entries_to_remove.back();
const uint32_t from_entry_number = entries_to_move.back();
// Special case 4: We don't want to move any entries to a higher
// index than their current. Once this occurs, we can stop the
// loop.
if (to_entry_number > from_entry_number) {
LOGD("Entries will not be moved further down the table");
break;
}
CdmUsageEntry from_entry;
CdmResponseType status =
GetEntry(from_entry_number, device_files, &from_entry);
if (status != NO_ERROR) {
LOGW("Could not get entry: entry_number = %u", from_entry_number);
// It is unlikely that an unretrievable entry will suddenly
// become retrievable later on when it is needed.
usage_entry_info_[from_entry_number].Clear();
entries_to_move.pop_back();
continue;
}
status = MoveEntry(from_entry_number, from_entry, to_entry_number,
device_files, metrics);
switch (status) {
case NO_ERROR: {
entries_to_remove.pop_back();
entries_to_move.pop_back();
break;
}
// Handle errors associated with the valid "from" entry.
case LOAD_USAGE_ENTRY_INVALID_SESSION: {
// This is a special error code when returned from LoadEntry()
// indicating that the entry is already in use in a different
// session. In this case, skip the entry and move on.
LOGD("From entry already in use: from_entry_number = %u",
from_entry_number);
entries_to_move.pop_back();
break;
}
case LOAD_USAGE_ENTRY_GENERATION_SKEW:
case LOAD_USAGE_ENTRY_SIGNATURE_FAILURE:
case LOAD_USAGE_ENTRY_UNKNOWN_ERROR: {
// The entry (from the CDM's point of view) is invalid and
// can no longer be used. Safe to continue loop.
// TODO(b/152256186): Remove local files associated with this
// entry.
usage_entry_info_[from_entry_number].Clear();
LOGW("From entry was corrupted: from_entry_number = %u",
from_entry_number);
entries_to_move.pop_back();
break;
}
// Handle errors associated with the invalid "to" entry.
case MOVE_USAGE_ENTRY_DESTINATION_IN_USE: {
// The usage entry specified by |to_entry_number| is currently
// being used by another session. This is unlikely, but still
// possible. Given that this entry is already marked as unknown
// storage type, it will likely be removed at a later time.
LOGD("To entry already in use: to_entry_number = %u", to_entry_number);
entries_to_remove.pop_back();
break;
}
case MOVE_USAGE_ENTRY_UNKNOWN_ERROR: {
// Something else wrong occurred when moving to the destination
// entry. This could be a problem with from entry or the to
// entry. Both should be skipped on the next iteration.
LOGW(
"Move failed, skipping both to entry and from entry: "
"to_entry_number = %u, from_entry_number = %u",
to_entry_number, from_entry_number);
entries_to_remove.pop_back();
entries_to_move.pop_back();
break;
}
// Handle other possible errors from the operations.
case INSUFFICIENT_CRYPTO_RESOURCES: {
// Cannot open any new sessions. The loop should end, but
// an attempt to shrink the table should still be made.
LOGW("Cannot open new session for table clean up");
entries_to_remove.clear();
entries_to_move.clear();
break;
}
default: {
// For all other cases, it may not be safe to proceed, even to
// shrink the table.
LOGE("Unrecoverable error occurred while defragging table: status = %d",
static_cast<int>(status));
return status;
}
} // End switch case.
} // End while loop.
// Step 5: Find the new last valid entry.
uint32_t new_last_valid_entry = usage_entry_info_.size();
for (uint32_t i = 0; i < usage_entry_info_.size(); ++i) {
const uint32_t entry_index = usage_entry_info_.size() - i - 1;
if (usage_entry_info_[entry_index].storage_type != kStorageTypeUnknown) {
new_last_valid_entry = entry_index;
break;
}
}
// Special case 5: No entries in the table are valid. This could
// have occurred if entries during the move process were found to be
// invalid. In this case, remove the whole table.
if (new_last_valid_entry == usage_entry_info_.size()) {
LOGD(
"All entries have been invalidated, shrinking entire table: size = %zu",
usage_entry_info_.size());
return Shrink(metrics, usage_entry_info_.size());
}
const size_t to_remove = usage_entry_info_.size() - new_last_valid_entry - 1;
// Special case 6: It is possible that the last entry in the table
// is valid and currently loaded in the table by another session.
// The loop above would have tried to move it but had failed. In
// this case, nothing more to do.
if (to_remove == 0) {
LOGD("Defrag completed without shrinking table");
StoreTable(device_files);
return NO_ERROR;
}
// Step 6: Shrink table to the new size.
LOGD("Clean up complete, shrinking table: count = %zu", to_remove);
return Shrink(metrics, to_remove);
} // End Defrag().
// Test only method.
void UsageTableHeader::DeleteEntryForTest(uint32_t usage_entry_number) {
void UsageTableHeader::InvalidateEntryForTest(uint32_t usage_entry_number) {
LOGV("Deleting entry for test: usage_entry_number = %u", usage_entry_number);
if (usage_entry_number >= usage_entry_info_.size()) {
LOGE(
@@ -767,7 +997,8 @@ void UsageTableHeader::DeleteEntryForTest(uint32_t usage_entry_number) {
usage_entry_number, usage_entry_info_.size());
return;
}
// Move last entry into deleted location and shrink usage entries
// Move last entry into invalidated entry location and shrink usage
// entries.
usage_entry_info_[usage_entry_number] =
usage_entry_info_[usage_entry_info_.size() - 1];
usage_entry_info_.resize(usage_entry_info_.size() - 1);
@@ -791,13 +1022,13 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() {
switch (usage_entry_info.storage_type) {
case kStorageLicense: {
retrieve_response = RetrieveOfflineLicense(
file_handle_.get(), usage_entry_info.key_set_id, &license_message,
device_files_.get(), usage_entry_info.key_set_id, &license_message,
&retrieved_entry_number);
break;
}
case kStorageUsageInfo: {
retrieve_response = RetrieveUsageInfoLicense(
file_handle_.get(), usage_entry_info.usage_info_file_name,
device_files_.get(), usage_entry_info.usage_info_file_name,
usage_entry_info.key_set_id, &license_message,
&retrieved_entry_number);
break;
@@ -871,7 +1102,7 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() {
for (size_t usage_entry_number : bad_license_file_entries) {
CdmUsageEntryInfo& usage_entry_info = usage_entry_info_[usage_entry_number];
if (usage_entry_info.storage_type == kStorageLicense) {
file_handle_->DeleteLicense(usage_entry_info.key_set_id);
device_files_->DeleteLicense(usage_entry_info.key_set_id);
} else if (usage_entry_info.storage_type == kStorageUsageInfo) {
// To reduce write cycles, the deletion of usage info will be done
// in bulk.
@@ -884,13 +1115,11 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() {
}
it->second.push_back(usage_entry_info.key_set_id);
} // else kStorageUnknown { Nothing special }.
usage_entry_info.storage_type = kStorageTypeUnknown;
usage_entry_info.key_set_id.clear();
usage_entry_info.usage_info_file_name.clear();
usage_entry_info.Clear();
}
for (const auto& p : usage_info_clean_up) {
file_handle_->DeleteMultipleUsageInfoByKeySetIds(p.first, p.second);
device_files_->DeleteMultipleUsageInfoByKeySetIds(p.first, p.second);
}
return true;

File diff suppressed because it is too large Load Diff