Import updates to the Widevine CENC DRM Plugin

This change incorporates the following CLs from the Widevine
cdm repository:

    Update the java request/response test app to match Drm API changes
    Don't build the mock liboemcrypto.so by default
    Do not build CDM tests by default
    Fix Build Break in DrmEngine Unit Tests
    Fix Build Break in WVDrmPlugin
    Initial version of roadmap for CDM projects.
    Implement License Query
    Implement Generic DRM in OEMCrypto Reference Implementation
    Add key_data_length field when calling OEMCrypto_LoadKeys
    Policy engine unittests
    Generalized DRM API for OEMCrypto
    Fixes proto buf libraries build.
    Add Version Number to OEMCrypto API
    Test key control block duration field in OEMCrypto
    Add fix for missing crypto offset.
    Fixed android/media*/test builds and added proto files for Cert. provisioning
    Refactor and clean up callback code in CDM.
    Add "device_id" name-value pair to LicenseRequest::ClientIdentification
    Separate unit and end-to-end tests from the top level makefie.
    Includes changes for 'fall back to l3 oemcrypto lib' in top level makefile.
    Fall Back to Level 3 if Level 1 Fails
    Fix compilation error in wvcdm_unittest.
    Fix Android build break due to Decrypt() signature change in cdm_engine.h.
    Wire up callbacks and errors in the Steel proxy.
    Fix lock assert if there is no keybox on the device.
    RSA Certificate Unit Test
    Change Generic_Verify signature to constant.

Change-Id: I2e42db9d0b4f8d4e833675ae81d0714509bbfd2c
This commit is contained in:
Jeff Tinker
2013-04-03 17:54:20 -07:00
parent 998d67fc8c
commit f3ec8c19d6
54 changed files with 5944 additions and 751 deletions

View File

@@ -15,7 +15,7 @@ LOCAL_C_INCLUDES += \
external/protobuf/src \
../oemcrypto/include
LOCAL_STATIC_LIBRARIES := license_protocol_protos
LOCAL_STATIC_LIBRARIES := cdm_protos
LOCAL_ADDITIONAL_DEPENDENCIES := $(cdm_proto_gen_headers)
SRC_DIR := src
@@ -28,6 +28,7 @@ LOCAL_SRC_FILES := \
$(CORE_SRC_DIR)/crypto_session.cpp \
$(CORE_SRC_DIR)/license.cpp \
$(CORE_SRC_DIR)/policy_engine.cpp \
$(CORE_SRC_DIR)/properties.cpp \
$(CORE_SRC_DIR)/string_conversions.cpp \
$(SRC_DIR)/clock.cpp \
$(SRC_DIR)/lock.cpp \
@@ -39,7 +40,3 @@ LOCAL_MODULE := libcdm
LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_LIBRARY)
#########################################################
# include the tests.
include $(LOCAL_PATH)/test/Android.mk

View File

@@ -30,7 +30,7 @@ class CdmEngine : public TimerHandler {
const CdmKeySystem& key_system,
const CdmInitData& init_data,
const CdmLicenseType license_type,
CdmNameValueMap& app_parameters,
CdmAppParameterMap& app_parameters,
CdmKeyMessage* key_request);
// Accept license response and extract key info.
@@ -59,10 +59,12 @@ class CdmEngine : public TimerHandler {
const CdmInitData& init_data,
const CdmKeyResponse& key_data);
// Query system information
CdmResponseType QueryStatus(CdmQueryMap* info);
// Query license information
CdmResponseType QueryKeyStatus(const CdmSessionId& session_id,
CdmNameValueMap* key_info);
CdmQueryMap* key_info);
// Provisioning related methods
CdmResponseType GetProvisioningRequest(CdmProvisioningRequest* request,
@@ -83,7 +85,7 @@ class CdmEngine : public TimerHandler {
size_t encrypted_size,
const std::vector<uint8_t>& iv,
size_t block_offset,
void* decrypted_buffer);
uint8_t* decrypted_buffer);
// Is the key known to any session?
bool IsKeyValid(const KeyId& key_id);

View File

@@ -42,11 +42,14 @@ class CdmSession {
// CancelKeyRequest() - Cancel session.
CdmResponseType CancelKeyRequest();
// Query license information
CdmResponseType QueryKeyStatus(CdmQueryMap* key_info);
// Decrypt() - Accept encrypted buffer and return decrypted data.
CdmResponseType Decrypt(const uint8_t* encrypted_buffer,
size_t encrypted_size,
size_t block_offset,
const std::string& iv,
const std::vector<uint8_t>& iv,
const KeyId& key_id,
uint8_t* decrypted_buffer);

View File

@@ -10,9 +10,15 @@
namespace wvcdm {
// Provides time related information. The implementation is platform dependent.
class Clock {
// Provides the number of seconds since an epoch (00:00 hours, Jan 1, 1970 UTC)
int64_t GetCurrentTime();
public:
Clock() {}
virtual ~Clock() {}
// Provides the number of seconds since an epoch - 01/01/1970 00:00 UTC
virtual int64_t GetCurrentTime();
};
}; // namespace wvcdm

View File

@@ -38,6 +38,8 @@ class CryptoEngine {
bool GetToken(std::string* token);
CdmResponseType Query(CdmQueryMap* info);
private:
void DeleteInstance();

View File

@@ -56,6 +56,11 @@ class CryptoSession {
// Media data path
bool SelectKey(const std::string& key_id);
bool Decrypt(const InputDescriptor input, OutputDescriptor output);
bool Decrypt(const uint8_t* encrypted_buffer,
size_t encrypted_size,
size_t block_offset,
const std::vector<uint8_t>& iv,
uint8_t* decrypted_buffer);
private:

View File

@@ -11,6 +11,7 @@ namespace wvcdm {
using video_widevine_server::sdk::LicenseIdentification;
class CryptoSession;
class PolicyEngine;
class CdmLicense {
@@ -19,7 +20,8 @@ class CdmLicense {
CdmLicense();
~CdmLicense();
bool Init(const std::string& token, CryptoSession* session);
bool Init(const std::string& token, CryptoSession* session,
PolicyEngine* policy_engine);
bool PrepareKeyRequest(const CdmInitData& init_data,
CdmKeyMessage* signed_request);
@@ -31,6 +33,7 @@ private:
LicenseIdentification license_id_;
CryptoSession* session_;
PolicyEngine* policy_engine_;
std::string token_;
CORE_DISALLOW_COPY_AND_ASSIGN(CdmLicense);

View File

@@ -10,6 +10,9 @@
namespace wvcdm {
class Clock;
class PolicyEngineTest;
// This acts as an oracle that basically says "Yes(true) you may still decrypt
// or no(false) you may not decrypt this data anymore."
class PolicyEngine {
@@ -17,8 +20,14 @@ class PolicyEngine {
PolicyEngine();
~PolicyEngine();
// |current_time| is used to check if license has to be renewed or expired.
void OnTimerEvent(int64_t current_time, bool event_occurred, CdmEventType& event);
// The value returned should be taken as a hint rather than an absolute
// status. It is computed during the last call to either SetLicense/
// UpdateLicense/OnTimerEvent/BeginDecryption and may be out of sync
// depending on the amount of time elapsed. The current decryption
// status is not calculated to avoid overhead in the decryption path.
inline bool can_decrypt() { return can_decrypt_; }
void OnTimerEvent(bool& event_occurred, CdmEventType& event);
// SetLicense is used in handling the initial license response. It stores
// an exact copy of the policy information stored in the license.
@@ -26,6 +35,11 @@ class PolicyEngine {
// permits playback.
void SetLicense(const video_widevine_server::sdk::License& license);
// Call this on first decrypt to set the start of playback. This is
// for cases where usage begins not when the license is received,
// but at the start of playback
void BeginDecryption(void);
// UpdateLicense is used in handling a license response for a renewal request.
// The response may only contain any policy fields that have changed. In this
// case an exact copy is not what we want to happen. We also will receive an
@@ -33,6 +47,8 @@ class PolicyEngine {
// kLicenseStateCanPlay if the license permits playback.
void UpdateLicense(const video_widevine_server::sdk::License& license);
CdmResponseType Query(CdmQueryMap* key_info);
const video_widevine_server::sdk::LicenseIdentification& license_id() {
return license_id_;
}
@@ -40,14 +56,17 @@ class PolicyEngine {
private:
typedef enum {
kLicenseStateInitial,
kLicenseStateInitialPendingUsage,
kLicenseStateCanPlay,
kLicenseStateCannotPlay,
kLicenseStateNeedRenewal,
kLicenseStateWaitingLicenseUpdate,
kLicenseStateExpired
} LicenseState;
void Init(Clock* clock);
bool IsLicenseDurationExpired(int64_t current_time);
bool IsPlaybackDurationExpired(int64_t current_time);
bool IsRenewalDelayExpired(int64_t current_time);
bool IsRenewalRecoveryDurationExpired(int64_t current_time);
bool IsRenewalRetryIntervalExpired(int64_t current_time);
@@ -55,6 +74,7 @@ class PolicyEngine {
void UpdateRenewalRequest(int64_t current_time);
LicenseState license_state_;
bool can_decrypt_;
// This is the current policy information for this license. This gets updated
// as license renewals occur.
@@ -69,14 +89,30 @@ class PolicyEngine {
// license request or renewal.
int64_t license_start_time_;
// This is the time at which the license was received and playback was
// started. These times are based off the local clock in case there is a
// discrepency between local and server time.
int64_t license_received_time_;
int64_t playback_start_time_;
// This is used as a reference point for policy management. This value
// represents an offset from license_start_time_. This is used to calculate
// the time where renewal retries should occur.
// represents an offset from license_received_time_. This is used to
// calculate the time where renewal retries should occur.
int64_t next_renewal_time_;
int64_t policy_max_duration_seconds_;
bool properties_valid_;
bool begin_license_usage_when_received_;
Clock* clock_;
// For testing
friend class PolicyEngineTest;
PolicyEngine(Clock* clock);
CORE_DISALLOW_COPY_AND_ASSIGN(PolicyEngine);
};
} // wvcdm
#endif // CDM_BASE_POLICY_ENGINE_H_

View File

@@ -0,0 +1,53 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef CDM_BASE_PROPERTIES_H_
#define CDM_BASE_PROPERTIES_H_
#include <map>
#include <string>
#include "lock.h"
#include "wv_cdm_types.h"
namespace wvcdm {
typedef std::map<std::string, bool> CdmBooleanPropertiesMap;
struct CdmBooleanProperties {
std::string name;
bool value;
};
// This class saves information about features and properties enabled
// for a given platform. At initialization it reads in properties from
// property_configuration.h. That file specifies features selected for each
// platform. Core CDM can then query enabled features though the GetProperty
// method and tailor its behaviour in a non-platform specific way.
//
// Additional features can be added at runtime as long as the key names do
// not clash. Also, only boolean properties are supported at this time, though
// it should be trivial to in support for other datatypes.
class Properties {
public:
static Properties* GetInstance();
// value argument is only set if the property was found (true is returned)
bool GetProperty(std::string& key, bool& value);
private:
Properties();
~Properties() {}
void SetProperty(std::string& key, bool value);
static Properties* instance_;
static Lock properties_lock_;
CdmBooleanPropertiesMap boolean_properties_;
CORE_DISALLOW_COPY_AND_ASSIGN(Properties);
};
} // namespace wvcdm
#endif // CDM_BASE_PROPERTIES_H_

View File

@@ -3,6 +3,8 @@
#ifndef CDM_BASE_WV_CDM_CONSTANTS_H_
#define CDM_BASE_WV_CDM_CONSTANTS_H_
#include <string>
namespace wvcdm {
static const size_t KEY_CONTROL_SIZE = 16;
// TODO(kqyang): Key ID size is not fixed in spec, but conventionally we
@@ -13,6 +15,38 @@ static const size_t KEY_IV_SIZE = 16;
static const size_t KEY_PAD_SIZE = 16;
static const size_t KEY_SIZE = 16;
static const size_t MAC_KEY_SIZE = 32;
// define boolean property keys here
// If false begin license usage on first playback
static std::string kPropertyKeyBeginLicenseUsageWhenReceived =
"WVBeginLicenseUsageWhenReceived";
// define query keys, values here
static const std::string QUERY_KEY_LICENSE_TYPE = "LicenseType";
// "Streaming", "Offline"
static const std::string QUERY_KEY_PLAY_ALLOWED = "PlayAllowed";
// "True", "False"
static const std::string QUERY_KEY_PERSIST_ALLOWED = "PersistAllowed";
// "True", "False"
static const std::string QUERY_KEY_RENEW_ALLOWED = "RenewAllowed";
// "True", "False"
static const std::string QUERY_KEY_LICENSE_DURATION_REMAINING =
"LicenseDurationRemaining"; // non-negative integer
static const std::string QUERY_KEY_PLAYBACK_DURATION_REMAINING =
"PlaybackDurationRemaining"; // non-negative integer
static const std::string QUERY_KEY_RENEWAL_SERVER_URL = "RenewalServerUrl";
// url
static const std::string QUERY_KEY_SECURITY_LEVEL = "SecurityLevel";
// "L1", "L3"
static const std::string QUERY_VALUE_TRUE = "True";
static const std::string QUERY_VALUE_FALSE = "False";
static const std::string QUERY_VALUE_STREAMING = "Streaming";
static const std::string QUERY_VALUE_OFFLINE = "Offline";
static const std::string QUERY_VALUE_SECURITY_LEVEL_L1 = "L1";
static const std::string QUERY_VALUE_SECURITY_LEVEL_L2 = "L2";
static const std::string QUERY_VALUE_SECURITY_LEVEL_L3 = "L3";
} // namespace wvcdm
#endif // CDM_BASE_WV_CDM_CONSTANTS_H_

View File

@@ -20,7 +20,8 @@ typedef std::string RequestId;
typedef uint32_t CryptoResult;
typedef uint32_t CryptoSessionId;
typedef std::string CryptoKeyId;
typedef std::map<std::string, std::string> CdmNameValueMap;
typedef std::map<std::string, std::string> CdmAppParameterMap;
typedef std::map<std::string, std::string> CdmQueryMap;
typedef std::vector<std::string> CdmSecureStops;
typedef std::vector<uint8_t> CdmSecureStopReleaseMessage;
typedef std::string CdmProvisioningRequest;

View File

@@ -6,6 +6,7 @@
#include "buffer_reader.h"
#include "cdm_session.h"
#include "crypto_engine.h"
#include "log.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
@@ -70,16 +71,15 @@ CdmResponseType CdmEngine::OpenSession(
CdmResponseType CdmEngine::CloseSession(CdmSessionId& session_id) {
LOGI("CdmEngine::CloseSession");
CdmSession* cdm_session = sessions_[session_id];
if (!cdm_session) {
CdmSessionIter iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::CloseSession: session not found = %s", session_id.c_str());
return KEY_ERROR;
}
sessions_.erase(session_id);
cdm_session->DestroySession();
delete cdm_session;
iter->second->DestroySession();
delete iter->second;
return NO_ERROR;
}
@@ -89,13 +89,12 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
const CdmKeySystem& key_system,
const CdmInitData& init_data,
const CdmLicenseType license_type,
CdmNameValueMap& app_parameters,
CdmAppParameterMap& app_parameters,
CdmKeyMessage* key_request) {
LOGI("CdmEngine::GenerateKeyRequest");
CdmSession* session = sessions_[session_id];
if (!session) {
CdmSessionIter iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::GenerateKeyRequest: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
@@ -123,8 +122,8 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
key_request->clear();
// TODO(edwinwong, rfrias): need to pass in license type and app parameters
CdmResponseType sts = session->GenerateKeyRequest(extracted_pssh,
key_request);
CdmResponseType sts = iter->second->GenerateKeyRequest(extracted_pssh,
key_request);
if (KEY_MESSAGE != sts) {
LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, sts=%d",
@@ -146,9 +145,8 @@ CdmResponseType CdmEngine::AddKey(
const CdmKeyResponse& key_data) {
LOGI("CdmEngine::AddKey");
CdmSession* session = sessions_[session_id];
if (!session) {
CdmSessionIter iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::AddKey: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
@@ -166,7 +164,7 @@ CdmResponseType CdmEngine::AddKey(
return KEY_ERROR;
}
CdmResponseType sts = session->AddKey(key_data);
CdmResponseType sts = iter->second->AddKey(key_data);
if (KEY_ADDED != sts) {
LOGE("CdmEngine::AddKey: keys not added, result = %d", (int)sts);
}
@@ -186,9 +184,8 @@ CdmResponseType CdmEngine::CancelKeyRequest(
// active sessions. Sessions are currently not being destroyed here. We can
// add this logic once the semantics of canceling the key is worked out.
CdmSession* session = sessions_[session_id];
if (!session) {
CdmSessionIter iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::CancelKeyRequest: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
@@ -210,9 +207,8 @@ CdmResponseType CdmEngine::GenerateRenewalRequest(
CdmKeyMessage* key_request) {
LOGI("CdmEngine::GenerateRenewalRequest");
CdmSession* session = sessions_[session_id];
if (!session) {
CdmSessionIter iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::GenerateRenewalRequest: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
@@ -232,7 +228,7 @@ CdmResponseType CdmEngine::GenerateRenewalRequest(
key_request->clear();
CdmResponseType sts = session->GenerateRenewalRequest(key_request);
CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request);
if (KEY_MESSAGE != sts) {
LOGE("CdmEngine::GenerateRenewalRequest: key request generation failed, sts=%d",
@@ -251,9 +247,8 @@ CdmResponseType CdmEngine::RenewKey(
const CdmKeyResponse& key_data) {
LOGI("CdmEngine::RenewKey");
CdmSession* session = sessions_[session_id];
if (!session) {
CdmSessionIter iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::RenewKey: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
@@ -271,7 +266,7 @@ CdmResponseType CdmEngine::RenewKey(
return KEY_ERROR;
}
CdmResponseType sts = session->RenewKey(key_data);
CdmResponseType sts = iter->second->RenewKey(key_data);
if (KEY_ADDED != sts) {
LOGE("CdmEngine::RenewKey: keys not added, sts=%d", (int)sts);
return sts;
@@ -280,11 +275,25 @@ CdmResponseType CdmEngine::RenewKey(
return KEY_ADDED;
}
CdmResponseType CdmEngine::QueryStatus(CdmQueryMap* key_info) {
LOGI("CdmEngine::QueryStatus");
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
if (crypto_engine) {
return crypto_engine->Query(key_info);
}
return KEY_ERROR;
}
CdmResponseType CdmEngine::QueryKeyStatus(
const CdmSessionId& session_id,
CdmNameValueMap* key_info) {
// TODO(edwinwong, rfrias): add implementation
return NO_ERROR;
CdmQueryMap* key_info) {
LOGI("CdmEngine::QueryKeyStatus");
CdmSessionIter iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::QueryKeyStatus: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
return iter->second->QueryKeyStatus(key_info);
}
CdmResponseType CdmEngine::GetProvisioningRequest(
@@ -320,16 +329,18 @@ CdmResponseType CdmEngine::Decrypt(
size_t encrypted_size,
const std::vector<uint8_t>& iv,
size_t block_offset,
void* decrypted_buffer) {
CdmSession* session = sessions_[session_id];
if (!session) {
uint8_t* decrypted_buffer) {
CdmSessionIter iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGW("CdmEngine::Decrypt: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
// TODO(edwinwong, rfrias): Need to add implemenation and hook up
// decryption though to oem_crypto
if (NO_ERROR != iter->second->Decrypt(encrypted_buffer, encrypted_size,
block_offset, iv, key_id,
decrypted_buffer))
return UNKNOWN_ERROR;
return NO_ERROR;
}

View File

@@ -30,7 +30,7 @@ bool CdmSession::Init() {
std::string token;
if (!crypto_engine->GetToken(&token)) return false;
return license_parser_.Init(token, crypto_session_);
return license_parser_.Init(token, crypto_session_, &policy_engine_);
}
bool CdmSession::DestroySession() {
@@ -67,6 +67,10 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
}
}
CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* key_info) {
return policy_engine_.Query(key_info);
}
// CancelKeyRequest() - Cancel session.
CdmResponseType CdmSession::CancelKeyRequest() {
// TODO(gmorgan): cancel and clean up session
@@ -78,10 +82,20 @@ CdmResponseType CdmSession::CancelKeyRequest() {
CdmResponseType CdmSession::Decrypt(const uint8_t* encrypted_buffer,
size_t encrypted_size,
size_t block_offset,
const std::string& iv,
const std::vector<uint8_t>& iv,
const KeyId& key_id,
uint8_t* decrypted_buffer) {
return UNKNOWN_ERROR;
if (!crypto_session_)
return UNKNOWN_ERROR;
if (!crypto_session_->SelectKey(key_id))
return UNKNOWN_ERROR;
if (!crypto_session_->Decrypt(encrypted_buffer, encrypted_size,
block_offset, iv, decrypted_buffer))
return UNKNOWN_ERROR;
return NO_ERROR;
}
// License renewal
@@ -131,7 +145,7 @@ void CdmSession::OnTimerEvent() {
bool event_occurred = false;
CdmEventType event;
policy_engine_.OnTimerEvent(GetCurrentTime(), event_occurred, event);
policy_engine_.OnTimerEvent(event_occurred, event);
if (event_occurred) {
for (CdmEventListenerIter iter = listeners_.begin();

View File

@@ -0,0 +1,43 @@
// Copyright 2013 Google Inc. All Rights Reserved.
// Author: tinskip@google.com (Thomas Inskip)
//
// Description:
// Public protocol buffer definitions for Widevine Device Certificate
// Provisioning protocol.
syntax = "proto2";
package video_widevine_server.sdk;
import "vendor/widevine/libwvdrmengine/cdm/core/src/client_identification.proto";
option optimize_for = LITE_RUNTIME;
// Provisioning request sent by client devices to provisioning service.
message ProvisioningRequest {
// Device root of trust and other client identification. Required.
optional ClientIdentification client_id = 1;
// Nonce value used to prevent replay attacks. Required.
optional bytes nonce = 2;
}
// Provisioning response sent by the provisioning server to client devices.
message ProvisioningResponse {
// AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded.
// Required.
optional bytes device_rsa_key = 1;
// Initialization vector used to encrypt device_rsa_key. Required.
optional bytes device_rsa_key_iv = 2;
// Serialized DeviceCertificate. Required.
optional bytes device_certificate = 3;
// Nonce value matching nonce in ProvisioningRequest. Required.
optional bytes nonce = 4;
}
// Serialized ProvisioningRequest or ProvisioningResponse signed with
// The message authentication key.
message SignedProvisioningMessage {
// Serialized ProvisioningRequest or ProvisioningResponse. Required.
optional bytes message = 1;
// HMAC-SHA256 signature of message. Required.
optional bytes signature = 2;
}

View File

@@ -0,0 +1,30 @@
// Copyright 2013 Google Inc. All Rights Reserved.
// Author: tinskip@google.com (Thomas Inskip)
//
// Description:
// ClientIdentification message used by provisioning and license protocols.
syntax = "proto2";
package video_widevine_server.sdk;
option java_outer_classname = "ClientIdentificationProtos";
option optimize_for = LITE_RUNTIME;
// ClientIdentification message used to authenticate the client device.
message ClientIdentification {
enum TokenType {
KEYBOX = 0;
}
message NameValue {
optional string name = 1;
optional string value = 2;
}
// Type of factory-provisioned device root of trust. Optional.
optional TokenType type = 1 [default = KEYBOX];
// Factory-provisioned device root of trust. Required.
optional bytes token = 2;
// Optional client information name/value pairs.
repeated NameValue client_info = 3;
}

View File

@@ -175,4 +175,10 @@ bool CryptoEngine::GetToken(std::string* token) {
return true;
}
CdmResponseType CryptoEngine::Query(CdmQueryMap* key_info) {
LOGV("CryptoEngine::GetToken: Query");
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = OEMCrypto_SecurityLevel();
return NO_ERROR;
}
}; // namespace wvcdm

31
libwvdrmengine/cdm/core/src/crypto_session.cpp Normal file → Executable file
View File

@@ -230,6 +230,7 @@ bool CryptoSession::LoadKeys(const std::string& message,
ko->key_id_length = ki->key_id().length();
ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv());
ko->key_data = msg + GetOffset(message, ki->key_data());
ko->key_data_length = ki->key_data().length();
if (ki->HasKeyControl()) {
ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv());
ko->key_control = msg + GetOffset(message, ki->key_control());
@@ -324,6 +325,36 @@ bool CryptoSession::Decrypt(const InputDescriptor input,
return true;
}
// TODO(jfore): Define InputDescriptor and OutputDecriptor and
// remove this method. For now this is a level 3 decrypt.
bool CryptoSession::Decrypt(const uint8_t* encrypted_buffer,
size_t encrypted_size,
size_t block_offset,
const std::vector<uint8_t>& iv,
uint8_t* decrypted_buffer) {
LOGV("CryptoSession::Decrypt: Lock");
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
AutoLock auto_lock(crypto_engine->crypto_lock_);
// TODO(gmorgan): handle inputs and outputs to decrypt call
const uint8_t* data_addr = NULL;
uint32_t data_length = 0;
bool is_encrypted = false;
uint32_t offset = block_offset;
OEMCrypto_DestBufferDesc out_buffer;
out_buffer.type = OEMCrypto_BufferType_Clear;
out_buffer.buffer.clear.address = decrypted_buffer;
out_buffer.buffer.clear.max_length = encrypted_size;
OEMCryptoResult sts = OEMCrypto_DecryptCTR(oec_session_id_, encrypted_buffer,
encrypted_size, true, &iv[0],
offset, &out_buffer);
if (OEMCrypto_SUCCESS != sts) {
return false;
}
return true;
}
bool CryptoSession::GenerateNonce(uint32_t* nonce) {
if (!nonce) {
LOGE("input parameter is null");

View File

@@ -0,0 +1,117 @@
// Copyright 2013 Google Inc. All Rights Reserved.
// Author: tinskip@google.com (Thomas Inskip)
//
// Description:
// Device certificate and certificate status list format definitions.
syntax = "proto2";
package video_widevine_server.sdk;
option optimize_for = LITE_RUNTIME;
option java_outer_classname = "DeviceCertificateProtos";
option java_package = "com.google.video.widevine.protos";
// Certificate definition for user devices, intermediate, and root certificates.
message DeviceCertificate {
enum CertificateType {
ROOT = 0;
INTERMEDIATE = 1;
USER_DEVICE = 2;
}
// Type of certificate. Required.
optional CertificateType type = 1;
// 128-bit globally unique serial number of certificate.
// Value is 0 for root certificate. Required.
optional bytes serial_number = 2;
// POSIX time, in seconds, when the certificate was created. Required.
optional uint32 creation_time_seconds = 3;
// Device public key. PKCS#1 ASN.1 DER-encoded. Required.
optional bytes public_key = 4;
// Widevine system ID for the device. Required for intermediate and
// user device certificates.
optional uint32 system_id = 5;
// True if the certificate corresponds to a test (non production) device.
// Optional.
optional bool test_device = 6 [default = false];
}
// DeviceCertificate signed with intermediate or root certificate private key.
message SignedDeviceCertificate {
// Serialized DeviceCertificate. Required.
optional bytes device_certificate = 1;
// Signature of device_certificate. Signed with root or intermediate
// certificate private key using RSASSA-PSS. Required.
optional bytes signature = 2;
// Intermediate signing certificate. Present only for user device
// certificates. All others signed with root certificate private key.
optional SignedDeviceCertificate signer = 3;
}
// Contains device model information for a provisioned device.
message ProvisionedDeviceInfo {
enum WvSecurityLevel {
// Defined in Widevine Security Integration Guide for DASH on Android:
// https://docs.google.com/a/google.com/document/d/1Zum-fcJeoIw6KG1kDP_KepIE5h9gAZg0PaMtemBvk9c/edit#heading=h.1t3h5sf
LEVEL_UNSPECIFIED = 0;
LEVEL_1 = 1;
LEVEL_2 = 2;
LEVEL_3 = 3;
}
// Widevine system ID for the device. Mandatory.
optional uint32 system_id = 1;
// Name of system-on-a-chip. Optional.
optional string soc = 2;
// Name of manufacturer. Optional.
optional string manufacturer = 3;
// Manufacturer's model name. Matches "brand" in device metadata. Optional.
optional string model = 4;
// Type of device (Phone, Tablet, TV, etc).
optional string device_type = 5;
// Device model year. Optional.
optional uint32 model_year = 6;
// Widevine-defined security level. Optional.
optional WvSecurityLevel security_level = 7 [default = LEVEL_UNSPECIFIED];
// True if the certificate corresponds to a test (non production) device.
// Optional.
optional bool test_device = 8 [default = false];
}
// Contains the status of the root or an intermediate DeviceCertificate.
message DeviceCertificateStatus {
enum CertificateStatus {
VALID = 0;
REVOKED = 1;
};
// Serial number of the DeviceCertificate to which this message refers.
// Required.
optional bytes serial_number = 1;
// Status of the certificate. Optional.
optional CertificateStatus status = 2 [default = VALID];
// Current version of a valid certificate. Present only if status = VALID.
optional uint32 current_certificate_version = 3;
// Device model information about the device to which the certificate
// corresponds. Required.
optional ProvisionedDeviceInfo device_info = 4;
}
// List of DeviceCertificateStatus. Used to propagate certificate revocation and
// update list.
message DeviceCertificateStatusList {
// POSIX time, in seconds, when the list was created. Required.
optional uint32 creation_time_seconds = 1;
// DeviceCertificateStatus for each certifificate.
repeated DeviceCertificateStatus certificate_status = 2;
}
// Signed CertificateStatusList
message SignedCertificateStatusList {
// Serialized CertificateStatusList. Required.
optional bytes certificate_status_list = 1;
// Signature of certificate_status_list. Signed with root certificate private
// key using RSASSA-PSS. Required.
optional bytes signature = 2;
}

View File

@@ -4,6 +4,7 @@
#include "crypto_session.h"
#include "log.h"
#include "policy_engine.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
@@ -25,13 +26,16 @@ CdmLicense::CdmLicense(): session_(NULL) {}
CdmLicense::~CdmLicense() {}
bool CdmLicense::Init(const std::string& token, CryptoSession* session) {
bool CdmLicense::Init(const std::string& token,
CryptoSession* session,
PolicyEngine* policy_engine) {
if (token.size() == 0)
return false;
if (session == NULL || !session->IsValid() || !session->IsOpen())
return false;
token_ = token;
session_ = session;
policy_engine_ = policy_engine;
return true;
}
@@ -239,9 +243,7 @@ bool CdmLicense::HandleKeyResponse(const CdmKeyResponse& license_response) {
if (num_keys == 0) return false;
// TODO(kqyang): move protocol buffer related stuff in policy
// engine to this file.
// policy_engine_.SetLicense(license);
policy_engine_->SetLicense(license);
bool status = session_->LoadKeys(signed_response.msg(),
signed_response.signature(),
@@ -282,9 +284,7 @@ bool CdmLicense::HandleKeyRenewalResponse(
//This is the normal case.
license_id_.CopyFrom(license.id());
// TODO(kqyang): should we move protocol buffer related stuff in policy
// engine to this file instead?
// policy_engine_.UpdateLicense(license);
policy_engine_->UpdateLicense(license);
} else {
// This isn't supposed to happen.
// TODO(jfore): Handle wrap? We can miss responses and that should be

View File

@@ -4,81 +4,106 @@
#include <algorithm>
#include <map>
#include <sstream>
#include <string>
#include <vector>
#include "log.h"
#include "properties.h"
#include "properties_configuration.h"
#include "string_conversions.h"
#include "clock.h"
namespace wvcdm {
PolicyEngine::PolicyEngine() :
license_state_(kLicenseStateInitial),
license_start_time_(0),
next_renewal_time_(0),
policy_max_duration_seconds_(0) {
PolicyEngine::PolicyEngine() {
Init(new Clock());
}
PolicyEngine::PolicyEngine(Clock* clock) {
Init(clock);
}
PolicyEngine::~PolicyEngine() {
if (clock_)
delete clock_;
}
void PolicyEngine::OnTimerEvent(int64_t current_time, bool event_occured, CdmEventType& event) {
void PolicyEngine::Init(Clock* clock) {
license_state_ = kLicenseStateInitial;
can_decrypt_ = false;
license_start_time_ = 0;
license_received_time_ = 0;
playback_start_time_ = 0;
next_renewal_time_ = 0;
policy_max_duration_seconds_ = 0;
clock_ = clock;
properties_valid_ = true;
if (!Properties::GetInstance()->GetProperty(
kPropertyKeyBeginLicenseUsageWhenReceived,
begin_license_usage_when_received_)) {
LOGW("PolicyEngine::PolicyEngine: Unable to access property - begin license usage");
properties_valid_ = false;
}
}
void PolicyEngine::OnTimerEvent(bool& event_occured, CdmEventType& event) {
event_occured = false;
int64_t current_time = clock_->GetCurrentTime();
// License expiration trumps all.
if (IsLicenseDurationExpired(current_time) &&
license_state_ != kLicenseStateExpired) {
if ((IsLicenseDurationExpired(current_time) ||
IsPlaybackDurationExpired(current_time)) &&
license_state_ != kLicenseStateExpired) {
license_state_ = kLicenseStateExpired;
can_decrypt_ = false;
event = LICENSE_EXPIRED_EVENT;
event_occured = true;
return;
}
bool renewal_needed = false;
// Test to determine if renewal should be attempted.
switch (license_state_) {
case kLicenseStateInitialPendingUsage:
case kLicenseStateCanPlay: {
if (IsRenewalDelayExpired(current_time)) {
license_state_ = kLicenseStateNeedRenewal;
UpdateRenewalRequest(current_time);
event = LICENSE_RENEWAL_NEEDED_EVENT;
event_occured = true;
}
return;
if (IsRenewalDelayExpired(current_time))
renewal_needed = true;
break;
}
case kLicenseStateNeedRenewal: {
UpdateRenewalRequest(current_time);
event = LICENSE_RENEWAL_NEEDED_EVENT;
event_occured = true;
return;
renewal_needed = true;
break;
}
case kLicenseStateWaitingLicenseUpdate: {
if (IsRenewalRetryIntervalExpired(current_time)) {
UpdateRenewalRequest(current_time);
event = LICENSE_RENEWAL_NEEDED_EVENT;
event_occured = true;
}
return;
if (IsRenewalRetryIntervalExpired(current_time))
renewal_needed = true;
break;
}
case kLicenseStateInitial:
case kLicenseStateExpired: {
return;
break;
}
default: {
license_state_ = kLicenseStateCannotPlay;
return;
license_state_ = kLicenseStateExpired;
can_decrypt_ = false;
break;
}
}
if (renewal_needed) {
UpdateRenewalRequest(current_time);
event = LICENSE_RENEWAL_NEEDED_EVENT;
event_occured = true;
}
}
// TODO: Fix up differences between Eureka and other platforms' use cases.
// Eureka does not get calls to decrypt. license usage begins
// when we receive the initial license. renew_with_usage will cause renewal to
// occur on the first call to OnTimeEvent after PolicyEngine::SetLicense is
// called.
void PolicyEngine::SetLicense(
const video_widevine_server::sdk::License& license) {
license_id_.Clear();
@@ -89,89 +114,158 @@ void PolicyEngine::SetLicense(
void PolicyEngine::UpdateLicense(
const video_widevine_server::sdk::License& license) {
if (!license.has_policy() || kLicenseStateExpired == license_state_)
if (!license.has_policy() || kLicenseStateExpired == license_state_ ||
!properties_valid_)
return;
policy_.MergeFrom(license.policy());
policy_max_duration_seconds_ = 0;
// Calculate policy_max_duration_seconds_. policy_max_duration_seconds_
// will be set to the minimum of the following policies :
// rental_duration_seconds, playback_duration_seconds, and
// license_duration_seconds. The value is used to determine
// when the license expires.
if (policy_.has_rental_duration_seconds())
policy_max_duration_seconds_ = policy_.rental_duration_seconds();
if ((policy_.has_playback_duration_seconds() &&
(policy_.playback_duration_seconds() < policy_max_duration_seconds_)) ||
!policy_max_duration_seconds_) {
policy_max_duration_seconds_ = policy_.playback_duration_seconds();
}
if ((policy_.has_license_duration_seconds() &&
(policy_.license_duration_seconds() < policy_max_duration_seconds_)) ||
!policy_max_duration_seconds_) {
policy_max_duration_seconds_ = policy_.license_duration_seconds();
}
switch (license_state_) {
case kLicenseStateInitial: {
// Process initial license.
license_start_time_ = license.license_start_time();
if (policy_.can_play()) {
license_state_ =
policy_.renew_with_usage() ?
kLicenseStateNeedRenewal : kLicenseStateCanPlay;
} else {
license_state_ = kLicenseStateExpired;
}
next_renewal_time_ = license_start_time_
+ policy_.renewal_delay_seconds();
}
break;
case kLicenseStateExpired:
// Ignore policy updates.
return;
default: {
// Process license renewal.
if (license.id().version() > license_id_.version()) {
// This is the normal case.
policy_.MergeFrom(license.policy());
license_id_.CopyFrom(license.id());
} else {
// This isn't supposed to happen.
// TODO(jfore): Handle wrap? We can miss responses and that should be
// considered normal until retries are exhausted.
policy_.set_can_play(false);
case kLicenseStateInitial:
case kLicenseStateInitialPendingUsage:
case kLicenseStateCanPlay:
case kLicenseStateNeedRenewal:
case kLicenseStateWaitingLicenseUpdate:
if (!policy_.can_play()) {
license_state_ = kLicenseStateExpired;
return;
}
if (license.has_license_start_time()) {
// license start has been updated. Transition back to the
// normal kLicenseStateCanPlay state if playback is allowed by
// the updated license.
license_start_time_ = license.license_start_time();
next_renewal_time_ = license_start_time_
+ policy_.renewal_delay_seconds();
license_state_ =
policy_.can_play() ? kLicenseStateCanPlay : kLicenseStateExpired;
} else {
// license start was not updated. Continue sending renewel requests
// at the specified retry rate. To perform this we transition directly
// to kLicenseStateWaitingLicenseUpdate. While in this state
// IsRenewalRetryIntervalExpired will always return false if retries are
// not allowed. Note that next_renewal_time_ was updated when this
// renewal was requested.
license_state_ =
policy_.can_play() ?
kLicenseStateWaitingLicenseUpdate : kLicenseStateExpired;
// some basic license validation
if (license_state_ == kLicenseStateInitial) {
// license start time needs to be present in the initial response
if (!license.has_license_start_time())
return;
}
else {
// TODO(edwingwong, rfrias): Check back with Thomas and see if
// we need to enforce that all duration windows are absent if
// license_start_time is not present. This is a TBD.
// if renewal, discard license if version has not been updated
if (license.id().version() > license_id_.version())
license_id_.CopyFrom(license.id());
else
return;
}
// Update time information
int64_t current_time = clock_->GetCurrentTime();
// TODO(edwingwong, rfrias): Check back with Thomas and see if
// we need to enforce that all duration windows are absent if
// license_start_time is not present. This is a TBD.
if (license.has_license_start_time())
license_start_time_ = license.license_start_time();
license_received_time_ = current_time;
next_renewal_time_ = current_time +
policy_.renewal_delay_seconds();
// Calculate policy_max_duration_seconds_. policy_max_duration_seconds_
// will be set to the minimum of the following policies :
// rental_duration_seconds and license_duration_seconds.
// The value is used to determine when the license expires.
policy_max_duration_seconds_ = 0;
if (policy_.has_rental_duration_seconds())
policy_max_duration_seconds_ = policy_.rental_duration_seconds();
if ((policy_.license_duration_seconds() > 0) &&
((policy_.license_duration_seconds() <
policy_max_duration_seconds_) ||
policy_max_duration_seconds_ == 0)) {
policy_max_duration_seconds_ = policy_.license_duration_seconds();
}
if (begin_license_usage_when_received_)
playback_start_time_ = current_time;
// Update state
if (begin_license_usage_when_received_) {
if (policy_.renew_with_usage()) {
license_state_ = kLicenseStateNeedRenewal;
}
else {
license_state_ = kLicenseStateCanPlay;
can_decrypt_ = true;
}
}
else {
if (license_state_ == kLicenseStateInitial) {
license_state_ = kLicenseStateInitialPendingUsage;
}
else {
license_state_ = kLicenseStateCanPlay;
can_decrypt_ = true;
}
}
break;
}
}
void PolicyEngine::BeginDecryption() {
if ((playback_start_time_ == 0) &&
(!begin_license_usage_when_received_)) {
switch (license_state_) {
case kLicenseStateInitialPendingUsage:
case kLicenseStateNeedRenewal:
case kLicenseStateWaitingLicenseUpdate:
playback_start_time_ = clock_->GetCurrentTime();
if (policy_.renew_with_usage()) {
license_state_ = kLicenseStateNeedRenewal;
}
else {
license_state_ = kLicenseStateCanPlay;
can_decrypt_ = true;
}
break;
case kLicenseStateCanPlay:
case kLicenseStateInitial:
case kLicenseStateExpired:
default:
break;
}
}
}
CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) {
std::stringstream ss;
int64_t current_time = clock_->GetCurrentTime();
if (license_state_ == kLicenseStateInitial)
return UNKNOWN_ERROR;
(*key_info)[QUERY_KEY_LICENSE_TYPE] =
license_id_.type() == video_widevine_server::sdk::STREAMING ?
QUERY_VALUE_STREAMING : QUERY_VALUE_OFFLINE;
(*key_info)[QUERY_KEY_PLAY_ALLOWED] = policy_.can_play() ?
QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
(*key_info)[QUERY_KEY_PERSIST_ALLOWED] = policy_.can_persist() ?
QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
(*key_info)[QUERY_KEY_RENEW_ALLOWED] = policy_.can_renew() ?
QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
int64_t remaining_time = policy_max_duration_seconds_ +
license_received_time_ - current_time;
if (remaining_time < 0)
remaining_time = 0;
ss << remaining_time;
(*key_info)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str();
remaining_time = policy_.playback_duration_seconds() + playback_start_time_ -
current_time;
if (remaining_time < 0)
remaining_time = 0;
ss << remaining_time;
(*key_info)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str();
(*key_info)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url();
return NO_ERROR;
}
void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
license_state_ = kLicenseStateWaitingLicenseUpdate;
next_renewal_time_ = current_time + policy_.renewal_retry_interval_seconds();
@@ -182,30 +276,38 @@ void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
// will always return false if the value is 0.
bool PolicyEngine::IsLicenseDurationExpired(int64_t current_time) {
return policy_max_duration_seconds_ &&
license_start_time_ + policy_max_duration_seconds_ <=
license_received_time_ + policy_max_duration_seconds_ <=
current_time;
}
bool PolicyEngine::IsPlaybackDurationExpired(int64_t current_time) {
return (policy_.playback_duration_seconds() > 0) &&
playback_start_time_ &&
playback_start_time_ + policy_.playback_duration_seconds() <=
current_time;
}
bool PolicyEngine::IsRenewalDelayExpired(int64_t current_time) {
return (policy_.renewal_delay_seconds() > 0) &&
license_start_time_ + policy_.renewal_delay_seconds() <=
return policy_.can_renew() &&
(policy_.renewal_delay_seconds() > 0) &&
license_received_time_ + policy_.renewal_delay_seconds() <=
current_time;
}
// TODO(jfore): there is some gray around how this should be
// implemented. It currently is not.
// TODO(jfore, edwinwong, rfrias): This field is in flux and currently
// not implemented. Will address after possible updates from Thomas.
bool PolicyEngine::IsRenewalRecoveryDurationExpired(
int64_t current_time) {
return (policy_.renewal_recovery_duration_seconds() > 0) &&
license_start_time_ + policy_.renewal_recovery_duration_seconds() <=
license_received_time_ + policy_.renewal_recovery_duration_seconds() <=
current_time;
}
bool PolicyEngine::IsRenewalRetryIntervalExpired(
int64_t current_time) {
return (policy_.renewal_retry_interval_seconds() > 0) &&
next_renewal_time_ <= current_time;
return policy_.can_renew() &&
(policy_.renewal_retry_interval_seconds() > 0) &&
next_renewal_time_ <= current_time;
}
} // wvcdm

View File

@@ -0,0 +1,44 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#include "properties.h"
#include "properties_configuration.h"
#include "wv_cdm_constants.h"
namespace wvcdm {
Properties* Properties::instance_ = NULL;
Lock Properties::properties_lock_;
Properties::Properties() {
// Read in static properties/features
uint32_t size = sizeof(kCdmBooleanProperties)/sizeof(CdmBooleanProperties);
for (uint32_t i = 0; i < size; i++) {
boolean_properties_[kCdmBooleanProperties[i].name] =
kCdmBooleanProperties[i].value;
}
}
Properties* Properties::GetInstance() {
AutoLock auto_lock(properties_lock_);
if (instance_ == NULL) {
instance_ = new Properties();
}
return instance_;
}
bool Properties::GetProperty(std::string& key, bool& value) {
CdmBooleanPropertiesMap::iterator itr = boolean_properties_.find(key);
if (itr == boolean_properties_.end()) {
return false;
}
value = itr->second;
return true;
}
void Properties::SetProperty(std::string& key, bool value) {
boolean_properties_[key] = value;
}
} // namespace wvcdm

View File

@@ -33,7 +33,7 @@ class WvCdmEngineTest : public testing::Test {
protected:
void GenerateKeyRequest(const std::string& key_system,
const std::string& init_data) {
wvcdm::CdmNameValueMap app_parameters;
wvcdm::CdmAppParameterMap app_parameters;
EXPECT_EQ(cdm_engine_.GenerateKeyRequest(session_id_,
true, // is_key_system_present
key_system,

View File

@@ -4,6 +4,7 @@
#include "crypto_session.h"
#include "license.h"
#include "gtest/gtest.h"
#include "policy_engine.h"
#include "string_conversions.h"
namespace {
@@ -57,7 +58,7 @@ class LicenseTest : public ::testing::Test {
EXPECT_TRUE(crypto_engine->GetToken(&token));
EXPECT_TRUE(session_->IsOpen());
EXPECT_TRUE(license_.Init(token, session_));
EXPECT_TRUE(license_.Init(token, session_, &policy_engine_));
}
virtual void TearDown() {
@@ -67,11 +68,12 @@ class LicenseTest : public ::testing::Test {
CryptoSession* session_;
CdmLicense license_;
PolicyEngine policy_engine_;
};
TEST(LicenseTestSession, InitNullSession) {
CdmLicense license;
EXPECT_FALSE(license.Init("Dummy", NULL));
EXPECT_FALSE(license.Init("Dummy", NULL, NULL));
}
TEST_F(LicenseTest, PrepareKeyRequest) {

View File

@@ -0,0 +1,737 @@
// Copyright 2012 Google Inc. All Rights Reserved.
#include <sstream>
#include "clock.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "license.h"
#include "policy_engine.h"
#include "wv_cdm_constants.h"
namespace wvcdm {
//protobuf generated classes.
using video_widevine_server::sdk::License;
using video_widevine_server::sdk::License_Policy;
using video_widevine_server::sdk::LicenseIdentification;
using video_widevine_server::sdk::STREAMING;
using video_widevine_server::sdk::OFFLINE;
// gmock methods
using ::testing::Return;
using ::testing::AtLeast;
class MockClock : public Clock {
public:
MOCK_METHOD0(GetCurrentTime, int64_t());
};
class PolicyEngineTest : public ::testing::Test {
protected:
virtual void SetUp() {
mock_clock_ = new MockClock();
policy_engine_ = new PolicyEngine(mock_clock_);
license_start_time_ = 1413517500; // ~ 01/01/2013
license_renewal_delay_ = 604200; // 7 days - 10 minutes
license_renewal_retry_interval_ = 30;
license_duration_ = 604800; // 7 days
playback_duration_ = 86400; // 24 hours
license_.set_license_start_time(license_start_time_);
LicenseIdentification* id = license_.mutable_id();
id->set_version(1);
id->set_type(STREAMING);
License_Policy* policy = license_.mutable_policy();
policy = license_.mutable_policy();
policy->set_can_play(true);
policy->set_can_persist(true);
policy->set_can_renew(true);
policy->set_rental_duration_seconds(license_duration_);
policy->set_playback_duration_seconds(playback_duration_);
policy->set_license_duration_seconds(license_duration_);
policy->set_renewal_recovery_duration_seconds(license_duration_ -
license_renewal_delay_); // 10 minutes
policy->set_renewal_server_url(
"https://jmt17.google.com/video-dev/license/GetCencLicense");
policy->set_renewal_delay_seconds(license_renewal_delay_);
policy->set_renewal_retry_interval_seconds(
license_renewal_retry_interval_);
policy->set_renew_with_usage(false);
}
virtual void TearDown() {
}
MockClock* mock_clock_;
PolicyEngine* policy_engine_;
License license_;
License_Policy* policy_;
int64_t license_start_time_;
int64_t license_renewal_delay_;
int64_t license_renewal_retry_interval_;
int64_t license_duration_;
int64_t playback_duration_;
};
TEST_F(PolicyEngineTest, NoLicense) {
EXPECT_FALSE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackSuccess) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(3)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 5))
.WillOnce(Return(license_start_time_ + 10));
policy_engine_->SetLicense(license_);
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackFailed_CanPlayFalse) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(3)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 5))
.WillOnce(Return(license_start_time_ + 10));
License_Policy* policy = license_.mutable_policy();
policy->set_can_play(false);
policy_engine_->SetLicense(license_);
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->BeginDecryption();
EXPECT_FALSE(policy_engine_->can_decrypt());
}
// TODO(edwinwong, rfrias): persist license verification test needed
TEST_F(PolicyEngineTest, PlaybackFails_RentalDurationExpired) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(4)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 5))
.WillOnce(Return(license_start_time_ + 3600))
.WillOnce(Return(license_start_time_ + 3601));
License_Policy* policy = license_.mutable_policy();
policy->set_rental_duration_seconds(3600);
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
EXPECT_FALSE(policy_engine_->can_decrypt());
}
// TODO(edwinwong, rfrias): tests needed when begin license usage when received
// is enabled
TEST_F(PolicyEngineTest, PlaybackFails_PlaybackDurationExpired) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(4)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 10000))
.WillOnce(Return(license_start_time_ + 13598))
.WillOnce(Return(license_start_time_ + 13602));
License_Policy* policy = license_.mutable_policy();
policy->set_playback_duration_seconds(3600);
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
EXPECT_FALSE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackFails_LicenseDurationExpired) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(4)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 5))
.WillOnce(Return(license_start_time_ + 3600))
.WillOnce(Return(license_start_time_ + 3601));
License_Policy* policy = license_.mutable_policy();
policy->set_license_duration_seconds(3600);
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
EXPECT_FALSE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackOk_RentalDuration0) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(4)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 5))
.WillOnce(Return(license_start_time_ + 3600))
.WillOnce(Return(license_start_time_ + 3601));
License_Policy* policy = license_.mutable_policy();
policy->set_rental_duration_seconds(0);
policy->set_license_duration_seconds(3600);
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
EXPECT_FALSE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackOk_PlaybackDuration0) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(4)
.WillOnce(Return(license_start_time_ + 10000))
.WillOnce(Return(license_start_time_ + 10005))
.WillOnce(Return(license_start_time_ + 13598))
.WillOnce(Return(license_start_time_ + 13602));
License_Policy* policy = license_.mutable_policy();
policy->set_playback_duration_seconds(0);
policy->set_license_duration_seconds(3600);
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
EXPECT_FALSE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackOk_LicenseDuration0) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(4)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 5))
.WillOnce(Return(license_start_time_ + 3600))
.WillOnce(Return(license_start_time_ + 3601));
License_Policy* policy = license_.mutable_policy();
policy->set_license_duration_seconds(0);
policy->set_rental_duration_seconds(3600);
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
EXPECT_FALSE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackOk_Durations0) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(4)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 5))
.WillOnce(Return(license_start_time_ + 604800))
.WillOnce(Return(license_start_time_ + 604810));
License_Policy* policy = license_.mutable_policy();
policy->set_rental_duration_seconds(0);
policy->set_playback_duration_seconds(0);
policy->set_license_duration_seconds(0);
policy->set_renewal_delay_seconds(604900);
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
EXPECT_TRUE(policy_engine_->can_decrypt());
}
// TODO(edwinwong, rfrias): renewal url test needed
TEST_F(PolicyEngineTest, PlaybackFailed_CanRenewFalse) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(5)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + license_duration_ -
playback_duration_ + 1))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10))
.WillOnce(Return(license_start_time_ + license_duration_ + 10));
License_Policy* policy = license_.mutable_policy();
policy->set_can_renew(false);
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
EXPECT_FALSE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccess) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(6)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + license_duration_ -
playback_duration_ + 1))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ - 15))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ +
license_renewal_retry_interval_ + 10));
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
EXPECT_TRUE(policy_engine_->can_decrypt());
license_.set_license_start_time(license_start_time_ +
license_renewal_delay_ + 15);
LicenseIdentification* id = license_.mutable_id();
id->set_version(2);
policy_engine_->UpdateLicense(license_);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
EXPECT_TRUE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackFailed_RenewFailedVersionNotUpdated) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(6)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + license_duration_ -
playback_duration_ + 1))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40))
.WillOnce(Return(license_start_time_ + license_duration_ + 10));
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
EXPECT_TRUE(policy_engine_->can_decrypt());
license_.set_license_start_time(license_start_time_ +
license_renewal_delay_ + 15);
policy_engine_->UpdateLicense(license_);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
EXPECT_TRUE(policy_engine_->can_decrypt());
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
EXPECT_FALSE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackOk_RepeatedRenewFailures) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(10)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + license_duration_ -
playback_duration_ + 1))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 50))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 70))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 80))
.WillOnce(Return(license_start_time_ + license_duration_ + 15));
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
EXPECT_TRUE(policy_engine_->can_decrypt());
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
EXPECT_TRUE(policy_engine_->can_decrypt());
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
EXPECT_TRUE(policy_engine_->can_decrypt());
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
EXPECT_FALSE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackOk_RenewedSuccessAfterExpiry) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(10)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + license_duration_ -
playback_duration_ + 1))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 50))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 55))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 67))
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 200));
policy_engine_->SetLicense(license_);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
EXPECT_TRUE(policy_engine_->can_decrypt());
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
EXPECT_TRUE(policy_engine_->can_decrypt());
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
license_.set_license_start_time(license_start_time_ +
license_renewal_delay_ + 55);
LicenseIdentification* id = license_.mutable_id();
id->set_version(2);
policy_engine_->UpdateLicense(license_);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
EXPECT_TRUE(policy_engine_->can_decrypt());
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
EXPECT_TRUE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, PlaybackOk_RenewedWithUsage) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(6)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 5))
.WillOnce(Return(license_start_time_ + 10))
.WillOnce(Return(license_start_time_ + 20))
.WillOnce(Return(license_start_time_ + 40))
.WillOnce(Return(license_start_time_ + 50));
License_Policy* policy = license_.mutable_policy();
policy->set_renew_with_usage(true);
policy_engine_->SetLicense(license_);
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->BeginDecryption();
EXPECT_FALSE(policy_engine_->can_decrypt());
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_TRUE(event_occurred);
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
license_.set_license_start_time(license_start_time_ + 30);
policy->set_renew_with_usage(false);
LicenseIdentification* id = license_.mutable_id();
id->set_version(2);
policy_engine_->UpdateLicense(license_);
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
EXPECT_TRUE(policy_engine_->can_decrypt());
}
TEST_F(PolicyEngineTest, QueryFailed_LicenseNotReceived) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(1)
.WillOnce(Return(license_start_time_));
CdmQueryMap query_info;
EXPECT_EQ(UNKNOWN_ERROR, policy_engine_->Query(&query_info));
}
TEST_F(PolicyEngineTest, QuerySuccess) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(2)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 100));
License_Policy* policy = license_.mutable_policy();
policy_engine_->SetLicense(license_);
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_TRUE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
int64_t remaining_time;
std::istringstream ss;
ss.str(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]);
ss >> remaining_time;
EXPECT_LT(0, remaining_time);
ss.str(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]);
ss >> remaining_time;
EXPECT_LT(0, remaining_time);
EXPECT_EQ(query_info[QUERY_KEY_RENEWAL_SERVER_URL],
policy->renewal_server_url());
}
TEST_F(PolicyEngineTest, QuerySuccess_Offline) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(4)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 5))
.WillOnce(Return(license_start_time_ + 10))
.WillOnce(Return(license_start_time_ + 100));
LicenseIdentification* id = license_.mutable_id();
id->set_type(OFFLINE);
License_Policy* policy = license_.mutable_policy();
policy->set_can_play(false);
policy->set_can_persist(false);
policy->set_can_renew(false);
policy_engine_->SetLicense(license_);
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->BeginDecryption();
EXPECT_FALSE(policy_engine_->can_decrypt());
CdmQueryMap query_info;
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_OFFLINE, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_RENEW_ALLOWED]);
int64_t remaining_time;
std::istringstream ss;
ss.str(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]);
ss >> remaining_time;
EXPECT_EQ(0, remaining_time);
ss.str(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]);
ss >> remaining_time;
EXPECT_EQ(0, remaining_time);
EXPECT_EQ(query_info[QUERY_KEY_RENEWAL_SERVER_URL],
policy->renewal_server_url());
}
TEST_F(PolicyEngineTest, QuerySuccess_DurationExpired) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.Times(4)
.WillOnce(Return(license_start_time_ + 1))
.WillOnce(Return(license_start_time_ + 5))
.WillOnce(Return(license_start_time_ + 10))
.WillOnce(Return(license_start_time_ + license_duration_ + 20));
LicenseIdentification* id = license_.mutable_id();
id->set_type(OFFLINE);
License_Policy* policy = license_.mutable_policy();
policy_engine_->SetLicense(license_);
bool event_occurred;
CdmEventType event;
policy_engine_->OnTimerEvent(event_occurred, event);
EXPECT_FALSE(event_occurred);
policy_engine_->BeginDecryption();
EXPECT_TRUE(policy_engine_->can_decrypt());
CdmQueryMap query_info;
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
EXPECT_EQ(QUERY_VALUE_OFFLINE, query_info[QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
int64_t remaining_time;
std::istringstream ss;
ss.str(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]);
ss >> remaining_time;
EXPECT_EQ(0, remaining_time);
ss.str(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]);
ss >> remaining_time;
EXPECT_EQ(0, remaining_time);
EXPECT_EQ(query_info[QUERY_KEY_RENEWAL_SERVER_URL],
policy->renewal_server_url());
}
} // wvcdm

View File

@@ -0,0 +1,18 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef CDM_BASE_PROPERTIES_CONFIGURATION_H_
#define CDM_BASE_PROPERTIES_CONFIGURATION_H_
#include "wv_cdm_constants.h"
#include "properties.h"
namespace wvcdm {
// set property values below
static CdmBooleanProperties kCdmBooleanProperties[] = {
{ .name = kPropertyKeyBeginLicenseUsageWhenReceived, .value = false }
};
} // namespace wvcdm
#endif // CDM_BASE_WV_PROPERTIES_CONFIGURATION_H_

View File

@@ -26,7 +26,7 @@ class WvContentDecryptionModule {
virtual CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id,
const CdmInitData& init_data,
const CdmLicenseType license_type,
CdmNameValueMap& app_parameters,
CdmAppParameterMap& app_parameters,
CdmKeyMessage* key_request);
// Accept license response and extract key info.
@@ -36,9 +36,12 @@ class WvContentDecryptionModule {
// Cancel session
virtual CdmResponseType CancelKeyRequest(const CdmSessionId& session_id);
// Query system information
virtual CdmResponseType QueryStatus(CdmQueryMap* key_info);
// Query license information
virtual CdmResponseType QueryKeyStatus(const CdmSessionId& session_id,
CdmNameValueMap* key_info);
CdmQueryMap* key_info);
// Provisioning related methods
virtual CdmResponseType GetProvisioningRequest(
@@ -61,7 +64,7 @@ class WvContentDecryptionModule {
size_t encrypted_size,
const std::vector<uint8_t>& iv,
size_t block_offset,
void* decrypted_buffer);
uint8_t* decrypted_buffer);
// Event listener related methods
virtual bool AttachEventListener(CdmSessionId& session_id,

View File

@@ -8,7 +8,7 @@
namespace wvcdm {
int64_t GetCurrentTime() {
int64_t Clock::GetCurrentTime() {
struct timeval tv;
tv.tv_sec = tv.tv_usec = 0;
gettimeofday(&tv, NULL);

View File

@@ -33,7 +33,7 @@ CdmResponseType WvContentDecryptionModule::GenerateKeyRequest(
const CdmSessionId& session_id,
const CdmInitData& init_data,
const CdmLicenseType license_type,
CdmNameValueMap& app_parameters,
CdmAppParameterMap& app_parameters,
CdmKeyMessage* key_request) {
CdmKeySystem key_system;
return cdm_engine_->GenerateKeyRequest(session_id, false, key_system,
@@ -56,9 +56,14 @@ CdmResponseType WvContentDecryptionModule::CancelKeyRequest(
return cdm_engine_->CancelKeyRequest(session_id, false, key_system);
}
CdmResponseType WvContentDecryptionModule::QueryStatus(
CdmQueryMap* key_info) {
return cdm_engine_->QueryStatus(key_info);
}
CdmResponseType WvContentDecryptionModule::QueryKeyStatus(
const CdmSessionId& session_id,
CdmNameValueMap* key_info) {
CdmQueryMap* key_info) {
return cdm_engine_->QueryKeyStatus(session_id, key_info);
}
@@ -91,7 +96,7 @@ CdmResponseType WvContentDecryptionModule::Decrypt(
size_t encrypted_size,
const std::vector<uint8_t>& iv,
size_t block_offset,
void* decrypted_buffer) {
uint8_t* decrypted_buffer) {
return cdm_engine_->Decrypt(session_id, is_encrypted, key_id,
encrypted_buffer, encrypted_size, iv,
block_offset, decrypted_buffer);

View File

@@ -15,6 +15,10 @@ test_name := license_unittest
test_src_dir := ../core/test
include $(LOCAL_PATH)/unit-test.mk
test_name := policy_engine_unittest
test_src_dir := ../core/test
include $(LOCAL_PATH)/unit-test.mk
test_name := request_license_test
test_src_dir := .
include $(LOCAL_PATH)/unit-test.mk

View File

@@ -9,6 +9,7 @@
#include "log.h"
#include "string_conversions.h"
#include "url_request.h"
#include "wv_cdm_constants.h"
#include "wv_content_decryption_module.h"
namespace {
@@ -33,7 +34,7 @@ class WvCdmRequestLicenseTest : public testing::Test {
protected:
void GenerateKeyRequest(const std::string& key_system,
const std::string& init_data) {
wvcdm::CdmNameValueMap app_parameters;
wvcdm::CdmAppParameterMap app_parameters;
EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_,
init_data,
kLicenseTypeStreaming,
@@ -45,7 +46,7 @@ class WvCdmRequestLicenseTest : public testing::Test {
const std::string& init_data) {
// TODO application makes a license request, CDM will renew the license
// when appropriate.
wvcdm::CdmNameValueMap app_parameters;
wvcdm::CdmAppParameterMap app_parameters;
EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_,
init_data,
kLicenseTypeStreaming,
@@ -100,7 +101,7 @@ class WvCdmRequestLicenseTest : public testing::Test {
if (is_renewal) {
// TODO application makes a license request, CDM will renew the license
// when appropriate
wvcdm::CdmNameValueMap app_parameters;
wvcdm::CdmAppParameterMap app_parameters;
EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_,
init_data,
kLicenseTypeStreaming,
@@ -153,6 +154,50 @@ TEST_F(WvCdmRequestLicenseTest, LicenseRenewal) {
}
#endif
TEST_F(WvCdmRequestLicenseTest, QueryKeyStatus) {
decryptor_.OpenSession(g_key_system, &session_id_);
GenerateKeyRequest(g_key_system, g_key_id);
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
CdmQueryMap query_info;
EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.QueryKeyStatus(session_id_, &query_info));
EXPECT_EQ(wvcdm::QUERY_VALUE_STREAMING,
query_info[wvcdm::QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(wvcdm::QUERY_VALUE_TRUE,
query_info[wvcdm::QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(wvcdm::QUERY_VALUE_FALSE,
query_info[wvcdm::QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(wvcdm::QUERY_VALUE_TRUE,
query_info[wvcdm::QUERY_KEY_RENEW_ALLOWED]);
int64_t remaining_time;
std::istringstream ss;
ss.str(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]);
ss >> remaining_time;
EXPECT_LE(0, remaining_time);
ss.str(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]);
ss >> remaining_time;
EXPECT_LE(0, remaining_time);
EXPECT_LE(0, (int)query_info[QUERY_KEY_RENEWAL_SERVER_URL].size());
decryptor_.CloseSession(session_id_);
}
TEST_F(WvCdmRequestLicenseTest, QueryStatus) {
decryptor_.OpenSession(g_key_system, &session_id_);
GenerateKeyRequest(g_key_system, g_key_id);
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
CdmQueryMap query_info;
EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.QueryStatus(&query_info));
EXPECT_EQ(wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3,
query_info[wvcdm::QUERY_KEY_SECURITY_LEVEL]);
decryptor_.CloseSession(session_id_);
}
} // namespace wvcdm
int main(int argc, char **argv) {

View File

@@ -20,6 +20,8 @@ LOCAL_C_INCLUDES := \
bionic \
external/gtest/include \
external/stlport/stlport \
$(LOCAL_PATH)/core/test/include \
vendor/widevine/libwvdrmengine/test/gmock/include \
vendor/widevine/libwvdrmengine/cdm/core/include \
vendor/widevine/libwvdrmengine/cdm/core/test \
vendor/widevine/libwvdrmengine/cdm/include
@@ -31,18 +33,20 @@ LOCAL_ADDITIONAL_DEPENDENCIES := $(cdm_proto_gen_headers)
LOCAL_STATIC_LIBRARIES := \
libcdm \
libgmock \
libgtest \
libgtest_main \
libl3crypto \
libprotobuf-cpp-2.3.0-lite
LOCAL_WHOLE_STATIC_LIBRARIES := \
license_protocol_protos
cdm_protos
LOCAL_SHARED_LIBRARIES := \
libstlport \
libchromium_net \
libcrypto \
liboemcrypto \
libdl \
libstlport \
libutils
include $(BUILD_EXECUTABLE)