blob: 0eec1049a7f41911409bbb7759a78cf8e6cc7fba [file] [log] [blame]
// Copyright 2018 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 <stdint.h>
#include <memory>
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/test_message_loop.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/mock_filters.h"
#include "media/cdm/cdm_proxy_context.h"
#include "media/mojo/interfaces/cdm_proxy.mojom.h"
#include "media/mojo/services/mojo_cdm_proxy.h"
#include "media/mojo/services/mojo_cdm_proxy_service.h"
#include "media/mojo/services/mojo_cdm_service_context.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::NotNull;
using ::testing::SaveArg;
using ::testing::StrictMock;
namespace media {
namespace {
MATCHER_P(StatusEq, status, "") {
return (arg == cdm::CdmProxyClient::Status::kOk &&
status == media::CdmProxy::Status::kOk) ||
(arg == cdm::CdmProxyClient::Status::kFail &&
status == media::CdmProxy::Status::kFail);
}
constexpr uint32_t kCryptoSessionId = 1010;
class MockCdmProxyContext : public CdmProxyContext {};
class MockCdmProxy : public media::CdmProxy, public media::CdmContext {
public:
MockCdmProxy() : weak_factory_(this) {}
~MockCdmProxy() override = default;
// media::CdmProxy implementation.
base::WeakPtr<CdmContext> GetCdmContext() override {
return weak_factory_.GetWeakPtr();
}
MOCK_METHOD2(Initialize, void(Client* client, InitializeCB init_cb));
MOCK_METHOD5(Process,
void(Function function,
uint32_t crypto_session_id,
const std::vector<uint8_t>& input_data,
uint32_t expected_output_data_size,
ProcessCB process_cb));
MOCK_METHOD2(CreateMediaCryptoSession,
void(const std::vector<uint8_t>& input_data,
CreateMediaCryptoSessionCB create_media_crypto_session_cb));
MOCK_METHOD5(SetKey,
void(uint32_t crypto_session_id,
const std::vector<uint8_t>& key_id,
KeyType key_type,
const std::vector<uint8_t>& key_blob,
SetKeyCB set_key_cb));
MOCK_METHOD3(RemoveKey,
void(uint32_t crypto_session_id,
const std::vector<uint8_t>& key_id,
RemoveKeyCB remove_key_cb));
// media::CdmContext implementation.
CdmProxyContext* GetCdmProxyContext() override {
return &mock_cdm_proxy_context_;
}
private:
MockCdmProxyContext mock_cdm_proxy_context_;
base::WeakPtrFactory<MockCdmProxy> weak_factory_;
};
class MockCdmProxyClient : public cdm::CdmProxyClient {
public:
MockCdmProxyClient() = default;
~MockCdmProxyClient() override = default;
MOCK_METHOD3(OnInitialized,
void(Status status,
Protocol protocol,
uint32_t crypto_session_id));
MOCK_METHOD3(OnProcessed,
void(Status status,
const uint8_t* output_data,
uint32_t output_data_size));
MOCK_METHOD3(OnMediaCryptoSessionCreated,
void(Status status,
uint32_t crypto_session_id,
uint64_t output_data));
MOCK_METHOD1(OnKeySet, void(Status status));
MOCK_METHOD1(OnKeyRemoved, void(Status status));
MOCK_METHOD0(NotifyHardwareReset, void());
};
} // namespace
class MojoCdmProxyTest : public ::testing::Test {
public:
using Status = CdmProxy::Status;
MojoCdmProxyTest() {
// Client side setup.
mojom::CdmProxyPtr cdm_proxy_ptr;
auto request = mojo::MakeRequest(&cdm_proxy_ptr);
mojo_cdm_proxy_.reset(new MojoCdmProxy(std::move(cdm_proxy_ptr), &client_));
cdm_proxy_ = mojo_cdm_proxy_.get();
// Service side setup.
std::unique_ptr<MockCdmProxy> mock_cdm_proxy(new MockCdmProxy());
mock_cdm_proxy_ = mock_cdm_proxy.get();
mojo_cdm_proxy_service_.reset(new MojoCdmProxyService(
std::move(mock_cdm_proxy), &mojo_cdm_service_context_));
binding_.reset(new mojo::Binding<mojom::CdmProxy>(
mojo_cdm_proxy_service_.get(), std::move(request)));
binding_->set_connection_error_handler(base::BindOnce(
&MojoCdmProxyTest::OnConnectionError, base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
~MojoCdmProxyTest() override = default;
void Initialize(Status expected_status = Status::kOk,
bool has_connection = true) {
if (has_connection) {
EXPECT_CALL(*mock_cdm_proxy_, Initialize(NotNull(), _))
.WillOnce([&](auto, auto init_cb) {
std::move(init_cb).Run(expected_status, CdmProxy::Protocol::kNone,
kCryptoSessionId);
});
EXPECT_CALL(client_,
OnInitialized(StatusEq(expected_status),
cdm::CdmProxyClient::kNone, kCryptoSessionId))
.WillOnce(SaveArg<2>(&crypto_session_id_));
} else {
// Client should always be called even without connection. But we only
// care about status in this case.
EXPECT_CALL(client_, OnInitialized(StatusEq(expected_status), _, _));
}
cdm_proxy_->Initialize();
base::RunLoop().RunUntilIdle();
}
void Process(Status expected_status = Status::kOk,
bool has_connection = true) {
const std::vector<uint8_t> kInputData = {1, 2};
const uint32_t kExpectedOutputDataSize = 111;
const std::vector<uint8_t> kOutputData = {3, 4, 5};
if (has_connection) {
EXPECT_CALL(
*mock_cdm_proxy_,
Process(CdmProxy::Function::kIntelNegotiateCryptoSessionKeyExchange,
crypto_session_id_, kInputData, kExpectedOutputDataSize, _))
.WillOnce([&](auto, auto, auto, auto, auto process_cb) {
std::move(process_cb).Run(expected_status, kOutputData);
});
EXPECT_CALL(client_, OnProcessed(StatusEq(expected_status), NotNull(),
kOutputData.size()));
} else {
// Client should always be called even without connection. But we only
// care about status in this case.
EXPECT_CALL(client_, OnProcessed(StatusEq(expected_status), _, _));
}
cdm_proxy_->Process(
cdm::CdmProxy::Function::kIntelNegotiateCryptoSessionKeyExchange,
crypto_session_id_, kInputData.data(), kInputData.size(),
kExpectedOutputDataSize);
base::RunLoop().RunUntilIdle();
}
void CreateMediaCryptoSession(Status expected_status = Status::kOk,
bool has_connection = true) {
const std::vector<uint8_t> kInputData = {6, 7};
const uint32_t kMediaCryptoSessionId = 222;
const uint64_t kOutputData = 333;
if (has_connection) {
EXPECT_CALL(*mock_cdm_proxy_, CreateMediaCryptoSession(kInputData, _))
.WillOnce([&](auto, auto create_media_crypto_session_cb) {
std::move(create_media_crypto_session_cb)
.Run(expected_status, kMediaCryptoSessionId, kOutputData);
});
EXPECT_CALL(client_, OnMediaCryptoSessionCreated(
StatusEq(expected_status), kMediaCryptoSessionId,
kOutputData));
} else {
// Client should always be called even without connection. But we only
// care about status in this case.
EXPECT_CALL(client_,
OnMediaCryptoSessionCreated(StatusEq(expected_status), _, _));
}
cdm_proxy_->CreateMediaCryptoSession(kInputData.data(), kInputData.size());
base::RunLoop().RunUntilIdle();
}
void SetKey() {
const std::vector<uint8_t> key_id = {8, 9};
const std::vector<uint8_t> key_blob = {10, 11, 12};
EXPECT_CALL(*mock_cdm_proxy_,
SetKey(crypto_session_id_, key_id, _, key_blob, _))
.WillOnce([&](auto, auto, auto, auto, auto set_key_cb) {
std::move(set_key_cb).Run(Status::kOk);
});
EXPECT_CALL(client_, OnKeySet(StatusEq(Status::kOk)));
cdm_proxy_->SetKey(crypto_session_id_, key_id.data(), key_id.size(),
cdm::CdmProxy::KeyType::kDecryptOnly, key_blob.data(),
key_blob.size());
base::RunLoop().RunUntilIdle();
}
void RemoveKey() {
const std::vector<uint8_t> key_id = {13, 14};
EXPECT_CALL(*mock_cdm_proxy_, RemoveKey(crypto_session_id_, key_id, _))
.WillOnce([&](auto, auto, auto remove_key_cb) {
std::move(remove_key_cb).Run(Status::kOk);
});
EXPECT_CALL(client_, OnKeyRemoved(StatusEq(Status::kOk)));
cdm_proxy_->RemoveKey(crypto_session_id_, key_id.data(), key_id.size());
base::RunLoop().RunUntilIdle();
}
// Simulate connecting the media component with the CdmContext. Can only be
// called after the CdmProxy is successfully initialized (see Initialize()).
void SetCdm() {
int cdm_id = mojo_cdm_proxy_service_->GetCdmIdForTesting();
cdm_context_ref_ = mojo_cdm_service_context_.GetCdmContextRef(cdm_id);
cdm_context_ = cdm_context_ref_->GetCdmContext();
}
CdmProxyContext* GetCdmProxyContext() {
return cdm_context_->GetCdmProxyContext();
}
void Destroy() {
mojo_cdm_proxy_.reset();
base::RunLoop().RunUntilIdle();
}
void OnConnectionError() { mojo_cdm_proxy_service_.reset(); }
void ForceConnectionError() {
binding_->CloseWithReason(2, "Test closed connection.");
mojo_cdm_proxy_service_.reset();
base::RunLoop().RunUntilIdle();
}
base::TestMessageLoop message_loop_;
uint32_t crypto_session_id_ = 0;
// Client side members.
StrictMock<MockCdmProxyClient> client_;
std::unique_ptr<MojoCdmProxy> mojo_cdm_proxy_;
cdm::CdmProxy* cdm_proxy_ = nullptr;
// Service side members.
MojoCdmServiceContext mojo_cdm_service_context_;
std::unique_ptr<MojoCdmProxyService> mojo_cdm_proxy_service_;
std::unique_ptr<mojo::Binding<mojom::CdmProxy>> binding_;
MockCdmProxy* mock_cdm_proxy_ = nullptr;
// Media component side members.
std::unique_ptr<CdmContextRef> cdm_context_ref_;
CdmContext* cdm_context_ = nullptr;
private:
DISALLOW_COPY_AND_ASSIGN(MojoCdmProxyTest);
};
TEST_F(MojoCdmProxyTest, Initialize) {
Initialize();
}
TEST_F(MojoCdmProxyTest, Initialize_Failure) {
Initialize(Status::kFail);
}
TEST_F(MojoCdmProxyTest, Process) {
Initialize();
Process();
}
TEST_F(MojoCdmProxyTest, Process_Failure) {
Initialize();
Process(Status::kFail);
}
TEST_F(MojoCdmProxyTest, CreateMediaCryptoSession) {
Initialize();
Process();
CreateMediaCryptoSession();
}
TEST_F(MojoCdmProxyTest, CreateMediaCryptoSession_Failure) {
Initialize();
Process();
CreateMediaCryptoSession(Status::kFail);
}
TEST_F(MojoCdmProxyTest, SetKey) {
Initialize();
Process();
CreateMediaCryptoSession();
SetKey();
}
TEST_F(MojoCdmProxyTest, RemoveKey) {
Initialize();
Process();
CreateMediaCryptoSession();
RemoveKey();
}
TEST_F(MojoCdmProxyTest, Destroy) {
Initialize();
Process();
EXPECT_TRUE(mojo_cdm_proxy_service_);
Destroy();
EXPECT_FALSE(mojo_cdm_proxy_service_);
}
TEST_F(MojoCdmProxyTest, ConnectionError_BeforeInitialize) {
ForceConnectionError();
Initialize(Status::kFail, false);
}
TEST_F(MojoCdmProxyTest, ConnectionError_AfterInitialize) {
Initialize();
Process();
CreateMediaCryptoSession();
ForceConnectionError();
// Calling Process() and CreateMediaCryptoSession() without connection. These
// calls should fail but the client should still get notified (about the
// failure).
Process(Status::kFail, false);
CreateMediaCryptoSession(Status::kFail, false);
}
TEST_F(MojoCdmProxyTest, GetCdmProxyContext) {
Initialize();
SetCdm();
EXPECT_TRUE(GetCdmProxyContext());
}
TEST_F(MojoCdmProxyTest, GetCdmProxyContext_AfterDestroy) {
Initialize();
SetCdm();
EXPECT_TRUE(GetCdmProxyContext());
Destroy();
EXPECT_FALSE(GetCdmProxyContext());
}
TEST_F(MojoCdmProxyTest, GetCdmProxyContext_AfterConnectionError) {
Initialize();
SetCdm();
EXPECT_TRUE(GetCdmProxyContext());
ForceConnectionError();
EXPECT_FALSE(GetCdmProxyContext());
}
} // namespace media