| // 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 "media/mojo/services/cdm_service.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/run_loop.h" |
| #include "base/test/task_environment.h" |
| #include "base/unguessable_token.h" |
| #include "build/build_config.h" |
| #include "media/base/mock_filters.h" |
| #include "media/cdm/default_cdm_factory.h" |
| #include "media/media_buildflags.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| using testing::_; |
| using testing::Invoke; |
| using testing::InvokeWithoutArgs; |
| using testing::NiceMock; |
| using testing::Return; |
| |
| MATCHER_P(MatchesResult, success, "") { |
| return arg->success == success; |
| } |
| |
| // MockCdmFactory treats any non-empty key system as valid and the empty key |
| // system as invalid. |
| const char kValidKeySystem[] = "org.w3.clearkey"; |
| const char kInvalidKeySystem[] = ""; |
| |
| // Needed since MockCdmServiceClient needs to return unique_ptr of CdmFactory. |
| class CdmFactoryWrapper : public CdmFactory { |
| public: |
| explicit CdmFactoryWrapper(CdmFactory* cdm_factory) |
| : cdm_factory_(cdm_factory) {} |
| |
| // CdmFactory implementation. |
| void Create(const std::string& key_system, |
| const CdmConfig& cdm_config, |
| const SessionMessageCB& session_message_cb, |
| const SessionClosedCB& session_closed_cb, |
| const SessionKeysChangeCB& session_keys_change_cb, |
| const SessionExpirationUpdateCB& session_expiration_update_cb, |
| CdmCreatedCB cdm_created_cb) override { |
| cdm_factory_->Create(key_system, cdm_config, session_message_cb, |
| session_closed_cb, session_keys_change_cb, |
| session_expiration_update_cb, |
| std::move(cdm_created_cb)); |
| } |
| |
| private: |
| CdmFactory* const cdm_factory_; |
| }; |
| |
| class MockCdmServiceClient : public media::CdmService::Client { |
| public: |
| explicit MockCdmServiceClient(CdmFactory* cdm_factory) |
| : cdm_factory_(cdm_factory) {} |
| ~MockCdmServiceClient() override = default; |
| |
| // media::CdmService::Client implementation. |
| MOCK_METHOD0(EnsureSandboxed, void()); |
| |
| std::unique_ptr<media::CdmFactory> CreateCdmFactory( |
| mojom::FrameInterfaceFactory* frame_interfaces) override { |
| return std::make_unique<CdmFactoryWrapper>(cdm_factory_); |
| } |
| |
| #if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION) |
| void AddCdmHostFilePaths(std::vector<media::CdmHostFilePath>*) override {} |
| #endif // BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION) |
| |
| private: |
| CdmFactory* const cdm_factory_; |
| }; |
| |
| class CdmServiceTest : public testing::Test { |
| public: |
| CdmServiceTest() = default; |
| |
| CdmServiceTest(const CdmServiceTest&) = delete; |
| CdmServiceTest& operator=(const CdmServiceTest&) = delete; |
| |
| ~CdmServiceTest() override = default; |
| |
| MOCK_METHOD0(CdmServiceIdle, void()); |
| MOCK_METHOD0(CdmFactoryConnectionClosed, void()); |
| MOCK_METHOD0(CdmConnectionClosed, void()); |
| |
| void Initialize() { |
| EXPECT_CALL(*mock_cdm_, GetCdmContext()) |
| .WillRepeatedly(Return(&cdm_context_)); |
| |
| auto mock_cdm_service_client = |
| std::make_unique<MockCdmServiceClient>(&mock_cdm_factory_); |
| mock_cdm_service_client_ = mock_cdm_service_client.get(); |
| |
| service_ = std::make_unique<CdmService>( |
| std::move(mock_cdm_service_client), |
| cdm_service_remote_.BindNewPipeAndPassReceiver()); |
| cdm_service_remote_.set_idle_handler( |
| base::TimeDelta(), base::BindRepeating(&CdmServiceTest::CdmServiceIdle, |
| base::Unretained(this))); |
| |
| mojo::PendingRemote<mojom::FrameInterfaceFactory> interfaces; |
| ignore_result(interfaces.InitWithNewPipeAndPassReceiver()); |
| |
| ASSERT_FALSE(cdm_factory_remote_); |
| cdm_service_remote_->CreateCdmFactory( |
| cdm_factory_remote_.BindNewPipeAndPassReceiver(), |
| std::move(interfaces)); |
| cdm_service_remote_.FlushForTesting(); |
| ASSERT_TRUE(cdm_factory_remote_); |
| cdm_factory_remote_.set_disconnect_handler(base::BindOnce( |
| &CdmServiceTest::CdmFactoryConnectionClosed, base::Unretained(this))); |
| } |
| |
| void InitializeCdm(const std::string& key_system, bool expected_result) { |
| cdm_factory_remote_->CreateCdm( |
| key_system, CdmConfig(), |
| base::BindOnce(&CdmServiceTest::OnCdmCreated, base::Unretained(this), |
| expected_result)); |
| cdm_factory_remote_.FlushForTesting(); |
| } |
| |
| void DestroyService() { service_.reset(); } |
| |
| MockCdmServiceClient* mock_cdm_service_client() { |
| return mock_cdm_service_client_; |
| } |
| |
| CdmService* cdm_service() { return service_.get(); } |
| |
| base::test::TaskEnvironment task_environment_; |
| mojo::Remote<mojom::CdmService> cdm_service_remote_; |
| mojo::Remote<mojom::CdmFactory> cdm_factory_remote_; |
| mojo::Remote<mojom::ContentDecryptionModule> cdm_remote_; |
| |
| // MojoCdmService will always create/use `mock_cdm_factory_` and `mock_cdm_`, |
| // so it's easier to set expectations on them. |
| scoped_refptr<MockCdm> mock_cdm_{new MockCdm()}; |
| MockCdmFactory mock_cdm_factory_{mock_cdm_}; |
| NiceMock<MockCdmContext> cdm_context_; |
| |
| private: |
| void OnCdmCreated(bool expected_result, |
| mojo::PendingRemote<mojom::ContentDecryptionModule> remote, |
| media::mojom::CdmContextPtr cdm_context, |
| const std::string& error_message) { |
| if (!expected_result) { |
| EXPECT_FALSE(remote); |
| EXPECT_FALSE(cdm_context); |
| EXPECT_TRUE(!error_message.empty()); |
| return; |
| } |
| EXPECT_TRUE(remote); |
| EXPECT_TRUE(error_message.empty()); |
| cdm_remote_.Bind(std::move(remote)); |
| cdm_remote_.set_disconnect_handler(base::BindOnce( |
| &CdmServiceTest::CdmConnectionClosed, base::Unretained(this))); |
| } |
| std::unique_ptr<CdmService> service_; |
| MockCdmServiceClient* mock_cdm_service_client_ = nullptr; |
| }; |
| |
| } // namespace |
| |
| TEST_F(CdmServiceTest, InitializeCdm_Success) { |
| Initialize(); |
| InitializeCdm(kValidKeySystem, true); |
| } |
| |
| TEST_F(CdmServiceTest, InitializeCdm_InvalidKeySystem) { |
| Initialize(); |
| InitializeCdm(kInvalidKeySystem, false); |
| } |
| |
| TEST_F(CdmServiceTest, DestroyAndRecreateCdm) { |
| Initialize(); |
| InitializeCdm(kValidKeySystem, true); |
| cdm_remote_.reset(); |
| InitializeCdm(kValidKeySystem, true); |
| } |
| |
| // CdmFactory disconnection will cause the service to idle. |
| TEST_F(CdmServiceTest, DestroyCdmFactory) { |
| Initialize(); |
| auto* service = cdm_service(); |
| |
| InitializeCdm(kValidKeySystem, true); |
| EXPECT_EQ(service->BoundCdmFactorySizeForTesting(), 1u); |
| EXPECT_EQ(service->UnboundCdmFactorySizeForTesting(), 0u); |
| |
| cdm_factory_remote_.reset(); |
| EXPECT_CALL(*this, CdmServiceIdle()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(service->BoundCdmFactorySizeForTesting(), 0u); |
| EXPECT_EQ(service->UnboundCdmFactorySizeForTesting(), 1u); |
| } |
| |
| // Destroy service will destroy the CdmFactory and all CDMs. |
| TEST_F(CdmServiceTest, DestroyCdmService_AfterCdmCreation) { |
| Initialize(); |
| InitializeCdm(kValidKeySystem, true); |
| |
| base::RunLoop run_loop; |
| // Ideally we should not care about order, and should only quit the loop when |
| // both connections are closed. |
| EXPECT_CALL(*this, CdmFactoryConnectionClosed()); |
| EXPECT_CALL(*this, CdmConnectionClosed()) |
| .WillOnce(Invoke(&run_loop, &base::RunLoop::Quit)); |
| DestroyService(); |
| run_loop.Run(); |
| } |
| |
| // Before the CDM is fully created, CdmService has been destroyed. We should |
| // fail gracefully instead of a crash. See crbug.com/1190319. |
| TEST_F(CdmServiceTest, DestroyCdmService_DuringCdmCreation) { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*this, CdmFactoryConnectionClosed()) |
| .WillOnce(Invoke(&run_loop, &base::RunLoop::Quit)); |
| mock_cdm_factory_.SetBeforeCreationCB(base::BindRepeating( |
| &CdmServiceTest::DestroyService, base::Unretained(this))); |
| Initialize(); |
| InitializeCdm(kValidKeySystem, true); |
| run_loop.Run(); |
| } |
| |
| } // namespace media |