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

@@ -110,6 +110,7 @@ class DeviceFiles {
virtual bool RetrieveLicense(const std::string& key_set_id,
CdmLicenseData* license_data,
ResponseType* result);
virtual bool DeleteLicense(const std::string& key_set_id);
virtual bool ListLicenses(std::vector<std::string>* key_set_ids);
virtual bool DeleteAllFiles();
@@ -153,6 +154,27 @@ class DeviceFiles {
virtual bool DeleteUsageInfo(const std::string& usage_info_file_name,
const std::string& provider_session_token);
// Deletes a set of provider sessions from the specified usage info.
// Sessions removed are based on the provided |key_set_ids|. If
// there are no remaining sessions associated with the usage info
// then the file will be deleted; otherwise, the remaining sessions
// are written back to the usage info file.
//
// Args:
// usage_info_file_name: name of the file containing the usage info
// message. This name should _not_ be the complete path, just
// the file name.
// key_set_ids: The list of key set IDs to be removed from the
// usage info. Note that any key set ids that are not present
// in the usage info are silently ignored.
// Returns:
// `true` if the file existed, and operations were completed as
// expected. `false` if the file does not exist or if there is an
// issue writing the result back to file.
virtual bool DeleteMultipleUsageInfoByKeySetIds(
const std::string& usage_info_file_name,
const std::vector<std::string>& key_set_ids);
// Delete usage information from the file system. Puts a list of all the
// psts that were deleted from the file into |provider_session_tokens|.
virtual bool DeleteAllUsageInfoForApp(
@@ -211,9 +233,12 @@ class DeviceFiles {
const CdmUsageTableHeader& usage_table_header,
const std::vector<CdmUsageEntryInfo>& usage_entry_info);
// When retrieving usage table information from the file system; any
// table that has yet to be updated for the LRU attributes will be
// indicated by |lru_upgrade|.
virtual bool RetrieveUsageTableInfo(
CdmUsageTableHeader* usage_table_header,
std::vector<CdmUsageEntryInfo>* usage_entry_info);
std::vector<CdmUsageEntryInfo>* usage_entry_info, bool* lru_upgrade);
virtual bool DeleteUsageTableInfo();
@@ -258,6 +283,7 @@ class DeviceFiles {
FRIEND_TEST(DeviceFilesUsageInfoTest, Store);
FRIEND_TEST(DeviceFilesUsageTableTest, Read);
FRIEND_TEST(DeviceFilesUsageTableTest, Store);
FRIEND_TEST(DeviceFilesUsageTableTest, ReadWithoutLruData);
FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest);
FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test);
FRIEND_TEST(WvCdmRequestLicenseTest, UsageInfoRetryTest);

View File

@@ -10,6 +10,7 @@
#include <string>
#include <vector>
#include "clock.h"
#include "crypto_session.h"
#include "device_files.h"
#include "disallow_copy_and_assign.h"
@@ -61,11 +62,13 @@ class UsageTableHeader {
bool persistent_license,
const CdmKeySetId& key_set_id,
const std::string& usage_info_filename,
const CdmKeyResponse& license_message,
uint32_t* usage_entry_number);
virtual CdmResponseType LoadEntry(CryptoSession* crypto_session,
const CdmUsageEntry& usage_entry,
uint32_t usage_entry_number);
virtual CdmResponseType UpdateEntry(CryptoSession* crypto_session,
virtual CdmResponseType UpdateEntry(uint32_t usage_entry_number,
CryptoSession* crypto_session,
CdmUsageEntry* usage_entry);
// The licenses or usage info records specified by |usage_entry_number|
@@ -85,6 +88,27 @@ class UsageTableHeader {
size_t size() { return usage_entry_info_.size(); }
const std::vector<CdmUsageEntryInfo>& usage_entry_info() const {
return usage_entry_info_;
}
// Set the reference clock used for the method GetCurrentTime().
void SetClock(Clock* clock) {
if (clock != nullptr)
clock_ref_ = clock;
else
clock_ref_ = &clock_;
}
static bool DetermineLicenseToRemoveForTesting(
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) {
return DetermineLicenseToRemove(usage_entry_info_list, current_time,
unexpired_threshold, removal_count,
removal_candidates);
}
private:
CdmResponseType MoveEntry(uint32_t from /* usage entry number */,
const CdmUsageEntry& from_usage_entry,
@@ -111,6 +135,59 @@ class UsageTableHeader {
virtual bool CreateDummyOldUsageEntry(CryptoSession* crypto_session);
// Performs and LRU upgrade on all loaded CdmUsageEntryInfo from a
// device file that had not yet been upgraded to use the LRU data.
virtual bool LruUpgradeAllUsageEntries();
virtual bool GetRemovalCandidates(std::vector<uint32_t>* removal_candidates);
int64_t GetCurrentTime() { return clock_ref_->GetCurrentTime(); }
// Uses an LRU-base algorithm to determine which licenses should be
// removed. This is intended to be used if the usage table is full
// and a new entry needs to be added.
//
// Algorithm overview:
// Given the set of all usage entry infos, the entries which are
// of unknown storage type or are the most stale will be returned,
// with some exceptions for offline licenses.
// 1) Expired licenses will always be considered. Expiration is
// determined using the usage entry info's
// |offline_license_expiry| compared to the provided
// |current_time|.
// 2) Unexpired offline licenses will only be considered for
// removal if the number of unexpired offline licenses exceeds
// |unexpired_threshold|.
// The number of licenses to be considered will be less than or
// equal to the requested |removal_count|.
//
// Unknown storage types will be considered above all other entry
// types.
//
// Parameters:
// [in] usage_entry_info_list: The complete list of known usage
// entries.
// [in] current_time: The current time to compare expiration times
// against.
// [in] unexpired_threshold: The maximum number of unexpired
// offline licenses that are present, before offline
// licenses would be considered for removal.
// [in] removal_count: The desired number of removal candidate to
// find. Note that the actual number will be anywhere
// between 1 and |removal_count|. Must be greater than or
// equal to 1.
// [out] removal_candidates: List of usage entry numbers of the
// entries to be removed. Assume to be unaffected if the
// function returns |false|.
//
// Returns:
// |true| if at least one removal candidate can be determined.
// Otherwise returns |false|.
static bool 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);
// 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.
@@ -133,6 +210,14 @@ class UsageTableHeader {
metrics::CryptoMetrics alternate_crypto_metrics_;
// |clock_| represents the system's "wall clock". For the clock's purpose
// we do not need a more secure clock.
Clock clock_;
// |clock_ref_| is a pointer to the clock which is to be used for
// obtaining the current time. By default, this points to the internal
// |clock_| variable, however, it can be overrided for testing purpose.
Clock* clock_ref_;
#if defined(UNIT_TEST)
// Test related declarations
friend class UsageTableHeaderTest;

View File

@@ -400,6 +400,7 @@ enum CdmResponseType {
INVALID_SRM_LIST = 346,
KEYSET_ID_NOT_FOUND_4 = 347,
SESSION_NOT_FOUND_22 = 348,
USAGE_INVALID_PARAMETERS_2 = 349,
// Don't forget to add new values to
// * core/test/test_printers.cpp.
// * android/include/mapErrors-inl.h
@@ -504,11 +505,25 @@ struct CdmUsageEntryInfo {
CdmUsageEntryStorageType storage_type;
CdmKeySetId key_set_id;
std::string usage_info_file_name;
int64_t last_use_time;
int64_t offline_license_expiry_time; // Only for offline licenses.
bool operator==(const CdmUsageEntryInfo& other) const {
return storage_type == other.storage_type &&
key_set_id == other.key_set_id &&
(storage_type != kStorageUsageInfo ||
usage_info_file_name == other.usage_info_file_name);
if (this == &other) {
return true;
}
if (storage_type != other.storage_type || key_set_id != other.key_set_id ||
last_use_time != other.last_use_time) {
return false;
}
// Certain fields only have meaning based on the storage type.
if (storage_type == kStorageUsageInfo) {
return usage_info_file_name == other.usage_info_file_name;
}
if (storage_type == kStorageLicense) {
return offline_license_expiry_time == other.offline_license_expiry_time;
}
// else storage_type == kStorageTypeUnknown
return true;
}
};

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

View File

@@ -122,7 +122,8 @@ class MockUsageTableHeader : public UsageTableHeader {
public:
MockUsageTableHeader() : UsageTableHeader() {}
MOCK_METHOD2(UpdateEntry, CdmResponseType(CryptoSession* crypto_session,
MOCK_METHOD3(UpdateEntry, CdmResponseType(uint32_t usage_entry_number,
CryptoSession* crypto_session,
CdmUsageEntry* usage_entry));
};
@@ -345,7 +346,7 @@ TEST_F(CdmSessionTest, UpdateUsageEntry) {
DoAll(SetArgPointee<0>(kUsageEntrySupport), Return(NO_ERROR)));
EXPECT_CALL(*license_parser_, provider_session_token())
.WillRepeatedly(Return("Mock provider session token"));
EXPECT_CALL(usage_table_header_, UpdateEntry(NotNull(), NotNull()))
EXPECT_CALL(usage_table_header_, UpdateEntry(_, NotNull(), NotNull()))
.WillRepeatedly(Return(NO_ERROR));
EXPECT_EQ(NO_ERROR, cdm_session_->Init(nullptr));

View File

@@ -1873,42 +1873,17 @@ constexpr size_t kNumberOfHlsAttributes = ArraySize(kHlsAttributesTestData);
// kUsageTableInfoTestData are equal.
const CdmUsageEntryInfo kUsageEntriesTestData[] = {
// usage entry 0
{
kStorageLicense,
"ksid0",
"",
},
{kStorageLicense, "ksid0", "", 1318402800, 1321254000},
// usage entry 1
{
kStorageLicense,
"ksid1",
"",
},
{kStorageLicense, "ksid1", "", 1050649200, 1053500400},
// usage entry 2
{
kStorageUsageInfo,
"",
"app_id_2",
},
{kStorageUsageInfo, "", "app_id_2", 694252800, 0},
// usage entry 3
{
kStorageUsageInfo,
"",
"app_id_3",
},
{kStorageUsageInfo, "", "app_id_3", 983001600, 0},
// usage entry 4
{
kStorageLicense,
"ksid4",
"",
},
{kStorageLicense, "ksid4", "", 316166400, 319017600},
// usage entry 5
{
kStorageUsageInfo,
"",
"app_id_5",
},
{kStorageUsageInfo, "", "app_id_5", 802162800, 0},
};
struct UsageTableTestInfo {
@@ -1920,47 +1895,90 @@ const UsageTableTestInfo kUsageTableInfoTestData[] = {
// usage table 0
{a2bs_hex("5574517CCC"),
a2bs_hex("0A18080510013A120A055574517CCC1209080112056B73696430122018268E3F"
"384F28D04BEE00304089C000463C22E987532855390915FD02C36B5C")},
a2bs_hex("0A26080510013A200A055574517CCC1215080112056B7369643020F0F5D4F404"
"28F0F882F6041801122055C2834976220A2819445B8D6EF5ADE9D7A050006941"
"3EDFB9948F928B317B89")},
// usage table 1
{a2bs_hex("CA870203010001288001"),
a2bs_hex("0A2C080510013A260A0ACA870203010001288001120B080112056B736964301A"
"00120B080112056B736964311A00122049A8F3481444A5B64B6C4F05FBCC2EF8"
"CB67444A08654763F2F5B80F658D7B38")},
a2bs_hex("0A46080510013A400A0ACA8702030100012880011217080112056B736964301A"
"0020F0F5D4F40428F0F882F6041217080112056B736964311A0020F0C4FEF403"
"28F0C7ACF60318011220B000674E4E24E67384C4631DE05CB3C1E07CDE6B0412"
"CACE359DEAED6CF2D566")},
// usage table 2
{a2bs_hex("7A7D507618A5D3A68F05228E023082010A028201"),
a2bs_hex("0A46080510013A400A147A7D507618A5D3A68F05228E023082010A028201120B"
"080112056B736964301A00120B080112056B736964311A00120E080212001A08"
"6170705F69645F321220783E93A02223BDB94E743856C0F69C35B213ACCDDE91"
"93E48E9186AA83B80584")},
a2bs_hex("0A66080510013A600A147A7D507618A5D3A68F05228E023082010A0282011217"
"080112056B736964301A0020F0F5D4F40428F0F882F6041217080112056B7369"
"64311A0020F0C4FEF40328F0C7ACF6031214080212001A086170705F69645F32"
"2080EA85CB02180112202701F8F75537EBA12217796401A81E9E0F59A5B10F51"
"F6C12E3BE3D8747CB745")},
// usage table 3
{a2bs_hex("E83A4902772DAFD2740B7748E9C3B1752D6F12859CED07E82969B4EC"),
a2bs_hex("0A5E080510013A580A1CE83A4902772DAFD2740B7748E9C3B1752D6F12859CED"
"07E82969B4EC120B080112056B736964301A00120B080112056B736964311A00"
"120E080212001A086170705F69645F32120E080212001A086170705F69645F33"
"122084E67F1338727291BC3D92E28442DC8B0F44CB5AF7B98A799313B7EB7F55"
"ED18")},
a2bs_hex("0A8401080510013A7E0A1CE83A4902772DAFD2740B7748E9C3B1752D6F12859C"
"ED07E82969B4EC1217080112056B736964301A0020F0F5D4F40428F0F882F604"
"1217080112056B736964311A0020F0C4FEF40328F0C7ACF6031214080212001A"
"086170705F69645F322080EA85CB021214080212001A086170705F69645F3320"
"80D4DDD40318011220776BD3D0BBCC573AEC7466A8B0E4F1A013495A3BE492F3"
"FF4BA3D15FC9697902")},
// usage table 4
{a2bs_hex("CA870203010001288001300112800250D1F8B1ECF849B60FF93E37C4DEEF"
"52F1CCFC047EF42300131F9C4758F4"),
a2bs_hex("0A7C080510013A760A2DCA870203010001288001300112800250D1F8B1ECF849"
"B60FF93E37C4DEEF52F1CCFC047EF42300131F9C4758F4120B080112056B7369"
"64301A00120B080112056B736964311A00120E080212001A086170705F69645F"
"32120E080212001A086170705F69645F33120B080112056B736964341A001220"
"1CDFCFED5E58A1DF77E1B335305424E1F0260340F9CC15985684C43A4207652"
"1")},
a2bs_hex("0AAF01080510013AA8010A2DCA870203010001288001300112800250D1F8B1EC"
"F849B60FF93E37C4DEEF52F1CCFC047EF42300131F9C4758F41217080112056B"
"736964301A0020F0F5D4F40428F0F882F6041217080112056B736964311A0020"
"F0C4FEF40328F0C7ACF6031214080212001A086170705F69645F322080EA85CB"
"021214080212001A086170705F69645F332080D4DDD4031217080112056B7369"
"64341A002080A2E196012880A58F980118011220E7F0F123E513FCF3BC6BC17B"
"1531A8317654C5EF005655348D82FA01FDAD85CB")},
// usage table 5
{a2bs_hex("EC83A4902772DAFD2740B7748E9C3B1752D6F12859CED07E8882969B433E"
"C29AC6FDBE79230B0FAED5D94CF6B829A420BBE3270323941776EE60DD6B"),
a2bs_hex("0A9C01080510013A95010A3CEC83A4902772DAFD2740B7748E9C3B1752D6F128"
a2bs_hex("0AD401080510013ACD010A3CEC83A4902772DAFD2740B7748E9C3B1752D6F128"
"59CED07E8882969B433EC29AC6FDBE79230B0FAED5D94CF6B829A420BBE32703"
"23941776EE60DD6B120B080112056B736964301A00120B080112056B73696431"
"1A00120E080212001A086170705F69645F32120E080212001A086170705F6964"
"5F33120B080112056B736964341A00120E080212001A086170705F69645F3512"
"20305C7A27A918268119E1996FC182C153DF805034A387F90C3585749E764731"
"32")},
"23941776EE60DD6B1217080112056B736964301A0020F0F5D4F40428F0F882F6"
"041217080112056B736964311A0020F0C4FEF40328F0C7ACF603121408021200"
"1A086170705F69645F322080EA85CB021214080212001A086170705F69645F33"
"2080D4DDD4031217080112056B736964341A002080A2E196012880A58F980112"
"14080212001A086170705F69645F3520F090C0FE0218011220A35C771A67AECF"
"06A72468DC2C380E5CFDCD377A8ADF848F26B7F22D24D23872")},
};
const CdmUsageEntryInfo kUsageEntriesWithoutLruData[] = {
{kStorageLicense, "ksid0", "", 0, 0},
{kStorageUsageInfo, "", "app_id_1", 0, 0}};
const std::string kUsageTableWithoutLruData = a2bs_hex(
"0A1F080510013A191209080112056B73696430120C08021A086170705F69645F"
"31122044C964271799F0631AE388BD150A873C5DD16B35F61BFF0300857AEEB8"
"454FA2");
// DeleteMultipleUsageInfoByKeySetIds test data.
const std::string kHashedUsageInfoFileWithThreeKeySetIds = a2bs_hex(
"0A36080310012A300A0E220C6B65795F7365745F69645F310A0E220C6B65795F"
"7365745F69645F320A0E220C6B65795F7365745F69645F331220781BE848CE8A"
"0CE84FF563D54D30150A115EAB27F7023C19191EC41BDC4EDAA9");
// Only contains key set ID 1 & 2.
const std::string kHashedUsageInfoFileWithTwoKeySetIds = a2bs_hex(
"0A26080310012A200A0E220C6B65795F7365745F69645F310A0E220C6B65795F"
"7365745F69645F321220B693E7142BF263FF51B6F8AF4DD7F20E2701059A841C"
"F947995A7B39354E1CA9");
const std::string kHashedUsageInfoFileWithKeySet1 = a2bs_hex(
"0A16080310012A100A0E220C6B65795F7365745F69645F3112200FBBB47C89DE"
"484D02BFB4CB20B19BA43CBCAD6F4A78EFB295ACC66BA0B83B85");
const std::string kHashedUsageInfoFileWithKeySet2 = a2bs_hex(
"0A16080310012A100A0E220C6B65795F7365745F69645F321220B5F53E5A1D8E"
"860196D2B3E027FFB32F11C1B2269784A904A3EA6E59C2A6A96D");
const std::string kHashedUsageInfoFileWithKeySet3 = a2bs_hex(
"0A16080310012A100A0E220C6B65795F7365745F69645F331220F28C1B20A302"
"543F44659D995A58899A03B9D51C65FD6C05AD1E6D2BACACADA7");
const std::vector<std::string> kHashedUsageInfoFileWithSingleKeySetList = {
kHashedUsageInfoFileWithKeySet1, kHashedUsageInfoFileWithKeySet2,
kHashedUsageInfoFileWithKeySet3};
const std::vector<std::string> kHashedUsageInfoFileKeySetList = {
"key_set_id_1", "key_set_id_2", "key_set_id_3"};
class MockFile : public File {
public:
MockFile() {}
@@ -1999,6 +2017,7 @@ using ::testing::_;
using ::testing::AllOf;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::Expectation;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::InSequence;
@@ -2078,6 +2097,10 @@ class DeviceFilesHlsAttributesTest
class DeviceFilesUsageTableTest : public DeviceFilesTest,
public ::testing::WithParamInterface<int> {};
class DeviceFilesDeleteMultipleUsageInfoTest
: public DeviceFilesTest,
public ::testing::WithParamInterface<int> {};
MATCHER(IsCreateFileFlagSet, "") { return FileSystem::kCreate & arg; }
MATCHER_P(IsStrEq, str, "") {
// Estimating the length of data. We can have gmock provide length
@@ -2606,6 +2629,221 @@ TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) {
}
}
// From a usage info file containing 3 provider sessions, 2 will be
// deleted using the |key_set_id| associated with them.
// It is expected that once the provider sessions are deleted, the
// usage info file will be overwritten with only the remaining provider
// session token present.
TEST_P(DeviceFilesDeleteMultipleUsageInfoTest, DeleteAllButOne) {
static const std::string kUsageInfoFileName = "usage_info_file.bin";
const int key_set_id_index = GetParam();
const std::string& result_hashed_usage_info_file =
kHashedUsageInfoFileWithSingleKeySetList[key_set_id_index];
MockFileSystem file_system;
DeviceFiles device_files(&file_system);
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
std::string file_path;
ASSERT_TRUE(Properties::GetDeviceFilesBasePath(kSecurityLevelL1, &file_path));
file_path += kUsageInfoFileName;
EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true));
EXPECT_CALL(file_system, FileSize(file_path))
.WillOnce(Return(kHashedUsageInfoFileWithThreeKeySetIds.size()));
// File read expectations.
MockFile* file_in = new MockFile();
EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly))
.WillOnce(Return(file_in));
Expectation read_original =
EXPECT_CALL(*file_in, Read(NotNull(), _))
.WillOnce(
DoAll(SetArrayArgument<0>(
kHashedUsageInfoFileWithThreeKeySetIds.cbegin(),
kHashedUsageInfoFileWithThreeKeySetIds.cend()),
Return(kHashedUsageInfoFileWithThreeKeySetIds.size())));
// File write expectations.
MockFile* file_out = new MockFile();
EXPECT_CALL(file_system,
DoOpen(file_path, FileSystem::kCreate | FileSystem::kTruncate))
.WillOnce(Return(file_out));
EXPECT_CALL(*file_out, Write(StrEq(result_hashed_usage_info_file), _))
.After(read_original)
.WillOnce(Return(result_hashed_usage_info_file.size()));
const auto not_current_key_set =
[key_set_id_index](const std::string& key_set_id) {
return key_set_id != kHashedUsageInfoFileKeySetList[key_set_id_index];
};
std::vector<std::string> to_remove;
std::copy_if(kHashedUsageInfoFileKeySetList.cbegin(),
kHashedUsageInfoFileKeySetList.cend(),
std::back_inserter(to_remove), not_current_key_set);
EXPECT_TRUE(device_files.DeleteMultipleUsageInfoByKeySetIds(
kUsageInfoFileName, to_remove));
}
INSTANTIATE_TEST_CASE_P(DeviceFilesTest, DeviceFilesDeleteMultipleUsageInfoTest,
::testing::Range(0, 3));
// Delete all provider sessions from a usage info file. It is expected
// that the usage info file will be deleted (not written back to with
// an empty provider session list).
TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, DeleteAllKeySetIds) {
static const std::string kUsageInfoFileName = "usage_info_file.bin";
// Setup
MockFileSystem file_system;
DeviceFiles device_files(&file_system);
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
std::string file_path;
ASSERT_TRUE(Properties::GetDeviceFilesBasePath(kSecurityLevelL1, &file_path));
file_path += kUsageInfoFileName;
// File read expectations.
MockFile* file_in = new MockFile();
EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true));
EXPECT_CALL(file_system, FileSize(file_path))
.WillOnce(Return(kHashedUsageInfoFileWithThreeKeySetIds.size()));
EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly))
.WillOnce(Return(file_in));
EXPECT_CALL(*file_in, Read(NotNull(), _))
.WillOnce(DoAll(
SetArrayArgument<0>(kHashedUsageInfoFileWithThreeKeySetIds.cbegin(),
kHashedUsageInfoFileWithThreeKeySetIds.cend()),
Return(kHashedUsageInfoFileWithThreeKeySetIds.size())));
EXPECT_CALL(file_system, Remove(file_path)).WillOnce(Return(true));
// Remove all sessions, and the file should be deleted.
EXPECT_TRUE(device_files.DeleteMultipleUsageInfoByKeySetIds(
kUsageInfoFileName, kHashedUsageInfoFileKeySetList));
}
// If the key set IDs provided cannot be found in the usage info file,
// then no action should be taken, and the function returns true.
TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, DeleteNone) {
static const std::string kUsageInfoFileName = "usage_info_file.bin";
// Setup
MockFileSystem file_system;
DeviceFiles device_files(&file_system);
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
std::string file_path;
ASSERT_TRUE(Properties::GetDeviceFilesBasePath(kSecurityLevelL1, &file_path));
file_path += kUsageInfoFileName;
EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true));
// Call, not providing any key set IDs. Should return true without any
// action, assuming the file exists.
EXPECT_TRUE(device_files.DeleteMultipleUsageInfoByKeySetIds(
kUsageInfoFileName, std::vector<std::string>()));
MockFile* file_in = new MockFile();
EXPECT_CALL(file_system, FileSize(file_path))
.WillOnce(Return(kHashedUsageInfoFileWithThreeKeySetIds.size()));
EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly))
.WillOnce(Return(file_in));
EXPECT_CALL(*file_in, Read(NotNull(), _))
.WillOnce(DoAll(
SetArrayArgument<0>(kHashedUsageInfoFileWithThreeKeySetIds.cbegin(),
kHashedUsageInfoFileWithThreeKeySetIds.cend()),
Return(kHashedUsageInfoFileWithThreeKeySetIds.size())));
// Call, providing key set IDs which do not exist in the usage info
// file.
const std::vector<std::string> key_set_ids = {"fictional_key_set_id_1",
"fictional_key_set_id_2",
"fictional_key_set_id_3"};
EXPECT_TRUE(device_files.DeleteMultipleUsageInfoByKeySetIds(
kUsageInfoFileName, key_set_ids));
}
// Delete a single key from the file.
// Test will delete "key_set_id_2" from the usage info file containing
// "key_set_id_1" & "key_set_id_2".
TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, DeleteOne) {
static const std::string kUsageInfoFileName = "usage_info_file.bin";
MockFileSystem file_system;
DeviceFiles device_files(&file_system);
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
std::string file_path;
ASSERT_TRUE(Properties::GetDeviceFilesBasePath(kSecurityLevelL1, &file_path));
file_path += kUsageInfoFileName;
EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true));
EXPECT_CALL(file_system, FileSize(file_path))
.WillOnce(Return(kHashedUsageInfoFileWithTwoKeySetIds.size()));
// File read expectations.
MockFile* file_in = new MockFile();
EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly))
.WillOnce(Return(file_in));
Expectation read_original =
EXPECT_CALL(*file_in, Read(NotNull(), _))
.WillOnce(DoAll(
SetArrayArgument<0>(kHashedUsageInfoFileWithTwoKeySetIds.cbegin(),
kHashedUsageInfoFileWithTwoKeySetIds.cend()),
Return(kHashedUsageInfoFileWithTwoKeySetIds.size())));
// File write expectations.
MockFile* file_out = new MockFile();
EXPECT_CALL(file_system,
DoOpen(file_path, FileSystem::kCreate | FileSystem::kTruncate))
.WillOnce(Return(file_out));
EXPECT_CALL(*file_out, Write(StrEq(kHashedUsageInfoFileWithKeySet1), _))
.After(read_original)
.WillOnce(Return(kHashedUsageInfoFileWithKeySet1.size()));
const std::vector<std::string> to_remove = {"key_set_id_2"};
EXPECT_TRUE(device_files.DeleteMultipleUsageInfoByKeySetIds(
kUsageInfoFileName, to_remove));
}
// There are a few error cases where DeleteMultipleUsageInfoByKeySetIds()
// will fail. Such as if the specified file does not exist, or if it
// the file is found to have a hash mismatch. In both these cases,
// false should be returned.
TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, BadFile) {
static const std::string kUsageInfoFileName = "usage_info_file.bin";
static const std::string kHashlessUsageInfoFile = a2bs_hex(
"0A36080310012A300A0E220C6B65795F7365745F69645F310A0E220C6B65795F"
"7365745F69645F320A0E220C6B65795F7365745F69645F331220000000000000"
"0000000000000000000000000000000000000000000000000000");
// Setup
MockFileSystem file_system;
DeviceFiles device_files(&file_system);
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
std::string file_path;
ASSERT_TRUE(Properties::GetDeviceFilesBasePath(kSecurityLevelL1, &file_path));
file_path += kUsageInfoFileName;
// File does not exist.
EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(false));
EXPECT_FALSE(device_files.DeleteMultipleUsageInfoByKeySetIds(
kUsageInfoFileName, kHashedUsageInfoFileKeySetList));
// File is missing hash.
MockFile* file_in = new MockFile();
EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true));
EXPECT_CALL(file_system, FileSize(file_path))
.WillOnce(Return(kHashlessUsageInfoFile.size()));
EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly))
.WillOnce(Return(file_in));
EXPECT_CALL(*file_in, Read(NotNull(), _))
.WillOnce(DoAll(SetArrayArgument<0>(kHashlessUsageInfoFile.cbegin(),
kHashlessUsageInfoFile.cend()),
Return(kHashlessUsageInfoFile.size())));
EXPECT_CALL(file_system, Remove(file_path)).WillOnce(Return(true));
// Remove all sessions, and the file should be deleted.
EXPECT_FALSE(device_files.DeleteMultipleUsageInfoByKeySetIds(
kUsageInfoFileName, kHashedUsageInfoFileKeySetList));
}
TEST_F(DeviceFilesUsageInfoTest, ListNullParam) {
MockFileSystem file_system;
@@ -3209,8 +3447,9 @@ TEST_P(DeviceFilesUsageTableTest, Read) {
std::vector<CdmUsageEntryInfo> usage_entry_info;
CdmUsageTableHeader usage_table_header;
ASSERT_TRUE(device_files.RetrieveUsageTableInfo(&usage_table_header,
&usage_entry_info));
bool lru_upgrade;
ASSERT_TRUE(device_files.RetrieveUsageTableInfo(
&usage_table_header, &usage_entry_info, &lru_upgrade));
EXPECT_EQ(kUsageTableInfoTestData[index].usage_table_header,
usage_table_header);
EXPECT_EQ(index + 1u, usage_entry_info.size());
@@ -3222,10 +3461,52 @@ TEST_P(DeviceFilesUsageTableTest, Read) {
usage_entry_info[i].key_set_id);
EXPECT_EQ(kUsageEntriesTestData[i].usage_info_file_name,
usage_entry_info[i].usage_info_file_name);
EXPECT_EQ(kUsageEntriesTestData[i].last_use_time,
usage_entry_info[i].last_use_time);
EXPECT_EQ(kUsageEntriesTestData[i].offline_license_expiry_time,
usage_entry_info[i].offline_license_expiry_time);
}
EXPECT_FALSE(lru_upgrade);
}
INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageTableTest,
::testing::Range(0, 6));
TEST_F(DeviceFilesUsageTableTest, ReadWithoutLruData) {
// Setup file.
MockFile* file = new MockFile();
EXPECT_CALL(*file, Read(NotNull(), Eq(kUsageTableWithoutLruData.size())))
.WillOnce(DoAll(SetArrayArgument<0>(kUsageTableWithoutLruData.cbegin(),
kUsageTableWithoutLruData.cend()),
Return(kUsageTableWithoutLruData.size())));
// Setup filesystem.
const std::string path =
device_base_path_ + DeviceFiles::GetUsageTableFileName();
MockFileSystem file_system;
EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true));
EXPECT_CALL(file_system, FileSize(StrEq(path)))
.WillRepeatedly(Return(kUsageTableWithoutLruData.size()));
EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file));
DeviceFiles device_files(&file_system);
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
std::vector<CdmUsageEntryInfo> usage_entry_info;
CdmUsageTableHeader usage_table_header;
bool lru_upgrade;
ASSERT_TRUE(device_files.RetrieveUsageTableInfo(
&usage_table_header, &usage_entry_info, &lru_upgrade));
EXPECT_EQ(ArraySize(kUsageEntriesWithoutLruData), usage_entry_info.size());
for (size_t i = 0; i < ArraySize(kUsageEntriesWithoutLruData); ++i) {
const CdmUsageEntryInfo& expected_entry = kUsageEntriesWithoutLruData[i];
const CdmUsageEntryInfo& retrieved_entry = usage_entry_info[i];
EXPECT_EQ(expected_entry, retrieved_entry);
}
EXPECT_TRUE(lru_upgrade);
}
} // namespace wvcdm

View File

@@ -15,6 +15,18 @@ class MockClock : public Clock {
MOCK_METHOD0(GetCurrentTime, int64_t());
};
// Frozen clock will always return the same value for the current time.
// Intended to be used for testing where using the actual time would
// cause flaky tests.
class FrozenClock : public Clock {
int64_t always_time_;
public:
FrozenClock(int64_t always_time = 0) : always_time_(always_time) {}
int64_t GetCurrentTime() override { return always_time_; }
void SetTime(int64_t new_time) { always_time_ = new_time; }
};
} // wvcdm
#endif // CDM_TEST_MOCK_CLOCK_H_

View File

@@ -884,6 +884,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
case USAGE_INVALID_PARAMETERS_1:
*os << "USAGE_INVALID_PARAMETERS_1";
break;
case USAGE_INVALID_PARAMETERS_2:
*os << "USAGE_INVALID_PARAMETERS_2";
break;
case USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE:
*os << "USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE";
break;

File diff suppressed because it is too large Load Diff

View File

@@ -281,10 +281,11 @@ enum {
kInvalidLicenseType2 = ERROR_DRM_VENDOR_MIN + 296,
kSignatureNotFound2 = ERROR_DRM_VENDOR_MIN + 297,
kSessionKeysNotFound2 = ERROR_DRM_VENDOR_MIN + 298,
kUsageInvalidParameters2 = ERROR_DRM_VENDOR_MIN + 299,
// This should always follow the last error code.
// The offset value should be updated each time a new error code is added.
kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 298,
kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 299,
// Used by crypto test mode
kErrorTestMode = ERROR_DRM_VENDOR_MAX,

View File

@@ -531,6 +531,8 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) {
return kUsageInvalidNewEntry;
case wvcdm::USAGE_INVALID_PARAMETERS_1:
return kUsageInvalidParameters1;
case wvcdm::USAGE_INVALID_PARAMETERS_2:
return kUsageInvalidParameters2;
case wvcdm::USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE:
return kUsageStoreEntryRetrieveInvalidStorageType;
case wvcdm::USAGE_STORE_ENTRY_RETRIEVE_LICENSE_FAILED:

View File

@@ -349,7 +349,7 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) {
case wvcdm::REWRAP_DEVICE_RSA_KEY_ERROR:
case wvcdm::REWRAP_DEVICE_RSA_KEY_30_ERROR:
case wvcdm::INVALID_SRM_LIST:
case wvcdm::USAGE_INVALID_PARAMETERS_2:
ALOGW("Returns UNKNOWN error for legacy status: %d", res);
return Status::ERROR_DRM_UNKNOWN;