Commit cf3b4f7d authored by xhwang@chromium.org's avatar xhwang@chromium.org

Implement "Key Presence" step in "Encrypted Block Encounted" algorithm in EME.

See related spec here: http://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/encrypted-media.html#algorithms-enrypted-block

If the AesDecryptor cannot find the decryption for the encrypted buffer, it returns a kNoKey.

In ProxyDecryptor, if the input cannot be decrypted due to kNoKey, the ProxyDecryptor will cache the encrypted buffers and callbacks and fire a needkey event. When some key is added (AddKey called) later, it will try to decrypt these buffers again. The callbacks will be fired on the same thread where Decrypt() was called originally.

BUG=125401, 125753
TEST=media_unittest, media layout_tests

Review URL: https://chromiumcodereview.appspot.com/10822026

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149952 0039d316-1c4b-4281-b951-d872f2087c98
parent a3fff4cd
......@@ -412,6 +412,7 @@
'../webkit/media/buffered_data_source_unittest.cc',
'../webkit/media/buffered_resource_loader_unittest.cc',
'../webkit/media/cache_util_unittest.cc',
'../webkit/media/crypto/proxy_decryptor_unittest.cc',
'../webkit/media/skcanvas_video_renderer_unittest.cc',
'../webkit/media/test_response_generator.cc',
'../webkit/media/test_response_generator.h',
......
......@@ -20,7 +20,7 @@ class DecoderBuffer;
// All public methods other than Decrypt() will be called on the renderer
// thread. Therefore, these calls should be fast and nonblocking, with key
// events fired asynchronously. Decrypt() will be called on the (video/audio)
// decoder thread synchronously.
// decoder thread.
class MEDIA_EXPORT Decryptor {
public:
enum KeyError {
......@@ -34,7 +34,8 @@ class MEDIA_EXPORT Decryptor {
enum DecryptStatus {
kSuccess, // Decryption successfully completed. Decrypted buffer ready.
kError // Error occurred during decryption.
kNoKey, // No key is available to decrypt.
kError // Key is available but an error occurred during decryption.
};
Decryptor() {}
......@@ -68,6 +69,8 @@ class MEDIA_EXPORT Decryptor {
//
// If the returned status is kSuccess, the |encrypted| buffer is successfully
// decrypted and the decrypted buffer is ready to be read.
// If the returned status is kNoKey, no decryption key is available to decrypt
// |encrypted| buffer. In this case the decrypted buffer must be NULL.
// If the returned status is kError, unexpected error has occurred. In this
// case the decrypted buffer must be NULL.
typedef base::Callback<void(DecryptStatus,
......@@ -75,6 +78,10 @@ class MEDIA_EXPORT Decryptor {
virtual void Decrypt(const scoped_refptr<DecoderBuffer>& encrypted,
const DecryptCB& decrypt_cb) = 0;
// Stops the decryptor and fires any pending DecryptCB immediately with kError
// and NULL buffer.
virtual void Stop() = 0;
private:
DISALLOW_COPY_AND_ASSIGN(Decryptor);
};
......
......@@ -212,6 +212,7 @@ class MockDecryptor : public Decryptor {
const std::string& session_id));
MOCK_METHOD2(Decrypt, void(const scoped_refptr<DecoderBuffer>& encrypted,
const DecryptCB& decrypt_cb));
MOCK_METHOD0(Stop, void());
private:
DISALLOW_COPY_AND_ASSIGN(MockDecryptor);
......
......@@ -267,9 +267,8 @@ void AesDecryptor::Decrypt(const scoped_refptr<DecoderBuffer>& encrypted,
DecryptionKey* key = GetKey(key_id);
if (!key) {
// TODO(xhwang): Fire a need_key event here and add a test.
DVLOG(1) << "Could not find a matching key for the given key ID.";
decrypt_cb.Run(kError, NULL);
decrypt_cb.Run(kNoKey, NULL);
return;
}
......@@ -310,6 +309,9 @@ void AesDecryptor::Decrypt(const scoped_refptr<DecoderBuffer>& encrypted,
decrypt_cb.Run(kSuccess, decrypted);
}
void AesDecryptor::Stop() {
}
void AesDecryptor::SetKey(const std::string& key_id,
scoped_ptr<DecryptionKey> decryption_key) {
base::AutoLock auto_lock(key_map_lock_);
......
......@@ -52,6 +52,7 @@ class MEDIA_EXPORT AesDecryptor : public Decryptor {
// succeeded through |decrypt_cb|.
virtual void Decrypt(const scoped_refptr<DecoderBuffer>& encrypted,
const DecryptCB& decrypt_cb) OVERRIDE;
virtual void Stop() OVERRIDE;
private:
// Helper class that manages the decryption key and HMAC key. The HMAC key
......
......@@ -346,6 +346,17 @@ TEST_F(AesDecryptorTest, WrongKey) {
ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(encrypted_data));
}
TEST_F(AesDecryptorTest, NoKey) {
const WebmEncryptedData& frame = kWebmEncryptedFrames[0];
GenerateKeyRequest(frame.key_id, frame.key_id_size);
scoped_refptr<DecoderBuffer> encrypted_data =
CreateWebMEncryptedBuffer(frame.encrypted_data, frame.encrypted_data_size,
frame.key_id, frame.key_id_size);
EXPECT_CALL(*this, BufferDecrypted(AesDecryptor::kNoKey, IsNull()));
decryptor_.Decrypt(encrypted_data, decrypt_cb_);
}
TEST_F(AesDecryptorTest, KeyReplacement) {
const WebmEncryptedData& frame = kWebmEncryptedFrames[0];
GenerateKeyRequest(frame.key_id, frame.key_id_size);
......
......@@ -4,10 +4,14 @@
#include "media/filters/chunk_demuxer.h"
#include <algorithm>
#include <deque>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/message_loop_proxy.h"
#include "base/string_util.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/stream_parser_buffer.h"
......@@ -381,14 +385,15 @@ void ChunkDemuxerStream::Shutdown() {
it->Run(DemuxerStream::kOk, StreamParserBuffer::CreateEOSBuffer());
}
// Helper function that makes sure |read_cb| runs on |message_loop|.
static void RunOnMessageLoop(const DemuxerStream::ReadCB& read_cb,
MessageLoop* message_loop,
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
if (MessageLoop::current() != message_loop) {
message_loop->PostTask(FROM_HERE, base::Bind(
&RunOnMessageLoop, read_cb, message_loop, status, buffer));
// Helper function that makes sure |read_cb| runs on |message_loop_proxy|.
static void RunOnMessageLoop(
const DemuxerStream::ReadCB& read_cb,
const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
if (!message_loop_proxy->BelongsToCurrentThread()) {
message_loop_proxy->PostTask(FROM_HERE, base::Bind(
&RunOnMessageLoop, read_cb, message_loop_proxy, status, buffer));
return;
}
......@@ -441,7 +446,7 @@ void ChunkDemuxerStream::DeferRead_Locked(const ReadCB& read_cb) {
// Wrap & store |read_cb| so that it will
// get called on the current MessageLoop.
read_cbs_.push_back(base::Bind(&RunOnMessageLoop, read_cb,
MessageLoop::current()));
base::MessageLoopProxy::current()));
}
void ChunkDemuxerStream::CreateReadDoneClosures_Locked(ClosureQueue* closures) {
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "base/bind.h"
#include "base/message_loop.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/decoder_buffer.h"
#include "media/base/mock_callback.h"
......@@ -671,6 +672,7 @@ class ChunkDemuxerTest : public testing::Test {
return true;
}
MessageLoop message_loop_;
MockDemuxerHost host_;
scoped_ptr<MockChunkDemuxerClient> client_;
......
......@@ -235,6 +235,9 @@ void FFmpegVideoDecoder::Stop(const base::Closure& closure) {
return;
}
if (decryptor_)
decryptor_->Stop();
stop_cb_ = closure;
// Defer stopping if a read is pending.
......@@ -358,7 +361,8 @@ void FFmpegVideoDecoder::DoBufferDecrypted(
return;
}
if (decrypt_status == Decryptor::kError) {
if (decrypt_status == Decryptor::kNoKey ||
decrypt_status == Decryptor::kError) {
state_ = kDecodeFinished;
base::ResetAndReturn(&read_cb_).Run(kDecryptError, NULL);
return;
......
......@@ -479,6 +479,28 @@ TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_DecryptError) {
message_loop_.RunAllPending();
}
// Test the case that the decryptor has no key to decrypt the encrypted buffer.
TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_NoDecryptionKey) {
Initialize();
// Simulate decoding a single encrypted frame.
EXPECT_CALL(*demuxer_, Read(_))
.WillRepeatedly(ReturnBuffer(encrypted_i_frame_buffer_));
EXPECT_CALL(*decryptor_, Decrypt(encrypted_i_frame_buffer_, _))
.WillRepeatedly(RunDecryptCB(Decryptor::kNoKey,
scoped_refptr<media::DecoderBuffer>()));
// Our read should still get satisfied with end of stream frame during an
// error.
VideoDecoder::DecoderStatus status;
scoped_refptr<VideoFrame> video_frame;
Read(&status, &video_frame);
EXPECT_EQ(VideoDecoder::kDecryptError, status);
EXPECT_FALSE(video_frame);
message_loop_.RunAllPending();
}
// Test the case that the decryptor fails to decrypt the encrypted buffer but
// cannot detect the decryption error and returns a corrupted buffer.
TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_CorruptedBufferReturned) {
......
......@@ -67,6 +67,9 @@ void PpapiDecryptor::Decrypt(
// }
}
void PpapiDecryptor::Stop() {
}
void PpapiDecryptor::DataReady(const DecryptCB& decrypt_cb,
const uint8* data, int data_size ) {
DCHECK(!decrypt_cb.is_null());
......
......@@ -43,6 +43,7 @@ class PpapiDecryptor : public media::Decryptor {
const std::string& session_id) OVERRIDE;
virtual void Decrypt(const scoped_refptr<media::DecoderBuffer>& encrypted,
const DecryptCB& decrypt_cb) OVERRIDE;
virtual void Stop() OVERRIDE;
private:
// Callback for the plugin to hand back the decrypted data.
......
......@@ -4,7 +4,12 @@
#include "webkit/media/crypto/proxy_decryptor.h"
#include <algorithm>
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/message_loop_proxy.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decryptor_client.h"
#include "media/crypto/aes_decryptor.h"
......@@ -41,13 +46,27 @@ static scoped_refptr<webkit::ppapi::PluginInstance> CreatePluginInstance(
return ppapi_plugin->instance();
}
// TODO(xhwang): Simplify this function. This is mostly caused by the fact that
// we need to copy a scoped_array<uint8>.
static void FireNeedKey(media::DecryptorClient* client,
const scoped_refptr<media::DecoderBuffer>& encrypted) {
DCHECK(client);
DCHECK(encrypted);
DCHECK(encrypted->GetDecryptConfig());
std::string key_id = encrypted->GetDecryptConfig()->key_id();
scoped_array<uint8> key_id_array(new uint8[key_id.size()]);
memcpy(key_id_array.get(), key_id.data(), key_id.size());
client->NeedKey("", "", key_id_array.Pass(), key_id.size());
}
ProxyDecryptor::ProxyDecryptor(
media::DecryptorClient* decryptor_client,
WebKit::WebMediaPlayerClient* web_media_player_client,
WebKit::WebFrame* web_frame)
: client_(decryptor_client),
web_media_player_client_(web_media_player_client),
web_frame_(web_frame) {
web_frame_(web_frame),
stopped_(false) {
}
ProxyDecryptor::~ProxyDecryptor() {
......@@ -58,12 +77,12 @@ void ProxyDecryptor::GenerateKeyRequest(const std::string& key_system,
int init_data_length) {
// We do not support run-time switching of decryptors. GenerateKeyRequest()
// only creates a new decryptor when |decryptor_| is not initialized.
DVLOG(1) << "GenerateKeyRequest: key_system = " << key_system;
if (!decryptor_.get()) {
base::AutoLock auto_lock(lock_);
decryptor_ = CreateDecryptor(key_system);
}
DCHECK(client_);
if (!decryptor_.get()) {
client_->KeyError(key_system, "", media::Decryptor::kUnknownError, 0);
return;
......@@ -78,14 +97,32 @@ void ProxyDecryptor::AddKey(const std::string& key_system,
const uint8* init_data,
int init_data_length,
const std::string& session_id) {
DVLOG(1) << "AddKey()";
// WebMediaPlayerImpl ensures GenerateKeyRequest() has been called.
DCHECK(decryptor_.get());
decryptor_->AddKey(key_system, key, key_length, init_data, init_data_length,
session_id);
std::vector<base::Closure> closures_to_run;
{
base::AutoLock auto_lock(lock_);
std::swap(pending_decrypt_closures_, closures_to_run);
}
// Fire all pending callbacks here because only the |decryptor_| knows if the
// pending buffers can be decrypted or not.
for (std::vector<base::Closure>::iterator iter = closures_to_run.begin();
iter != closures_to_run.end();
++iter) {
iter->Run();
}
}
void ProxyDecryptor::CancelKeyRequest(const std::string& key_system,
const std::string& session_id) {
DVLOG(1) << "CancelKeyRequest()";
// WebMediaPlayerImpl ensures GenerateKeyRequest() has been called.
DCHECK(decryptor_.get());
decryptor_->CancelKeyRequest(key_system, session_id);
......@@ -98,15 +135,49 @@ void ProxyDecryptor::Decrypt(
media::Decryptor* decryptor = NULL;
{
base::AutoLock auto_lock(lock_);
if (stopped_) {
DVLOG(1) << "Decrypt(): fire decrypt callbacks with kError.";
decrypt_cb.Run(kError, NULL);
return;
}
decryptor = decryptor_.get();
if (!decryptor) {
DVLOG(1) << "ProxyDecryptor::Decrypt(): decryptor not initialized.";
pending_decrypt_closures_.push_back(
base::Bind(&ProxyDecryptor::DecryptOnMessageLoop,
base::Unretained(this),
base::MessageLoopProxy::current(), encrypted,
decrypt_cb));
// TODO(xhwang): The same NeedKey may be fired here and multiple times in
// OnBufferDecrypted(). While the spec says only one NeedKey should be
// fired. Leave them as is since the spec about this may change.
FireNeedKey(client_, encrypted);
return;
}
}
if (!decryptor) {
DVLOG(1) << "ProxyDecryptor::Decrypt(): decryptor not initialized.";
decrypt_cb.Run(kError, NULL);
return;
decryptor->Decrypt(encrypted, base::Bind(
&ProxyDecryptor::OnBufferDecrypted, base::Unretained(this),
base::MessageLoopProxy::current(), encrypted, decrypt_cb));
}
void ProxyDecryptor::Stop() {
DVLOG(1) << "AddKey()";
std::vector<base::Closure> closures_to_run;
{
base::AutoLock auto_lock(lock_);
if (decryptor_.get())
decryptor_->Stop();
stopped_ = true;
std::swap(pending_decrypt_closures_, closures_to_run);
}
decryptor->Decrypt(encrypted, decrypt_cb);
for (std::vector<base::Closure>::iterator iter = closures_to_run.begin();
iter != closures_to_run.end();
++iter) {
iter->Run();
}
}
scoped_ptr<media::Decryptor> ProxyDecryptor::CreatePpapiDecryptor(
......@@ -141,4 +212,62 @@ scoped_ptr<media::Decryptor> ProxyDecryptor::CreateDecryptor(
return CreatePpapiDecryptor(key_system);
}
void ProxyDecryptor::DecryptOnMessageLoop(
const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
const scoped_refptr<media::DecoderBuffer>& encrypted,
const media::Decryptor::DecryptCB& decrypt_cb) {
DCHECK(decryptor_.get());
if (!message_loop_proxy->BelongsToCurrentThread()) {
message_loop_proxy->PostTask(FROM_HERE, base::Bind(
&ProxyDecryptor::DecryptOnMessageLoop, base::Unretained(this),
message_loop_proxy, encrypted, decrypt_cb));
return;
}
{
base::AutoLock auto_lock(lock_);
if (stopped_) {
DVLOG(1) << "DecryptOnMessageLoop(): fire decrypt callbacks with kError.";
decrypt_cb.Run(kError, NULL);
return;
}
}
decryptor_->Decrypt(encrypted, base::Bind(
&ProxyDecryptor::OnBufferDecrypted, base::Unretained(this),
message_loop_proxy, encrypted, decrypt_cb));
}
void ProxyDecryptor::OnBufferDecrypted(
const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
const scoped_refptr<media::DecoderBuffer>& encrypted,
const media::Decryptor::DecryptCB& decrypt_cb,
media::Decryptor::DecryptStatus status,
const scoped_refptr<media::DecoderBuffer>& decrypted) {
if (status == media::Decryptor::kSuccess ||
status == media::Decryptor::kError) {
decrypt_cb.Run(status, decrypted);
return;
}
DCHECK_EQ(status, media::Decryptor::kNoKey);
DVLOG(1) << "OnBufferDecrypted(): kNoKey fired";
{
base::AutoLock auto_lock(lock_);
if (stopped_) {
DVLOG(1) << "OnBufferDecrypted(): fire decrypt callbacks with kError.";
decrypt_cb.Run(kError, NULL);
return;
}
pending_decrypt_closures_.push_back(base::Bind(
&ProxyDecryptor::DecryptOnMessageLoop, base::Unretained(this),
message_loop_proxy, encrypted, decrypt_cb));
}
// TODO(xhwang): The same NeedKey may be fired multiple times here and also
// in Decrypt(). While the spec says only one NeedKey should be fired. Leave
// them as is since the spec about this may change.
FireNeedKey(client_, encrypted);
}
} // namespace webkit_media
......@@ -6,11 +6,16 @@
#define WEBKIT_MEDIA_CRYPTO_PROXY_DECRYPTOR_H_
#include <string>
#include <vector>
#include "base/memory/scoped_ptr.h"
#include "base/synchronization/lock.h"
#include "media/base/decryptor.h"
namespace base {
class MessageLoopProxy;
}
namespace media {
class DecryptorClient;
}
......@@ -33,6 +38,10 @@ class ProxyDecryptor : public media::Decryptor {
WebKit::WebFrame* web_frame);
virtual ~ProxyDecryptor();
void set_decryptor_for_testing(scoped_ptr<media::Decryptor> decryptor) {
decryptor_ = decryptor.Pass();
}
// media::Decryptor implementation.
virtual void GenerateKeyRequest(const std::string& key_system,
const uint8* init_data,
......@@ -47,26 +56,40 @@ class ProxyDecryptor : public media::Decryptor {
const std::string& session_id) OVERRIDE;
virtual void Decrypt(const scoped_refptr<media::DecoderBuffer>& encrypted,
const DecryptCB& decrypt_cb) OVERRIDE;
virtual void Stop() OVERRIDE;
private:
scoped_ptr<media::Decryptor> CreatePpapiDecryptor(
const std::string& key_system);
scoped_ptr<media::Decryptor> CreateDecryptor(const std::string& key_system);
// Helper function that makes sure decryptor_->Decrypt() runs on the
// |message_loop|.
void DecryptOnMessageLoop(
const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
const scoped_refptr<media::DecoderBuffer>& encrypted,
const media::Decryptor::DecryptCB& decrypt_cb);
// Callback used to pass into decryptor_->Decrypt().
void OnBufferDecrypted(
const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
const scoped_refptr<media::DecoderBuffer>& encrypted,
const media::Decryptor::DecryptCB& decrypt_cb,
media::Decryptor::DecryptStatus status,
const scoped_refptr<media::DecoderBuffer>& decrypted);
media::DecryptorClient* client_;
// Needed to create the PpapiDecryptor.
WebKit::WebMediaPlayerClient* web_media_player_client_;
WebKit::WebFrame* web_frame_;
// Protects the |decryptor_|. The Decryptor interface specifies that the
// Decrypt() function will be called on the decoder thread and all other
// methods on the renderer thread. The |decryptor_| itself is thread safe
// when this rule is obeyed. This lock is solely to prevent the race condition
// between setting the |decryptor_| in GenerateKeyRequest() and using it in
// Decrypt().
// Protects the |decryptor_| and |pending_decrypt_closures_|. Note that
// |decryptor_| itself should be thread safe as per the Decryptor interface.
base::Lock lock_;
scoped_ptr<media::Decryptor> decryptor_; // Protected by the |lock_|.
scoped_ptr<media::Decryptor> decryptor_;
std::vector<base::Closure> pending_decrypt_closures_;
bool stopped_;
DISALLOW_COPY_AND_ASSIGN(ProxyDecryptor);
};
......
// 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 "webkit/media/crypto/proxy_decryptor.h"
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "media/base/mock_filters.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AtLeast;
using ::testing::IsNull;
using ::testing::NotNull;
using media::DecoderBuffer;
using media::DecryptConfig;
using media::Decryptor;
namespace webkit_media {
static const uint8 kFakeKeyId[] = { 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44 };
static const uint8 kFakeIv[DecryptConfig::kDecryptionKeySize] = { 0 };
static const uint8 kFakeCheckSum[] = { 0, 0 };
static const char kFakeKeySystem[] = "system.key.fake";
static const char kFakeSessionId[] = "FakeSessionId";
static const uint8 kFakeKey[] = { 0x4b, 0x65, 0x79 };
static const uint8 kEncryptedData[] = { 0x65, 0x6E, 0x63, 0x72, 0x79 };
static const uint8 kDecryptedData[] = { 0x64, 0x65, 0x63, 0x72, 0x79 };
// Creates a fake non-empty encrypted buffer.
static scoped_refptr<DecoderBuffer> CreateFakeEncryptedBuffer() {
const int encrypted_frame_offset = 1; // This should be non-zero.
scoped_refptr<DecoderBuffer> encrypted_buffer =
DecoderBuffer::CopyFrom(kEncryptedData, arraysize(kEncryptedData));
encrypted_buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(
new DecryptConfig(
std::string(reinterpret_cast<const char*>(kFakeKeyId),
arraysize(kFakeKeyId)),
std::string(reinterpret_cast<const char*>(kFakeIv),
DecryptConfig::kDecryptionKeySize),
std::string(reinterpret_cast<const char*>(kFakeCheckSum),
arraysize(kFakeCheckSum)),
encrypted_frame_offset,
std::vector<media::SubsampleEntry>())));
return encrypted_buffer;
}
ACTION_P2(RunDecryptCB, status, buffer) {
arg1.Run(status, buffer);
}
ACTION_P(ScheduleMessageLoopToStop, message_loop) {
message_loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
}
// Tests the interaction between external Decryptor calls and concrete Decryptor
// implementations. This test is not interested in any specific Decryptor
// implementation. A MockDecryptor is used here to serve this purpose.
class ProxyDecryptorTest : public testing::Test {
public:
ProxyDecryptorTest()
: proxy_decryptor_(&client_, NULL, NULL),
real_decryptor_(new media::MockDecryptor()),
encrypted_buffer_(CreateFakeEncryptedBuffer()),
decrypted_buffer_(DecoderBuffer::CopyFrom(kDecryptedData,
arraysize(kDecryptedData))),
null_buffer_(scoped_refptr<DecoderBuffer>()),
decrypt_cb_(base::Bind(&ProxyDecryptorTest::BufferDecrypted,
base::Unretained(this))) {
}
// Instead of calling ProxyDecryptor::GenerateKeyRequest() here to create a
// real Decryptor, inject a MockDecryptor for testing purpose.
void GenerateKeyRequest() {
proxy_decryptor_.set_decryptor_for_testing(
scoped_ptr<Decryptor>(real_decryptor_));
}
// Since we are using the MockDecryptor, we can simulate any decryption
// behavior we want. Therefore, we do not care which key is really added,
// hence always use fake key IDs and keys.
void AddKey() {
EXPECT_CALL(*real_decryptor_, AddKey(kFakeKeySystem,
kFakeKeyId, arraysize(kFakeKeyId),
kFakeKey, arraysize(kFakeKey),
kFakeSessionId));
proxy_decryptor_.AddKey(kFakeKeySystem,
kFakeKeyId, arraysize(kFakeKeyId),
kFakeKey, arraysize(kFakeKey),
kFakeSessionId);
}
MOCK_METHOD2(BufferDecrypted, void(Decryptor::DecryptStatus,
const scoped_refptr<DecoderBuffer>&));
protected:
MessageLoop message_loop_;
media::MockDecryptorClient client_;
ProxyDecryptor proxy_decryptor_;
media::MockDecryptor* real_decryptor_;
scoped_refptr<DecoderBuffer> encrypted_buffer_;
scoped_refptr<DecoderBuffer> decrypted_buffer_;
scoped_refptr<DecoderBuffer> null_buffer_;
Decryptor::DecryptCB decrypt_cb_;
private:
DISALLOW_COPY_AND_ASSIGN(ProxyDecryptorTest);
};
// Tests a typical use case: GKR(), AddKey() and Decrypt() succeeds.
TEST_F(ProxyDecryptorTest, NormalDecryption_Success) {
GenerateKeyRequest();
AddKey();
EXPECT_CALL(*real_decryptor_, Decrypt(encrypted_buffer_, _))
.WillOnce(RunDecryptCB(Decryptor::kSuccess, decrypted_buffer_));
EXPECT_CALL(*this, BufferDecrypted(Decryptor::kSuccess, decrypted_buffer_));
proxy_decryptor_.Decrypt(encrypted_buffer_, decrypt_cb_);
}
// Tests the case where Decrypt() fails.
TEST_F(ProxyDecryptorTest, NormalDecryption_Error) {
GenerateKeyRequest();
AddKey();
EXPECT_CALL(*real_decryptor_, Decrypt(encrypted_buffer_, _))
.WillOnce(RunDecryptCB(Decryptor::kError, null_buffer_));
EXPECT_CALL(*this, BufferDecrypted(Decryptor::kError, IsNull()));
proxy_decryptor_.Decrypt(encrypted_buffer_, decrypt_cb_);
}
// Tests the case where no key is available for decryption.
TEST_F(ProxyDecryptorTest, NormalDecryption_NoKey) {
GenerateKeyRequest();
EXPECT_CALL(*real_decryptor_, Decrypt(encrypted_buffer_, _))
.WillOnce(RunDecryptCB(Decryptor::kNoKey, null_buffer_));
EXPECT_CALL(client_, NeedKeyMock("", "", NotNull(), arraysize(kFakeKeyId)));
proxy_decryptor_.Decrypt(encrypted_buffer_, decrypt_cb_);
}
// Tests the case where Decrypt() is called before GKR() is called and the right
// key is added.
TEST_F(ProxyDecryptorTest, DecryptBeforeGenerateKeyRequest) {
EXPECT_CALL(client_, NeedKeyMock("", "", NotNull(), arraysize(kFakeKeyId)));
proxy_decryptor_.Decrypt(encrypted_buffer_, decrypt_cb_);
EXPECT_CALL(*real_decryptor_, Decrypt(encrypted_buffer_, _))
.WillOnce(RunDecryptCB(Decryptor::kSuccess, decrypted_buffer_));
EXPECT_CALL(*this, BufferDecrypted(Decryptor::kSuccess, decrypted_buffer_))
.WillOnce(ScheduleMessageLoopToStop(&message_loop_));
GenerateKeyRequest();
AddKey();
message_loop_.Run();
}
// Tests the case where multiple AddKey() is called to add some irrelevant keys
// before the real key that can decrypt |encrypted_buffer_| is added.
TEST_F(ProxyDecryptorTest, MultipleAddKeys) {
EXPECT_CALL(client_, NeedKeyMock("", "", NotNull(), arraysize(kFakeKeyId)))
.Times(AtLeast(1));
proxy_decryptor_.Decrypt(encrypted_buffer_, decrypt_cb_);
EXPECT_CALL(*real_decryptor_, Decrypt(encrypted_buffer_, _))
.WillRepeatedly(RunDecryptCB(Decryptor::kNoKey, null_buffer_));
GenerateKeyRequest();
const int number_of_irrelevant_addkey = 5;
for (int i = 0; i < number_of_irrelevant_addkey; ++i)
AddKey(); // Some irrelevant keys are added.
EXPECT_CALL(*real_decryptor_, Decrypt(encrypted_buffer_, _))
.WillOnce(RunDecryptCB(Decryptor::kSuccess, decrypted_buffer_));
EXPECT_CALL(*this, BufferDecrypted(Decryptor::kSuccess, decrypted_buffer_))
.WillOnce(ScheduleMessageLoopToStop(&message_loop_));
AddKey(); // Correct key added.
message_loop_.Run();
}
// Tests the case where Decrypt() is called multiple times (e.g. from multiple
// stream) before the right key is added via AddKey().
TEST_F(ProxyDecryptorTest, MultiplePendingDecryptions) {
EXPECT_CALL(client_, NeedKeyMock("", "", NotNull(), arraysize(kFakeKeyId)))
.Times(AtLeast(1));
EXPECT_CALL(*real_decryptor_, Decrypt(encrypted_buffer_, _))
.WillRepeatedly(RunDecryptCB(Decryptor::kNoKey, null_buffer_));
proxy_decryptor_.Decrypt(encrypted_buffer_, decrypt_cb_);
GenerateKeyRequest();
proxy_decryptor_.Decrypt(encrypted_buffer_, decrypt_cb_);
AddKey(); // An irrelevant key is added.
proxy_decryptor_.Decrypt(encrypted_buffer_, decrypt_cb_);
EXPECT_CALL(*real_decryptor_, Decrypt(encrypted_buffer_, _))
.WillRepeatedly(RunDecryptCB(Decryptor::kSuccess, decrypted_buffer_));
EXPECT_CALL(*this, BufferDecrypted(Decryptor::kSuccess, decrypted_buffer_))
.Times(3);
AddKey(); // Correct key added.
message_loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure());
message_loop_.Run();
}
TEST_F(ProxyDecryptorTest, StopWhenDecryptionsPending) {
EXPECT_CALL(client_, NeedKeyMock("", "", NotNull(), arraysize(kFakeKeyId)))
.Times(AtLeast(1));
EXPECT_CALL(*real_decryptor_, Decrypt(encrypted_buffer_, _))
.WillRepeatedly(RunDecryptCB(Decryptor::kNoKey, null_buffer_));
proxy_decryptor_.Decrypt(encrypted_buffer_, decrypt_cb_);
GenerateKeyRequest();
proxy_decryptor_.Decrypt(encrypted_buffer_, decrypt_cb_);
AddKey(); // An irrelevant key is added.
proxy_decryptor_.Decrypt(encrypted_buffer_, decrypt_cb_);
EXPECT_CALL(*real_decryptor_, Stop());
EXPECT_CALL(*this, BufferDecrypted(Decryptor::kError, null_buffer_))
.Times(3);
proxy_decryptor_.Stop();
message_loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure());
message_loop_.Run();
}
} // namespace webkit_media
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment