blob: 75c69d255b73d4edda24eece3d69e9f9da9a2537 [file] [log] [blame]
// Copyright (c) 2012 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 <cstring>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/pp_stdint.h"
#include "ppapi/c/private/pp_content_decryptor.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/core.h"
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/logging.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/pass_ref.h"
#include "ppapi/cpp/resource.h"
#include "ppapi/cpp/var.h"
#include "ppapi/cpp/var_array_buffer.h"
#include "ppapi/cpp/dev/buffer_dev.h"
#include "ppapi/cpp/private/content_decryptor_private.h"
#include "ppapi/utility/completion_callback_factory.h"
#include "webkit/media/crypto/ppapi/linked_ptr.h"
#include "webkit/media/crypto/ppapi/content_decryption_module.h"
namespace {
// This must be consistent with MediaKeyError defined in the spec:
// http://goo.gl/rbdnR
// TODO(xhwang): Add PP_MediaKeyError enum to avoid later static_cast in
// PluginInstance.
enum MediaKeyError {
kUnknownError = 1,
kClientError,
kServiceError,
kOutputError,
kHardwareChangeError,
kDomainError
};
bool IsMainThread() {
return pp::Module::Get()->core()->IsMainThread();
}
void CallOnMain(pp::CompletionCallback cb) {
// TODO(tomfinegan): This is only necessary because PPAPI doesn't allow calls
// off the main thread yet. Remove this once the change lands.
if (IsMainThread())
cb.Run(PP_OK);
else
pp::Module::Get()->core()->CallOnMainThread(0, cb, PP_OK);
}
} // namespace
namespace webkit_media {
// Provides access to memory owned by a pp::Buffer_Dev created by
// PpbBufferAllocator::Allocate(). This class holds a reference to the
// Buffer_Dev throughout its lifetime.
class PpbBuffer : public cdm::Buffer {
public:
// cdm::Buffer methods.
virtual void Destroy() OVERRIDE { delete this; }
virtual uint8_t* buffer() OVERRIDE {
return static_cast<uint8_t*>(buffer_.data());
}
virtual int32_t size() const OVERRIDE { return buffer_.size(); }
pp::Buffer_Dev buffer_dev() const { return buffer_; }
private:
explicit PpbBuffer(pp::Buffer_Dev buffer) : buffer_(buffer) {}
virtual ~PpbBuffer() {}
pp::Buffer_Dev buffer_;
friend class PpbBufferAllocator;
DISALLOW_COPY_AND_ASSIGN(PpbBuffer);
};
class PpbBufferAllocator : public cdm::Allocator {
public:
explicit PpbBufferAllocator(pp::Instance* instance);
virtual ~PpbBufferAllocator();
// cdm::Allocator methods.
// Allocates a pp::Buffer_Dev of the specified size and wraps it in a
// PpbBuffer, which it returns. The caller own the returned buffer and must
// free it by calling ReleaseBuffer(). Returns NULL on failure.
virtual cdm::Buffer* Allocate(int32_t size) OVERRIDE;
private:
pp::Instance* const instance_;
DISALLOW_COPY_AND_ASSIGN(PpbBufferAllocator);
};
class KeyMessageImpl : public cdm::KeyMessage {
public:
KeyMessageImpl() : message_(NULL) {}
virtual ~KeyMessageImpl();
// cdm::KeyMessage methods.
virtual void set_session_id(const char* session_id, int32_t length) OVERRIDE;
virtual const char* session_id() const OVERRIDE;
virtual int32_t session_id_length() const OVERRIDE;
virtual void set_message(cdm::Buffer* message) OVERRIDE;
virtual cdm::Buffer* message() const OVERRIDE;
virtual void set_default_url(const char* default_url,
int32_t length) OVERRIDE;
virtual const char* default_url() const OVERRIDE;
virtual int32_t default_url_length() const OVERRIDE;
std::string session_id_string() const { return session_id_; }
std::string default_url_string() const { return default_url_; }
private:
PpbBuffer* message_;
std::string session_id_;
std::string default_url_;
DISALLOW_COPY_AND_ASSIGN(KeyMessageImpl);
};
class OutputBufferImpl : public cdm::OutputBuffer {
public:
OutputBufferImpl() : buffer_(NULL), timestamp_(0) {}
virtual ~OutputBufferImpl();
virtual void set_buffer(cdm::Buffer* buffer) OVERRIDE;
virtual cdm::Buffer* buffer() const OVERRIDE;
virtual void set_timestamp(int64_t timestamp) OVERRIDE;
virtual int64_t timestamp() const OVERRIDE;
private:
PpbBuffer* buffer_;
int64_t timestamp_;
DISALLOW_COPY_AND_ASSIGN(OutputBufferImpl);
};
KeyMessageImpl::~KeyMessageImpl() {
if (message_)
message_->Destroy();
}
void KeyMessageImpl::set_session_id(const char* session_id, int32_t length) {
session_id_.assign(session_id, length);
}
const char* KeyMessageImpl::session_id() const {
return session_id_.c_str();
}
int32_t KeyMessageImpl::session_id_length() const {
return session_id_.length();
}
void KeyMessageImpl::set_message(cdm::Buffer* buffer) {
message_ = static_cast<PpbBuffer*>(buffer);
}
cdm::Buffer* KeyMessageImpl::message() const {
return message_;
}
void KeyMessageImpl::set_default_url(const char* default_url, int32_t length) {
default_url_.assign(default_url, length);
}
const char* KeyMessageImpl::default_url() const {
return default_url_.c_str();
}
int32_t KeyMessageImpl::default_url_length() const {
return default_url_.length();
}
OutputBufferImpl::~OutputBufferImpl() {
if (buffer_)
buffer_->Destroy();
}
void OutputBufferImpl::set_buffer(cdm::Buffer* buffer) {
buffer_ = static_cast<PpbBuffer*>(buffer);
}
cdm::Buffer* OutputBufferImpl::buffer() const {
return buffer_;
}
void OutputBufferImpl::set_timestamp(int64_t timestamp) {
timestamp_ = timestamp;
}
int64_t OutputBufferImpl::timestamp() const {
return timestamp_;
}
// A wrapper class for abstracting away PPAPI interaction and threading for a
// Content Decryption Module (CDM).
class CdmWrapper : public pp::Instance,
public pp::ContentDecryptor_Private {
public:
CdmWrapper(PP_Instance instance, pp::Module* module);
virtual ~CdmWrapper();
virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
return true;
}
// PPP_ContentDecryptor_Private methods
// Note: As per comments in PPP_ContentDecryptor_Private, these calls should
// return false if the call was not forwarded to the CDM and should return
// true otherwise. Once the call reaches the CDM, the call result/status
// should be reported through the PPB_ContentDecryptor_Private interface.
virtual void GenerateKeyRequest(const std::string& key_system,
pp::VarArrayBuffer init_data) OVERRIDE;
virtual void AddKey(const std::string& session_id,
pp::VarArrayBuffer key,
pp::VarArrayBuffer init_data) OVERRIDE;
virtual void CancelKeyRequest(const std::string& session_id) OVERRIDE;
virtual void Decrypt(
pp::Buffer_Dev encrypted_buffer,
const PP_EncryptedBlockInfo& encrypted_block_info) OVERRIDE;
virtual void DecryptAndDecode(
pp::Buffer_Dev encrypted_buffer,
const PP_EncryptedBlockInfo& encrypted_block_info) OVERRIDE;
private:
typedef linked_ptr<KeyMessageImpl> LinkedKeyMessage;
typedef linked_ptr<OutputBufferImpl> LinkedOutputBuffer;
// <code>PPB_ContentDecryptor_Private</code> dispatchers. These are passed to
// <code>callback_factory_</code> to ensure that calls into
// <code>PPP_ContentDecryptor_Private</code> are asynchronous.
void KeyAdded(int32_t result, const std::string& session_id);
void KeyMessage(int32_t result, const LinkedKeyMessage& message);
void KeyError(int32_t result, const std::string& session_id);
void DeliverBlock(int32_t result,
const cdm::Status& status,
const LinkedOutputBuffer& output_buffer,
const PP_DecryptTrackingInfo& tracking_info);
PpbBufferAllocator allocator_;
pp::CompletionCallbackFactory<CdmWrapper> callback_factory_;
cdm::ContentDecryptionModule* cdm_;
std::string key_system_;
};
PpbBufferAllocator::PpbBufferAllocator(pp::Instance* instance)
: instance_(instance) {
}
PpbBufferAllocator::~PpbBufferAllocator() {
}
cdm::Buffer* PpbBufferAllocator::Allocate(int32_t size) {
PP_DCHECK(size > 0);
pp::Buffer_Dev buffer(instance_, size);
if (buffer.is_null())
return NULL;
return new PpbBuffer(buffer);
}
CdmWrapper::CdmWrapper(PP_Instance instance, pp::Module* module)
: pp::Instance(instance),
pp::ContentDecryptor_Private(this),
allocator_(this),
cdm_(NULL) {
callback_factory_.Initialize(this);
}
CdmWrapper::~CdmWrapper() {
if (cdm_)
DestroyCdmInstance(cdm_);
}
void CdmWrapper::GenerateKeyRequest(const std::string& key_system,
pp::VarArrayBuffer init_data) {
PP_DCHECK(!key_system.empty());
if (!cdm_) {
cdm_ = CreateCdmInstance(&allocator_);
if (!cdm_)
return;
}
LinkedKeyMessage key_request(new KeyMessageImpl());
cdm::Status status = cdm_->GenerateKeyRequest(
reinterpret_cast<const uint8_t*>(init_data.Map()),
init_data.ByteLength(),
key_request.get());
if (status != cdm::kSuccess ||
!key_request->message() ||
key_request->message()->size() == 0) {
CallOnMain(callback_factory_.NewCallback(&CdmWrapper::KeyError,
std::string()));
return;
}
// TODO(xhwang): Remove unnecessary CallOnMain calls here and below once we
// only support out-of-process.
// If running out-of-process, PPB calls will always behave asynchronously
// since IPC is involved. In that case, if we are already on main thread,
// we don't need to use CallOnMain to help us call PPB call on main thread,
// or to help call PPB asynchronously.
key_system_ = key_system;
CallOnMain(callback_factory_.NewCallback(&CdmWrapper::KeyMessage,
key_request));
}
void CdmWrapper::AddKey(const std::string& session_id,
pp::VarArrayBuffer key,
pp::VarArrayBuffer init_data) {
const uint8_t* key_ptr = reinterpret_cast<const uint8_t*>(key.Map());
int key_size = key.ByteLength();
const uint8_t* init_data_ptr =
reinterpret_cast<const uint8_t*>(init_data.Map());
int init_data_size = init_data.ByteLength();
if (!key_ptr || key_size <= 0 || !init_data_ptr || init_data_size <= 0)
return;
PP_DCHECK(cdm_);
cdm::Status status = cdm_->AddKey(session_id.data(), session_id.size(),
key_ptr, key_size,
init_data_ptr, init_data_size);
if (status != cdm::kSuccess) {
CallOnMain(callback_factory_.NewCallback(&CdmWrapper::KeyError,
session_id));
return;
}
CallOnMain(callback_factory_.NewCallback(&CdmWrapper::KeyAdded, session_id));
}
void CdmWrapper::CancelKeyRequest(const std::string& session_id) {
PP_DCHECK(cdm_);
cdm::Status status = cdm_->CancelKeyRequest(session_id.data(),
session_id.size());
if (status != cdm::kSuccess) {
CallOnMain(callback_factory_.NewCallback(&CdmWrapper::KeyError,
session_id));
}
}
void CdmWrapper::Decrypt(pp::Buffer_Dev encrypted_buffer,
const PP_EncryptedBlockInfo& encrypted_block_info) {
PP_DCHECK(!encrypted_buffer.is_null());
PP_DCHECK(cdm_);
// TODO(xhwang): Simplify the following data conversion.
cdm::InputBuffer input_buffer;
input_buffer.data = reinterpret_cast<uint8_t*>(encrypted_buffer.data());
input_buffer.data_size = encrypted_buffer.size();
input_buffer.data_offset = encrypted_block_info.data_offset;
input_buffer.key_id = encrypted_block_info.key_id;
input_buffer.key_id_size = encrypted_block_info.key_id_size;
input_buffer.iv = encrypted_block_info.iv;
input_buffer.iv_size = encrypted_block_info.iv_size;
input_buffer.num_subsamples = encrypted_block_info.num_subsamples;
std::vector<cdm::SubsampleEntry> subsamples;
if (encrypted_block_info.num_subsamples > 0) {
subsamples.reserve(encrypted_block_info.num_subsamples);
for (uint32_t i = 0; i < encrypted_block_info.num_subsamples; ++i) {
subsamples.push_back(cdm::SubsampleEntry(
encrypted_block_info.subsamples[i].clear_bytes,
encrypted_block_info.subsamples[i].cipher_bytes));
}
input_buffer.subsamples = &subsamples[0];
}
input_buffer.timestamp = encrypted_block_info.tracking_info.timestamp;
LinkedOutputBuffer output_buffer(new OutputBufferImpl());
cdm::Status status = cdm_->Decrypt(input_buffer, output_buffer.get());
CallOnMain(callback_factory_.NewCallback(
&CdmWrapper::DeliverBlock,
status,
output_buffer,
encrypted_block_info.tracking_info));
}
void CdmWrapper::DecryptAndDecode(
pp::Buffer_Dev encrypted_buffer,
const PP_EncryptedBlockInfo& encrypted_block_info) {
}
void CdmWrapper::KeyAdded(int32_t result, const std::string& session_id) {
pp::ContentDecryptor_Private::KeyAdded(key_system_, session_id);
}
void CdmWrapper::KeyMessage(int32_t result,
const LinkedKeyMessage& key_message) {
pp::Buffer_Dev message_buffer =
static_cast<const PpbBuffer*>(key_message->message())->buffer_dev();
pp::ContentDecryptor_Private::KeyMessage(
key_system_,
key_message->session_id_string(),
message_buffer,
key_message->default_url_string());
}
// TODO(xhwang): Support MediaKeyError (see spec: http://goo.gl/rbdnR) in CDM
// interface and in this function.
void CdmWrapper::KeyError(int32_t result, const std::string& session_id) {
pp::ContentDecryptor_Private::KeyError(key_system_,
session_id,
kUnknownError,
0);
}
void CdmWrapper::DeliverBlock(int32_t result,
const cdm::Status& status,
const LinkedOutputBuffer& output_buffer,
const PP_DecryptTrackingInfo& tracking_info) {
PP_DecryptedBlockInfo decrypted_block_info;
decrypted_block_info.tracking_info.request_id = tracking_info.request_id;
decrypted_block_info.tracking_info.timestamp = output_buffer->timestamp();
switch (status) {
case cdm::kSuccess:
decrypted_block_info.result = PP_DECRYPTRESULT_SUCCESS;
PP_DCHECK(output_buffer.get() && output_buffer->buffer());
break;
case cdm::kNoKey:
decrypted_block_info.result = PP_DECRYPTRESULT_DECRYPT_NOKEY;
break;
default:
decrypted_block_info.result = PP_DECRYPTRESULT_DECRYPT_ERROR;
}
const pp::Buffer_Dev& buffer =
output_buffer.get() && output_buffer->buffer() ?
static_cast<PpbBuffer*>(output_buffer->buffer())->buffer_dev() :
pp::Buffer_Dev();
pp::ContentDecryptor_Private::DeliverBlock(buffer, decrypted_block_info);
}
// This object is the global object representing this plugin library as long
// as it is loaded.
class CdmWrapperModule : public pp::Module {
public:
CdmWrapperModule() : pp::Module() {}
virtual ~CdmWrapperModule() {}
virtual pp::Instance* CreateInstance(PP_Instance instance) {
return new CdmWrapper(instance, this);
}
};
} // namespace webkit_media
namespace pp {
// Factory function for your specialization of the Module object.
Module* CreateModule() {
return new webkit_media::CdmWrapperModule();
}
} // namespace pp