Source release v3.0.0-0-g8d3792b-ce + third_party
Change-Id: I399e71ddfffcd436171d1c60283c63ab4658e0b1
This commit is contained in:
889
cdm/src/cdm.cpp
Normal file
889
cdm/src/cdm.cpp
Normal file
@@ -0,0 +1,889 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
#include "cdm.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h> // memcpy
|
||||
|
||||
#include <vector>
|
||||
|
||||
// core:
|
||||
#include "cdm_client_property_set.h"
|
||||
#include "cdm_engine.h"
|
||||
#include "clock.h"
|
||||
#include "crypto_session.h"
|
||||
#include "file_store.h"
|
||||
#include "license.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_event_listener.h"
|
||||
|
||||
// CE:
|
||||
#include "cdm_version.h"
|
||||
#include "override.h"
|
||||
#include "properties_ce.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
using namespace wvcdm;
|
||||
|
||||
namespace {
|
||||
|
||||
const int64_t kPolicyTimerDurationMilliseconds = 5000;
|
||||
void* const kPolicyTimerContext = NULL;
|
||||
|
||||
struct HostType {
|
||||
Cdm::IStorage* storage;
|
||||
Cdm::IClock* clock;
|
||||
Cdm::ITimer* timer;
|
||||
CdmEngine* provisioning_engine;
|
||||
bool initialized;
|
||||
|
||||
HostType()
|
||||
: storage(NULL),
|
||||
clock(NULL),
|
||||
timer(NULL),
|
||||
provisioning_engine(NULL),
|
||||
initialized(false) {}
|
||||
} host;
|
||||
|
||||
class PropertySet : public CdmClientPropertySet {
|
||||
public:
|
||||
PropertySet()
|
||||
: use_privacy_mode_(false) {}
|
||||
|
||||
virtual ~PropertySet() {}
|
||||
|
||||
virtual const std::string& security_level() const OVERRIDE {
|
||||
// Unused on CE platforms. Used by Android to switch to L3.
|
||||
return empty_string_;
|
||||
}
|
||||
|
||||
void set_use_privacy_mode(bool use) {
|
||||
use_privacy_mode_ = use;
|
||||
}
|
||||
|
||||
virtual bool use_privacy_mode() const OVERRIDE {
|
||||
return use_privacy_mode_;
|
||||
}
|
||||
|
||||
virtual const std::string& service_certificate() const OVERRIDE {
|
||||
return service_certificate_;
|
||||
}
|
||||
|
||||
virtual void set_service_certificate(const std::string& cert) OVERRIDE {
|
||||
service_certificate_ = cert;
|
||||
}
|
||||
|
||||
virtual bool is_session_sharing_enabled() const OVERRIDE {
|
||||
// Unused on CE platforms.
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual uint32_t session_sharing_id() const OVERRIDE {
|
||||
// Unused on CE platforms.
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual void set_session_sharing_id(uint32_t id) OVERRIDE {
|
||||
// Unused on CE platforms.
|
||||
return;
|
||||
}
|
||||
|
||||
virtual const std::string& app_id() const OVERRIDE {
|
||||
// Unused on CE platforms.
|
||||
return empty_string_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool use_privacy_mode_;
|
||||
std::string service_certificate_;
|
||||
|
||||
// This is empty, but g++ 4.8 will not allow app_id() to return a string
|
||||
// literal as a const reference to std::string.
|
||||
const std::string empty_string_;
|
||||
};
|
||||
|
||||
class CdmImpl : public Cdm,
|
||||
public Cdm::ITimer::IClient,
|
||||
public WvCdmEventListener {
|
||||
public:
|
||||
CdmImpl(IEventListener* listener,
|
||||
bool privacy_mode);
|
||||
|
||||
virtual ~CdmImpl();
|
||||
|
||||
// Cdm:
|
||||
virtual Status setServerCertificate(const std::string& certificate) OVERRIDE;
|
||||
|
||||
virtual Status createSession(SessionType session_type,
|
||||
std::string* session_id) OVERRIDE;
|
||||
|
||||
virtual Status generateRequest(const std::string& session_id,
|
||||
InitDataType init_data_type,
|
||||
const std::string& init_data) OVERRIDE;
|
||||
|
||||
virtual Status load(const std::string& session_id) OVERRIDE;
|
||||
|
||||
virtual Status update(const std::string& session_id,
|
||||
const std::string& response) OVERRIDE;
|
||||
|
||||
virtual Status getExpiration(const std::string& session_id,
|
||||
int64_t* expiration) OVERRIDE;
|
||||
|
||||
virtual Status getKeyStatuses(const std::string& session_id,
|
||||
KeyStatusMap* key_statuses) OVERRIDE;
|
||||
|
||||
virtual Status close(const std::string& session_id) OVERRIDE;
|
||||
|
||||
virtual Status remove(const std::string& session_id) OVERRIDE;
|
||||
|
||||
virtual Status decrypt(const InputBuffer& input,
|
||||
const OutputBuffer& output) OVERRIDE;
|
||||
|
||||
// ITimer::IClient:
|
||||
virtual void onTimerExpired(void* context) OVERRIDE;
|
||||
|
||||
// WvCdmEventListener:
|
||||
virtual void OnSessionRenewalNeeded(const CdmSessionId& session_id) OVERRIDE;
|
||||
|
||||
virtual void OnSessionKeysChange(const CdmSessionId& session_id,
|
||||
const CdmKeyStatusMap& keys_status,
|
||||
bool has_new_usable_key) OVERRIDE;
|
||||
|
||||
virtual void OnExpirationUpdate(const CdmSessionId& session_id,
|
||||
int64_t new_expiry_time_seconds) OVERRIDE;
|
||||
|
||||
private:
|
||||
IEventListener* listener_;
|
||||
bool policy_timer_enabled_;
|
||||
|
||||
CdmEngine cdm_engine_;
|
||||
PropertySet property_set_;
|
||||
|
||||
std::map<std::string, SessionType> new_session_types_;
|
||||
std::map<std::string, int64_t> session_expirations_;
|
||||
std::map<std::string, KeyStatusMap> session_key_statuses_;
|
||||
};
|
||||
|
||||
CdmImpl::CdmImpl(IEventListener* listener,
|
||||
bool privacy_mode)
|
||||
: listener_(listener),
|
||||
policy_timer_enabled_(false) {
|
||||
property_set_.set_use_privacy_mode(privacy_mode);
|
||||
}
|
||||
|
||||
CdmImpl::~CdmImpl() {}
|
||||
|
||||
Cdm::Status CdmImpl::setServerCertificate(const std::string& certificate) {
|
||||
if (!property_set_.use_privacy_mode()) {
|
||||
LOGE("Cannot set server certificate if privacy mode is disabled.");
|
||||
return kNotSupported;
|
||||
}
|
||||
|
||||
if (certificate.empty()) {
|
||||
LOGE("An empty server certificate is invalid.");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
if (CdmLicense::VerifySignedServiceCertificate(certificate) != NO_ERROR) {
|
||||
LOGE("Invalid server certificate!");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
property_set_.set_service_certificate(certificate);
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Cdm::Status CdmImpl::createSession(SessionType session_type,
|
||||
std::string* session_id) {
|
||||
if (!session_id) {
|
||||
LOGE("Missing session ID pointer.");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
// Important! The caller may pass a pre-filled string, which must be cleared
|
||||
// before being given to CdmEngine.
|
||||
session_id->clear();
|
||||
|
||||
switch (session_type) {
|
||||
case kTemporary:
|
||||
case kPersistent:
|
||||
break;
|
||||
default:
|
||||
LOGE("Unsupported session type: %d", session_type);
|
||||
return kNotSupported;
|
||||
}
|
||||
|
||||
std::string empty_origin;
|
||||
CdmResponseType result = cdm_engine_.OpenSession(
|
||||
"com.widevine.alpha", &property_set_, empty_origin, this,
|
||||
NULL, session_id);
|
||||
switch (result) {
|
||||
case NO_ERROR:
|
||||
new_session_types_[*session_id] = session_type;
|
||||
return kSuccess;
|
||||
case NEED_PROVISIONING:
|
||||
LOGE("A device certificate is needed.");
|
||||
return kNeedsDeviceCertificate;
|
||||
default:
|
||||
LOGE("Unexpected error %d", result);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
}
|
||||
|
||||
Cdm::Status CdmImpl::generateRequest(const std::string& session_id,
|
||||
InitDataType init_data_type,
|
||||
const std::string& init_data) {
|
||||
if (!cdm_engine_.IsOpenSession(session_id)) {
|
||||
LOGE("No such session: %s", session_id.c_str());
|
||||
return kSessionNotFound;
|
||||
}
|
||||
|
||||
std::map<std::string, SessionType>::iterator it =
|
||||
new_session_types_.find(session_id);
|
||||
if (it == new_session_types_.end()) {
|
||||
LOGE("Request already generated: %s", session_id.c_str());
|
||||
return kInvalidState;
|
||||
}
|
||||
|
||||
SessionType session_type = it->second;
|
||||
CdmLicenseType license_type;
|
||||
switch (session_type) {
|
||||
case kTemporary:
|
||||
license_type = kLicenseTypeStreaming;
|
||||
break;
|
||||
case kPersistent:
|
||||
license_type = kLicenseTypeOffline;
|
||||
break;
|
||||
default:
|
||||
LOGE("Unexpected session type: %d", session_type);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
std::string init_data_type_name;
|
||||
switch (init_data_type) {
|
||||
case kCenc:
|
||||
init_data_type_name = CENC_INIT_DATA_FORMAT;
|
||||
break;
|
||||
case kKeyIds:
|
||||
LOGE("Key IDs init data type is not supported.");
|
||||
return kNotSupported;
|
||||
case kWebM:
|
||||
init_data_type_name = WEBM_INIT_DATA_FORMAT;
|
||||
break;
|
||||
default:
|
||||
LOGE("Invalid init data type: %d", init_data_type);
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
InitializationData init_data_obj(init_data_type_name, init_data);
|
||||
|
||||
if (init_data_obj.IsEmpty()) {
|
||||
LOGE("Failed to parse init data.");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
CdmAppParameterMap empty_app_parameters;
|
||||
std::string key_request;
|
||||
CdmKeyRequestType key_request_type;
|
||||
std::string ignored_server_url;
|
||||
|
||||
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
|
||||
session_id, session_id, init_data_obj,
|
||||
license_type, empty_app_parameters, &key_request, &key_request_type,
|
||||
&ignored_server_url, NULL);
|
||||
|
||||
if (result != KEY_MESSAGE) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
new_session_types_.erase(it);
|
||||
assert(key_request_type == kKeyRequestTypeInitial);
|
||||
MessageType message_type = kLicenseRequest;
|
||||
if (property_set_.use_privacy_mode() &&
|
||||
property_set_.service_certificate().empty()) {
|
||||
// We can deduce that this is a server cert request, even though CdmEgine
|
||||
// cannot currently inform us of this.
|
||||
message_type = kIndividualizationRequest;
|
||||
LOGI("A server certificate request has been generated.");
|
||||
} else {
|
||||
LOGI("A license request has been generated.");
|
||||
}
|
||||
listener_->onMessage(session_id, message_type, key_request);
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Cdm::Status CdmImpl::load(const std::string& session_id) {
|
||||
if (session_id.empty()) {
|
||||
LOGE("Empty session ID.");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
if (cdm_engine_.IsOpenSession(session_id)) {
|
||||
LOGE("Session ID already loaded.");
|
||||
return kQuotaExceeded;
|
||||
}
|
||||
|
||||
std::string empty_origin;
|
||||
std::string ignored_session_id_output;
|
||||
CdmResponseType result = cdm_engine_.OpenSession(
|
||||
"com.widevine.alpha", &property_set_, empty_origin, this,
|
||||
&session_id, &ignored_session_id_output);
|
||||
switch (result) {
|
||||
case NO_ERROR:
|
||||
break;
|
||||
case NEED_PROVISIONING:
|
||||
LOGE("A device certificate is needed.");
|
||||
return kNeedsDeviceCertificate;
|
||||
default:
|
||||
LOGE("Unexpected error %d", result);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
result = cdm_engine_.RestoreKey(session_id, session_id);
|
||||
if (result == GET_LICENSE_ERROR) {
|
||||
LOGE("Unable to load license: %s", session_id.c_str());
|
||||
return kSessionNotFound;
|
||||
} else if (result == GET_RELEASED_LICENSE_ERROR) {
|
||||
// This was partially removed already.
|
||||
// The EME spec states that we should send a release message right away.
|
||||
InitializationData empty_initialization_data;
|
||||
CdmAppParameterMap empty_app_parameters;
|
||||
CdmKeyMessage key_request;
|
||||
std::string ignored_server_url;
|
||||
|
||||
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
|
||||
session_id, session_id, empty_initialization_data,
|
||||
kLicenseTypeRelease, empty_app_parameters, &key_request, NULL,
|
||||
&ignored_server_url, NULL);
|
||||
if (result != KEY_MESSAGE) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
cdm_engine_.CloseSession(session_id);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
LOGI("A license release has been generated.");
|
||||
MessageType message_type = kLicenseRelease;
|
||||
listener_->onMessage(session_id, message_type, key_request);
|
||||
} else if (result != KEY_ADDED) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Cdm::Status CdmImpl::update(const std::string& session_id,
|
||||
const std::string& response) {
|
||||
if (!cdm_engine_.IsOpenSession(session_id)) {
|
||||
LOGE("No such session: %s", session_id.c_str());
|
||||
return kSessionNotFound;
|
||||
}
|
||||
|
||||
if (new_session_types_.find(session_id) != new_session_types_.end()) {
|
||||
LOGE("Request not yet generated: %s", session_id.c_str());
|
||||
return kInvalidState;
|
||||
}
|
||||
|
||||
if (response.empty()) {
|
||||
LOGE("Empty response.");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
bool predicted_to_be_server_cert_response =
|
||||
property_set_.use_privacy_mode() &&
|
||||
property_set_.service_certificate().empty();
|
||||
|
||||
// NOTE: If the CdmSession object recognizes that this is not the first
|
||||
// AddKey(), it will internally delegate to RenewKey().
|
||||
CdmKeySetId key_set_id = session_id;
|
||||
CdmResponseType result =
|
||||
cdm_engine_.AddKey(session_id, response, &key_set_id);
|
||||
|
||||
if (result == NEED_KEY) {
|
||||
// We just provisioned a server certificate.
|
||||
assert(predicted_to_be_server_cert_response);
|
||||
|
||||
// The cert is now available to all sessions in this CDM instance.
|
||||
// This is consistent with the behavior of the Chrome CDM.
|
||||
assert(!property_set_.service_certificate().empty());
|
||||
|
||||
// The underlying session in CdmEngine has stored a copy of the original
|
||||
// init data, so we can use an empty one this time.
|
||||
InitializationData empty_init_data;
|
||||
CdmAppParameterMap empty_app_parameters;
|
||||
std::string key_request;
|
||||
CdmKeyRequestType key_request_type;
|
||||
std::string ignored_server_url;
|
||||
|
||||
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
|
||||
session_id, session_id, empty_init_data, kLicenseTypeDeferred,
|
||||
empty_app_parameters, &key_request, &key_request_type,
|
||||
&ignored_server_url, NULL);
|
||||
|
||||
if (result != KEY_MESSAGE) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
LOGI("A deferred license request has been generated.");
|
||||
assert(key_request_type == kKeyRequestTypeInitial);
|
||||
MessageType message_type = kLicenseRequest;
|
||||
listener_->onMessage(session_id, message_type, key_request);
|
||||
return kSuccess;
|
||||
}
|
||||
assert(!predicted_to_be_server_cert_response);
|
||||
|
||||
if (result != KEY_ADDED) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
if (!policy_timer_enabled_) {
|
||||
policy_timer_enabled_ = true;
|
||||
host.timer->setTimeout(kPolicyTimerDurationMilliseconds,
|
||||
this,
|
||||
kPolicyTimerContext);
|
||||
}
|
||||
|
||||
if (cdm_engine_.IsReleaseSession(session_id)) {
|
||||
cdm_engine_.CloseSession(session_id);
|
||||
listener_->onRemoveComplete(session_id);
|
||||
}
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Cdm::Status CdmImpl::getExpiration(const std::string& session_id,
|
||||
int64_t* expiration) {
|
||||
if (!cdm_engine_.IsOpenSession(session_id)) {
|
||||
LOGE("No such session: %s", session_id.c_str());
|
||||
return kSessionNotFound;
|
||||
}
|
||||
|
||||
*expiration = session_expirations_[session_id];
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Cdm::Status CdmImpl::getKeyStatuses(const std::string& session_id,
|
||||
KeyStatusMap* key_statuses) {
|
||||
if (!cdm_engine_.IsOpenSession(session_id)) {
|
||||
LOGE("No such session: %s", session_id.c_str());
|
||||
return kSessionNotFound;
|
||||
}
|
||||
|
||||
*key_statuses = session_key_statuses_[session_id];
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Cdm::Status CdmImpl::close(const std::string& session_id) {
|
||||
if (!cdm_engine_.IsOpenSession(session_id)) {
|
||||
LOGE("No such session: %s", session_id.c_str());
|
||||
return kSessionNotFound;
|
||||
}
|
||||
|
||||
CdmResponseType result = cdm_engine_.CloseSession(session_id);
|
||||
if (result != NO_ERROR) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Cdm::Status CdmImpl::remove(const std::string& session_id) {
|
||||
if (!cdm_engine_.IsOpenSession(session_id)) {
|
||||
LOGE("No such session: %s", session_id.c_str());
|
||||
return kSessionNotFound;
|
||||
}
|
||||
|
||||
if (new_session_types_.find(session_id) != new_session_types_.end()) {
|
||||
LOGE("Request not yet generated: %s", session_id.c_str());
|
||||
return kInvalidState;
|
||||
}
|
||||
|
||||
if (!cdm_engine_.IsOfflineSession(session_id)) {
|
||||
LOGE("Not a persistent session: %s", session_id.c_str());
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
InitializationData empty_initialization_data;
|
||||
CdmAppParameterMap empty_app_parameters;
|
||||
CdmKeyMessage key_request;
|
||||
std::string ignored_server_url;
|
||||
|
||||
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
|
||||
session_id, session_id, empty_initialization_data,
|
||||
kLicenseTypeRelease, empty_app_parameters, &key_request, NULL,
|
||||
&ignored_server_url, NULL);
|
||||
if (result != KEY_MESSAGE) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
cdm_engine_.CloseSession(session_id);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
LOGI("A license release has been generated.");
|
||||
MessageType message_type = kLicenseRelease;
|
||||
listener_->onMessage(session_id, message_type, key_request);
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Cdm::Status CdmImpl::decrypt(const InputBuffer& input,
|
||||
const OutputBuffer& output) {
|
||||
if (input.is_encrypted && input.iv_length != 16) {
|
||||
LOGE("The IV must be 16 bytes long.");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
if (PropertiesCE::GetSecureOutputType() == kNoSecureOutput &&
|
||||
output.is_secure) {
|
||||
LOGE("The CDM is configured without secure output support.");
|
||||
return kNotSupported;
|
||||
}
|
||||
|
||||
std::string key_id(reinterpret_cast<const char *>(input.key_id),
|
||||
input.key_id_length);
|
||||
std::vector<uint8_t> iv(input.iv, input.iv + input.iv_length);
|
||||
|
||||
CdmDecryptionParameters parameters;
|
||||
parameters.is_encrypted = input.is_encrypted;
|
||||
parameters.is_secure = output.is_secure;
|
||||
parameters.key_id = &key_id;
|
||||
parameters.encrypt_buffer = input.data;
|
||||
parameters.encrypt_length = input.data_length;
|
||||
parameters.iv = &iv;
|
||||
parameters.block_offset = input.block_offset;
|
||||
parameters.decrypt_buffer = output.data;
|
||||
parameters.decrypt_buffer_length = output.data_length;
|
||||
parameters.decrypt_buffer_offset = output.data_offset;
|
||||
parameters.subsample_flags =
|
||||
(input.first_subsample ? OEMCrypto_FirstSubsample : 0) |
|
||||
(input.last_subsample ? OEMCrypto_LastSubsample : 0);
|
||||
parameters.is_video = input.is_video;
|
||||
|
||||
CdmSessionId empty_session_id;
|
||||
CdmResponseType result = cdm_engine_.Decrypt(empty_session_id, parameters);
|
||||
if (result == NEED_KEY || result == SESSION_NOT_FOUND_FOR_DECRYPT) {
|
||||
LOGE("Key not available.");
|
||||
return kNoKey;
|
||||
}
|
||||
if (result == NO_ERROR) {
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
LOGE("Decrypt error: %d", result);
|
||||
return kDecryptError;
|
||||
}
|
||||
|
||||
void CdmImpl::onTimerExpired(void* context) {
|
||||
if (context == kPolicyTimerContext) {
|
||||
if (policy_timer_enabled_) {
|
||||
cdm_engine_.OnTimerEvent();
|
||||
host.timer->setTimeout(kPolicyTimerDurationMilliseconds,
|
||||
this,
|
||||
kPolicyTimerContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) {
|
||||
CdmKeyMessage message;
|
||||
std::string server_url;
|
||||
CdmResponseType result =
|
||||
cdm_engine_.GenerateRenewalRequest(session_id, &message, &server_url);
|
||||
if (result != KEY_MESSAGE) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGI("A license renewal has been generated.");
|
||||
MessageType message_type = kLicenseRenewal;
|
||||
listener_->onMessage(session_id, message_type, message);
|
||||
}
|
||||
|
||||
void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
|
||||
const CdmKeyStatusMap& keys_status,
|
||||
bool has_new_usable_key) {
|
||||
KeyStatusMap& map = session_key_statuses_[session_id];
|
||||
|
||||
CdmKeyStatusMap::const_iterator it;
|
||||
for (it = keys_status.begin(); it != keys_status.end(); ++it) {
|
||||
KeyStatus status;
|
||||
switch (it->second) {
|
||||
case kKeyStatusUsable:
|
||||
map[it->first] = kUsable;
|
||||
break;
|
||||
case kKeyStatusExpired:
|
||||
map[it->first] = kExpired;
|
||||
break;
|
||||
case kKeyStatusOutputNotAllowed:
|
||||
map[it->first] = kOutputNotAllowed;
|
||||
break;
|
||||
case kKeyStatusPending:
|
||||
map[it->first] = kStatusPending;
|
||||
break;
|
||||
case kKeyStatusInternalError:
|
||||
map[it->first] = kInternalError;
|
||||
break;
|
||||
default:
|
||||
LOGE("Unrecognized key status: %d", it->second);
|
||||
map[it->first] = kInternalError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
listener_->onKeyStatusesChange(session_id);
|
||||
}
|
||||
|
||||
void CdmImpl::OnExpirationUpdate(const CdmSessionId& session_id,
|
||||
int64_t new_expiry_time_seconds) {
|
||||
session_expirations_[session_id] = new_expiry_time_seconds * 1000;
|
||||
}
|
||||
|
||||
|
||||
bool VerifyL1() {
|
||||
CryptoSession cs;
|
||||
return cs.GetSecurityLevel() == kSecurityLevelL1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// static
|
||||
Cdm::Status Cdm::initialize(
|
||||
SecureOutputType secure_output_type,
|
||||
const ClientInfo& client_info,
|
||||
IStorage* storage,
|
||||
IClock* clock,
|
||||
ITimer* timer,
|
||||
DeviceCertificateRequest* device_certificate_request,
|
||||
LogLevel verbosity) {
|
||||
// If you want to direct-render on L3, CryptoSession will pass that request
|
||||
// along to OEMCrypto. But if you want to use an opaque handle on L3,
|
||||
// CryptoSession will silently ignore you and tell OEMCrypto to treat the
|
||||
// address as a clear buffer. :-(
|
||||
//
|
||||
// So this logic mirrors that in CryptoSession. Effectively, we are
|
||||
// detecting at init time the conditions that would prevent CryptoSession (in
|
||||
// its current form) from passing the desired buffer type constant to
|
||||
// OEMCrypto.
|
||||
// TODO: Discuss changes to CryptoSession.
|
||||
switch (secure_output_type) {
|
||||
case kOpaqueHandle:
|
||||
// This output type requires an OEMCrypto that reports L1.
|
||||
// This requirement comes from CryptoSession::SetDestinationBufferType().
|
||||
if (!VerifyL1()) {
|
||||
LOGE("Not an L1 implementation, kOpaqueHandle cannot be used!");
|
||||
return kNotSupported;
|
||||
}
|
||||
break;
|
||||
case kDirectRender:
|
||||
case kNoSecureOutput:
|
||||
break;
|
||||
default:
|
||||
LOGE("Invalid output type!");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
if (client_info.product_name.empty() ||
|
||||
client_info.company_name.empty() ||
|
||||
client_info.model_name.empty()) {
|
||||
LOGE("Client info requires product_name, company_name, model_name!");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
if (!storage || !clock || !timer) {
|
||||
LOGE("All interfaces are required!");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
if (!device_certificate_request) {
|
||||
LOGE("Device certificate request pointer is required!");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
// Our enum values match those in core/include/log.h
|
||||
g_cutoff = static_cast<LogPriority>(verbosity);
|
||||
|
||||
PropertiesCE::SetSecureOutputType(secure_output_type);
|
||||
PropertiesCE::SetClientInfo(client_info);
|
||||
Properties::Init();
|
||||
host.storage = storage;
|
||||
host.clock = clock;
|
||||
host.timer = timer;
|
||||
|
||||
device_certificate_request->needed = false;
|
||||
|
||||
if (!host.provisioning_engine) {
|
||||
host.provisioning_engine = new CdmEngine();
|
||||
}
|
||||
bool has_cert = host.provisioning_engine->IsProvisioned(
|
||||
kSecurityLevelL1, "" /* origin */);
|
||||
|
||||
if (!has_cert) {
|
||||
device_certificate_request->needed = true;
|
||||
std::string empty_authority;
|
||||
std::string empty_origin;
|
||||
std::string base_url;
|
||||
std::string signed_request;
|
||||
CdmResponseType result = host.provisioning_engine->GetProvisioningRequest(
|
||||
kCertificateWidevine, empty_authority, empty_origin,
|
||||
&signed_request, &base_url);
|
||||
if (result != NO_ERROR) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
device_certificate_request->url = base_url;
|
||||
device_certificate_request->url.append("&signedRequest=");
|
||||
device_certificate_request->url.append(signed_request);
|
||||
}
|
||||
|
||||
host.initialized = true;
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
// static
|
||||
const char* Cdm::version() {
|
||||
return CDM_VERSION;
|
||||
}
|
||||
|
||||
// static
|
||||
Cdm* Cdm::create(IEventListener* listener,
|
||||
bool privacy_mode) {
|
||||
if (!host.initialized) {
|
||||
LOGE("Not initialized!");
|
||||
return NULL;
|
||||
}
|
||||
if (!listener) {
|
||||
LOGE("No listener!");
|
||||
return NULL;
|
||||
}
|
||||
return new CdmImpl(listener, privacy_mode);
|
||||
}
|
||||
|
||||
Cdm::Status Cdm::DeviceCertificateRequest::acceptReply(
|
||||
const std::string& reply) {
|
||||
if (!host.provisioning_engine) {
|
||||
LOGE("Provisioning reply received while not in a provisioning state!");
|
||||
return kInvalidAccess;
|
||||
}
|
||||
|
||||
std::string empty_origin;
|
||||
std::string ignored_cert;
|
||||
std::string ignored_wrapped_key;
|
||||
|
||||
CdmResponseType result =
|
||||
host.provisioning_engine->HandleProvisioningResponse(
|
||||
empty_origin, reply, &ignored_cert, &ignored_wrapped_key);
|
||||
if (result != NO_ERROR) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
// Missing symbols from core:
|
||||
namespace wvcdm {
|
||||
|
||||
using namespace widevine;
|
||||
|
||||
int64_t Clock::GetCurrentTime() {
|
||||
return host.clock->now() / 1000;
|
||||
}
|
||||
|
||||
struct File::Impl {
|
||||
std::string name;
|
||||
bool read_only;
|
||||
bool truncate;
|
||||
};
|
||||
|
||||
File::File() : impl_(NULL) {}
|
||||
|
||||
File::~File() {
|
||||
Close();
|
||||
}
|
||||
|
||||
bool File::Open(const std::string& file_path, int flags) {
|
||||
if (!(flags & kCreate) && !host.storage->exists(file_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl_ = new Impl;
|
||||
impl_->name = file_path;
|
||||
impl_->read_only = (flags & kReadOnly);
|
||||
impl_->truncate = (flags & kTruncate);
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t File::Read(char* buffer, size_t bytes) {
|
||||
if (!impl_) {
|
||||
return -1;
|
||||
}
|
||||
std::string data;
|
||||
if (!host.storage->read(impl_->name, &data)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t to_copy = std::min(bytes, data.size());
|
||||
memcpy(buffer, data.data(), to_copy);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
ssize_t File::Write(const char* buffer, size_t bytes) {
|
||||
if (!impl_) {
|
||||
return -1;
|
||||
}
|
||||
if (!impl_->truncate) {
|
||||
LOGE("Internal error: files cannot be appended to.");
|
||||
return -1;
|
||||
}
|
||||
std::string data(buffer, bytes);
|
||||
if (!host.storage->write(impl_->name, data)) {
|
||||
return -1;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void File::Close() {
|
||||
if (impl_) {
|
||||
delete impl_;
|
||||
}
|
||||
impl_ = NULL;
|
||||
}
|
||||
|
||||
bool File::Exists(const std::string& file_path) {
|
||||
return host.storage->exists(file_path);
|
||||
}
|
||||
|
||||
bool File::Remove(const std::string& file_path) {
|
||||
return host.storage->remove(file_path);
|
||||
}
|
||||
|
||||
bool File::Copy(const std::string& old_path, const std::string& new_path) {
|
||||
std::string data;
|
||||
bool read_ok = host.storage->read(old_path, &data);
|
||||
if (!read_ok) return false;
|
||||
return host.storage->write(new_path, data);
|
||||
}
|
||||
|
||||
bool File::List(const std::string& path, std::vector<std::string>* files) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool File::CreateDirectory(const std::string dir_path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::IsDirectory(const std::string& dir_path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool File::IsRegularFile(const std::string& file_path) {
|
||||
return host.storage->exists(file_path);
|
||||
}
|
||||
|
||||
ssize_t File::FileSize(const std::string& file_path) {
|
||||
return host.storage->size(file_path);
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
Reference in New Issue
Block a user