Reworked DeleteEntry() into InvalidateEntry(). am: da48461ba2 am: 44cc2500b3 am: 5558f13d57
Change-Id: Ib25887b0a3111971813b518b4cbb9d0563a9a59f
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user