Initial import of Widevine Common Encryption DRM engine

Builds libwvmdrmengine.so, which is loaded by the new
MediaDrm APIs to support playback of Widevine/CENC
protected content.

Change-Id: I6f57dd37083dfd96c402cb9dd137c7d74edc8f1c
This commit is contained in:
Jeff Tinker
2013-03-21 17:39:02 -07:00
parent 38334efbe7
commit 1a8aa0dd05
211 changed files with 51913 additions and 144 deletions

View File

@@ -0,0 +1,463 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#include "cdm_engine.h"
#include <iostream>
#include "buffer_reader.h"
#include "cdm_session.h"
#include "log.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
#ifndef CDM_POLICY_TIMER_DURATION_SECONDS
#define CDM_POLICY_TIMER_DURATION_SECONDS 1
#endif
namespace wvcdm {
typedef std::map<CdmSessionId,CdmSession*>::iterator CdmSessionIter;
CdmEngine::~CdmEngine() {
CancelSessions();
CdmSessionMap::iterator i(sessions_.begin());
for (; i != sessions_.end(); ++i)
delete i->second;
sessions_.clear();
}
CdmResponseType CdmEngine::OpenSession(
const CdmKeySystem& key_system,
CdmSessionId* session_id) {
LOGI("CdmEngine::OpenSession");
if (!ValidateKeySystem(key_system)) {
LOGI("CdmEngine::OpenSession: invalid key_system = %s", key_system.c_str());
return KEY_ERROR;
}
if (!session_id) {
LOGE("CdmEngine::OpenSession: no session ID destination provided");
return KEY_ERROR;
}
// TODO(edwinwong, rfrias): Save key_system in session for validation checks
CdmSession* new_session = new CdmSession();
if (!new_session) {
return KEY_ERROR;
}
if (new_session->session_id().empty()) {
LOGE("CdmEngine::OpenSession: failure to generate session ID");
delete(new_session);
return UNKNOWN_ERROR;
}
CdmSessionId new_session_id = new_session->session_id();
if (!new_session->Init()) {
LOGE("CdmEngine::OpenSession: bad session init");
delete(new_session);
return UNKNOWN_ERROR;
}
sessions_[new_session_id] = new_session;
*session_id = new_session_id;
return NO_ERROR;
}
CdmResponseType CdmEngine::CloseSession(CdmSessionId& session_id) {
LOGI("CdmEngine::CloseSession");
CdmSession* cdm_session = sessions_[session_id];
if (!cdm_session) {
LOGE("CdmEngine::CloseSession: session not found = %s", session_id.c_str());
return KEY_ERROR;
}
sessions_.erase(session_id);
cdm_session->DestroySession();
delete cdm_session;
return NO_ERROR;
}
CdmResponseType CdmEngine::GenerateKeyRequest(
const CdmSessionId& session_id,
bool is_key_system_present,
const CdmKeySystem& key_system,
const CdmInitData& init_data,
const CdmLicenseType license_type,
CdmNameValueMap& app_parameters,
CdmKeyMessage* key_request) {
LOGI("CdmEngine::GenerateKeyRequest");
CdmSession* session = sessions_[session_id];
if (!session) {
LOGE("CdmEngine::GenerateKeyRequest: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
if (is_key_system_present) {
// TODO(edwinwong, rfrias): validate key_system has not changed
}
if (init_data.empty()) {
LOGE("CdmEngine::GenerateKeyRequest: no init_data provided");
return KEY_ERROR;
}
if (!key_request) {
LOGE("CdmEngine::GenerateKeyRequest: no key request destination provided");
return KEY_ERROR;
}
CdmInitData extracted_pssh;
if (!ExtractWidevinePssh(init_data, &extracted_pssh)) {
key_request->clear();
return KEY_ERROR;
}
key_request->clear();
// TODO(edwinwong, rfrias): need to pass in license type and app parameters
CdmResponseType sts = session->GenerateKeyRequest(extracted_pssh,
key_request);
if (KEY_MESSAGE != sts) {
LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, sts=%d",
(int)sts);
return sts;
}
// TODO(edwinwong, rfrias): persist init_data, license_type, app_parameters
// in session
return KEY_MESSAGE;
}
CdmResponseType CdmEngine::AddKey(
const CdmSessionId& session_id,
bool is_key_system_init_data_present,
const CdmKeySystem& key_system,
const CdmInitData& init_data,
const CdmKeyResponse& key_data) {
LOGI("CdmEngine::AddKey");
CdmSession* session = sessions_[session_id];
if (!session) {
LOGE("CdmEngine::AddKey: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate key_system has not changed
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate init_data has not changed
}
if (key_data.empty()) {
LOGE("CdmEngine::AddKey: no key_data");
return KEY_ERROR;
}
CdmResponseType sts = session->AddKey(key_data);
if (KEY_ADDED != sts) {
LOGE("CdmEngine::AddKey: keys not added, result = %d", (int)sts);
}
return sts;
}
CdmResponseType CdmEngine::CancelKeyRequest(
const CdmSessionId& session_id,
bool is_key_system_present,
const CdmKeySystem& key_system) {
LOGI("CdmEngine::CancelKeyRequest");
//TODO(gmorgan): Issue: what is semantics of canceling a key request. Should
//this call cancel all keys for the session?
// TODO(jfore): We should disable the policy timer here if there are no
// 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) {
LOGE("CdmEngine::CancelKeyRequest: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
if (is_key_system_present) {
// TODO(edwinwong, rfrias): validate key_system has not changed
}
// TODO(edwinwong, rfrias): unload keys here
return NO_ERROR;
}
CdmResponseType CdmEngine::GenerateRenewalRequest(
const CdmSessionId& session_id,
bool is_key_system_init_data_present,
const CdmKeySystem& key_system,
const CdmInitData& init_data,
CdmKeyMessage* key_request) {
LOGI("CdmEngine::GenerateRenewalRequest");
CdmSession* session = sessions_[session_id];
if (!session) {
LOGE("CdmEngine::GenerateRenewalRequest: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate key_system has not changed
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate init_data has not changed
}
if (!key_request) {
LOGE("CdmEngine::GenerateRenewalRequest: no key request destination provided");
return KEY_ERROR;
}
key_request->clear();
CdmResponseType sts = session->GenerateRenewalRequest(key_request);
if (KEY_MESSAGE != sts) {
LOGE("CdmEngine::GenerateRenewalRequest: key request generation failed, sts=%d",
(int)sts);
return sts;
}
return KEY_MESSAGE;
}
CdmResponseType CdmEngine::RenewKey(
const CdmSessionId& session_id,
bool is_key_system_init_data_present,
const CdmKeySystem& key_system,
const CdmInitData& init_data,
const CdmKeyResponse& key_data) {
LOGI("CdmEngine::RenewKey");
CdmSession* session = sessions_[session_id];
if (!session) {
LOGE("CdmEngine::RenewKey: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate key_system has not changed
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate init_data has not changed
}
if (key_data.empty()) {
LOGE("CdmEngine::RenewKey: no key_data");
return KEY_ERROR;
}
CdmResponseType sts = session->RenewKey(key_data);
if (KEY_ADDED != sts) {
LOGE("CdmEngine::RenewKey: keys not added, sts=%d", (int)sts);
return sts;
}
return KEY_ADDED;
}
CdmResponseType CdmEngine::QueryKeyStatus(
const CdmSessionId& session_id,
CdmNameValueMap* key_info) {
// TODO(edwinwong, rfrias): add implementation
return NO_ERROR;
}
CdmResponseType CdmEngine::GetProvisioningRequest(
CdmProvisioningRequest* request,
std::string* default_url) {
// TODO(edwinwong, rfrias): add implementation
return NO_ERROR;
}
CdmResponseType CdmEngine::HandleProvisioningResponse(
CdmProvisioningResponse& response) {
// TODO(edwinwong, rfrias): add implementation
return NO_ERROR;
}
CdmResponseType CdmEngine::GetSecureStops(
CdmSecureStops* secure_stops) {
// TODO(edwinwong, rfrias): add implementation
return NO_ERROR;
}
CdmResponseType CdmEngine::ReleaseSecureStops(
const CdmSecureStopReleaseMessage& message) {
// TODO(edwinwong, rfrias): add implementation
return NO_ERROR;
}
CdmResponseType CdmEngine::Decrypt(
const CdmSessionId& session_id,
bool is_encrypted,
const KeyId& key_id,
const uint8_t* encrypted_buffer,
size_t encrypted_size,
const std::vector<uint8_t>& iv,
size_t block_offset,
void* decrypted_buffer) {
CdmSession* session = sessions_[session_id];
if (!session) {
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
return NO_ERROR;
}
bool CdmEngine::IsKeyValid(const KeyId& key_id) {
for (CdmSessionIter iter = sessions_.begin();
iter != sessions_.end(); ++iter) {
if (iter->second->IsKeyValid(key_id)) {
return true;
}
}
return false;
}
bool CdmEngine::AttachEventListener(
CdmSessionId& session_id,
WvCdmEventListener* listener) {
CdmSessionIter iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
return false;
}
return iter->second->AttachEventListener(listener);
}
bool CdmEngine::DetachEventListener(
CdmSessionId& session_id,
WvCdmEventListener* listener) {
CdmSessionIter iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
return false;
}
return iter->second->DetachEventListener(listener);
}
bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) {
return (key_system.find("widevine") != std::string::npos);
}
bool CdmEngine::CancelSessions() {
// TODO(gmorgan) Implement CancelSessions()
return true;
}
// Parse a blob of multiple concatenated PSSH atoms to extract the first
// widevine pssh
// TODO(kqyang): temporary workaround - remove after b/7928472 is resolved
bool CdmEngine::ExtractWidevinePssh(
const CdmInitData& init_data, CdmInitData* output) {
BufferReader reader(
reinterpret_cast<const uint8_t*>(init_data.data()), init_data.length());
// TODO(kqyang): Extracted from an actual init_data;
// Need to find out where it comes from.
static const uint8_t kWidevineSystemId[] = {
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE,
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
};
// one PSSH blob consists of:
// 4 byte size of the PSSH atom, inclusive
// "pssh"
// 4 byte flags, value 0
// 16 byte system id
// 4 byte size of PSSH data, exclusive
while (1) {
// size of PSSH atom, used for skipping
uint32_t size;
if (!reader.Read4(&size)) return false;
// "pssh"
std::vector<uint8_t> pssh;
if (!reader.ReadVec(&pssh, 4)) return false;
if (memcmp(&pssh[0], "pssh", 4)) return false;
// flags
uint32_t flags;
if (!reader.Read4(&flags)) return false;
if (flags != 0) return false;
// system id
std::vector<uint8_t> system_id;
if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) return false;
if (memcmp(&system_id[0], kWidevineSystemId,
sizeof(kWidevineSystemId))) {
// skip the remaining contents of the atom,
// after size field, atom name, flags and system id
if (!reader.SkipBytes(
size - 4 - 4 - 4 - sizeof(kWidevineSystemId))) return false;
continue;
}
// size of PSSH box
uint32_t pssh_length;
if (!reader.Read4(&pssh_length)) return false;
output->clear();
if (!reader.ReadString(output, pssh_length)) return false;
return true;
}
// we did not find a matching record
return false;
}
void CdmEngine::EnablePolicyTimer() {
if (!policy_timer_.IsRunning())
policy_timer_.Start(this, CDM_POLICY_TIMER_DURATION_SECONDS);
}
void CdmEngine::DisablePolicyTimer() {
if (policy_timer_.IsRunning())
policy_timer_.Stop();
}
void CdmEngine::OnTimerEvent() {
for (CdmSessionIter iter = sessions_.begin();
iter != sessions_.end(); ++iter) {
iter->second->OnTimerEvent();
}
}
} // namespace wvcdm