blob: cee91d507f160bc9400a029c39694655a0cb2050 [file] [log] [blame]
// Copyright 2017 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/gpu/windows/d3d11_cdm_proxy.h"
#include <d3d11.h>
#include <d3d11_1.h>
#include <initguid.h>
#include "base/bind.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_monitor_source.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "media/base/callback_registry.h"
#include "media/base/win/d3d11_mocks.h"
#include "media/cdm/cdm_proxy_context.h"
#include "testing/gtest/include/gtest/gtest.h"
using Microsoft::WRL::ComPtr;
using ::testing::_;
using ::testing::AllOf;
using ::testing::AtLeast;
using ::testing::AtMost;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::Lt;
using ::testing::Mock;
using ::testing::Ne;
using ::testing::NiceMock;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SetArgPointee;
using ::testing::WithArgs;
namespace media {
namespace {
// TODO(rkuroiwa): Although inheriting from different classes, there are several
// mock CdmProxy clients already. They all have NotifyHardwareReset(), so share
// a single mock class that inherits from all the CdmProxy client classes.
class MockProxyClient : public CdmProxy::Client {
public:
MOCK_METHOD0(NotifyHardwareReset, void());
};
class MockPowerMonitorSource : public base::PowerMonitorSource {
public:
// Use this method to send a power resume event.
void Resume() {
// Due to how ProcessPowerEvent() works, it has to be suspended first to
// resume.
ProcessPowerEvent(SUSPEND_EVENT);
ProcessPowerEvent(RESUME_EVENT);
}
MOCK_METHOD0(Shutdown, void());
MOCK_METHOD0(IsOnBatteryPowerImpl, bool());
};
// The values doesn't matter as long as this is consistently used thruout the
// test.
const CdmProxy::Protocol kTestProtocol = CdmProxy::Protocol::kIntel;
const CdmProxy::Function kTestFunction =
CdmProxy::Function::kIntelNegotiateCryptoSessionKeyExchange;
// TODO(rkuroiwa): Add test cases for KeyType.
const CdmProxy::KeyType kTestKeyType = CdmProxy::KeyType::kDecryptOnly;
const uint32_t kTestFunctionId = 123;
// clang-format off
DEFINE_GUID(CRYPTO_TYPE_GUID,
0x01020304, 0xffee, 0xefba,
0x93, 0xaa, 0x47, 0x77, 0x43, 0xb1, 0x22, 0x98);
// clang-format on
} // namespace
// Class for mocking the callbacks that get passed to the proxy methods.
class CallbackMock {
public:
MOCK_METHOD3(InitializeCallback, CdmProxy::InitializeCB::RunType);
MOCK_METHOD2(ProcessCallback, CdmProxy::ProcessCB::RunType);
MOCK_METHOD3(CreateMediaCryptoSessionCallback,
CdmProxy::CreateMediaCryptoSessionCB::RunType);
MOCK_METHOD1(SetKeyCallback, CdmProxy::SetKeyCB::RunType);
MOCK_METHOD1(RemoveKeyCallback, CdmProxy::RemoveKeyCB::RunType);
};
class D3D11CdmProxyTest : public ::testing::Test {
protected:
void SetUp() override {
std::map<CdmProxy::Function, uint32_t> function_id_map;
function_id_map[kTestFunction] = kTestFunctionId;
// Use NiceMock because we don't care about base::PowerMonitorSource events
// other than calling Resume() directly.
auto mock_power_monitor_source =
std::make_unique<NiceMock<MockPowerMonitorSource>>();
mock_power_monitor_source_ = mock_power_monitor_source.get();
power_monitor_ = std::make_unique<base::PowerMonitor>(
std::move(mock_power_monitor_source));
proxy_ = std::make_unique<D3D11CdmProxy>(CRYPTO_TYPE_GUID, kTestProtocol,
function_id_map);
device_mock_ = CreateD3D11Mock<D3D11DeviceMock>();
video_device_mock_ = CreateD3D11Mock<D3D11VideoDeviceMock>();
video_device1_mock_ = CreateD3D11Mock<D3D11VideoDevice1Mock>();
crypto_session_mock_ = CreateD3D11Mock<D3D11CryptoSessionMock>();
device_context_mock_ = CreateD3D11Mock<D3D11DeviceContextMock>();
video_context_mock_ = CreateD3D11Mock<D3D11VideoContextMock>();
video_context1_mock_ = CreateD3D11Mock<D3D11VideoContext1Mock>();
dxgi_device_ = CreateD3D11Mock<DXGIDevice2Mock>();
dxgi_adapter_ = CreateD3D11Mock<NiceMock<DXGIAdapter3Mock>>();
// These flags are a reasonable subset of flags to get HARDWARE protected
// playback.
content_protection_caps_.Caps =
D3D11_CONTENT_PROTECTION_CAPS_HARDWARE |
D3D11_CONTENT_PROTECTION_CAPS_HARDWARE_PROTECT_UNCOMPRESSED |
D3D11_CONTENT_PROTECTION_CAPS_HARDWARE_PROTECTED_MEMORY_PAGEABLE |
D3D11_CONTENT_PROTECTION_CAPS_HARDWARE_TEARDOWN |
D3D11_CONTENT_PROTECTION_CAPS_HARDWARE_DRM_COMMUNICATION;
// 1 for the mock behavior below for CheckCryptoKeyExchange().
content_protection_caps_.KeyExchangeTypeCount = 1;
// This is arbitrary but 1 is reasonable, meaning doesn't need to be
// aligned.
content_protection_caps_.BlockAlignmentSize = 1;
// This value is arbitrary.
content_protection_caps_.ProtectedMemorySize = 10000000;
OnCallsForInitialize();
proxy_->SetCreateDeviceCallbackForTesting(
base::BindRepeating(&D3D11CreateDeviceMock::Create,
base::Unretained(&create_device_mock_)));
}
// Sets up ON_CALLs for the mock objects. These can be overriden with
// EXPECT_CALLs.
// |content_protection_caps_| should be set.
void OnCallsForInitialize() {
ON_CALL(create_device_mock_,
Create(_, D3D_DRIVER_TYPE_HARDWARE, _, _, _, _, _, _, _, _))
.WillByDefault(
DoAll(SetComPointee<7>(device_mock_.Get()),
SetComPointeeAndReturnOk<9>(device_context_mock_.Get())));
COM_ON_CALL(device_mock_, QueryInterface(IID_ID3D11VideoDevice, _))
.WillByDefault(SetComPointeeAndReturnOk<1>(video_device_mock_.Get()));
COM_ON_CALL(device_mock_, QueryInterface(IID_ID3D11VideoDevice1, _))
.WillByDefault(SetComPointeeAndReturnOk<1>(video_device1_mock_.Get()));
COM_ON_CALL(device_mock_, QueryInterface(IID_IDXGIDevice2, _))
.WillByDefault(SetComPointeeAndReturnOk<1>(dxgi_device_.Get()));
COM_ON_CALL(dxgi_device_, GetParent(IID_IDXGIAdapter3, _))
.WillByDefault(SetComPointeeAndReturnOk<1>(dxgi_adapter_.Get()));
COM_ON_CALL(dxgi_adapter_,
RegisterHardwareContentProtectionTeardownStatusEvent(_, _))
.WillByDefault(DoAll(SaveArg<0>(&teardown_event_), Return(S_OK)));
COM_ON_CALL(device_context_mock_, QueryInterface(IID_ID3D11VideoContext, _))
.WillByDefault(SetComPointeeAndReturnOk<1>(video_context_mock_.Get()));
COM_ON_CALL(device_context_mock_,
QueryInterface(IID_ID3D11VideoContext1, _))
.WillByDefault(SetComPointeeAndReturnOk<1>(video_context1_mock_.Get()));
COM_ON_CALL(
video_device_mock_,
CreateCryptoSession(Pointee(CRYPTO_TYPE_GUID), _,
Pointee(D3D11_KEY_EXCHANGE_HW_PROTECTION), _))
.WillByDefault(SetComPointeeAndReturnOk<3>(crypto_session_mock_.Get()));
COM_ON_CALL(video_device1_mock_, GetCryptoSessionPrivateDataSize(
Pointee(CRYPTO_TYPE_GUID), _, _, _, _))
.WillByDefault(DoAll(SetArgPointee<3>(kPrivateInputSize),
SetArgPointee<4>(kPrivateOutputSize),
Return(S_OK)));
COM_ON_CALL(video_device_mock_, GetContentProtectionCaps(_, _, _))
.WillByDefault(
DoAll(SetArgPointee<2>(content_protection_caps_), Return(S_OK)));
COM_ON_CALL(video_device_mock_, CheckCryptoKeyExchange(_, _, Lt(1u), _))
.WillByDefault(DoAll(SetArgPointee<3>(D3D11_KEY_EXCHANGE_HW_PROTECTION),
Return(S_OK)));
}
// Helper method to do Initialize(). The returned mock objects are accessible
// thru member variables.
void Initialize(CdmProxy::Client* client, CdmProxy::InitializeCB callback) {
EXPECT_CALL(create_device_mock_,
Create(_, D3D_DRIVER_TYPE_HARDWARE, _, _, _, _, _, _, _, _));
COM_EXPECT_CALL(device_mock_, QueryInterface(IID_ID3D11VideoDevice, _))
.Times(AtLeast(1));
COM_EXPECT_CALL(device_mock_, QueryInterface(IID_IDXGIDevice2, _))
.Times(AtLeast(1));
COM_EXPECT_CALL(dxgi_device_, GetParent(IID_IDXGIAdapter3, _))
.Times(AtLeast(1));
COM_EXPECT_CALL(dxgi_adapter_,
RegisterHardwareContentProtectionTeardownStatusEvent(_, _))
.Times(AtLeast(1));
COM_EXPECT_CALL(device_mock_, QueryInterface(IID_ID3D11VideoDevice1, _))
.Times(AtLeast(1));
COM_EXPECT_CALL(device_context_mock_,
QueryInterface(IID_ID3D11VideoContext, _))
.Times(AtLeast(1));
COM_EXPECT_CALL(device_context_mock_,
QueryInterface(IID_ID3D11VideoContext1, _))
.Times(AtLeast(1));
COM_EXPECT_CALL(
video_device_mock_,
CreateCryptoSession(Pointee(CRYPTO_TYPE_GUID), _,
Pointee(D3D11_KEY_EXCHANGE_HW_PROTECTION), _));
COM_EXPECT_CALL(
video_device1_mock_,
GetCryptoSessionPrivateDataSize(Pointee(CRYPTO_TYPE_GUID), _, _, _, _));
COM_EXPECT_CALL(video_device_mock_, GetContentProtectionCaps(_, _, _));
COM_EXPECT_CALL(video_device_mock_,
CheckCryptoKeyExchange(_, _, Lt(1u), _));
proxy_->Initialize(client, std::move(callback));
Mock::VerifyAndClearExpectations(device_mock_.Get());
Mock::VerifyAndClearExpectations(video_device_mock_.Get());
Mock::VerifyAndClearExpectations(video_device1_mock_.Get());
Mock::VerifyAndClearExpectations(crypto_session_mock_.Get());
Mock::VerifyAndClearExpectations(device_context_mock_.Get());
Mock::VerifyAndClearExpectations(video_context_mock_.Get());
Mock::VerifyAndClearExpectations(video_context1_mock_.Get());
}
// Test case where the proxy is initialized and then hardware content
// protection teardown is notified.
void HardwareContentProtectionTeardown() {
base::RunLoop run_loop;
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kOk, _, _));
ASSERT_NO_FATAL_FAILURE(Initialize(
&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
EXPECT_CALL(client_, NotifyHardwareReset());
base::MockCallback<CdmContext::EventCB> event_cb;
auto callback_registration =
proxy_->GetCdmContext()->RegisterEventCB(event_cb.Get());
EXPECT_CALL(event_cb, Run(CdmContext::Event::kHardwareContextLost))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
SetEvent(teardown_event_);
run_loop.Run();
}
MockProxyClient client_;
std::unique_ptr<D3D11CdmProxy> proxy_;
std::unique_ptr<base::PowerMonitor> power_monitor_;
// Owned by power_monitor_. Use this to simulate a power-resume.
MockPowerMonitorSource* mock_power_monitor_source_;
D3D11CreateDeviceMock create_device_mock_;
CallbackMock callback_mock_;
ComPtr<D3D11DeviceMock> device_mock_;
ComPtr<D3D11VideoDeviceMock> video_device_mock_;
ComPtr<D3D11VideoDevice1Mock> video_device1_mock_;
ComPtr<D3D11CryptoSessionMock> crypto_session_mock_;
ComPtr<D3D11DeviceContextMock> device_context_mock_;
ComPtr<D3D11VideoContextMock> video_context_mock_;
ComPtr<D3D11VideoContext1Mock> video_context1_mock_;
ComPtr<DXGIDevice2Mock> dxgi_device_;
ComPtr<NiceMock<DXGIAdapter3Mock>> dxgi_adapter_;
D3D11_VIDEO_CONTENT_PROTECTION_CAPS content_protection_caps_ = {};
// Event captured in Initialize(). Used in tests to notify hardware content
// protection teardown.
HANDLE teardown_event_;
// These size values are arbitrary. Used for mocking
// GetCryptoSessionPrivateDataSize().
const UINT kPrivateInputSize = 10;
const UINT kPrivateOutputSize = 40;
// ObjectWatcher uses SequencedTaskRunnerHandle.
base::test::ScopedTaskEnvironment scoped_task_environment_;
};
// Verifies that if device creation fails, then the call fails.
TEST_F(D3D11CdmProxyTest, FailedToCreateDevice) {
EXPECT_CALL(create_device_mock_, Create(_, _, _, _, _, _, _, _, _, _))
.WillOnce(Return(E_FAIL));
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kFail, _, _));
proxy_->Initialize(&client_,
base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_)));
}
// Initialize() success case.
TEST_F(D3D11CdmProxyTest, Initialize) {
EXPECT_CALL(callback_mock_, InitializeCallback(CdmProxy::Status::kOk, _, _));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
}
// Hardware content protection teardown is notified to the proxy.
// Verify that the client is notified.
TEST_F(D3D11CdmProxyTest, HardwareContentProtectionTeardown) {
EXPECT_NO_FATAL_FAILURE(HardwareContentProtectionTeardown());
}
// Verify that initialization after hardware content protection teardown works..
TEST_F(D3D11CdmProxyTest, HardwareContentProtectionTeardownThenInitialize) {
ASSERT_NO_FATAL_FAILURE(HardwareContentProtectionTeardown());
EXPECT_CALL(callback_mock_, InitializeCallback(CdmProxy::Status::kOk, _, _));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
}
// Verify that failing to register to hardware content protection teardown
// status event results in initialization failure.
TEST_F(D3D11CdmProxyTest, FailedToRegisterForContentProtectionTeardown) {
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kFail, _, _));
COM_EXPECT_CALL(dxgi_adapter_,
RegisterHardwareContentProtectionTeardownStatusEvent(_, _))
.Times(AtLeast(1))
.WillRepeatedly(Return(E_FAIL));
proxy_->Initialize(&client_,
base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_)));
}
// Verify that the client is notified on power suspend.
TEST_F(D3D11CdmProxyTest, PowerResume) {
base::RunLoop run_loop;
EXPECT_CALL(callback_mock_, InitializeCallback(CdmProxy::Status::kOk, _, _));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
EXPECT_CALL(client_, NotifyHardwareReset()).WillOnce(Invoke([&run_loop]() {
run_loop.Quit();
}));
mock_power_monitor_source_->Resume();
run_loop.Run();
}
// IRL power resume is notified and then hardware content protection teardown
// is notified. Make sure that the two notifications don't signal the clients
// more than once (without being reinitialized in between the notifications).
// Note that this test uses QuitWhenIdle(). If both notifications are processed
// this test will run forever.
TEST_F(D3D11CdmProxyTest, PowerResumeAndHardwareContentProtectionTeardown) {
base::RunLoop run_loop;
EXPECT_CALL(callback_mock_, InitializeCallback(CdmProxy::Status::kOk, _, _));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
EXPECT_CALL(client_, NotifyHardwareReset())
.Times(1)
.WillOnce(Invoke([&run_loop]() { run_loop.QuitWhenIdle(); }));
mock_power_monitor_source_->Resume();
SetEvent(teardown_event_);
run_loop.Run();
}
// Verify that if there isn't a power monitor, initialization fails.
TEST_F(D3D11CdmProxyTest, NoPowerMonitor) {
power_monitor_ = nullptr;
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kFail, _, _));
proxy_->Initialize(&client_,
base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_)));
}
// Initialization failure because HW key exchange is not available.
TEST_F(D3D11CdmProxyTest, NoHwKeyExchange) {
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kFail, _, _));
// GUID is set to non-D3D11_KEY_EXCHANGE_HW_PROTECTION, which means no HW key
// exchange.
COM_EXPECT_CALL(video_device_mock_, CheckCryptoKeyExchange(_, _, Lt(1u), _))
.WillOnce(
DoAll(SetArgPointee<3>(D3D11_CRYPTO_TYPE_AES128_CTR), Return(S_OK)));
proxy_->Initialize(&client_,
base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_)));
}
// Verifies that Process() won't work if not initialized.
TEST_F(D3D11CdmProxyTest, ProcessUninitialized) {
// The size nor value here matter, so making non empty non zero vector.
const std::vector<uint8_t> kAnyInput(16, 0xFF);
// Output size is also arbitrary, just has to match with the mock.
const uint32_t kExpectedOutputDataSize = 20;
EXPECT_CALL(callback_mock_, ProcessCallback(CdmProxy::Status::kFail, _));
proxy_->Process(kTestFunction, 0, kAnyInput, kExpectedOutputDataSize,
base::BindOnce(&CallbackMock::ProcessCallback,
base::Unretained(&callback_mock_)));
}
// Verifies that using a crypto session that is not reported will fail.
TEST_F(D3D11CdmProxyTest, ProcessInvalidCryptoSessionID) {
uint32_t crypto_session_id = 0;
EXPECT_CALL(callback_mock_, InitializeCallback(CdmProxy::Status::kOk, _, _))
.WillOnce(SaveArg<2>(&crypto_session_id));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
Mock::VerifyAndClearExpectations(&callback_mock_);
// The size nor value here matter, so making non empty non zero vector.
const std::vector<uint8_t> kAnyInput(16, 0xFF);
// Output size is also arbitrary, just has to match with the mock.
const uint32_t kExpectedOutputDataSize = 20;
EXPECT_CALL(callback_mock_, ProcessCallback(CdmProxy::Status::kFail, _));
// Use a crypto session ID that hasn't been reported.
proxy_->Process(kTestFunction, crypto_session_id + 1, kAnyInput,
kExpectedOutputDataSize,
base::BindOnce(&CallbackMock::ProcessCallback,
base::Unretained(&callback_mock_)));
}
// Matcher for checking whether the structure passed to
// NegotiateCryptoSessionKeyExchange has the expected values.
MATCHER_P2(MatchesKeyExchangeStructure, expected, input_struct_size, "") {
D3D11_KEY_EXCHANGE_HW_PROTECTION_DATA* actual =
static_cast<D3D11_KEY_EXCHANGE_HW_PROTECTION_DATA*>(arg);
if (expected->HWProtectionFunctionID != actual->HWProtectionFunctionID) {
*result_listener << "function IDs mismatch. Expected "
<< expected->HWProtectionFunctionID << " actual "
<< actual->HWProtectionFunctionID;
return false;
}
D3D11_KEY_EXCHANGE_HW_PROTECTION_INPUT_DATA* expected_input_data =
expected->pInputData;
D3D11_KEY_EXCHANGE_HW_PROTECTION_INPUT_DATA* actual_input_data =
actual->pInputData;
if (memcmp(expected_input_data, actual_input_data, input_struct_size) != 0) {
*result_listener
<< "D3D11_KEY_EXCHANGE_HW_PROTECTION_INPUT_DATA don't match.";
return false;
}
D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA* expected_output_data =
expected->pOutputData;
D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA* actual_output_data =
actual->pOutputData;
// Don't check that pbOutput field. It's filled by the callee.
if (expected_output_data->PrivateDataSize !=
actual_output_data->PrivateDataSize) {
*result_listener << "D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA::"
"PrivateDataSize don't match. Expected "
<< expected_output_data->PrivateDataSize << " actual "
<< actual_output_data->PrivateDataSize;
return false;
}
if (expected_output_data->HWProtectionDataSize !=
actual_output_data->HWProtectionDataSize) {
*result_listener << "D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA::"
"HWProtectionDataSize don't match. Expected "
<< expected_output_data->HWProtectionDataSize << " actual "
<< actual_output_data->HWProtectionDataSize;
return false;
}
if (expected_output_data->TransportTime !=
actual_output_data->TransportTime) {
*result_listener << "D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA::"
"TransportTime don't match. Expected "
<< expected_output_data->TransportTime << " actual "
<< actual_output_data->TransportTime;
return false;
}
if (expected_output_data->ExecutionTime !=
actual_output_data->ExecutionTime) {
*result_listener << "D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA::"
"ExecutionTime don't match. Expected "
<< expected_output_data->ExecutionTime << " actual "
<< actual_output_data->ExecutionTime;
return false;
}
if (expected_output_data->MaxHWProtectionDataSize !=
actual_output_data->MaxHWProtectionDataSize) {
*result_listener << "D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA::"
"MaxHWProtectionDataSize don't match. Expected "
<< expected_output_data->MaxHWProtectionDataSize
<< " actual "
<< actual_output_data->MaxHWProtectionDataSize;
return false;
}
return true;
}
// Verifies that Process() works.
TEST_F(D3D11CdmProxyTest, Process) {
uint32_t crypto_session_id = 0;
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kOk, kTestProtocol, _))
.WillOnce(SaveArg<2>(&crypto_session_id));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
Mock::VerifyAndClearExpectations(&callback_mock_);
// The size nor value here matter, so making non empty non zero vector.
const std::vector<uint8_t> kAnyInput(16, 0xFF);
// Output size is also arbitrary, just has to match with the mock.
const uint32_t kExpectedOutputDataSize = 20;
const uint32_t input_structure_size =
sizeof(D3D11_KEY_EXCHANGE_HW_PROTECTION_INPUT_DATA) - 4 +
kAnyInput.size();
const uint32_t output_structure_size =
sizeof(D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA) - 4 +
kExpectedOutputDataSize;
std::unique_ptr<uint8_t[]> input_data_raw(new uint8_t[input_structure_size]);
std::unique_ptr<uint8_t[]> output_data_raw(
new uint8_t[output_structure_size]);
D3D11_KEY_EXCHANGE_HW_PROTECTION_INPUT_DATA* input_data =
reinterpret_cast<D3D11_KEY_EXCHANGE_HW_PROTECTION_INPUT_DATA*>(
input_data_raw.get());
D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA* output_data =
reinterpret_cast<D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA*>(
output_data_raw.get());
D3D11_KEY_EXCHANGE_HW_PROTECTION_DATA expected_key_exchange_data = {};
expected_key_exchange_data.HWProtectionFunctionID = kTestFunctionId;
expected_key_exchange_data.pInputData = input_data;
expected_key_exchange_data.pOutputData = output_data;
input_data->PrivateDataSize = kPrivateInputSize;
input_data->HWProtectionDataSize = 0;
memcpy(input_data->pbInput, kAnyInput.data(), kAnyInput.size());
output_data->PrivateDataSize = kPrivateOutputSize;
output_data->HWProtectionDataSize = 0;
output_data->TransportTime = 0;
output_data->ExecutionTime = 0;
output_data->MaxHWProtectionDataSize = kExpectedOutputDataSize;
// The value does not matter, so making non zero vector.
std::vector<uint8_t> test_output_data(kExpectedOutputDataSize, 0xAA);
EXPECT_CALL(callback_mock_,
ProcessCallback(CdmProxy::Status::kOk, test_output_data));
auto set_test_output_data = [&test_output_data](void* output) {
D3D11_KEY_EXCHANGE_HW_PROTECTION_DATA* kex_struct =
static_cast<D3D11_KEY_EXCHANGE_HW_PROTECTION_DATA*>(output);
memcpy(kex_struct->pOutputData->pbOutput, test_output_data.data(),
test_output_data.size());
};
COM_EXPECT_CALL(video_context_mock_,
NegotiateCryptoSessionKeyExchange(
_, sizeof(expected_key_exchange_data),
MatchesKeyExchangeStructure(&expected_key_exchange_data,
input_structure_size)))
.WillOnce(DoAll(WithArgs<2>(Invoke(set_test_output_data)), Return(S_OK)));
proxy_->Process(kTestFunction, crypto_session_id, kAnyInput,
kExpectedOutputDataSize,
base::BindOnce(&CallbackMock::ProcessCallback,
base::Unretained(&callback_mock_)));
}
TEST_F(D3D11CdmProxyTest, CreateMediaCryptoSessionUninitialized) {
// The size nor value here matter, so making non empty non zero vector.
const std::vector<uint8_t> kAnyInput(16, 0xFF);
EXPECT_CALL(callback_mock_,
CreateMediaCryptoSessionCallback(CdmProxy::Status::kFail, _, _));
proxy_->CreateMediaCryptoSession(
kAnyInput, base::BindOnce(&CallbackMock::CreateMediaCryptoSessionCallback,
base::Unretained(&callback_mock_)));
}
// Tests the case where no extra data is specified. This is a success case.
TEST_F(D3D11CdmProxyTest, CreateMediaCryptoSessionNoExtraData) {
uint32_t crypto_session_id_from_initialize = 0;
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kOk, kTestProtocol, _))
.WillOnce(SaveArg<2>(&crypto_session_id_from_initialize));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
Mock::VerifyAndClearExpectations(&callback_mock_);
// Expect a new crypto session.
EXPECT_CALL(callback_mock_, CreateMediaCryptoSessionCallback(
CdmProxy::Status::kOk,
Ne(crypto_session_id_from_initialize), _));
auto media_crypto_session_mock = CreateD3D11Mock<D3D11CryptoSessionMock>();
COM_EXPECT_CALL(video_device_mock_,
CreateCryptoSession(Pointee(CRYPTO_TYPE_GUID), _,
Pointee(CRYPTO_TYPE_GUID), _))
.WillOnce(SetComPointeeAndReturnOk<3>(media_crypto_session_mock.Get()));
COM_EXPECT_CALL(video_context1_mock_, GetDataForNewHardwareKey(_, _, _, _))
.Times(0);
COM_EXPECT_CALL(video_context1_mock_,
CheckCryptoSessionStatus(media_crypto_session_mock.Get(), _))
.WillOnce(DoAll(SetArgPointee<1>(D3D11_CRYPTO_SESSION_STATUS_OK),
Return(S_OK)));
proxy_->CreateMediaCryptoSession(
std::vector<uint8_t>(),
base::BindOnce(&CallbackMock::CreateMediaCryptoSessionCallback,
base::Unretained(&callback_mock_)));
}
// |arg| is void*. This casts the pointer to uint8_t* and checks whether they
// match.
MATCHER_P(CastedToUint8Are, expected, "") {
const uint8_t* actual = static_cast<const uint8_t*>(arg);
for (size_t i = 0; i < expected.size(); ++i) {
if (actual[i] != expected[i]) {
*result_listener << "Mismatch at element " << i;
return false;
}
}
return true;
}
// Verifies that extra data is used when creating a media crypto session.
TEST_F(D3D11CdmProxyTest, CreateMediaCryptoSessionWithExtraData) {
uint32_t crypto_session_id_from_initialize = 0;
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kOk, kTestProtocol, _))
.WillOnce(SaveArg<2>(&crypto_session_id_from_initialize));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
Mock::VerifyAndClearExpectations(&callback_mock_);
// Expect a new crypto session.
EXPECT_CALL(callback_mock_, CreateMediaCryptoSessionCallback(
CdmProxy::Status::kOk,
Ne(crypto_session_id_from_initialize), _));
auto media_crypto_session_mock = CreateD3D11Mock<D3D11CryptoSessionMock>();
COM_EXPECT_CALL(video_device_mock_,
CreateCryptoSession(Pointee(CRYPTO_TYPE_GUID), _,
Pointee(CRYPTO_TYPE_GUID), _))
.WillOnce(SetComPointeeAndReturnOk<3>(media_crypto_session_mock.Get()));
// The size nor value here matter, so making non empty non zero vector.
const std::vector<uint8_t> kAnyInput(16, 0xFF);
const uint64_t kAnyOutputData = 23298u;
COM_EXPECT_CALL(video_context1_mock_,
GetDataForNewHardwareKey(media_crypto_session_mock.Get(),
kAnyInput.size(),
CastedToUint8Are(kAnyInput), _))
.WillOnce(DoAll(SetArgPointee<3>(kAnyOutputData), Return(S_OK)));
COM_EXPECT_CALL(video_context1_mock_,
CheckCryptoSessionStatus(media_crypto_session_mock.Get(), _))
.WillOnce(DoAll(SetArgPointee<1>(D3D11_CRYPTO_SESSION_STATUS_OK),
Return(S_OK)));
proxy_->CreateMediaCryptoSession(
kAnyInput, base::BindOnce(&CallbackMock::CreateMediaCryptoSessionCallback,
base::Unretained(&callback_mock_)));
}
// Verify that GetCdmContext() is implemented and does not return null.
TEST_F(D3D11CdmProxyTest, GetCdmContext) {
base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
ASSERT_TRUE(context);
}
TEST_F(D3D11CdmProxyTest, GetCdmProxyContext) {
base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
ASSERT_TRUE(context);
ASSERT_TRUE(context->GetCdmProxyContext());
}
// No keys are set.
TEST_F(D3D11CdmProxyTest, GetD3D11DecryptContextNoKey) {
base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
ASSERT_TRUE(context);
CdmProxyContext* proxy_context = context->GetCdmProxyContext();
auto decrypt_context =
proxy_context->GetD3D11DecryptContext(kTestKeyType, "");
EXPECT_FALSE(decrypt_context);
}
// A key is set but no keys for the key type requested.
TEST_F(D3D11CdmProxyTest, GetD3D11DecryptContextNoKeyForKeyType) {
uint32_t crypto_session_id_from_initialize = 0;
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kOk, kTestProtocol, _))
.WillOnce(SaveArg<2>(&crypto_session_id_from_initialize));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
Mock::VerifyAndClearExpectations(&callback_mock_);
const std::vector<uint8_t> kAnyBlob = {0x01, 0x4f, 0x83};
EXPECT_CALL(callback_mock_, SetKeyCallback(CdmProxy::Status::kOk));
proxy_->SetKey(crypto_session_id_from_initialize, kAnyBlob,
CdmProxy::KeyType::kDecryptAndDecode, kAnyBlob,
base::BindOnce(&CallbackMock::SetKeyCallback,
base::Unretained(&callback_mock_)));
base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
CdmProxyContext* proxy_context = context->GetCdmProxyContext();
auto decrypt_context = proxy_context->GetD3D11DecryptContext(
CdmProxy::KeyType::kDecryptOnly,
std::string(kAnyBlob.begin(), kAnyBlob.end()));
EXPECT_FALSE(decrypt_context);
}
// Verifies that keys are set and is accessible with a getter.
TEST_F(D3D11CdmProxyTest, SetKeyAndGetDecryptContext) {
base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
ASSERT_TRUE(context);
CdmProxyContext* proxy_context = context->GetCdmProxyContext();
uint32_t crypto_session_id_from_initialize = 0;
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kOk, kTestProtocol, _))
.WillOnce(SaveArg<2>(&crypto_session_id_from_initialize));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
Mock::VerifyAndClearExpectations(&callback_mock_);
std::vector<uint8_t> kKeyId = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
};
std::vector<uint8_t> kKeyBlob = {
0xab, 0x01, 0x20, 0xd3, 0xee, 0x05, 0x99, 0x87,
0xff, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x7F,
};
base::MockCallback<CdmContext::EventCB> event_cb;
auto callback_registration = context->RegisterEventCB(event_cb.Get());
EXPECT_CALL(event_cb, Run(CdmContext::Event::kHasAdditionalUsableKey));
EXPECT_CALL(callback_mock_, SetKeyCallback(CdmProxy::Status::kOk));
proxy_->SetKey(crypto_session_id_from_initialize, kKeyId, kTestKeyType,
kKeyBlob,
base::BindOnce(&CallbackMock::SetKeyCallback,
base::Unretained(&callback_mock_)));
// |event_cb| is posted. Run the loop to make sure it's fired.
base::RunLoop().RunUntilIdle();
std::string key_id_str(kKeyId.begin(), kKeyId.end());
auto decrypt_context =
proxy_context->GetD3D11DecryptContext(kTestKeyType, key_id_str);
ASSERT_TRUE(decrypt_context);
EXPECT_TRUE(decrypt_context->crypto_session)
<< "Crypto session should not be null.";
const uint8_t* key_blob =
reinterpret_cast<const uint8_t*>(decrypt_context->key_blob);
EXPECT_EQ(kKeyBlob, std::vector<uint8_t>(
key_blob, key_blob + decrypt_context->key_blob_size));
EXPECT_EQ(CRYPTO_TYPE_GUID, decrypt_context->key_info_guid);
}
// Verify that the keys are not accessible via CdmProxyContext, after a
// teardown..
TEST_F(D3D11CdmProxyTest, ClearKeysAfterHardwareContentProtectionTeardown) {
base::RunLoop run_loop;
uint32_t crypto_session_id_from_initialize = 0;
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kOk, kTestProtocol, _))
.WillOnce(SaveArg<2>(&crypto_session_id_from_initialize));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
Mock::VerifyAndClearExpectations(&callback_mock_);
std::vector<uint8_t> kKeyId = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
};
std::vector<uint8_t> kKeyBlob = {
0xab, 0x01, 0x20, 0xd3, 0xee, 0x05, 0x99, 0x87,
0xff, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x7F,
};
EXPECT_CALL(callback_mock_, SetKeyCallback(CdmProxy::Status::kOk));
proxy_->SetKey(crypto_session_id_from_initialize, kKeyId, kTestKeyType,
kKeyBlob,
base::BindOnce(&CallbackMock::SetKeyCallback,
base::Unretained(&callback_mock_)));
EXPECT_CALL(client_, NotifyHardwareReset()).WillOnce(Invoke([&run_loop]() {
run_loop.Quit();
}));
SetEvent(teardown_event_);
run_loop.Run();
base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
ASSERT_TRUE(context);
CdmProxyContext* proxy_context = context->GetCdmProxyContext();
std::string key_id_str(kKeyId.begin(), kKeyId.end());
auto decrypt_context =
proxy_context->GetD3D11DecryptContext(kTestKeyType, key_id_str);
ASSERT_FALSE(decrypt_context);
}
// Verify that removing a key works.
TEST_F(D3D11CdmProxyTest, RemoveKey) {
base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
ASSERT_TRUE(context);
CdmProxyContext* proxy_context = context->GetCdmProxyContext();
uint32_t crypto_session_id_from_initialize = 0;
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kOk, kTestProtocol, _))
.WillOnce(SaveArg<2>(&crypto_session_id_from_initialize));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
Mock::VerifyAndClearExpectations(&callback_mock_);
std::vector<uint8_t> kKeyId = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
};
std::vector<uint8_t> kKeyBlob = {
0xab, 0x01, 0x20, 0xd3, 0xee, 0x05, 0x99, 0x87,
0xff, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x7F,
};
EXPECT_CALL(callback_mock_, SetKeyCallback(CdmProxy::Status::kOk));
EXPECT_CALL(callback_mock_, RemoveKeyCallback(CdmProxy::Status::kOk));
proxy_->SetKey(crypto_session_id_from_initialize, kKeyId, kTestKeyType,
kKeyBlob,
base::BindOnce(&CallbackMock::SetKeyCallback,
base::Unretained(&callback_mock_)));
proxy_->RemoveKey(crypto_session_id_from_initialize, kKeyId,
base::BindOnce(&CallbackMock::RemoveKeyCallback,
base::Unretained(&callback_mock_)));
std::string keyblob_str(kKeyId.begin(), kKeyId.end());
auto decrypt_context =
proxy_context->GetD3D11DecryptContext(kTestKeyType, keyblob_str);
EXPECT_FALSE(decrypt_context);
}
// Calling SetKey() and RemoveKey() for non-existent crypto session should
// fail but not crash.
TEST_F(D3D11CdmProxyTest, SetRemoveKeyWrongCryptoSessionId) {
const uint32_t kAnyCryptoSessionId = 0x9238;
const std::vector<uint8_t> kEmpty;
EXPECT_CALL(callback_mock_, RemoveKeyCallback(CdmProxy::Status::kFail));
EXPECT_CALL(callback_mock_, SetKeyCallback(CdmProxy::Status::kFail));
proxy_->RemoveKey(kAnyCryptoSessionId, kEmpty,
base::BindOnce(&CallbackMock::RemoveKeyCallback,
base::Unretained(&callback_mock_)));
proxy_->SetKey(kAnyCryptoSessionId, kEmpty, kTestKeyType, kEmpty,
base::BindOnce(&CallbackMock::SetKeyCallback,
base::Unretained(&callback_mock_)));
}
TEST_F(D3D11CdmProxyTest, ProxyInvalidationInvalidatesCdmContext) {
base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
EXPECT_TRUE(context);
proxy_.reset();
EXPECT_FALSE(context);
}
} // namespace media