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