Reworked DeleteEntry() into InvalidateEntry(). am: da48461ba2 am: 44cc2500b3 am: 5558f13d57

Change-Id: Ib25887b0a3111971813b518b4cbb9d0563a9a59f
This commit is contained in:
Alex Dale
2020-05-01 01:57:06 +00:00
committed by Automerger Merge Worker
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