Delete usage information on insufficient resources
[ Merge of http://go/wvgerrit/58460 ] If OEMCrypto runs out of space in the usage table header+entries adding a new license or loading/using an existing one might fail. This CL makes two modifications to handle this scenario. * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES will be returned from OEMCrypto_CreateNewUsageEntry or OEMCrypto_LoadUsageEntry. An attempt will be made to release a LRU entry from the usage table and retry the operation. This may be retried 3 times unless success occurs earlier. * On initialization, the usage table header is loaded. If there are more than the minimum number of usage entries (200), an attempt is made to add a usage entry. If this fails, we are likely in an unrecoverable state. We then delete all offline licenses, usage information and recreate the usage table header. This will allow future playback attempts to succeed and offline licenses to be able to be downloaded but will lose all current offline licenses and secure stops. Bug: 112486006 Test: WV unit/integration tests, GtsMediaDrmTest Playback tests using Netflix and Play movies. Change-Id: I41a18d69a329f8a96c7b607d299ce73af3d56177
This commit is contained in:
@@ -129,6 +129,8 @@ class DeviceFiles {
|
||||
const std::string& usage_info_file_name,
|
||||
std::vector<std::string>* provider_session_tokens);
|
||||
|
||||
virtual bool DeleteAllUsageInfo();
|
||||
|
||||
// Retrieve one usage info from the file. Subsequent calls will retrieve
|
||||
// subsequent entries in the table for this app_id.
|
||||
virtual bool RetrieveUsageInfo(
|
||||
@@ -186,6 +188,8 @@ class DeviceFiles {
|
||||
CdmUsageTableHeader* usage_table_header,
|
||||
std::vector<CdmUsageEntryInfo>* usage_entry_info);
|
||||
|
||||
virtual bool DeleteUsageTableInfo();
|
||||
|
||||
private:
|
||||
// Extract serial number and system ID from DRM Device certificate
|
||||
bool ExtractDeviceInfo(const std::string& device_certificate,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "crypto_session.h"
|
||||
#include "device_files.h"
|
||||
#include "file_store.h"
|
||||
#include "lock.h"
|
||||
@@ -17,8 +18,6 @@
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CryptoSession;
|
||||
|
||||
// Offline licenses/secure stops may be securely tracked using usage
|
||||
// tables (OEMCrypto v9-12) or usage table headers+usage entries
|
||||
// (OEMCrypto v13+). This class assists with the latter, synchronizing
|
||||
@@ -117,6 +116,8 @@ class UsageTableHeader {
|
||||
// data-structures
|
||||
Lock usage_table_header_lock_;
|
||||
|
||||
metrics::CryptoMetrics alternate_crypto_metrics_;
|
||||
|
||||
// Test related declarations
|
||||
friend class UsageTableHeaderTest;
|
||||
|
||||
|
||||
@@ -2085,6 +2085,8 @@ CdmResponseType CryptoSession::LoadUsageEntry(
|
||||
return LOAD_USAGE_ENTRY_GENERATION_SKEW;
|
||||
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
|
||||
return LOAD_USAGE_ENTRY_SIGNATURE_FAILURE;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES_3;
|
||||
default:
|
||||
return LOAD_USAGE_ENTRY_UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
@@ -611,6 +611,15 @@ bool DeviceFiles::DeleteAllUsageInfoForApp(
|
||||
return RemoveFile(usage_info_file_name);
|
||||
}
|
||||
|
||||
bool DeviceFiles::DeleteAllUsageInfo() {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::DeleteAllUsageInfo: not initialized");
|
||||
return false;
|
||||
}
|
||||
return RemoveFile(kUsageInfoFileNamePrefix + std::string(kWildcard) +
|
||||
kUsageInfoFileNameExt);
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveUsageInfo(
|
||||
const std::string& usage_info_file_name,
|
||||
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> >* usage_info) {
|
||||
@@ -1105,6 +1114,14 @@ bool DeviceFiles::RetrieveUsageTableInfo(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::DeleteUsageTableInfo() {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::DeleteUsageTableInfo: not initialized");
|
||||
return false;
|
||||
}
|
||||
return RemoveFile(GetUsageTableFileName());
|
||||
}
|
||||
|
||||
bool DeviceFiles::StoreFileWithHash(const std::string& name,
|
||||
const std::string& serialized_file) {
|
||||
// calculate SHA hash
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
namespace {
|
||||
std::string kEmptyString;
|
||||
size_t kMaxCryptoRetries = 3;
|
||||
size_t kMinUsageEntriesSupported = 200;
|
||||
wvcdm::CdmKeySetId kDummyKeySetId = "DummyKsid";
|
||||
uint64_t kOldUsageEntryTimeSinceLicenseReceived = 0;
|
||||
uint64_t kOldUsageEntryTimeSinceFirstDecrypt = 0;
|
||||
uint64_t kOldUsageEntryTimeSinceLastDecrypt = 0;
|
||||
@@ -58,14 +61,42 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
|
||||
}
|
||||
|
||||
CdmResponseType status = USAGE_INFO_NOT_FOUND;
|
||||
metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics();
|
||||
if (metrics == NULL) metrics = &alternate_crypto_metrics_;
|
||||
|
||||
if (file_handle_->RetrieveUsageTableInfo(&usage_table_header_,
|
||||
&usage_entry_info_)) {
|
||||
status = crypto_session->LoadUsageTableHeader(usage_table_header_);
|
||||
|
||||
// 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 (usage_entry_info_.size() > kMinUsageEntriesSupported) {
|
||||
uint32_t temporary_usage_entry_number;
|
||||
CdmResponseType result = AddEntry(crypto_session, true,
|
||||
kDummyKeySetId, kEmptyString,
|
||||
&temporary_usage_entry_number);
|
||||
if (result == NO_ERROR) {
|
||||
result = DeleteEntry(temporary_usage_entry_number,
|
||||
file_handle_.get(), metrics);
|
||||
}
|
||||
if (result != NO_ERROR) {
|
||||
LOGE("UsageTableHeader::Init: Unable to create/delete new entry. "
|
||||
"Clear usage entries, security level: %d, usage entries: %d",
|
||||
security_level, usage_entry_info_.size());
|
||||
status = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status != NO_ERROR) {
|
||||
LOGE(
|
||||
"UsageTableHeader::Init: load usage table failed, security level: %d",
|
||||
security_level);
|
||||
file_handle_->DeleteAllLicenses();
|
||||
file_handle_->DeleteAllUsageInfo();
|
||||
file_handle_->DeleteUsageTableInfo();
|
||||
usage_entry_info_.clear();
|
||||
usage_table_header_.clear();
|
||||
status = crypto_session->CreateUsageTableHeader(&usage_table_header_);
|
||||
@@ -77,11 +108,6 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
|
||||
if (status != NO_ERROR) return false;
|
||||
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
|
||||
|
||||
metrics::CryptoMetrics alternate_metrics;
|
||||
metrics::CryptoMetrics* metrics =
|
||||
crypto_session->GetCryptoMetrics() != NULL ?
|
||||
crypto_session->GetCryptoMetrics() : &alternate_metrics;
|
||||
|
||||
UpgradeFromUsageTable(file_handle_.get(), metrics);
|
||||
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
|
||||
}
|
||||
@@ -95,11 +121,25 @@ CdmResponseType UsageTableHeader::AddEntry(
|
||||
const CdmKeySetId& key_set_id, const std::string& usage_info_file_name,
|
||||
uint32_t* usage_entry_number) {
|
||||
LOGV("UsageTableHeader::AddEntry: Lock");
|
||||
AutoLock auto_lock(usage_table_header_lock_);
|
||||
CdmResponseType status = crypto_session->CreateUsageEntry(usage_entry_number);
|
||||
|
||||
metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics();
|
||||
if (metrics == NULL) metrics = &alternate_crypto_metrics_;
|
||||
|
||||
uint32_t retry_count = 0;
|
||||
CdmResponseType status = NO_ERROR;
|
||||
do {
|
||||
{
|
||||
AutoLock auto_lock(usage_table_header_lock_);
|
||||
status = crypto_session->CreateUsageEntry(usage_entry_number);
|
||||
}
|
||||
if (status == INSUFFICIENT_CRYPTO_RESOURCES_3)
|
||||
DeleteEntry(retry_count, file_handle_.get(), metrics);
|
||||
} while (status == INSUFFICIENT_CRYPTO_RESOURCES_3 &&
|
||||
++retry_count < kMaxCryptoRetries);
|
||||
|
||||
if (status != NO_ERROR) return status;
|
||||
|
||||
AutoLock auto_lock(usage_table_header_lock_);
|
||||
if (*usage_entry_number < usage_entry_info_.size()) {
|
||||
LOGE("UsageTableHeader::AddEntry: new entry %d smaller than table size: %d",
|
||||
*usage_entry_number, usage_entry_info_.size());
|
||||
@@ -134,17 +174,34 @@ CdmResponseType UsageTableHeader::AddEntry(
|
||||
CdmResponseType UsageTableHeader::LoadEntry(CryptoSession* crypto_session,
|
||||
const CdmUsageEntry& usage_entry,
|
||||
uint32_t usage_entry_number) {
|
||||
LOGV("UsageTableHeader::LoadEntry: Lock");
|
||||
AutoLock auto_lock(usage_table_header_lock_);
|
||||
{
|
||||
LOGV("UsageTableHeader::LoadEntry: Lock");
|
||||
AutoLock auto_lock(usage_table_header_lock_);
|
||||
|
||||
if (usage_entry_number >= usage_entry_info_.size()) {
|
||||
LOGE(
|
||||
"UsageTableHeader::LoadEntry: usage entry number %d larger than table "
|
||||
"size: %d",
|
||||
usage_entry_number, usage_entry_info_.size());
|
||||
return USAGE_INVALID_LOAD_ENTRY;
|
||||
if (usage_entry_number >= usage_entry_info_.size()) {
|
||||
LOGE(
|
||||
"UsageTableHeader::LoadEntry: usage entry number %d larger than "
|
||||
"table size: %d",
|
||||
usage_entry_number, usage_entry_info_.size());
|
||||
return USAGE_INVALID_LOAD_ENTRY;
|
||||
}
|
||||
}
|
||||
return crypto_session->LoadUsageEntry(usage_entry_number, usage_entry);
|
||||
metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics();
|
||||
if (metrics == NULL) metrics = &alternate_crypto_metrics_;
|
||||
|
||||
uint32_t retry_count = 0;
|
||||
CdmResponseType status = NO_ERROR;
|
||||
do {
|
||||
{
|
||||
AutoLock auto_lock(usage_table_header_lock_);
|
||||
status = crypto_session->LoadUsageEntry(usage_entry_number, usage_entry);
|
||||
}
|
||||
if (status == INSUFFICIENT_CRYPTO_RESOURCES_3)
|
||||
DeleteEntry(retry_count, file_handle_.get(), metrics);
|
||||
} while (status == INSUFFICIENT_CRYPTO_RESOURCES_3 &&
|
||||
++retry_count < kMaxCryptoRetries);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
CdmResponseType UsageTableHeader::UpdateEntry(CryptoSession* crypto_session,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -268,16 +268,26 @@ class WvCdmExtendedDurationTest : public WvCdmTestBase {
|
||||
|
||||
void GenerateKeyRequest(const std::string& init_data,
|
||||
CdmLicenseType license_type) {
|
||||
CdmResponseType response;
|
||||
GenerateKeyRequest(init_data, license_type, &response);
|
||||
EXPECT_EQ(KEY_MESSAGE, response);
|
||||
}
|
||||
|
||||
void GenerateKeyRequest(const std::string& init_data,
|
||||
CdmLicenseType license_type,
|
||||
CdmResponseType *response) {
|
||||
CdmAppParameterMap app_parameters;
|
||||
CdmKeyRequest key_request;
|
||||
|
||||
EXPECT_EQ(KEY_MESSAGE, decryptor_.GenerateKeyRequest(
|
||||
session_id_, key_set_id_, "video/mp4", init_data,
|
||||
license_type, app_parameters, NULL,
|
||||
kDefaultCdmIdentifier, &key_request));
|
||||
EXPECT_EQ(kKeyRequestTypeInitial, key_request.type);
|
||||
key_msg_ = key_request.message;
|
||||
EXPECT_EQ(0u, key_request.url.size());
|
||||
*response = decryptor_.GenerateKeyRequest(
|
||||
session_id_, key_set_id_, "video/mp4", init_data,
|
||||
license_type, app_parameters, NULL,
|
||||
kDefaultCdmIdentifier, &key_request);
|
||||
if (*response == KEY_MESSAGE) {
|
||||
EXPECT_EQ(kKeyRequestTypeInitial, key_request.type);
|
||||
key_msg_ = key_request.message;
|
||||
EXPECT_EQ(0u, key_request.url.size());
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateRenewalRequest(CdmLicenseType license_type,
|
||||
@@ -406,14 +416,21 @@ class WvCdmExtendedDurationTest : public WvCdmTestBase {
|
||||
void VerifyKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth,
|
||||
bool is_renewal) {
|
||||
std::string resp = GetKeyRequestResponse(server_url, client_auth);
|
||||
VerifyKeyRequestResponse(server_url, client_auth, is_renewal, NULL);
|
||||
}
|
||||
|
||||
if (is_renewal) {
|
||||
// TODO application makes a license request, CDM will renew the license
|
||||
// when appropriate
|
||||
EXPECT_EQ(KEY_ADDED, decryptor_.AddKey(session_id_, resp, &key_set_id_));
|
||||
void VerifyKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth,
|
||||
bool /* is_renewal */,
|
||||
CdmResponseType* status) {
|
||||
std::string resp = GetKeyRequestResponse(server_url, client_auth);
|
||||
CdmResponseType sts =
|
||||
decryptor_.AddKey(session_id_, resp, &key_set_id_);
|
||||
|
||||
if (status == NULL) {
|
||||
EXPECT_EQ(KEY_ADDED, sts);
|
||||
} else {
|
||||
EXPECT_EQ(KEY_ADDED, decryptor_.AddKey(session_id_, resp, &key_set_id_));
|
||||
*status = sts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1574,6 +1591,55 @@ TEST_P(WvCdmOfflineUsageReportTest, UsageTest) {
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Cdm, WvCdmOfflineUsageReportTest,
|
||||
::testing::Values(0, 1, 2));
|
||||
|
||||
// This test verifies that a device can still work if the maximum capacity
|
||||
// of the usage entry table is reached. New usage entries, for offline
|
||||
// licenses, can still be added and existing licenses can still be played back.
|
||||
TEST_F(WvCdmExtendedDurationTest, MaxUsageEntryOfflineRecoveryTest) {
|
||||
Unprovision();
|
||||
Provision();
|
||||
|
||||
// override default settings unless configured through the command line
|
||||
std::string key_id;
|
||||
std::string client_auth;
|
||||
GetOfflineConfiguration(&key_id, &client_auth);
|
||||
|
||||
// Download large number of offline licenses. If OEMCrypto returns
|
||||
// OEMCrypto_ERROR_INSUFFICIENT_RESOURCES when usage table is at capacity,
|
||||
// licenses will be deleted internally to make space and we might
|
||||
// not encounter an error.
|
||||
CdmResponseType response = NO_ERROR;
|
||||
for (size_t i = 0; i < 2000; ++i) {
|
||||
decryptor_.OpenSession(g_key_system, NULL, kDefaultCdmIdentifier, NULL,
|
||||
&session_id_);
|
||||
GenerateKeyRequest(key_id, kLicenseTypeOffline, &response);
|
||||
if (response != KEY_MESSAGE) {
|
||||
decryptor_.CloseSession(session_id_);
|
||||
break;
|
||||
}
|
||||
VerifyKeyRequestResponse(kUatLicenseServer, client_auth, false, &response);
|
||||
if (response != KEY_ADDED) {
|
||||
decryptor_.CloseSession(session_id_);
|
||||
break;
|
||||
}
|
||||
EXPECT_EQ(KEY_ADDED, response);
|
||||
decryptor_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
// If we encountered an error, verify that on UsageTableHeader creation
|
||||
// the usage entries will be deleted and that we can add new ones.
|
||||
if (response != KEY_ADDED && response != KEY_MESSAGE) {
|
||||
Provision();
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
decryptor_.OpenSession(g_key_system, NULL, kDefaultCdmIdentifier, NULL,
|
||||
&session_id_);
|
||||
GenerateKeyRequest(key_id, kLicenseTypeOffline);
|
||||
VerifyKeyRequestResponse(kUatLicenseServer, client_auth, false);
|
||||
decryptor_.CloseSession(session_id_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
void show_menu(char* prog_name) {
|
||||
|
||||
Reference in New Issue
Block a user