Merges to android Pi release (part 7)

These are a set of CLs merged from the wv cdm repo to the android repo.

* Resolve intermittent decrypt error.

  Author: Jeff Fore <jfore@google.com>

  [ Merge of http://go/wvgerrit/35720 ]

  The CdmSession's closed state was not properly
  initialized resulting in intermittent
  SESSION_NOT_FOUND_FOR_DECRYPT errors.

  In CdmEngine::Decrypt the session is looked up by
  the key id. A list of open sessions is acquired
  by calling CdmSessionMap::GetSessionList and each
  session in the list is queried to see if it has
  the key.

  In building the list in CdmSessionMap::GetSessionList,
  sessions are only added to the query list *if* the session
  is not closed.

  The closed status was not initialized and during testing
  the query list would not contain the session causing
  CdmEngine::Decrypt to return SESSION_NOT_FOUND_FOR_DECRYPT
  resulting in the ce cdm api returning widevine::Cdm::kNoKey.

* No support for pre- C++11 compilation.

  Author: Gene Morgan <gmorgan@google.com>

  [ Merge of http://go/wvgerrit/35381 ]

* Handle unaligned nonce pointer in RewrapDeviceRSAKey calls.

  Author: Gene Morgan <gmorgan@google.com>

  [ Merge of http://go/wvgerrit/35340 ]

  The pointer points into a message and it may not be aligned.
  Always copy the nonce into aligned memory before checking it.

  BUG: 38140370

  Add note to CHANGELOG for this.

* Compiler strictness: more checks and code cleanup.

  Author: Gene Morgan <gmorgan@google.com>

  [ Merge of http://go/wvgerrit/35300 ]

  Use the switches proposed in b/38033653 (as much as possible - some
  conflicts with protobufs and gtest prevent fully accepting them).

  Switch to clang for x32 build; ensure that both x86-64 and x86-32 builds
  compile and link cleanly.

  BUG: 38032429
  BUG: 38033653

  This partially resolves b/38458986

* Android build fixes

  Author: Rahul Frias <rfrias@google.com>

  [ Merge of http://go/wvgerrit/35102 ]

  These corrections address compile warnings and errors for android
  and unit tests.

* Embedded License: Add sub license key sessions.

  Author: Jeff Fore <jfore@google.com>

  [ Merge of http://go/wvgerrit/33680 ]

  NOTE: this adds the AddSubSession() method, but it is not yet being
  used. Use and proper cleanup is in an upcoming CL.

* Embedded license: Add track label field.

  Author: Jeff Fore <jfore@google.com>

  [ Merge of http://go/wvgerrit/33660 ]

  A new track label field (a string) is added to the key container and the
  sub session data objects.

  This field will be used in handling sub license requests.

* Embedded license: extract keys from init_data.

  Author: Jeff Fore <jfore@google.com>

  [ Merge of http://go/wvgerrit/33621 ]

* Embedded license: add protobuf messages.

  Author: Jeff Fore <jfore@google.com>

  [ Merge of http://go/wvgerrit/33620 ]

  also sync the widevine header definition with recent naming changes.

* Improve handling of provisioning response errors.

  Author: Gene Morgan <gmorgan@google.com>

  [ Merge of http://go/wvgerrit/33600 ]

  Separate out the case of no response and the case
  where the message is believed to be a JSON+base64
  message but it doesn't parse properly.

BUG: 71650075
Test: Not currently passing. Will be addressed in a subsequent
  commit in the chain.

Change-Id: I3c86f1c54980b071aec7461ac58541836551f896
This commit is contained in:
Rahul Frias
2018-01-09 23:36:42 -08:00
parent 00da44bb68
commit 80659961ac
38 changed files with 1249 additions and 360 deletions

View File

@@ -19,7 +19,8 @@ namespace wvcdm {
class CryptoKey;
class UsageTableHeader;
typedef std::map<CryptoKeyId, CryptoKey*> CryptoKeyMap;
typedef std::map<std::string, CryptoKey*> CryptoKeyMap;
typedef std::map<std::string, CryptoSessionId> SubLicenseSessionMap;
class CryptoSession {
public:
@@ -167,8 +168,19 @@ class CryptoSession {
virtual CdmResponseType ShrinkUsageTableHeader(
uint32_t new_entry_count, CdmUsageTableHeader* usage_table_header);
virtual CdmResponseType MoveUsageEntry(uint32_t new_entry_number);
virtual bool CreateOldUsageEntry(
uint64_t time_since_license_received,
uint64_t time_since_first_decrypt,
uint64_t time_since_last_decrypt,
UsageDurationStatus status,
const std::string& server_mac_key,
const std::string& client_mac_key,
const std::string& provider_session_token);
virtual CdmResponseType CopyOldUsageEntry(
const std::string& provider_session_token);
virtual metrics::CryptoMetrics* GetCryptoMetrics() { return metrics_; }
virtual CdmResponseType AddSubSession(const std::string& sub_session_key_id);
private:
bool GetProvisioningMethod(CdmClientTokenType* token_type);
@@ -231,6 +243,7 @@ class CryptoSession {
std::string oem_token_; // Cached OEMCrypto Public Key
bool update_usage_table_after_close_session_;
CryptoSessionId oec_session_id_;
SubLicenseSessionMap sub_license_oec_sessions_;
OEMCryptoBufferType destination_buffer_type_;
bool is_destination_buffer_type_valid_;

View File

@@ -5,6 +5,7 @@
#include <string>
#include "license_protocol.pb.h"
#include "wv_cdm_types.h"
namespace wvcdm {
@@ -27,6 +28,7 @@ class InitializationData {
const CdmInitData& data() const { return data_; }
std::vector<uint8_t> hls_iv() const { return hls_iv_; }
CdmHlsMethod hls_method() const { return hls_method_; }
std::vector<video_widevine::SubLicense> ExtractEmbeddedKeys() const;
private:
// Parse a blob of multiple concatenated PSSH atoms to extract the first

View File

@@ -51,7 +51,7 @@ class CdmLicense {
const CdmKeyResponse& license_response,
const CdmKeyResponse& license_renewal_response,
int64_t playback_start_time, int64_t last_playback_time,
int64_t grace_period_end_time);
int64_t grace_period_end_time, CdmSession* cdm_session);
virtual bool RestoreLicenseForRelease(const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response);
virtual bool IsKeyLoaded(const KeyId& key_id);

View File

@@ -154,6 +154,9 @@ class PolicyEngine {
// expiry time changes.
void NotifyExpirationUpdate(int64_t current_time);
// Guard against clock rollbacks
int64_t GetCurrentTime();
// set_clock() is for testing only. It alters ownership of the
// passed-in pointer.
void set_clock(Clock* clock);
@@ -185,6 +188,9 @@ class PolicyEngine {
// calculate the time where renewal retries should occur.
int64_t next_renewal_time_;
// to assist in clock rollback checks
int64_t last_recorded_current_time_;
// Used to dispatch CDM events.
CdmSessionId session_id_;
WvCdmEventListener* event_listener_;

View File

@@ -92,6 +92,8 @@ class UsageTableHeader {
virtual bool is_inited() { return is_inited_; }
virtual bool CreateDummyOldUsageEntry(CryptoSession* crypto_session);
// 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.

View File

@@ -21,7 +21,6 @@ typedef std::string CdmKeySetId;
typedef std::string RequestId;
typedef uint32_t CryptoResult;
typedef uint32_t CryptoSessionId;
typedef std::string CryptoKeyId;
typedef std::map<std::string, std::string> CdmAppParameterMap;
typedef std::map<std::string, std::string> CdmQueryMap;
typedef std::vector<std::string> CdmUsageInfo;

View File

@@ -68,8 +68,8 @@ CdmEngine::CdmEngine(FileSystem* file_system, const std::string& spoid)
usage_session_(NULL),
last_usage_information_update_time_(0) {
assert(file_system);
Properties::Init();
if (!seeded_) {
Properties::Init();
srand(clock_.GetCurrentTime());
seeded_ = true;
}
@@ -1296,17 +1296,31 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) {
switch (usage_session_->get_usage_support_type()) {
case kUsageEntrySupport: {
std::vector<DeviceFiles::CdmUsageData> usage_data;
if (!handle.RetrieveUsageInfo(
DeviceFiles::GetUsageInfoFileName(app_id), &usage_data)) {
status = RELEASE_ALL_USAGE_INFO_ERROR_4;
} else {
for (size_t k = 0; k < usage_data.size(); ++k) {
CdmResponseType status2 = usage_session_->DeleteUsageEntry(
usage_data[k].usage_entry_number);
if (status == NO_ERROR && status2 != NO_ERROR)
status = status2;
// Retrieve all usage information but delete only one before
// refetching. This is because deleting the usage entry
// might cause other entries to be shifted and information updated.
do {
if (!handle.RetrieveUsageInfo(
DeviceFiles::GetUsageInfoFileName(app_id),
&usage_data)) {
status = RELEASE_ALL_USAGE_INFO_ERROR_4;
break;
}
}
if (usage_data.empty()) break;
status = usage_session_->DeleteUsageEntry(
usage_data[0].usage_entry_number);
if (status != NO_ERROR) break;
if (!handle.DeleteUsageInfo(
DeviceFiles::GetUsageInfoFileName(app_id),
usage_data[0].provider_session_token)) {
status = RELEASE_ALL_USAGE_INFO_ERROR_6;
break;
}
} while (status == NO_ERROR && !usage_data.empty());
std::vector<std::string> provider_session_tokens;
if (!handle.DeleteAllUsageInfoForApp(

View File

@@ -28,6 +28,7 @@ CdmSession::CdmSession(FileSystem* file_system,
metrics::SessionMetrics* metrics) :
metrics_(metrics),
initialized_(false),
closed_(true),
file_handle_(new DeviceFiles(file_system)),
license_received_(false),
is_offline_(false),
@@ -193,6 +194,7 @@ CdmResponseType CdmSession::Init(
license_received_ = false;
is_initial_decryption_ = true;
initialized_ = true;
closed_ = false;
return NO_ERROR;
}
@@ -230,7 +232,8 @@ CdmResponseType CdmSession::RestoreOfflineSession(
std::string provider_session_token;
if (usage_support_type_ == kUsageEntrySupport) {
if (!license_parser_->ExtractProviderSessionToken(
key_response_, &provider_session_token)) {
key_response_, &provider_session_token) ||
usage_table_header_ == nullptr) {
provider_session_token.clear();
} else {
CdmResponseType sts =
@@ -252,13 +255,14 @@ CdmResponseType CdmSession::RestoreOfflineSession(
} else {
if (!license_parser_->RestoreOfflineLicense(
key_request_, key_response_, offline_key_renewal_response_,
playback_start_time, last_playback_time, grace_period_end_time)) {
playback_start_time, last_playback_time, grace_period_end_time,
this)) {
return RESTORE_OFFLINE_LICENSE_ERROR_2;
}
}
if (usage_support_type_ == kUsageEntrySupport &&
!provider_session_token.empty()) {
!provider_session_token.empty() && usage_table_header_ != nullptr) {
CdmResponseType sts =
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
if (sts != NO_ERROR) {
@@ -290,9 +294,10 @@ CdmResponseType CdmSession::RestoreUsageSession(
usage_entry_number_ = usage_data.usage_entry_number;
usage_provider_session_token_ = usage_data.provider_session_token;
if (usage_support_type_ == kUsageEntrySupport) {
if (usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
CdmResponseType sts = usage_table_header_->LoadEntry(
crypto_session_.get(), usage_entry_, usage_entry_number_);
crypto_session_.get(), usage_entry_, usage_entry_number_);
if (sts != NO_ERROR) {
LOGE("CdmSession::RestoreUsageSession: failed to load usage entry = %d",
sts);
@@ -304,7 +309,8 @@ CdmResponseType CdmSession::RestoreUsageSession(
return RELEASE_LICENSE_ERROR_2;
}
if (usage_support_type_ == kUsageEntrySupport) {
if (usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
CdmResponseType sts =
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
if (sts != NO_ERROR) {
@@ -424,7 +430,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
// to be created.
CdmResponseType sts;
std::string provider_session_token;
if (usage_support_type_ == kUsageEntrySupport) {
if (usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
if (license_parser_->ExtractProviderSessionToken(
key_response, &provider_session_token) &&
!provider_session_token.empty()) {
@@ -440,7 +447,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
// Update or delete entry if usage table header+entries are supported
if (usage_support_type_ == kUsageEntrySupport &&
!provider_session_token.empty()) {
!provider_session_token.empty() &&
usage_table_header_ != nullptr) {
if (sts != KEY_ADDED) {
CdmResponseType sts =
usage_table_header_->DeleteEntry(usage_entry_number_,
@@ -463,7 +471,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
if (is_offline_ || has_provider_session_token()) {
if (has_provider_session_token() &&
usage_support_type_ == kUsageEntrySupport) {
usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
}
@@ -671,8 +680,7 @@ CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
}
CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) {
if (usage_support_type_ != kUsageEntrySupport ||
!has_provider_session_token()) {
if (usage_support_type_ != kUsageEntrySupport) {
LOGE("CdmSession::DeleteUsageEntry: Unexpected usage type supported: %d",
usage_support_type_);
return INCORRECT_USAGE_SUPPORT_TYPE_1;
@@ -881,7 +889,8 @@ CdmResponseType CdmSession::UpdateUsageTableInformation() {
CdmResponseType CdmSession::UpdateUsageEntryInformation() {
if (usage_support_type_ != kUsageEntrySupport ||
!has_provider_session_token()) {
!has_provider_session_token() ||
usage_table_header_ == nullptr) {
LOGE("CdmSession::UpdateUsageEntryInformation: Unexpected usage type "
"supported: %d", usage_support_type_);
return INCORRECT_USAGE_SUPPORT_TYPE_2;

View File

@@ -280,29 +280,33 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
* Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails.
*/
CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
FileSystem* file_system, const CdmProvisioningResponse& response,
FileSystem* file_system, const CdmProvisioningResponse& response_message,
std::string* cert, std::string* wrapped_key) {
std::string raw_string;
if (!wvcdm::Properties::provisioning_messages_are_binary()) {
// The response is base64 encoded in a JSON wrapper.
// Extract it and decode it. If errors, return an empty string.
ExtractAndDecodeSignedMessage(response, &raw_string);
} else {
raw_string.assign(response);
if (response_message.empty()) {
LOGE("HandleProvisioningResponse: response message is empty.");
return CERT_PROVISIONING_RESPONSE_ERROR_1;
}
if (raw_string.empty()) {
LOGE("HandleProvisioningResponse: response message is empty or "
"an invalid JSON/base64 string.");
return CERT_PROVISIONING_RESPONSE_ERROR_1;
std::string response;
if (wvcdm::Properties::provisioning_messages_are_binary()) {
response.assign(response_message);
} else {
// The response is base64 encoded in a JSON wrapper.
// Extract it and decode it. On error return an empty string.
ExtractAndDecodeSignedMessage(response_message, &response);
if (response.empty()) {
LOGE("HandleProvisioningResponse: response message is "
"an invalid JSON/base64 string.");
return CERT_PROVISIONING_RESPONSE_ERROR_1;
}
}
// Authenticates provisioning response using D1s (server key derived from
// the provisioing request's input). Validate provisioning response and
// stores private device RSA key and certificate.
SignedProvisioningMessage signed_response;
if (!signed_response.ParseFromString(raw_string)) {
if (!signed_response.ParseFromString(response)) {
LOGE("HandleProvisioningResponse: fails to parse signed response");
return CERT_PROVISIONING_RESPONSE_ERROR_2;
}

View File

@@ -502,24 +502,25 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
CdmSecurityLevel security_level = GetSecurityLevel();
if (security_level == kSecurityLevelL1 ||
security_level == kSecurityLevelL3) {
UsageTableHeader* header = security_level == kSecurityLevelL1 ?
usage_table_header_l1_ : usage_table_header_l3_;
if (header == NULL) {
header = new UsageTableHeader();
UsageTableHeader** header = security_level == kSecurityLevelL1 ?
&usage_table_header_l1_ : &usage_table_header_l3_;
if (*header == NULL) {
*header = new UsageTableHeader();
// Ignore errors since we do not know when a session is opened,
// if it is intended to be used for offline/usage session related
// or otherwise.
if (!header->Init(security_level, this)) {
delete header;
crypto_lock_.Release();
bool is_usage_table_header_inited =
(*header)->Init(security_level, this);
crypto_lock_.Acquire();
if (!is_usage_table_header_inited) {
delete *header;
*header = NULL;
usage_table_header_ = NULL;
return NO_ERROR;
}
if (security_level == kSecurityLevelL1)
usage_table_header_l1_ = header;
else
usage_table_header_l3_ = header;
}
usage_table_header_ = header;
usage_table_header_ = *header;
}
}
}
@@ -2258,6 +2259,55 @@ CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) {
return NO_ERROR;
}
bool CryptoSession::CreateOldUsageEntry(
uint64_t time_since_license_received,
uint64_t time_since_first_decrypt,
uint64_t time_since_last_decrypt,
UsageDurationStatus usage_duration_status,
const std::string& server_mac_key,
const std::string& client_mac_key,
const std::string& provider_session_token) {
LOGV("CreateOldUsageEntry: Lock");
AutoLock auto_lock(crypto_lock_);
if (server_mac_key.size() < MAC_KEY_SIZE ||
client_mac_key.size() < MAC_KEY_SIZE) {
LOGE("CreateOldUsageEntry: Invalid mac key size: server mac key size %d, "
"client mac key size: %d", server_mac_key.size(),
client_mac_key.size());
return false;
}
OEMCrypto_Usage_Entry_Status status;
switch (usage_duration_status) {
case kUsageDurationsInvalid: status = kUnused; break;
case kUsageDurationPlaybackNotBegun: status = kInactiveUnused; break;
case kUsageDurationsValid: status = kActive; break;
default:
LOGE("CreateOldUsageEntry: Unrecognized usage entry status: %d", status);
status = kUnused;
return false;
}
OEMCryptoResult result =
OEMCrypto_CreateOldUsageEntry(
requested_security_level_, time_since_license_received,
time_since_first_decrypt, time_since_last_decrypt, status,
reinterpret_cast<uint8_t*>(
const_cast<char*>(server_mac_key.data())),
reinterpret_cast<uint8_t*>(
const_cast<char*>(client_mac_key.data())),
reinterpret_cast<const uint8_t*>(provider_session_token.data()),
provider_session_token.size());
if (result != OEMCrypto_SUCCESS) {
LOGE("CreateOldUsageEntry: OEMCrypto_CreateOldUsageEntry error: %d",
result);
return false;
}
return true;
}
CdmResponseType CryptoSession::CopyOldUsageEntry(
const std::string& provider_session_token) {
LOGV("CopyOldUsageEntry: id=%ld", (uint32_t)oec_session_id_);
@@ -2275,6 +2325,36 @@ CdmResponseType CryptoSession::CopyOldUsageEntry(
return NO_ERROR;
}
CdmResponseType CryptoSession::AddSubSession(
const std::string& sub_session_key_id) {
size_t exists = sub_license_oec_sessions_.count(sub_session_key_id);
if (exists > 0) {
// TODO(jfore): Should this be an error if the key exists? If so add a new
// error. If not, perhaps this should just print info message.
LOGE("AddSubSession: SubSession already exists for id: %s",
sub_session_key_id.c_str());
return UNKNOWN_ERROR;
}
OEMCryptoResult sts;
CryptoSessionId sid;
sts = OEMCrypto_OpenSession(&sid,requested_security_level_);
if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) {
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
sts, session_count_, (int)initialized_);
return INSUFFICIENT_CRYPTO_RESOURCES;
} else if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
sts, session_count_, (int)initialized_);
return UNKNOWN_ERROR;
}
// TODO(jfore): Is there some session count metrics that should be add or
// update?
sub_license_oec_sessions_[sub_session_key_id] = sid;
return NO_ERROR;
}
OEMCrypto_Algorithm CryptoSession::GenericSigningAlgorithm(
CdmSigningAlgorithm algorithm) {
if (kSigningAlgorithmHmacSha256 == algorithm) {

View File

@@ -7,7 +7,6 @@
#include "buffer_reader.h"
#include "jsmn.h"
#include "license_protocol.pb.h"
#include "log.h"
#include "properties.h"
#include "string_conversions.h"
@@ -35,9 +34,9 @@ const int kDefaultNumJsonTokens = 128;
namespace wvcdm {
// Protobuf generated classes.
using video_widevine::WidevineCencHeader;
using video_widevine::WidevineCencHeader_Algorithm;
using video_widevine::WidevineCencHeader_Algorithm_AESCTR;
using video_widevine::WidevinePsshData;
using video_widevine::WidevinePsshData_Algorithm;
using video_widevine::WidevinePsshData_Algorithm_AESCTR;
InitializationData::InitializationData(const std::string& type,
const CdmInitData& data)
@@ -70,6 +69,22 @@ InitializationData::InitializationData(const std::string& type,
}
}
// Parse the pssh data and return the embedded key data if it exists.
std::vector<video_widevine::SubLicense>
InitializationData::ExtractEmbeddedKeys() const {
std::vector<video_widevine::SubLicense> keys;
WidevinePsshData cenc_header;
if (!is_cenc_ || !cenc_header.ParseFromString(data_) ||
cenc_header.sub_licenses().size() == 0)
return keys;
keys.reserve(cenc_header.sub_licenses().size());
for (int i = 0; i < cenc_header.sub_licenses().size(); ++i) {
keys.push_back(cenc_header.sub_licenses(i));
}
return keys;
}
// Parse a blob of multiple concatenated PSSH atoms to extract the first
// Widevine PSSH.
bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
@@ -339,8 +354,9 @@ bool InitializationData::ConstructWidevineInitData(
return false;
}
if (method != kHlsMethodAes128 && method != kHlsMethodSampleAes) {
LOGV("InitializationData::ConstructWidevineInitData: Invalid method"
" parameter");
LOGV(
"InitializationData::ConstructWidevineInitData: Invalid method"
" parameter");
return false;
}
@@ -466,10 +482,10 @@ bool InitializationData::ConstructWidevineInitData(
}
// Now format as Widevine init data protobuf
WidevineCencHeader cenc_header;
WidevinePsshData cenc_header;
// TODO(rfrias): The algorithm is a deprecated field, but proto changes
// have not yet been pushed to production. Set until then.
cenc_header.set_algorithm(WidevineCencHeader_Algorithm_AESCTR);
cenc_header.set_algorithm(WidevinePsshData_Algorithm_AESCTR);
for (size_t i = 0; i < key_ids.size(); ++i) {
cenc_header.add_key_id(key_ids[i]);
}

View File

@@ -599,7 +599,8 @@ bool CdmLicense::RestoreOfflineLicense(
const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response,
const CdmKeyResponse& license_renewal_response, int64_t playback_start_time,
int64_t last_playback_time, int64_t grace_period_end_time) {
int64_t last_playback_time, int64_t grace_period_end_time,
CdmSession* cdm_session) {
if (license_request.empty() || license_response.empty()) {
LOGE(
"CdmLicense::RestoreOfflineLicense: key_request or response empty: "
@@ -641,9 +642,15 @@ bool CdmLicense::RestoreOfflineLicense(
}
if (!provider_session_token_.empty()) {
if (cdm_session) {
CdmResponseType status = cdm_session->UpdateUsageEntryInformation();
if (NO_ERROR != status) return false;
}
std::string usage_report;
CryptoSession::UsageDurationStatus usage_duration_status =
CryptoSession::kUsageDurationsInvalid;
int64_t seconds_since_started, seconds_since_last_played;
sts = crypto_session_->GenerateUsageReport(
provider_session_token_, &usage_report, &usage_duration_status,

View File

@@ -101,6 +101,7 @@ message License {
CONTENT = 2;
KEY_CONTROL = 3;
OPERATOR_SESSION = 4;
SUB_SESSION = 5;
}
// The SecurityLevel enumeration allows the server to communicate the level
@@ -199,6 +200,9 @@ message License {
// supports anti rollback of the user table. Content provider can query the
// client capabilities to determine if the client support this feature.
optional bool anti_rollback_usage_table = 11 [default = false];
// Optional not limited to commonly known track types such as SD, HD.
// It can be some provider defined label to identify the track.
optional string track_label = 12;
}
optional LicenseIdentification id = 1;
@@ -270,6 +274,19 @@ message LicenseRequest {
//}
}
message SubSessionData {
// Required. The key ID for the corresponding SUB_SESSION_KEY. The
// value must match the sub_session_key_id field for a
// corresponding SubLicense message from the PSSH.
optional string sub_session_key_id = 1;
// Required. The nonce for the track.
optional uint32 nonce = 2;
// Required for initial license request used for each CONTENT key_container
// to know which nonce to use for building its key control block.
// Not needed for renewal license request.
optional string track_label = 3;
}
enum RequestType {
NEW = 1;
RENEWAL = 2;
@@ -293,6 +310,9 @@ message LicenseRequest {
optional uint32 key_control_nonce = 7;
// Encrypted ClientIdentification message, used for privacy purposes.
optional EncryptedClientIdentification encrypted_client_id = 8;
// Optional sub session context information. Required for using
// SubLicenses from the PSSH.
repeated SubSessionData sub_session_data = 9;
}
message LicenseError {
@@ -306,6 +326,7 @@ message LicenseError {
// or similar circumstances.
SERVICE_UNAVAILABLE = 3;
}
optional Error error_code = 1;
}
@@ -410,7 +431,7 @@ message ProvisioningOptions {
optional CertificateType certificate_type = 1 [default = WIDEVINE_DRM];
// Contains the application-specific name used to identify the certificate
// authority for signing the generated certificate. This is required iff the
// authority for signing the generated certificate. This is required if the
// certificate type is X509.
optional string certificate_authority = 2;
}
@@ -680,19 +701,33 @@ message ProvisionedDeviceInfo {
}
// ----------------------------------------------------------------------------
// widevine_header.proto
// widevine_pssh.proto
// ----------------------------------------------------------------------------
// Description:
// Public protocol buffer definitions for Widevine Cenc Header
// protocol.
message WidevineCencHeader {
// Each SubLicense message represents a single content key. These keys can be
// added to Widevine CENC initialization data to support both content grouping
// and key rotation.
message SubLicense {
// Required. The key_id of a SUB_SESSION_KEY received in the master license.
// SUB_SESSION_KEY is defined in the Widevine License Protocol.
optional string sub_session_key_id = 1;
// Required. The key_msg contains the bytes of a serialized SignedMessage
// proto. Internally the message field will contain a serialized KeyContainer
// holding a single content key.
optional bytes key_msg = 2;
}
message WidevinePsshData {
enum Algorithm {
UNENCRYPTED = 0;
AESCTR = 1;
};
// Replaced with protection_scheme.
optional Algorithm algorithm = 1 [deprecated=true];
optional Algorithm algorithm = 1;
repeated bytes key_id = 2;
// Content provider name.
@@ -717,10 +752,22 @@ message WidevineCencHeader {
// serialized SignedMessage.
optional bytes grouped_license = 8;
// Protection scheme identifying the encryption algorithm. Represented as one
// of the following 4CC values: 'cenc' (AES-CTR), 'cbc1' (AES-CBC),
// 'cens' (AES-CTR subsample), 'cbcs' (AES-CBC subsample).
// Protection scheme identifying the encryption algorithm. The protection
// scheme is represented as a uint32 value. The uint32 contains 4 bytes each
// representing a single ascii character in one of the 4CC protection scheme
// values.
// 'cenc' (AES-CTR) protection_scheme = 0x63656E63,
// 'cbc1' (AES-CBC) protection_scheme = 0x63626331,
// 'cens' (AES-CTR subsample) protection_scheme = 0x63656E73,
// 'cbcs' (AES-CBC subsample) protection_scheme = 0x63626373.
optional uint32 protection_scheme = 9;
// Optional. For media using key rotation, this represents the duration
// of each crypto period in seconds.
optional uint32 crypto_period_seconds = 10;
// Required when using content keys that are embedded in content.
repeated SubLicense sub_licenses = 11;
}
// Signed device certificate definition.

View File

@@ -506,6 +506,10 @@ class WatchDog {
status_ = OEMCrypto_ERROR_INIT_FAILED;
LOGE("XXX WATCH DOG ERROR XXX");
SaveFailureInformation();
// This tells the worker thread to clean up after itself. It is not
// really needed since we are going to abort. However, if somebody
// removes the "abort()" below, then this is needed.
pthread_detach(thread_);
// This is controversial. The argument for an abort here is that if we
// do not abort, we will suck all the life out of the user's battery. By
// saving information to the file system, above, we can still track
@@ -517,7 +521,10 @@ class WatchDog {
bool should_delete = !gave_up_;
OEMCryptoResult status = status_;
pthread_mutex_unlock(&mutex_);
if (should_delete) delete this;
if (should_delete) {
pthread_join(thread_, NULL);
delete this;
}
return status;
}

View File

@@ -14,6 +14,14 @@
using video_widevine::License;
namespace {
const int kCdmPolicyTimerDurationSeconds = 1;
const int kClockSkewDelta = 5; // seconds
const int64_t kHdcpCheckInterval = 10;
} // namespace
namespace wvcdm {
PolicyEngine::PolicyEngine(CdmSessionId session_id,
@@ -28,6 +36,7 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id,
last_expiry_time_set_(false),
was_expired_on_load_(false),
next_renewal_time_(0),
last_recorded_current_time_(0),
session_id_(session_id),
event_listener_(event_listener),
license_keys_(new LicenseKeys),
@@ -81,7 +90,8 @@ void PolicyEngine::CheckDeviceHdcpStatus() {
}
void PolicyEngine::OnTimerEvent() {
int64_t current_time = clock_->GetCurrentTime();
last_recorded_current_time_ += kCdmPolicyTimerDurationSeconds;
int64_t current_time = GetCurrentTime();
// If we have passed the grace period, the expiration will update.
if (grace_period_end_time_ == 0 && HasPlaybackStarted(current_time)) {
@@ -194,7 +204,7 @@ void PolicyEngine::UpdateLicense(const License& license) {
license_start_time_ = license.license_start_time();
next_renewal_time_ = license_start_time_ + policy_.renewal_delay_seconds();
int64_t current_time = clock_->GetCurrentTime();
int64_t current_time = GetCurrentTime();
if (!policy_.can_play() ||
HasLicenseOrPlaybackDurationExpired(current_time)) {
license_state_ = kLicenseStateExpired;
@@ -219,7 +229,7 @@ void PolicyEngine::BeginDecryption() {
case kLicenseStateCanPlay:
case kLicenseStateNeedRenewal:
case kLicenseStateWaitingLicenseUpdate:
playback_start_time_ = clock_->GetCurrentTime();
playback_start_time_ = GetCurrentTime();
last_playback_time_ = playback_start_time_;
if (policy_.play_start_grace_period_seconds() == 0)
grace_period_end_time_ = playback_start_time_;
@@ -239,7 +249,7 @@ void PolicyEngine::BeginDecryption() {
}
void PolicyEngine::DecryptionEvent() {
last_playback_time_ = clock_->GetCurrentTime();
last_playback_time_ = GetCurrentTime();
}
void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) {
@@ -253,7 +263,7 @@ void PolicyEngine::NotifySessionExpiration() {
CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) {
std::stringstream ss;
int64_t current_time = clock_->GetCurrentTime();
int64_t current_time = GetCurrentTime();
if (license_state_ == kLicenseStateInitial) {
query_response->clear();
@@ -294,7 +304,7 @@ CdmResponseType PolicyEngine::QueryKeyAllowedUsage(
bool PolicyEngine::GetSecondsSinceStarted(int64_t* seconds_since_started) {
if (playback_start_time_ == 0) return false;
*seconds_since_started = clock_->GetCurrentTime() - playback_start_time_;
*seconds_since_started = GetCurrentTime() - playback_start_time_;
return (*seconds_since_started >= 0) ? true : false;
}
@@ -302,12 +312,12 @@ bool PolicyEngine::GetSecondsSinceLastPlayed(
int64_t* seconds_since_last_played) {
if (last_playback_time_ == 0) return false;
*seconds_since_last_played = clock_->GetCurrentTime() - last_playback_time_;
*seconds_since_last_played = GetCurrentTime() - last_playback_time_;
return (*seconds_since_last_played >= 0) ? true : false;
}
int64_t PolicyEngine::GetLicenseOrPlaybackDurationRemaining() {
const int64_t current_time = clock_->GetCurrentTime();
const int64_t current_time = GetCurrentTime();
const int64_t expiry_time =
GetExpiryTime(current_time,
/* ignore_soft_enforce_playback_duration */ false);
@@ -331,7 +341,7 @@ void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time,
playback_start_time_ = grace_period_end_time;
}
const int64_t current_time = clock_->GetCurrentTime();
const int64_t current_time = GetCurrentTime();
const int64_t expiry_time =
GetExpiryTime(current_time,
/* ignore_soft_enforce_playback_duration */ true);
@@ -402,6 +412,8 @@ int64_t PolicyEngine::GetLicenseOrRentalDurationRemaining(
if (license_expiry_time == NEVER_EXPIRES) return LLONG_MAX;
if (license_expiry_time < current_time) return 0;
const int64_t policy_license_duration = policy_.license_duration_seconds();
if (policy_license_duration == NEVER_EXPIRES)
return license_expiry_time - current_time;
return std::min(license_expiry_time - current_time, policy_license_duration);
}
@@ -469,6 +481,15 @@ void PolicyEngine::NotifyExpirationUpdate(int64_t current_time) {
last_expiry_time_set_ = true;
}
int64_t PolicyEngine::GetCurrentTime() {
int64_t current_time = clock_->GetCurrentTime();
if (current_time + kClockSkewDelta < last_recorded_current_time_)
current_time = last_recorded_current_time_;
else
last_recorded_current_time_ = current_time;
return current_time;
}
void PolicyEngine::set_clock(Clock* clock) { clock_.reset(clock); }
} // namespace wvcdm

View File

@@ -5,9 +5,17 @@
#include "crypto_session.h"
#include "license.h"
#include "log.h"
#include "wv_cdm_constants.h"
namespace {
std::string kEmptyString;
uint64_t kOldUsageEntryTimeSinceLicenseReceived = 0;
uint64_t kOldUsageEntryTimeSinceFirstDecrypt = 0;
uint64_t kOldUsageEntryTimeSinceLastDecrypt = 0;
std::string kOldUsageEntryServerMacKey(wvcdm::MAC_KEY_SIZE, 0);
std::string kOldUsageEntryClientMacKey(wvcdm::MAC_KEY_SIZE, 0);
std::string kOldUsageEntryPoviderSessionToken =
"nahZ6achSheiqua3TohQuei0ahwohv";
}
namespace wvcdm {
@@ -38,30 +46,43 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
}
security_level_ = security_level;
requested_security_level_ =
security_level_ == kSecurityLevelL3 ? kLevel3 : kLevelDefault;
if (!file_handle_->Init(security_level)) {
LOGE("UsageTableHeader::Init: device files initialization failed");
return false;
}
if (!file_handle_->RetrieveUsageTableInfo(&usage_table_header_,
CdmResponseType status = USAGE_INFO_NOT_FOUND;
if (file_handle_->RetrieveUsageTableInfo(&usage_table_header_,
&usage_entry_info_)) {
CdmResponseType status =
crypto_session->CreateUsageTableHeader(&usage_table_header_);
if (status != NO_ERROR) return false;
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
} else {
CdmResponseType status =
crypto_session->LoadUsageTableHeader(usage_table_header_);
status = crypto_session->LoadUsageTableHeader(usage_table_header_);
if (status != NO_ERROR) {
LOGE(
"UsageTableHeader::Init: load usage table failed, security level: %d",
security_level);
return false;
file_handle_->DeleteAllLicenses();
usage_entry_info_.clear();
usage_table_header_.clear();
status = crypto_session->CreateUsageTableHeader(&usage_table_header_);
if (status != NO_ERROR) return false;
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
}
} else {
status = crypto_session->CreateUsageTableHeader(&usage_table_header_);
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() != nullptr ?
crypto_session->GetCryptoMetrics() : &alternate_metrics;
UpgradeFromUsageTable(file_handle_.get(), metrics);
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
}
requested_security_level_ =
security_level_ == kSecurityLevelL3 ? kLevel3 : kLevelDefault;
is_inited_ = true;
return true;
}
@@ -141,8 +162,11 @@ CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number,
metrics::CryptoMetrics* metrics) {
LOGV("UsageTableHeader::DeleteEntry: Lock");
AutoLock auto_lock(usage_table_header_lock_);
if (usage_entry_number >= usage_entry_info_.size())
if (usage_entry_number >= usage_entry_info_.size()) {
LOGE("UsageTableHeader::DeleteEntry: usage entry number %d larger than "
"usage entry size %d", usage_entry_number, usage_entry_info_.size());
return USAGE_INVALID_PARAMETERS_1;
}
// Find the last valid entry number, in order to swap
size_t swap_entry_number = usage_entry_info_.size() - 1;
@@ -427,7 +451,7 @@ bool UsageTableHeader::UpgradeLicensesFromUsageTable(
// * save the usage table header and store the usage entry number and
// usage entry along with the license to persistent memory
std::vector<std::string> key_set_ids;
if (handle->ListLicenses(&key_set_ids)) {
if (!handle->ListLicenses(&key_set_ids)) {
LOGW(
"UpgradeUsageTableHeader::UpgradeLicensesFromUsageTable: unable to "
"retrieve list of licenses");
@@ -470,6 +494,9 @@ bool UsageTableHeader::UpgradeLicensesFromUsageTable(
if (status != NO_ERROR) continue;
// TODO(fredgc): remove when b/65730828 is addressed
if (!CreateDummyOldUsageEntry(&crypto_session)) continue;
status = AddEntry(&crypto_session, true /* persistent license */,
key_set_ids[i], kEmptyString, &usage_entry_number);
@@ -518,7 +545,7 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
// information to persistent memory along with usage entry number and usage
// entry.
std::vector<std::string> usage_info_file_names;
if (handle->ListUsageInfoFiles(&usage_info_file_names)) {
if (!handle->ListUsageInfoFiles(&usage_info_file_names)) {
LOGW(
"UpgradeUsageTableHeader::UpgradeUsageInfoFromUsageTable: Unable to "
"retrieve list of usage info file names");
@@ -535,7 +562,7 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
continue;
}
for (size_t j = 0; j < usage_data.size(); --j) {
for (size_t j = 0; j < usage_data.size(); ++j) {
if (usage_data[j].provider_session_token.empty()) {
LOGW(
"UsageTableHeader::UpgradeUsageInfoFromUsageTable: Provider "
@@ -548,16 +575,19 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
if (status != NO_ERROR) continue;
// TODO(fredgc): remove when b/65730828 is addressed
if (!CreateDummyOldUsageEntry(&crypto_session)) continue;
// 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, false /* usage info */,
usage_data[j].key_set_id, kEmptyString,
&(usage_data[i].usage_entry_number));
usage_data[j].key_set_id, usage_info_file_names[i],
&(usage_data[j].usage_entry_number));
if (status != NO_ERROR) continue;
status = crypto_session.CopyOldUsageEntry(
usage_data[i].provider_session_token);
usage_data[j].provider_session_token);
if (status != NO_ERROR) {
crypto_session.Close();
@@ -565,7 +595,7 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
continue;
}
status = UpdateEntry(&crypto_session, &(usage_data[i].usage_entry));
status = UpdateEntry(&crypto_session, &(usage_data[j].usage_entry));
if (status != NO_ERROR) {
crypto_session.Close();
@@ -586,4 +616,16 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
return NO_ERROR;
}
// TODO(fredgc): remove when b/65730828 is addressed
bool UsageTableHeader::CreateDummyOldUsageEntry(CryptoSession* crypto_session) {
return crypto_session->CreateOldUsageEntry(
kOldUsageEntryTimeSinceLicenseReceived,
kOldUsageEntryTimeSinceFirstDecrypt,
kOldUsageEntryTimeSinceLastDecrypt,
CryptoSession::kUsageDurationsInvalid,
kOldUsageEntryServerMacKey,
kOldUsageEntryClientMacKey,
kOldUsageEntryPoviderSessionToken);
}
} // namespace wvcdm

View File

@@ -30,48 +30,6 @@ std::string g_provisioning_server;
std::string g_license_service_certificate;
std::string g_provisioning_service_certificate;
/*
* Locate the portion of the server's response message that is between
* the strings jason_start_substr and json_end_substr. Returns the string
* through *result. If the start substring match fails, assume the entire
* string represents a serialized protobuf mesaage and return true with
* the entire string. If the end_substring match fails, return false with
* an empty *result.
*/
bool ExtractSignedMessage(const std::string& response,
const std::string& json_start_substr,
const std::string& json_end_substr,
std::string* result) {
std::string b64_string;
size_t start = response.find(json_start_substr);
if (start == response.npos) {
// Assume web safe protobuf
b64_string.assign(response);
} else {
// Assume JSON-wrapped protobuf
size_t end = response.find(json_end_substr,
start + json_start_substr.length());
if (end == response.npos) {
LOGE("ExtractSignedMessage cannot locate end substring");
result->clear();
return false;
}
size_t b64_string_size = end - start - json_start_substr.length();
b64_string.assign(response, start + json_start_substr.length(),
b64_string_size);
}
if (b64_string.empty()) {
LOGE("Response message is empty");
result->clear();
return false;
}
result->swap(b64_string);
return true;
}
} // namespace
namespace wvcdm {
@@ -219,7 +177,7 @@ class WvGenericOperationsTest : public testing::Test {
protected:
virtual void Provision() {
LOGE("WvCdmEnginePreProvTest::Provision: url=%s",
LOGE("WvGenericOperationsTest::Provision: url=%s",
g_provisioning_server.c_str());
CdmProvisioningRequest prov_request;
std::string provisioning_server_url;
@@ -232,7 +190,7 @@ class WvGenericOperationsTest : public testing::Test {
cert_type, cert_authority, &prov_request,
&provisioning_server_url));
LOGV("WvCdmEnginePreProvTest::Provision: req=%s", prov_request.c_str());
LOGV("WvGenericOperationsTest::Provision: req=%s", prov_request.c_str());
// Ignore URL provided by CdmEngine. Use ours, as configured
// for test vs. production server.
@@ -244,13 +202,12 @@ class WvGenericOperationsTest : public testing::Test {
bool ok = url_request.GetResponse(&http_message);
EXPECT_TRUE(ok);
LOGV("WvCdmEnginePreProvTest::Provision: http_message: \n%s\n",
LOGV("WvGenericOperationsTest::Provision: http_message: \n%s\n",
http_message.c_str());
ASSERT_EQ(NO_ERROR,
cdm_engine_->HandleProvisioningResponse(http_message,
&cert, &wrapped_key));
ASSERT_EQ(NO_ERROR,
cdm_engine_->SetServiceCertificate(g_license_service_certificate));
}

View File

@@ -17,7 +17,7 @@
namespace wvcdm {
// Protobuf generated classes.
using video_widevine::WidevineCencHeader;
using video_widevine::WidevinePsshData;
namespace {
@@ -164,6 +164,19 @@ const std::string kZeroSizedPsshBox = a2bs_hex(
// data:
"08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031");
const std::string kSubLicensePsshBox = a2bs_hex(
// Widevine PSSH box
"0000009f" // atom size (whole buffer)
"70737368" // atom type="pssh"
"00000000" // v0, flags=0
"edef8ba979d64acea3c827dcd51d21ed" // system id (Widevine)
"0000007f" // data size
// data:
"0801120d746573745f6b65795f69645f30120d746573745f6b65795f69645f31220f"
"746573745f636f6e74656e745f69645a250a147375625f73657373696f6e5f6b6579"
"5f69645f30120d7375625f6c6963656e73655f305a250a147375625f73657373696f"
"6e5f6b65795f69645f31120d7375625f6c6963656e73655f31");
// HLS test attribute key and values
const std::string kHlsIvHexValue = "6DF49213A781E338628D0E9C812D328E";
const std::string kHlsIvValue = "0x" + kHlsIvHexValue;
@@ -353,7 +366,9 @@ struct HlsInitDataVariant {
HlsInitDataVariant(CdmHlsMethod method, const std::string& provider,
const std::string& content_id, const std::string& key_id,
bool success)
: method_(method), provider_(provider), content_id_(content_id),
: method_(method),
provider_(provider),
content_id_(content_id),
success_(success) {
if (key_id.size() > 0) key_ids_.push_back(key_id);
}
@@ -405,18 +420,42 @@ class HlsParseTest : public ::testing::TestWithParam<HlsAttributeVariant> {};
class HlsTest : public ::testing::Test {};
} // namespace
TEST_F(InitializationDataTest, BadType) {
InitializationData init_data("bad", kSubLicensePsshBox);
EXPECT_TRUE(init_data.IsEmpty());
}
TEST_P(InitializationDataTest, Parse) {
InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, GetParam());
EXPECT_FALSE(init_data.IsEmpty());
}
INSTANTIATE_TEST_CASE_P(ParsePssh, InitializationDataTest,
::testing::Values(kWidevinePssh, kWidevinePsshFirst,
kWidevinePsshAfterV0Pssh,
kWidevinePsshAfterNonZeroFlags,
kWidevinePsshAfterV1Pssh,
kWidevineV1Pssh, kOtherBoxFirst,
kZeroSizedPsshBox));
INSTANTIATE_TEST_CASE_P(
ParsePssh, InitializationDataTest,
::testing::Values(kWidevinePssh, kWidevinePsshFirst,
kWidevinePsshAfterV0Pssh, kWidevinePsshAfterNonZeroFlags,
kWidevinePsshAfterV1Pssh, kWidevineV1Pssh, kOtherBoxFirst,
kZeroSizedPsshBox, kSubLicensePsshBox));
TEST_F(InitializationDataTest, ExtractSubLicense) {
InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, kSubLicensePsshBox);
ASSERT_FALSE(init_data.IsEmpty());
std::vector<video_widevine::SubLicense> keys =
init_data.ExtractEmbeddedKeys();
ASSERT_EQ(keys.size(), 2UL);
EXPECT_EQ(keys[0].sub_session_key_id(), "sub_session_key_id_0");
EXPECT_EQ(keys[1].sub_session_key_id(), "sub_session_key_id_1");
EXPECT_EQ(keys[0].key_msg(), "sub_license_0");
EXPECT_EQ(keys[1].key_msg(), "sub_license_1");
}
TEST_F(InitializationDataTest, ExtractEmptySubLicense) {
InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, kWidevinePssh);
ASSERT_FALSE(init_data.IsEmpty());
std::vector<video_widevine::SubLicense> keys =
init_data.ExtractEmbeddedKeys();
ASSERT_TRUE(keys.empty());
}
TEST_P(HlsKeyFormatVersionsExtractionTest, ExtractKeyFormatVersions) {
std::vector<std::string> versions = GetParam();
@@ -598,9 +637,9 @@ TEST_P(HlsConstructionTest, InitData) {
EXPECT_EQ(param.success_, InitializationData::ConstructWidevineInitData(
param.method_, uri, &value));
if (param.success_) {
WidevineCencHeader cenc_header;
WidevinePsshData cenc_header;
EXPECT_TRUE(cenc_header.ParseFromString(value));
EXPECT_EQ(video_widevine::WidevineCencHeader_Algorithm_AESCTR,
EXPECT_EQ(video_widevine::WidevinePsshData_Algorithm_AESCTR,
cenc_header.algorithm());
for (size_t i = 0; i < param.key_ids_.size(); ++i) {
bool key_id_found = false;
@@ -620,9 +659,14 @@ TEST_P(HlsConstructionTest, InitData) {
cenc_header.content_id());
uint32_t protection_scheme = 0;
switch (param.method_) {
case kHlsMethodAes128: protection_scheme = kFourCcCbc1; break;
case kHlsMethodSampleAes: protection_scheme = kFourCcCbcs; break;
default: break;
case kHlsMethodAes128:
protection_scheme = kFourCcCbc1;
break;
case kHlsMethodSampleAes:
protection_scheme = kFourCcCbcs;
break;
default:
break;
}
EXPECT_EQ(protection_scheme, cenc_header.protection_scheme());
}
@@ -633,8 +677,8 @@ INSTANTIATE_TEST_CASE_P(
::testing::Values(
HlsInitDataVariant(kHlsMethodAes128, "", kHlsTestContentId,
kHlsTestKeyId1, false),
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
"", kHlsTestKeyId1, false),
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, "",
kHlsTestKeyId1, false),
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
kHlsTestContentId, "", false),
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
@@ -699,9 +743,9 @@ TEST_P(HlsParseTest, Parse) {
EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_method());
}
WidevineCencHeader cenc_header;
WidevinePsshData cenc_header;
EXPECT_TRUE(cenc_header.ParseFromString(init_data.data()));
EXPECT_EQ(video_widevine::WidevineCencHeader_Algorithm_AESCTR,
EXPECT_EQ(video_widevine::WidevinePsshData_Algorithm_AESCTR,
cenc_header.algorithm());
if (param.key_.compare(kJsonProvider) == 0) {
EXPECT_EQ(param.value_, cenc_header.provider());

View File

@@ -599,6 +599,128 @@ TEST_F(PolicyEngineTest, PlaybackOk_PlaybackAndRental0) {
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
}
TEST_F(PolicyEngineTest, PlaybackOk_PlaybackAndLicense0_WithoutPlayback) {
License_Policy* policy = license_.mutable_policy();
policy->clear_license_duration_seconds();
policy->clear_playback_duration_seconds();
// Only |rental_duration_seconds| set.
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.WillOnce(Return(kLicenseStartTime + 1))
.WillOnce(Return(kLicenseStartTime + 10))
.WillOnce(Return(kLicenseStartTime + kRentalDuration - 10))
.WillOnce(Return(kLicenseStartTime + kRentalDuration + 10));
ExpectSessionKeysChange(kKeyStatusExpired, false);
ExpectSessionKeysChange(kKeyStatusUsable, true);
EXPECT_CALL(mock_event_listener_,
OnExpirationUpdate(_, kLicenseStartTime + kRentalDuration));
policy_engine_->SetLicense(license_);
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
policy_engine_->OnTimerEvent();
EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
}
TEST_F(PolicyEngineTest, PlaybackOk_PlaybackAndLicense0_WithPlayback) {
License_Policy* policy = license_.mutable_policy();
policy->clear_license_duration_seconds();
policy->clear_playback_duration_seconds();
// Only |rental_duration_seconds| set.
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.WillOnce(Return(kLicenseStartTime + 1))
.WillOnce(Return(kPlaybackStartTime))
.WillOnce(Return(kLicenseStartTime + 10))
.WillOnce(Return(kLicenseStartTime + kRentalDuration + 10));
ExpectSessionKeysChange(kKeyStatusUsable, true);
EXPECT_CALL(mock_event_listener_,
OnExpirationUpdate(_, 0));
EXPECT_CALL(mock_event_listener_,
OnExpirationUpdate(_, kLicenseStartTime + kRentalDuration));
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
}
TEST_F(PolicyEngineTest, PlaybackOk_RentalAndLicense0_WithoutPlayback) {
License_Policy* policy = license_.mutable_policy();
policy->clear_license_duration_seconds();
policy->clear_rental_duration_seconds();
// Only |playback_duration_seconds| set.
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.WillOnce(Return(kLicenseStartTime + 1))
.WillOnce(Return(kLicenseStartTime + 10))
.WillOnce(Return(kLicenseStartTime + kPlaybackDuration + 10));
ExpectSessionKeysChange(kKeyStatusUsable, true);
EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, 0));
policy_engine_->SetLicense(license_);
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
}
TEST_F(PolicyEngineTest, PlaybackOk_RentalAndLicense0_WithPlayback) {
License_Policy* policy = license_.mutable_policy();
policy->clear_license_duration_seconds();
policy->clear_rental_duration_seconds();
// Only |playback_duration_seconds| set.
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.WillOnce(Return(kLicenseStartTime + 1))
.WillOnce(Return(kPlaybackStartTime))
.WillOnce(Return(kPlaybackStartTime + kPlaybackDuration - 10))
.WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 10));
ExpectSessionKeysChange(kKeyStatusExpired, false);
ExpectSessionKeysChange(kKeyStatusUsable, true);
EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, 0));
EXPECT_CALL(mock_event_listener_,
OnExpirationUpdate(_, kPlaybackStartTime + kPlaybackDuration));
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
policy_engine_->OnTimerEvent();
EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
}
TEST_F(PolicyEngineTest, PlaybackOk_Durations0) {
License_Policy* policy = license_.mutable_policy();
policy->set_rental_duration_seconds(kDurationUnlimited);
@@ -2188,6 +2310,303 @@ TEST_F(PolicyEngineQueryTest, QuerySuccess_LicenseDuration0) {
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
}
TEST_F(PolicyEngineQueryTest, QuerySuccess_PlaybackAndRental0) {
License_Policy* policy = license_.mutable_policy();
policy->set_rental_duration_seconds(kDurationUnlimited);
policy->set_playback_duration_seconds(kDurationUnlimited);
policy->set_license_duration_seconds(kLowDuration);
// Only |license_duration_seconds| set.
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.WillOnce(Return(kLicenseStartTime + 1))
.WillOnce(Return(kPlaybackStartTime))
.WillOnce(Return(kLicenseStartTime + 10))
.WillOnce(Return(kLicenseStartTime + kLowDuration - 10))
.WillOnce(Return(kLicenseStartTime + kLowDuration + 10))
.WillOnce(Return(kLicenseStartTime + kLowDuration + 10));
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
CdmQueryMap query_info;
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(10, ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(LLONG_MAX,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
policy_engine_->OnTimerEvent();
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(0, ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(LLONG_MAX,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
}
TEST_F(PolicyEngineQueryTest, QuerySuccess_PlaybackAndLicense0_WithoutPlayback) {
License_Policy* policy = license_.mutable_policy();
policy->set_rental_duration_seconds(kRentalDuration);
policy->set_playback_duration_seconds(kDurationUnlimited);
policy->set_license_duration_seconds(kDurationUnlimited);
// Only |rental_duration_seconds| set.
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.WillOnce(Return(kLicenseStartTime + 1))
.WillOnce(Return(kLicenseStartTime + 10))
.WillOnce(Return(kLicenseStartTime + kRentalDuration - 10))
.WillOnce(Return(kLicenseStartTime + kRentalDuration - 10))
.WillOnce(Return(kLicenseStartTime + kRentalDuration + 10))
.WillOnce(Return(kLicenseStartTime + kRentalDuration + 10));
policy_engine_->SetLicense(license_);
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
CdmQueryMap query_info;
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(10, ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(LLONG_MAX,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
policy_engine_->OnTimerEvent();
EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(0, ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(LLONG_MAX,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
}
TEST_F(PolicyEngineQueryTest, QuerySuccess_PlaybackAndLicense0_WithPlayback) {
License_Policy* policy = license_.mutable_policy();
policy->set_rental_duration_seconds(kRentalDuration);
policy->set_playback_duration_seconds(kDurationUnlimited);
policy->set_license_duration_seconds(kDurationUnlimited);
// Only |rental_duration_seconds| set.
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.WillOnce(Return(kLicenseStartTime + 1))
.WillOnce(Return(kPlaybackStartTime))
.WillOnce(Return(kLicenseStartTime + kPlaybackDuration - 10))
.WillOnce(Return(kLicenseStartTime + kPlaybackDuration - 10))
.WillOnce(Return(kLicenseStartTime + kPlaybackDuration + 10))
.WillOnce(Return(kLicenseStartTime + kPlaybackDuration + 10))
.WillOnce(Return(kLicenseStartTime + kRentalDuration + 10))
.WillOnce(Return(kLicenseStartTime + kRentalDuration + 10));
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
CdmQueryMap query_info;
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(kRentalDuration - kPlaybackDuration + 10,
ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(LLONG_MAX,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(kRentalDuration - kPlaybackDuration - 10,
ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(LLONG_MAX,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(0, ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(LLONG_MAX,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
}
TEST_F(PolicyEngineQueryTest, QuerySuccess_RentalAndLicense0_WithoutPlayback) {
License_Policy* policy = license_.mutable_policy();
policy->set_rental_duration_seconds(kDurationUnlimited);
policy->set_playback_duration_seconds(kPlaybackDuration);
policy->set_license_duration_seconds(kDurationUnlimited);
// Only |playback_duration_seconds| set.
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.WillOnce(Return(kLicenseStartTime + 1))
.WillOnce(Return(kLicenseStartTime + 10))
.WillOnce(Return(kLicenseStartTime + 10))
.WillOnce(Return(kLicenseStartTime + kPlaybackDuration + 10))
.WillOnce(Return(kLicenseStartTime + kPlaybackDuration + 10));
policy_engine_->SetLicense(license_);
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
CdmQueryMap query_info;
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(LLONG_MAX,
ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(kPlaybackDuration,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(LLONG_MAX,
ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(kPlaybackDuration,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
}
TEST_F(PolicyEngineQueryTest, QuerySuccess_RentalAndLicense0_WithPlayback) {
License_Policy* policy = license_.mutable_policy();
policy->set_rental_duration_seconds(kDurationUnlimited);
policy->set_playback_duration_seconds(kPlaybackDuration);
policy->set_license_duration_seconds(kDurationUnlimited);
// Only |playback_duration_seconds| set.
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.WillOnce(Return(kLicenseStartTime + 1))
.WillOnce(Return(kPlaybackStartTime))
.WillOnce(Return(kPlaybackStartTime + 10))
.WillOnce(Return(kPlaybackStartTime + 10))
.WillOnce(Return(kPlaybackStartTime + kPlaybackDuration - 10))
.WillOnce(Return(kPlaybackStartTime + kPlaybackDuration - 10))
.WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 10))
.WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 10));
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
CdmQueryMap query_info;
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(LLONG_MAX,
ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(kPlaybackDuration - 10,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
policy_engine_->OnTimerEvent();
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(LLONG_MAX,
ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(10,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
policy_engine_->OnTimerEvent();
EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId));
EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId));
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
EXPECT_EQ(0,
ParseInt(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]));
EXPECT_EQ(0,
ParseInt(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]));
EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]);
}
TEST_F(PolicyEngineQueryTest, QuerySuccess_Durations0) {
License_Policy* policy = license_.mutable_policy();
policy->set_rental_duration_seconds(kDurationUnlimited);

View File

@@ -50,6 +50,46 @@ const CdmUsageEntryInfo kUsageEntryInfoStorageTypeUnknown = {
.storage_type = kStorageTypeUnknown,
.key_set_id = "",
.usage_info_file_name = ""};
const std::vector<std::string> kEmptyLicenseList;
const std::vector<std::string> kLicenseList = {
kUsageEntryInfoOfflineLicense1.key_set_id,
kUsageEntryInfoOfflineLicense2.key_set_id,
kUsageEntryInfoOfflineLicense3.key_set_id,
};
const std::vector<std::string> kEmptyUsageInfoFilesList;
const std::vector<std::string> kUsageInfoFileList = {
kUsageEntryInfoSecureStop1.usage_info_file_name,
kUsageEntryInfoSecureStop2.usage_info_file_name,
kUsageEntryInfoSecureStop3.usage_info_file_name,
};
const DeviceFiles::CdmUsageData kCdmUsageData1 = {
.provider_session_token = "provider_session_token_1",
.license_request = "license_request_1",
.license = "license_1",
.key_set_id = "key_set_id_1",
.usage_entry = "usage_entry_1",
.usage_entry_number = 0,
};
const DeviceFiles::CdmUsageData kCdmUsageData2 = {
.provider_session_token = "provider_session_token_2",
.license_request = "license_request_2",
.license = "license_2",
.key_set_id = "key_set_id_2",
.usage_entry = "usage_entry_2",
.usage_entry_number = 0,
};
const DeviceFiles::CdmUsageData kCdmUsageData3 = {
.provider_session_token = "provider_session_token_3",
.license_request = "license_request_3",
.license = "license_3",
.key_set_id = "key_set_id_3",
.usage_entry = "usage_entry_3",
.usage_entry_number = 0,
};
const std::vector<DeviceFiles::CdmUsageData> kEmptyUsageInfoUsageDataList;
const std::vector<DeviceFiles::CdmUsageData> kUsageInfoUsageDataList = {
kCdmUsageData1, kCdmUsageData2, kCdmUsageData3,
};
const std::vector<CdmUsageEntryInfo> kEmptyUsageEntryInfoVector;
const std::vector<CdmUsageEntryInfo> kUsageEntryInfoVector = {
kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1,
@@ -82,12 +122,17 @@ class MockDeviceFiles : public DeviceFiles {
bool(const std::string&, const std::string&, std::string*,
CdmKeyMessage*, CdmKeyResponse*, CdmUsageEntry*,
uint32_t*));
MOCK_METHOD0(DeleteAllLicenses, bool());
MOCK_METHOD7(StoreUsageInfo,
bool(const std::string&, const CdmKeyMessage&,
const CdmKeyResponse&, const std::string&,
const std::string&, const CdmUsageEntry&, uint32_t));
MOCK_METHOD2(RetrieveUsageInfo,
bool(const std::string&, std::vector<CdmUsageData>*));
MOCK_METHOD1(ListLicenses,
bool(std::vector<std::string>* key_set_ids));
MOCK_METHOD1(ListUsageInfoFiles,
bool(std::vector<std::string>* usage_info_files));
private:
FileSystem file_system_;
@@ -170,18 +215,76 @@ class UsageTableHeaderInitializationTest
public ::testing::WithParamInterface<CdmSecurityLevel> {};
TEST_P(UsageTableHeaderInitializationTest, CreateUsageTableHeader) {
std::vector<CdmUsageEntryInfo> empty_usage_entry_info;
EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader),
SetArgPointee<1>(kEmptyUsageEntryInfoVector),
Return(false)));
EXPECT_CALL(*device_files_, ListLicenses(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kEmptyLicenseList),
Return(false)));
EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageInfoFilesList),
Return(false)));
EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull()))
.WillOnce(
DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR)));
EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader,
kEmptyUsageEntryInfoVector))
.WillOnce(Return(true));
.Times(2)
.WillRepeatedly(Return(true));
EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_));
}
TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveLicenses) {
EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader),
SetArgPointee<1>(kEmptyUsageEntryInfoVector),
Return(false)));
EXPECT_CALL(*device_files_, ListLicenses(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kLicenseList),
Return(true)));
EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageInfoFilesList),
Return(false)));
EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull()))
.WillOnce(
DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR)));
EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true));
EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader,
kEmptyUsageEntryInfoVector))
.Times(2)
.WillRepeatedly(Return(true));
for (size_t i = 0; i < kLicenseList.size(); ++i)
device_files_->DeleteLicense(kLicenseList[i]);
EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_));
}
TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveUsageInfo) {
EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader),
SetArgPointee<1>(kEmptyUsageEntryInfoVector),
Return(false)));
EXPECT_CALL(*device_files_, ListLicenses(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kEmptyLicenseList),
Return(false)));
EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kUsageInfoFileList),
Return(true)));
EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull()))
.WillOnce(
DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR)));
EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader,
kEmptyUsageEntryInfoVector))
.Times(2)
.WillRepeatedly(Return(true));
for (size_t i = 0; i < kUsageInfoFileList.size(); ++i) {
EXPECT_CALL(*device_files_,
RetrieveUsageInfo(kUsageInfoFileList[i], NotNull()))
.WillOnce(DoAll(SetArgPointee<1>(kEmptyUsageInfoUsageDataList),
Return(false)));
}
EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_));
}
@@ -560,7 +663,6 @@ TEST_F(UsageTableHeaderTest,
usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1
metrics::CryptoMetrics metrics;
device_files_->DeleteAllLicenses();
EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR));
EXPECT_CALL(
*crypto_session_,
@@ -1558,4 +1660,27 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreSecureStopAndUnknknown) {
device_files_, &metrics));
}
// If the crypto session says the usage table header is stale, init should fail.
TEST_F(UsageTableHeaderTest, StaleHeader) {
const std::vector<CdmUsageEntryInfo> usage_entry_info_vector = {
kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1,
kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2};
EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader),
SetArgPointee<1>(usage_entry_info_vector),
Return(true)));
EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader))
.WillOnce(Return(LOAD_USAGE_HEADER_GENERATION_SKEW));
EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull()))
.WillOnce(
DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR)));
EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true));
EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader,
kEmptyUsageEntryInfoVector))
.WillOnce(Return(true));
EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_));
}
} // namespace wvcdm