| // 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/cdm_proxy_context.h" |
| #include "media/base/gmock_callback_support.h" |
| #include "media/base/mock_filters.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. |
| // Note: As move-only parameters (e.g. OnceCallback) aren't supported by mock |
| // methods, add On... methods to pass a non-const reference to OnceCallback. |
| |
| base::WeakPtr<CdmContext> GetCdmContext() override { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void Initialize(Client* client, InitializeCB init_cb) override { |
| OnInitialize(client, init_cb); |
| } |
| MOCK_METHOD2(OnInitialize, void(Client* client, InitializeCB& init_cb)); |
| |
| void Process(Function function, |
| uint32_t crypto_session_id, |
| const std::vector<uint8_t>& input_data, |
| uint32_t expected_output_data_size, |
| ProcessCB process_cb) override { |
| OnProcess(function, crypto_session_id, input_data, |
| expected_output_data_size, process_cb); |
| } |
| MOCK_METHOD5(OnProcess, |
| void(Function function, |
| uint32_t crypto_session_id, |
| const std::vector<uint8_t>& input_data, |
| uint32_t expected_output_data_size, |
| ProcessCB& process_cb)); |
| |
| void CreateMediaCryptoSession( |
| const std::vector<uint8_t>& input_data, |
| CreateMediaCryptoSessionCB create_media_crypto_session_cb) override { |
| OnCreateMediaCryptoSession(input_data, create_media_crypto_session_cb); |
| } |
| MOCK_METHOD2( |
| OnCreateMediaCryptoSession, |
| void(const std::vector<uint8_t>& input_data, |
| CreateMediaCryptoSessionCB& create_media_crypto_session_cb)); |
| |
| MOCK_METHOD3(SetKey, |
| void(uint32_t crypto_session_id, |
| const std::vector<uint8_t>& key_id, |
| const std::vector<uint8_t>& key_blob)); |
| MOCK_METHOD2(RemoveKey, |
| void(uint32_t crypto_session_id, |
| const std::vector<uint8_t>& key_id)); |
| |
| // 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_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_, OnInitialize(NotNull(), _)) |
| .WillOnce(RunOnceCallback<1>( |
| 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_, |
| OnProcess(CdmProxy::Function::kIntelNegotiateCryptoSessionKeyExchange, |
| crypto_session_id_, kInputData, kExpectedOutputDataSize, _)) |
| .WillOnce(RunOnceCallback<4>(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_, OnCreateMediaCryptoSession(kInputData, _)) |
| .WillOnce(RunOnceCallback<1>(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)); |
| cdm_proxy_->SetKey(crypto_session_id_, key_id.data(), key_id.size(), |
| 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)); |
| 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 |