Merge CDM LRU change to Android.

[ Merge of http://go/wvgerrit/81903 ]
[ Merge of http://go/wvgerrit/87473 ]
[ Merge of http://go/wvgerrit/82568 ]
[ Merge of http://go/wvgerrit/87266 ]
[ Merge of http://go/wvgerrit/87474 ]
[ Merge of http://go/wvgerrit/87475 ]

Bug: 135046978
Test: GTS and Android unit tests
Change-Id: Iff2ff62cea21eeb36d7b56c8bb852fce8447ff89
This commit is contained in:
Alex Dale
2019-11-12 18:23:17 -08:00
parent e24b1ef824
commit 061b0e7caf
15 changed files with 2159 additions and 174 deletions

View File

@@ -313,8 +313,8 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id,
if (usage_support_type_ == kUsageEntrySupport &&
!provider_session_token.empty() && usage_table_header_ != nullptr) {
CdmResponseType sts =
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
CdmResponseType sts = usage_table_header_->UpdateEntry(
usage_entry_number_, crypto_session_.get(), &usage_entry_);
if (sts != NO_ERROR) {
LOGE("Failed to update usage entry: status = %d", static_cast<int>(sts));
return sts;
@@ -367,8 +367,8 @@ CdmResponseType CdmSession::RestoreUsageSession(
if (usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
sts =
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
sts = usage_table_header_->UpdateEntry(
usage_entry_number_, crypto_session_.get(), &usage_entry_);
if (sts != NO_ERROR) {
LOGE("Failed to update usage entry: status = %d", static_cast<int>(sts));
return sts;
@@ -513,7 +513,8 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) {
GetApplicationId(&app_id);
sts = usage_table_header_->AddEntry(
crypto_session_.get(), is_offline_, key_set_id_,
DeviceFiles::GetUsageInfoFileName(app_id), &usage_entry_number_);
DeviceFiles::GetUsageInfoFileName(app_id), key_response,
&usage_entry_number_);
crypto_metrics_->usage_table_header_add_entry_.Increment(sts);
if (sts != NO_ERROR) return sts;
}
@@ -553,7 +554,8 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) {
if (has_provider_session_token() &&
usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
usage_table_header_->UpdateEntry(usage_entry_number_,
crypto_session_.get(), &usage_entry_);
}
if (!is_offline_)
@@ -735,8 +737,8 @@ CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyRequest* key_request) {
if (has_provider_session_token() &&
usage_support_type_ == kUsageEntrySupport) {
status =
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
status = usage_table_header_->UpdateEntry(
usage_entry_number_, crypto_session_.get(), &usage_entry_);
if (status != NO_ERROR) {
LOGE("Update usage entry failed: status = %d", static_cast<int>(status));
@@ -1055,8 +1057,8 @@ CdmResponseType CdmSession::UpdateUsageEntryInformation() {
// TODO(blueeyes): Add measurements to all UpdateEntry calls in a way that
// allos us to isolate this particular use case within
// UpdateUsageEntryInformation.
M_TIME(sts = usage_table_header_->UpdateEntry(crypto_session_.get(),
&usage_entry_),
M_TIME(sts = usage_table_header_->UpdateEntry(
usage_entry_number_, crypto_session_.get(), &usage_entry_),
crypto_metrics_, usage_table_header_update_entry_, sts);
if (sts != NO_ERROR) return sts;

View File

@@ -532,6 +532,49 @@ bool DeviceFiles::DeleteAllUsageInfoForApp(
return RemoveFile(usage_info_file_name);
}
bool DeviceFiles::DeleteMultipleUsageInfoByKeySetIds(
const std::string& usage_info_file_name,
const std::vector<std::string>& key_set_ids) {
if (!FileExists(usage_info_file_name)) return false;
if (key_set_ids.empty()) {
LOGW("No key set IDs provided");
return true;
}
video_widevine_client::sdk::File file;
if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) {
LOGW("Unable to retrieve usage info file");
return false;
}
const auto is_deletable =
[&key_set_ids](
const video_widevine_client::sdk::UsageInfo::ProviderSession& session)
-> bool {
return std::find(key_set_ids.cbegin(), key_set_ids.cend(),
session.key_set_id()) != key_set_ids.cend();
};
auto sessions = file.mutable_usage_info()->mutable_sessions();
const int initial_size = sessions->size();
sessions->erase(
std::remove_if(sessions->begin(), sessions->end(), is_deletable),
sessions->end());
if (sessions->size() == initial_size) {
// Nothing deleted.
return true;
}
if (sessions->size() > 0) {
std::string serialized_file;
file.SerializeToString(&serialized_file);
return StoreFileWithHash(usage_info_file_name, serialized_file) == kNoError;
}
return RemoveFile(usage_info_file_name);
}
bool DeviceFiles::DeleteAllUsageInfo() {
RETURN_FALSE_IF_UNINITIALIZED();
return RemoveFile(kUsageInfoFileNamePrefix + std::string(kWildcard) +
@@ -891,12 +934,16 @@ bool DeviceFiles::StoreUsageTableInfo(
case kStorageLicense:
info->set_storage(
UsageTableInfo_UsageEntryInfo_UsageEntryStorage_LICENSE);
info->set_last_use_time(usage_entry_info[i].last_use_time);
info->set_offline_license_expiry_time(
usage_entry_info[i].offline_license_expiry_time);
break;
case kStorageUsageInfo:
info->set_storage(
UsageTableInfo_UsageEntryInfo_UsageEntryStorage_USAGE_INFO);
info->set_usage_info_file_name(
usage_entry_info[i].usage_info_file_name);
info->set_last_use_time(usage_entry_info[i].last_use_time);
break;
case kStorageTypeUnknown:
default:
@@ -905,6 +952,7 @@ bool DeviceFiles::StoreUsageTableInfo(
break;
}
}
usage_table_info->set_use_lru(true);
std::string serialized_file;
file.SerializeToString(&serialized_file);
@@ -915,10 +963,11 @@ bool DeviceFiles::StoreUsageTableInfo(
bool DeviceFiles::RetrieveUsageTableInfo(
CdmUsageTableHeader* usage_table_header,
std::vector<CdmUsageEntryInfo>* usage_entry_info) {
std::vector<CdmUsageEntryInfo>* usage_entry_info, bool* lru_upgrade) {
RETURN_FALSE_IF_UNINITIALIZED();
RETURN_FALSE_IF_NULL(usage_table_header);
RETURN_FALSE_IF_NULL(usage_entry_info);
RETURN_FALSE_IF_NULL(lru_upgrade);
video_widevine_client::sdk::File file;
if (RetrieveHashedFile(GetUsageTableFileName(), &file) != kNoError) {
@@ -947,6 +996,8 @@ bool DeviceFiles::RetrieveUsageTableInfo(
const UsageTableInfo& usage_table_info = file.usage_table_info();
*lru_upgrade = !usage_table_info.use_lru();
*usage_table_header = usage_table_info.usage_table_header();
usage_entry_info->resize(usage_table_info.usage_entry_info_size());
for (int i = 0; i < usage_table_info.usage_entry_info_size(); ++i) {
@@ -956,11 +1007,15 @@ bool DeviceFiles::RetrieveUsageTableInfo(
switch (info.storage()) {
case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_LICENSE:
(*usage_entry_info)[i].storage_type = kStorageLicense;
(*usage_entry_info)[i].last_use_time = info.last_use_time();
(*usage_entry_info)[i].offline_license_expiry_time =
info.offline_license_expiry_time();
break;
case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_USAGE_INFO:
(*usage_entry_info)[i].storage_type = kStorageUsageInfo;
(*usage_entry_info)[i].usage_info_file_name =
info.usage_info_file_name();
(*usage_entry_info)[i].last_use_time = info.last_use_time();
break;
case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_UNKNOWN:
default:

View File

@@ -33,6 +33,10 @@ message License {
optional LicenseState state = 1;
optional bytes pssh_data = 2;
// |license_request|, |license|, |renewal_request|, |renewal| represents a
// serialized video_widevine::SignedMessage containing a
// video_widevine::License or video_widevine::LicenseRequest from
// license_protocol.proto
optional bytes license_request = 3;
optional bytes license = 4;
optional bytes renewal_request = 5;
@@ -53,6 +57,8 @@ message UsageInfo {
message ProviderSession {
optional bytes token = 1;
optional bytes license_request = 2;
// |license| represents a video_widevine::SignedMessage containing a
// video_widevine::License from license_protocol.proto
optional bytes license = 3;
optional bytes key_set_id = 4;
optional bytes usage_entry = 5;
@@ -82,10 +88,16 @@ message UsageTableInfo {
optional UsageEntryStorage storage = 1;
optional bytes key_set_id = 2;
optional bytes usage_info_file_name = 3; // hash of the app_id
// LRU table replacement data.
optional int64 last_use_time = 4 [default = 0];
// Only used if storage == LICENSE (offline license).
optional int64 offline_license_expiry_time = 5 [default = 0];
}
optional bytes usage_table_header = 1;
repeated UsageEntryInfo usage_entry_info = 2;
optional bool use_lru = 3 [default = false];
}
message File {

View File

@@ -4,12 +4,16 @@
#include "usage_table_header.h"
#include <algorithm>
#include "cdm_random.h"
#include "crypto_session.h"
#include "license.h"
#include "log.h"
#include "wv_cdm_constants.h"
namespace wvcdm {
namespace {
std::string kEmptyString;
size_t kMaxCryptoRetries = 3;
@@ -22,15 +26,119 @@ std::string kOldUsageEntryServerMacKey(wvcdm::MAC_KEY_SIZE, 0);
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.
constexpr size_t kLruRemovalSetSize = 3;
// Threshold for maximum number of unexpired offline licenses before
// they are considered to be removed. This could occur if there are
// not enough expired offline or streaming licenses to remove.
// This threshold is set to prevent thrashing in the case that there
// are a very large number of unexpired offline licenses and few
// expired / streaming licenses (ie, number of unexpired licenses nears
// the capacity of the usage table).
constexpr size_t kLruUnexpiredThreshold = 150;
// Convert |license_message| -> SignedMessage -> License.
bool ParseLicenseFromLicenseMessage(const CdmKeyResponse& license_message,
video_widevine::License* license) {
using video_widevine::SignedMessage;
if (license == nullptr) {
LOGE("Output parameter |license| is null");
return false;
}
SignedMessage signed_license_response;
if (!signed_license_response.ParseFromString(license_message)) {
LOGW("Unabled to parse signed license response");
return false;
}
if (signed_license_response.type() != SignedMessage::LICENSE) {
LOGW("Unexpected signed message: type = %d, expected_type = %d",
static_cast<int>(signed_license_response.type()),
static_cast<int>(SignedMessage::LICENSE));
return false;
}
if (!signed_license_response.has_signature()) {
LOGW("License response message is not signed");
return false;
}
if (!license->ParseFromString(signed_license_response.msg())) {
LOGW("Failed to parse license");
return false;
}
return true;
}
bool RetrieveOfflineLicense(DeviceFiles* device_files,
const std::string& key_set_id,
CdmKeyResponse* license_message,
uint32_t* usage_entry_number) {
if (device_files == nullptr) {
LOGE("DeviceFiles handle is null");
return false;
}
if (license_message == nullptr) {
LOGE("Output parameter |license_message| is null");
return false;
}
if (usage_entry_number == nullptr) {
LOGE("Output parameter |usage_entry_number| is null");
return false;
}
DeviceFiles::CdmLicenseData license_data;
DeviceFiles::ResponseType result = DeviceFiles::kNoError;
if (!device_files->RetrieveLicense(key_set_id, &license_data, &result)) {
LOGW("Failed to retrieve license: key_set_id = %s, result = %d",
key_set_id.c_str(), static_cast<int>(result));
return false;
}
*license_message = std::move(license_data.license);
*usage_entry_number = license_data.usage_entry_number;
return true;
}
bool RetrieveUsageInfoLicense(DeviceFiles* device_files,
const std::string& usage_info_file_name,
const std::string& key_set_id,
CdmKeyResponse* license_message,
uint32_t* usage_entry_number) {
if (device_files == nullptr) {
LOGE("DeviceFiles handle is null");
return false;
}
if (license_message == nullptr) {
LOGE("Output parameter |license_message| is null");
return false;
}
if (usage_entry_number == nullptr) {
LOGE("Output parameter |usage_entry_number| is null");
return false;
}
CdmUsageEntry usage_entry;
std::string provider_session_token;
CdmKeyMessage license_request;
if (!device_files->RetrieveUsageInfoByKeySetId(
usage_info_file_name, key_set_id, &provider_session_token,
&license_request, license_message, &usage_entry,
usage_entry_number)) {
LOGW(
"Failed to retrieve usage information: "
"key_set_id = %s, usage_info_file_name = %s",
key_set_id.c_str(), usage_info_file_name.c_str());
return false;
}
return true;
}
} // namespace
namespace wvcdm {
UsageTableHeader::UsageTableHeader()
: security_level_(kSecurityLevelUninitialized),
requested_security_level_(kLevelDefault),
is_inited_(false) {
is_inited_(false),
clock_ref_(&clock_) {
file_system_.reset(new FileSystem());
file_handle_.reset(new DeviceFiles(file_system_.get()));
}
@@ -67,15 +175,28 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics();
if (metrics == nullptr) metrics = &alternate_crypto_metrics_;
if (file_handle_->RetrieveUsageTableInfo(&usage_table_header_,
&usage_entry_info_)) {
bool run_lru_upgrade = false;
if (file_handle_->RetrieveUsageTableInfo(
&usage_table_header_, &usage_entry_info_, &run_lru_upgrade)) {
LOGI("Number of usage entries: %zu", usage_entry_info_.size());
status = crypto_session->LoadUsageTableHeader(usage_table_header_);
bool lru_success = true;
if (status == NO_ERROR && run_lru_upgrade) {
// If the loaded table info does not contain LRU information, then
// the information must be added immediately before being used.
if (!LruUpgradeAllUsageEntries()) {
LOGE(
"Unable to init usage table header: "
"Failed to perform LRU upgrade to usage entry table");
lru_success = false;
}
}
// If the usage table header has been successfully loaded, and is at
// minimum capacity (>200), we need to make sure we can still add and
// remove entries. If not, clear files/data and recreate usage header table.
if (status == NO_ERROR) {
if (status == NO_ERROR && lru_success) {
if (usage_entry_info_.size() > kMinUsageEntriesSupported) {
uint32_t temporary_usage_entry_number;
@@ -89,14 +210,15 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
CryptoSession* local_crypto_session = test_crypto_session_.get();
if (local_crypto_session == nullptr) {
scoped_crypto_session.reset(
(CryptoSession::MakeCryptoSession(metrics)));
CryptoSession::MakeCryptoSession(metrics));
local_crypto_session = scoped_crypto_session.get();
}
result = local_crypto_session->Open(requested_security_level_);
if (result == NO_ERROR) {
result = AddEntry(local_crypto_session, true, kDummyKeySetId,
kEmptyString, &temporary_usage_entry_number);
kEmptyString, kEmptyString,
&temporary_usage_entry_number);
}
}
if (result == NO_ERROR) {
@@ -113,9 +235,9 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
}
}
if (status != NO_ERROR) {
LOGE("Failed to load usage table: security_level = %d",
static_cast<int>(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();
@@ -141,7 +263,7 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
CdmResponseType UsageTableHeader::AddEntry(
CryptoSession* crypto_session, bool persistent_license,
const CdmKeySetId& key_set_id, const std::string& usage_info_file_name,
uint32_t* usage_entry_number) {
const CdmKeyResponse& license_message, uint32_t* usage_entry_number) {
LOGI("Adding usage entry");
metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics();
@@ -149,19 +271,33 @@ CdmResponseType UsageTableHeader::AddEntry(
CdmResponseType status = crypto_session->CreateUsageEntry(usage_entry_number);
// If usage entry creation fails due to insufficient resources, release a
// random entry and try again.
for (uint32_t retry_count = 0; retry_count < kMaxCryptoRetries &&
status == INSUFFICIENT_CRYPTO_RESOURCES_3;
++retry_count) {
if (usage_entry_info_.size() == 0) {
break;
if (status == INSUFFICIENT_CRYPTO_RESOURCES_3) {
// If usage entry creation fails due to insufficient resources, release an
// entry based on LRU.
std::vector<uint32_t> removal_candidates;
if (!GetRemovalCandidates(&removal_candidates)) {
LOGE("Could not determine which license to remove");
return status;
}
uint32_t entry_number_to_delete =
CdmRandom::RandomInRange(usage_entry_info_.size() - 1);
DeleteEntry(entry_number_to_delete, file_handle_.get(), metrics);
status = crypto_session->CreateUsageEntry(usage_entry_number);
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];
if (DeleteEntry(entry_number_to_delete, file_handle_.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;
}
}
}
status = crypto_session->CreateUsageEntry(usage_entry_number);
}
}
if (status != NO_ERROR) return status;
@@ -181,7 +317,7 @@ CdmResponseType UsageTableHeader::AddEntry(
"New entry number is larger than table size, resizing: "
"entry_info_number = %u, table_size = %zu",
*usage_entry_number, usage_entry_info_.size());
size_t number_of_entries = usage_entry_info_.size();
const size_t number_of_entries = usage_entry_info_.size();
usage_entry_info_.resize(*usage_entry_number + 1);
for (size_t i = number_of_entries; i < usage_entry_info_.size() - 1; ++i) {
usage_entry_info_[i].storage_type = kStorageTypeUnknown;
@@ -195,9 +331,28 @@ CdmResponseType UsageTableHeader::AddEntry(
usage_entry_info_[*usage_entry_number].storage_type =
persistent_license ? kStorageLicense : kStorageUsageInfo;
usage_entry_info_[*usage_entry_number].key_set_id = key_set_id;
if (!persistent_license)
usage_entry_info_[*usage_entry_number].last_use_time = GetCurrentTime();
if (!persistent_license) {
usage_entry_info_[*usage_entry_number].usage_info_file_name =
usage_info_file_name;
usage_entry_info_[*usage_entry_number].offline_license_expiry_time = 0;
} else {
// Need to determine the expire time for offline licenses.
video_widevine::License license;
if (license_message.size() > 0 &&
ParseLicenseFromLicenseMessage(license_message, &license)) {
const video_widevine::License::Policy& policy = license.policy();
usage_entry_info_[*usage_entry_number].offline_license_expiry_time =
license.license_start_time() + policy.rental_duration_seconds() +
policy.playback_duration_seconds();
} else {
// If the license duration cannot be determined for any reason, it
// is assumed to last at most 33 days.
usage_entry_info_[*usage_entry_number].offline_license_expiry_time =
usage_entry_info_[*usage_entry_number].last_use_time +
kDefaultExpireDuration;
}
}
LOGI("New usage entry: usage_entry_number = %u", *usage_entry_number);
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
@@ -243,17 +398,29 @@ CdmResponseType UsageTableHeader::LoadEntry(CryptoSession* crypto_session,
status = crypto_session->LoadUsageEntry(usage_entry_number, usage_entry);
}
if (status == NO_ERROR) {
usage_entry_info_[usage_entry_number].last_use_time = GetCurrentTime();
}
return status;
}
CdmResponseType UsageTableHeader::UpdateEntry(CryptoSession* crypto_session,
CdmResponseType UsageTableHeader::UpdateEntry(uint32_t usage_entry_number,
CryptoSession* crypto_session,
CdmUsageEntry* usage_entry) {
LOGI("Locking to update entry");
std::unique_lock<std::mutex> auto_lock(usage_table_header_lock_);
if (usage_entry_number >= usage_entry_info_.size()) {
LOGE("Usage entry number %u is larger than usage entry size %zu",
usage_entry_number, usage_entry_info_.size());
return USAGE_INVALID_PARAMETERS_2;
}
CdmResponseType status =
crypto_session->UpdateUsageEntry(&usage_table_header_, usage_entry);
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_);
return NO_ERROR;
@@ -602,7 +769,7 @@ bool UsageTableHeader::UpgradeLicensesFromUsageTable(
if (!CreateDummyOldUsageEntry(crypto_session.get())) continue;
status = AddEntry(crypto_session.get(), true /* persistent license */,
key_set_ids[i], kEmptyString,
key_set_ids[i], kEmptyString, license_data.license,
&license_data.usage_entry_number);
if (status != NO_ERROR) continue;
@@ -615,7 +782,8 @@ bool UsageTableHeader::UpgradeLicensesFromUsageTable(
continue;
}
status = UpdateEntry(crypto_session.get(), &license_data.usage_entry);
status = UpdateEntry(license_data.usage_entry_number, crypto_session.get(),
&license_data.usage_entry);
if (status != NO_ERROR) {
crypto_session->Close();
@@ -676,9 +844,10 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
// TODO(rfrias): We need to fill in the app id, but it is hashed
// and we have no way to extract. Use the hased filename instead?
status = AddEntry(crypto_session.get(), false /* usage info */,
usage_data[j].key_set_id, usage_info_file_names[i],
&(usage_data[j].usage_entry_number));
status =
AddEntry(crypto_session.get(), false /* usage info */,
usage_data[j].key_set_id, usage_info_file_names[i],
usage_data[j].license, &(usage_data[j].usage_entry_number));
if (status != NO_ERROR) continue;
@@ -691,7 +860,8 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
continue;
}
status = UpdateEntry(crypto_session.get(), &(usage_data[j].usage_entry));
status = UpdateEntry(usage_data[j].usage_entry_number,
crypto_session.get(), &(usage_data[j].usage_entry));
if (status != NO_ERROR) {
crypto_session->Close();
@@ -735,4 +905,258 @@ void UsageTableHeader::DeleteEntryForTest(uint32_t usage_entry_number) {
usage_entry_info_.resize(usage_entry_info_.size() - 1);
}
bool UsageTableHeader::LruUpgradeAllUsageEntries() {
LOGV("Upgrading all usage entries with LRU information");
if (usage_entry_info_.size() == 0) return true; // Nothing to upgrade.
// For each entry, the status upgrading that entry is stored. At the
// end, all problematic licenses will be marked as invalid.
std::vector<uint32_t> bad_license_file_entries;
for (size_t usage_entry_number = 0;
usage_entry_number < usage_entry_info_.size(); ++usage_entry_number) {
CdmUsageEntryInfo& usage_entry_info = usage_entry_info_[usage_entry_number];
uint32_t retrieved_entry_number;
CdmKeyResponse license_message;
bool retrieve_response = false;
switch (usage_entry_info.storage_type) {
case kStorageLicense: {
retrieve_response = RetrieveOfflineLicense(
file_handle_.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,
usage_entry_info.key_set_id, &license_message,
&retrieved_entry_number);
break;
}
case kStorageTypeUnknown:
bad_license_file_entries.push_back(usage_entry_number);
continue;
default: {
LOGW("Unknown usage entry storage type: %d, usage_entry_number = %u",
static_cast<int>(usage_entry_info.storage_type),
usage_entry_number);
bad_license_file_entries.push_back(usage_entry_number);
continue;
}
}
if (!retrieve_response) {
LOGW("Could not retrieve license message: usage_entry_number = %u",
usage_entry_number);
bad_license_file_entries.push_back(usage_entry_number);
continue;
}
if (retrieved_entry_number != usage_entry_number) {
LOGW(
"Usage entry number mismatched: usage_entry_number = %u, "
"retrieved_entry_number = %u",
usage_entry_number, retrieved_entry_number);
bad_license_file_entries.push_back(usage_entry_number);
continue;
}
video_widevine::License license;
if (!ParseLicenseFromLicenseMessage(license_message, &license)) {
LOGW("Could not parse license: usage_entry_number = %u",
usage_entry_number);
bad_license_file_entries.push_back(usage_entry_number);
continue;
}
// If |license_start_time| is 0, then this entry will be considered
// for replacement above all others.
usage_entry_info.last_use_time = license.license_start_time();
if (usage_entry_info.storage_type == kStorageLicense) {
// Only offline licenses need |offline_license_expiry_time| set.
const video_widevine::License::Policy& policy = license.policy();
// TODO(b/139372190): Change how these fields are set once feature is
// implemented.
if (policy.license_duration_seconds() == 0) {
// Zero implies unlimited license duration.
usage_entry_info.offline_license_expiry_time =
license.license_start_time() + policy.rental_duration_seconds() +
policy.playback_duration_seconds();
} else {
usage_entry_info.offline_license_expiry_time =
license.license_start_time() + policy.license_duration_seconds();
}
} else {
usage_entry_info.offline_license_expiry_time = 0;
}
} // End for loop.
if (bad_license_file_entries.size() == usage_entry_info_.size()) {
LOGE("Failed to perform LRU upgrade for every entry: count = %zu",
usage_entry_info_.size());
return false;
}
// Maps <usage_info_file_name> -> [<key_set_id>].
std::map<std::string, std::vector<std::string>> usage_info_clean_up;
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);
} else if (usage_entry_info.storage_type == kStorageUsageInfo) {
// To reduce write cycles, the deletion of usage info will be done
// in bulk.
auto it = usage_info_clean_up.find(usage_entry_info.usage_info_file_name);
if (it == usage_info_clean_up.end()) {
it = usage_info_clean_up
.emplace(usage_entry_info.usage_info_file_name,
std::vector<std::string>())
.first;
}
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();
}
for (const auto& p : usage_info_clean_up) {
file_handle_->DeleteMultipleUsageInfoByKeySetIds(p.first, p.second);
}
return true;
}
bool UsageTableHeader::GetRemovalCandidates(
std::vector<uint32_t>* removal_candidates) {
LOGI("Locking to determine removal candidates");
std::unique_lock<std::mutex> auto_lock(usage_table_header_lock_);
return DetermineLicenseToRemove(usage_entry_info_, GetCurrentTime(),
kLruUnexpiredThreshold, kLruRemovalSetSize,
removal_candidates);
}
// Static.
bool UsageTableHeader::DetermineLicenseToRemove(
const std::vector<CdmUsageEntryInfo>& usage_entry_info_list,
int64_t current_time, size_t unexpired_threshold, size_t removal_count,
std::vector<uint32_t>* removal_candidates) {
if (removal_candidates == nullptr) {
LOGE("Output parameter |removal_candidates| is null");
return false;
}
if (removal_count == 0) {
LOGE("|removal_count| cannot be zero");
return false;
}
if (usage_entry_info_list.empty()) {
return false;
}
removal_candidates->clear();
std::vector<uint32_t> unknown_storage_entry_numbers;
// |entry_numbers| contains expired offline and streaming license.
std::vector<uint32_t> entry_numbers;
std::vector<uint32_t> unexpired_offline_license_entry_numbers;
// Separate the entries based on their priority properties.
for (uint32_t entry_number = 0; entry_number < usage_entry_info_list.size();
++entry_number) {
const CdmUsageEntryInfo& usage_entry_info =
usage_entry_info_list[entry_number];
if (usage_entry_info.storage_type == kStorageLicense) {
if (usage_entry_info.offline_license_expiry_time > current_time) {
// Unexpired offline.
unexpired_offline_license_entry_numbers.push_back(entry_number);
} else {
// Expired offline.
entry_numbers.push_back(entry_number);
}
} else if (usage_entry_info.storage_type == kStorageUsageInfo) {
// Streaming.
entry_numbers.push_back(entry_number);
} else {
// Unknown entries.
unknown_storage_entry_numbers.push_back(entry_number);
}
}
// Select any entries of unknown storage type.
if (!unknown_storage_entry_numbers.empty()) {
if (unknown_storage_entry_numbers.size() >= removal_count) {
// Case: There are enough entries with unknown storage types to
// fill the removal set.
removal_candidates->insert(
removal_candidates->begin(), unknown_storage_entry_numbers.begin(),
unknown_storage_entry_numbers.begin() + removal_count);
return true;
}
// Fill whatever are available, and check for more.
*removal_candidates = std::move(unknown_storage_entry_numbers);
}
// Sort licenses based on last used time.
const auto compare_last_used = [&](uint32_t i, uint32_t j) {
return usage_entry_info_list[i].last_use_time <
usage_entry_info_list[j].last_use_time;
};
// Check if unexpired licenses should be considered too.
if (unexpired_offline_license_entry_numbers.size() > unexpired_threshold) {
std::sort(unexpired_offline_license_entry_numbers.begin(),
unexpired_offline_license_entry_numbers.end(), compare_last_used);
if (unexpired_offline_license_entry_numbers.size() > removal_count) {
unexpired_offline_license_entry_numbers.resize(removal_count);
}
// Merge the sets.
entry_numbers.insert(entry_numbers.end(),
unexpired_offline_license_entry_numbers.begin(),
unexpired_offline_license_entry_numbers.end());
}
// Sort expired offline and streaming license based on last used time.
std::sort(entry_numbers.begin(), entry_numbers.end(), compare_last_used);
if ((entry_numbers.size() + removal_candidates->size()) <= removal_count) {
// Under testing conditions, it is possible for there to be fewer usage
// entries than there are being requested.
// Move whatever values are available to the removal candidates.
removal_candidates->insert(removal_candidates->end(), entry_numbers.begin(),
entry_numbers.end());
return removal_candidates->size() > 0;
}
const size_t remaining_removal_count =
removal_count - removal_candidates->size();
// Based on the last use time the |remaining_removal_count|-th
// least recently used entry, filter out all elements which have
// been used more recently than it. This might result in a set
// which is larger than the desired size.
const int64_t cutoff_last_use_time =
usage_entry_info_list[entry_numbers[remaining_removal_count - 1]]
.last_use_time;
const auto equal_to_cutoff = [&](uint32_t entry_number) {
return usage_entry_info_list[entry_number].last_use_time ==
cutoff_last_use_time;
};
const auto first_cutoff_it =
std::find_if(entry_numbers.begin(), entry_numbers.end(), equal_to_cutoff);
const auto after_last_cutoff_it =
std::find_if_not(first_cutoff_it, entry_numbers.end(), equal_to_cutoff);
// To avoid always selecting the greatest entry number (due to the
// sort & reverse), we randomize the set.
std::shuffle(first_cutoff_it, after_last_cutoff_it,
std::default_random_engine(CdmRandom::Rand()));
removal_candidates->insert(removal_candidates->end(), entry_numbers.cbegin(),
entry_numbers.cbegin() + remaining_removal_count);
return true;
}
} // namespace wvcdm