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:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user