blob: 0e9fc16b98351ac8d8bdf3d23456794ed8f89e86 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/cdm/cdm_adapter.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/stl_util.h"
#include "base/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "media/base/cdm_context.h"
#include "media/base/cdm_initialized_promise.h"
#include "media/base/cdm_key_information.h"
#include "media/base/limits.h"
#include "media/cdm/cdm_wrapper.h"
namespace media {
namespace {
cdm::SessionType MediaSessionTypeToCdmSessionType(
MediaKeys::SessionType session_type) {
switch (session_type) {
case MediaKeys::TEMPORARY_SESSION:
return cdm::kTemporary;
case MediaKeys::PERSISTENT_LICENSE_SESSION:
return cdm::kPersistentLicense;
case MediaKeys::PERSISTENT_RELEASE_MESSAGE_SESSION:
return cdm::kPersistentKeyRelease;
}
NOTREACHED();
return cdm::kTemporary;
}
cdm::InitDataType MediaInitDataTypeToCdmInitDataType(
EmeInitDataType init_data_type) {
switch (init_data_type) {
case EmeInitDataType::CENC:
return cdm::kCenc;
case EmeInitDataType::KEYIDS:
return cdm::kKeyIds;
case EmeInitDataType::WEBM:
return cdm::kWebM;
case EmeInitDataType::UNKNOWN:
break;
}
NOTREACHED();
return cdm::kKeyIds;
}
MediaKeys::Exception CdmErrorTypeToMediaExceptionType(cdm::Error error) {
switch (error) {
case cdm::kNotSupportedError:
return MediaKeys::NOT_SUPPORTED_ERROR;
case cdm::kInvalidStateError:
return MediaKeys::INVALID_STATE_ERROR;
case cdm::kInvalidAccessError:
return MediaKeys::INVALID_ACCESS_ERROR;
case cdm::kQuotaExceededError:
return MediaKeys::QUOTA_EXCEEDED_ERROR;
case cdm::kUnknownError:
return MediaKeys::UNKNOWN_ERROR;
case cdm::kClientError:
return MediaKeys::CLIENT_ERROR;
case cdm::kOutputError:
return MediaKeys::OUTPUT_ERROR;
}
NOTREACHED();
return MediaKeys::UNKNOWN_ERROR;
}
MediaKeys::MessageType CdmMessageTypeToMediaMessageType(
cdm::MessageType message_type) {
switch (message_type) {
case cdm::kLicenseRequest:
return MediaKeys::LICENSE_REQUEST;
case cdm::kLicenseRenewal:
return MediaKeys::LICENSE_RENEWAL;
case cdm::kLicenseRelease:
return MediaKeys::LICENSE_RELEASE;
}
NOTREACHED();
return MediaKeys::LICENSE_REQUEST;
}
CdmKeyInformation::KeyStatus CdmKeyStatusToCdmKeyInformationKeyStatus(
cdm::KeyStatus status) {
switch (status) {
case cdm::kUsable:
return CdmKeyInformation::USABLE;
case cdm::kInternalError:
return CdmKeyInformation::INTERNAL_ERROR;
case cdm::kExpired:
return CdmKeyInformation::EXPIRED;
case cdm::kOutputRestricted:
return CdmKeyInformation::OUTPUT_RESTRICTED;
case cdm::kOutputDownscaled:
return CdmKeyInformation::OUTPUT_DOWNSCALED;
case cdm::kStatusPending:
return CdmKeyInformation::KEY_STATUS_PENDING;
case cdm::kReleased:
return CdmKeyInformation::RELEASED;
}
NOTREACHED();
return CdmKeyInformation::INTERNAL_ERROR;
}
static void* GetCdmHost(int host_interface_version, void* user_data) {
if (!host_interface_version || !user_data)
return nullptr;
static_assert(
cdm::ContentDecryptionModule::Host::kVersion == cdm::Host_8::kVersion,
"update the code below");
// Ensure IsSupportedCdmHostVersion matches implementation of this function.
// Always update this DCHECK when updating this function.
// If this check fails, update this function and DCHECK or update
// IsSupportedCdmHostVersion.
DCHECK(
// Future version is not supported.
!IsSupportedCdmHostVersion(cdm::Host_8::kVersion + 1) &&
// Current version is supported.
IsSupportedCdmHostVersion(cdm::Host_8::kVersion) &&
// Include all previous supported versions (if any) here.
IsSupportedCdmHostVersion(cdm::Host_7::kVersion) &&
// One older than the oldest supported version is not supported.
!IsSupportedCdmHostVersion(cdm::Host_7::kVersion - 1));
DCHECK(IsSupportedCdmHostVersion(host_interface_version));
CdmAdapter* cdm_adapter = static_cast<CdmAdapter*>(user_data);
DVLOG(1) << "Create CDM Host with version " << host_interface_version;
switch (host_interface_version) {
case cdm::Host_8::kVersion:
return static_cast<cdm::Host_8*>(cdm_adapter);
case cdm::Host_7::kVersion:
return static_cast<cdm::Host_7*>(cdm_adapter);
default:
NOTREACHED();
return nullptr;
}
}
} // namespace
// static
void CdmAdapter::Create(
const std::string& key_system,
const base::FilePath& cdm_path,
const CdmConfig& cdm_config,
const SessionMessageCB& session_message_cb,
const SessionClosedCB& session_closed_cb,
const LegacySessionErrorCB& legacy_session_error_cb,
const SessionKeysChangeCB& session_keys_change_cb,
const SessionExpirationUpdateCB& session_expiration_update_cb,
const CdmCreatedCB& cdm_created_cb) {
DCHECK(!key_system.empty());
DCHECK(!session_message_cb.is_null());
DCHECK(!session_closed_cb.is_null());
DCHECK(!legacy_session_error_cb.is_null());
DCHECK(!session_keys_change_cb.is_null());
DCHECK(!session_expiration_update_cb.is_null());
scoped_refptr<CdmAdapter> cdm =
new CdmAdapter(key_system, cdm_config, session_message_cb,
session_closed_cb, legacy_session_error_cb,
session_keys_change_cb, session_expiration_update_cb);
// |cdm| ownership passed to the promise.
scoped_ptr<CdmInitializedPromise> cdm_created_promise(
new CdmInitializedPromise(cdm_created_cb, cdm));
cdm->Initialize(cdm_path, cdm_created_promise.Pass());
}
CdmAdapter::CdmAdapter(
const std::string& key_system,
const CdmConfig& cdm_config,
const SessionMessageCB& session_message_cb,
const SessionClosedCB& session_closed_cb,
const LegacySessionErrorCB& legacy_session_error_cb,
const SessionKeysChangeCB& session_keys_change_cb,
const SessionExpirationUpdateCB& session_expiration_update_cb)
: key_system_(key_system),
cdm_config_(cdm_config),
session_message_cb_(session_message_cb),
session_closed_cb_(session_closed_cb),
legacy_session_error_cb_(legacy_session_error_cb),
session_keys_change_cb_(session_keys_change_cb),
session_expiration_update_cb_(session_expiration_update_cb),
task_runner_(base::ThreadTaskRunnerHandle::Get()),
weak_factory_(this) {
DCHECK(!key_system_.empty());
DCHECK(!session_message_cb_.is_null());
DCHECK(!session_closed_cb_.is_null());
DCHECK(!legacy_session_error_cb_.is_null());
DCHECK(!session_keys_change_cb_.is_null());
DCHECK(!session_expiration_update_cb_.is_null());
}
CdmAdapter::~CdmAdapter() {}
CdmWrapper* CdmAdapter::CreateCdmInstance(const std::string& key_system,
const base::FilePath& cdm_path) {
DCHECK(task_runner_->BelongsToCurrentThread());
// TODO(jrummell): We need to call INITIALIZE_CDM_MODULE() and
// DeinitializeCdmModule(). However, that should only be done once for the
// library.
base::NativeLibraryLoadError error;
library_.Reset(base::LoadNativeLibrary(cdm_path, &error));
if (!library_.is_valid()) {
DVLOG(1) << "CDM instance for " + key_system + " could not be created. "
<< error.ToString();
return nullptr;
}
CreateCdmFunc create_cdm_func = reinterpret_cast<CreateCdmFunc>(
library_.GetFunctionPointer("CreateCdmInstance"));
if (!create_cdm_func) {
DVLOG(1) << "No CreateCdmInstance() in library for " + key_system;
return nullptr;
}
CdmWrapper* cdm = CdmWrapper::Create(create_cdm_func, key_system.data(),
key_system.size(), GetCdmHost, this);
DVLOG(1) << "CDM instance for " + key_system + (cdm ? "" : " could not be") +
" created.";
return cdm;
}
void CdmAdapter::Initialize(const base::FilePath& cdm_path,
scoped_ptr<media::SimpleCdmPromise> promise) {
cdm_.reset(CreateCdmInstance(key_system_, cdm_path));
if (!cdm_) {
promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0,
"Unable to create CDM.");
return;
}
cdm_->Initialize(cdm_config_.allow_distinctive_identifier,
cdm_config_.allow_persistent_state);
promise->resolve();
}
void CdmAdapter::SetServerCertificate(const std::vector<uint8_t>& certificate,
scoped_ptr<SimpleCdmPromise> promise) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (certificate.size() < limits::kMinCertificateLength ||
certificate.size() > limits::kMaxCertificateLength) {
promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0,
"Incorrect certificate.");
return;
}
uint32_t promise_id = cdm_promise_adapter_.SavePromise(promise.Pass());
cdm_->SetServerCertificate(promise_id, vector_as_array(&certificate),
certificate.size());
}
void CdmAdapter::CreateSessionAndGenerateRequest(
SessionType session_type,
EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data,
scoped_ptr<NewSessionCdmPromise> promise) {
DCHECK(task_runner_->BelongsToCurrentThread());
uint32_t promise_id = cdm_promise_adapter_.SavePromise(promise.Pass());
cdm_->CreateSessionAndGenerateRequest(
promise_id, MediaSessionTypeToCdmSessionType(session_type),
MediaInitDataTypeToCdmInitDataType(init_data_type),
vector_as_array(&init_data), init_data.size());
}
void CdmAdapter::LoadSession(SessionType session_type,
const std::string& session_id,
scoped_ptr<NewSessionCdmPromise> promise) {
DCHECK(task_runner_->BelongsToCurrentThread());
uint32_t promise_id = cdm_promise_adapter_.SavePromise(promise.Pass());
cdm_->LoadSession(promise_id, MediaSessionTypeToCdmSessionType(session_type),
session_id.data(), session_id.size());
}
void CdmAdapter::UpdateSession(const std::string& session_id,
const std::vector<uint8_t>& response,
scoped_ptr<SimpleCdmPromise> promise) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!session_id.empty());
DCHECK(!response.empty());
uint32_t promise_id = cdm_promise_adapter_.SavePromise(promise.Pass());
cdm_->UpdateSession(promise_id, session_id.data(), session_id.size(),
vector_as_array(&response), response.size());
}
void CdmAdapter::CloseSession(const std::string& session_id,
scoped_ptr<SimpleCdmPromise> promise) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!session_id.empty());
uint32_t promise_id = cdm_promise_adapter_.SavePromise(promise.Pass());
cdm_->CloseSession(promise_id, session_id.data(), session_id.size());
}
void CdmAdapter::RemoveSession(const std::string& session_id,
scoped_ptr<SimpleCdmPromise> promise) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!session_id.empty());
uint32_t promise_id = cdm_promise_adapter_.SavePromise(promise.Pass());
cdm_->RemoveSession(promise_id, session_id.data(), session_id.size());
}
CdmContext* CdmAdapter::GetCdmContext() {
DCHECK(task_runner_->BelongsToCurrentThread());
// TODO(jrummell): Support the Decryptor interface.
NOTIMPLEMENTED();
return nullptr;
}
cdm::Buffer* CdmAdapter::Allocate(uint32_t capacity) {
DCHECK(task_runner_->BelongsToCurrentThread());
// TODO(jrummell): Figure out how memory should be passed around when
// decrypting.
NOTIMPLEMENTED();
return nullptr;
}
void CdmAdapter::SetTimer(int64_t delay_ms, void* context) {
DCHECK(task_runner_->BelongsToCurrentThread());
task_runner_->PostDelayedTask(FROM_HERE,
base::Bind(&CdmAdapter::TimerExpired,
weak_factory_.GetWeakPtr(), context),
base::TimeDelta::FromMilliseconds(delay_ms));
}
void CdmAdapter::TimerExpired(void* context) {
DCHECK(task_runner_->BelongsToCurrentThread());
cdm_->TimerExpired(context);
}
cdm::Time CdmAdapter::GetCurrentWallTime() {
DCHECK(task_runner_->BelongsToCurrentThread());
return base::Time::Now().ToDoubleT();
}
void CdmAdapter::OnResolvePromise(uint32_t promise_id) {
DCHECK(task_runner_->BelongsToCurrentThread());
cdm_promise_adapter_.ResolvePromise(promise_id);
}
void CdmAdapter::OnResolveNewSessionPromise(uint32_t promise_id,
const char* session_id,
uint32_t session_id_size) {
DCHECK(task_runner_->BelongsToCurrentThread());
cdm_promise_adapter_.ResolvePromise(promise_id,
std::string(session_id, session_id_size));
}
void CdmAdapter::OnRejectPromise(uint32_t promise_id,
cdm::Error error,
uint32_t system_code,
const char* error_message,
uint32_t error_message_size) {
DCHECK(task_runner_->BelongsToCurrentThread());
cdm_promise_adapter_.RejectPromise(
promise_id, CdmErrorTypeToMediaExceptionType(error), system_code,
std::string(error_message, error_message_size));
}
void CdmAdapter::OnSessionMessage(const char* session_id,
uint32_t session_id_size,
cdm::MessageType message_type,
const char* message,
uint32_t message_size,
const char* legacy_destination_url,
uint32_t legacy_destination_url_size) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(legacy_destination_url_size == 0 ||
message_type != cdm::MessageType::kLicenseRequest);
GURL verified_gurl =
GURL(std::string(legacy_destination_url, legacy_destination_url_size));
if (!verified_gurl.is_valid()) {
DLOG(WARNING) << "SessionMessage legacy_destination_url is invalid : "
<< verified_gurl.possibly_invalid_spec();
verified_gurl = GURL::EmptyGURL(); // Replace invalid destination_url.
}
const uint8_t* message_ptr = reinterpret_cast<const uint8*>(message);
session_message_cb_.Run(
std::string(session_id, session_id_size),
CdmMessageTypeToMediaMessageType(message_type),
std::vector<uint8_t>(message_ptr, message_ptr + message_size),
verified_gurl);
}
void CdmAdapter::OnSessionKeysChange(const char* session_id,
uint32_t session_id_size,
bool has_additional_usable_key,
const cdm::KeyInformation* keys_info,
uint32_t keys_info_count) {
DCHECK(task_runner_->BelongsToCurrentThread());
CdmKeysInfo keys;
keys.reserve(keys_info_count);
for (uint32_t i = 0; i < keys_info_count; ++i) {
const auto& info = keys_info[i];
keys.push_back(new CdmKeyInformation(
info.key_id, info.key_id_size,
CdmKeyStatusToCdmKeyInformationKeyStatus(info.status),
info.system_code));
}
session_keys_change_cb_.Run(std::string(session_id, session_id_size),
has_additional_usable_key, keys.Pass());
}
void CdmAdapter::OnExpirationChange(const char* session_id,
uint32_t session_id_size,
cdm::Time new_expiry_time) {
DCHECK(task_runner_->BelongsToCurrentThread());
session_expiration_update_cb_.Run(std::string(session_id, session_id_size),
base::Time::FromDoubleT(new_expiry_time));
}
void CdmAdapter::OnSessionClosed(const char* session_id,
uint32_t session_id_size) {
DCHECK(task_runner_->BelongsToCurrentThread());
session_closed_cb_.Run(std::string(session_id, session_id_size));
}
void CdmAdapter::OnLegacySessionError(const char* session_id,
uint32_t session_id_size,
cdm::Error error,
uint32_t system_code,
const char* error_message,
uint32_t error_message_size) {
DCHECK(task_runner_->BelongsToCurrentThread());
legacy_session_error_cb_.Run(std::string(session_id, session_id_size),
CdmErrorTypeToMediaExceptionType(error),
system_code,
std::string(error_message, error_message_size));
}
void CdmAdapter::SendPlatformChallenge(const char* service_id,
uint32_t service_id_size,
const char* challenge,
uint32_t challenge_size) {
DCHECK(task_runner_->BelongsToCurrentThread());
// TODO(jrummell): If platform verification is available, use it.
NOTIMPLEMENTED();
cdm::PlatformChallengeResponse platform_challenge_response = {};
cdm_->OnPlatformChallengeResponse(platform_challenge_response);
}
void CdmAdapter::EnableOutputProtection(uint32_t desired_protection_mask) {
DCHECK(task_runner_->BelongsToCurrentThread());
// TODO(jrummell): If output protection is available, use it.
NOTIMPLEMENTED();
}
void CdmAdapter::QueryOutputProtectionStatus() {
DCHECK(task_runner_->BelongsToCurrentThread());
// TODO(jrummell): If output protection is available, use it.
NOTIMPLEMENTED();
cdm_->OnQueryOutputProtectionStatus(cdm::kQueryFailed, 0, 0);
}
void CdmAdapter::OnDeferredInitializationDone(cdm::StreamType stream_type,
cdm::Status decoder_status) {
DCHECK(task_runner_->BelongsToCurrentThread());
// Not initializing a decoder, so this should never happen.
NOTREACHED();
}
// The CDM owns the returned object and must call FileIO::Close() to release it.
cdm::FileIO* CdmAdapter::CreateFileIO(cdm::FileIOClient* client) {
DCHECK(task_runner_->BelongsToCurrentThread());
// TODO(jrummell): This should use the mojo FileIO client.
NOTIMPLEMENTED();
return nullptr;
}
} // namespace media