blob: b188fc5478e630c0768332b147773b2a06a367b7 [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.
// TODO(crbug.com/787657): Handle hardware key reset and notify the client.
#include "media/gpu/windows/d3d11_cdm_proxy.h"
#include <initguid.h>
#include "base/bind.h"
#include "base/logging.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_observer.h"
#include "base/stl_util.h"
#include "base/synchronization/waitable_event.h"
#include "base/win/object_watcher.h"
#include "media/base/callback_registry.h"
#include "media/base/cdm_context.h"
#include "media/cdm/cdm_proxy_context.h"
#include "media/gpu/windows/d3d11_decryptor.h"
namespace media {
namespace {
// Checks whether there is a hardware protected key exhange method.
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn894125(v=vs.85).aspx
// The key exhange capabilities are checked using these.
// https://msdn.microsoft.com/en-us/library/windows/desktop/hh447640%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
// https://msdn.microsoft.com/en-us/library/windows/desktop/hh447782(v=vs.85).aspx
bool CanDoHardwareProtectedKeyExchange(
Microsoft::WRL::ComPtr<ID3D11VideoDevice> video_device,
const GUID& crypto_type) {
D3D11_VIDEO_CONTENT_PROTECTION_CAPS caps = {};
HRESULT hresult = video_device->GetContentProtectionCaps(
&crypto_type, &D3D11_DECODER_PROFILE_H264_VLD_NOFGT, &caps);
if (FAILED(hresult)) {
DVLOG(1) << "Failed to get content protection caps.";
return false;
}
for (uint32_t i = 0; i < caps.KeyExchangeTypeCount; ++i) {
GUID kex_guid = {};
hresult = video_device->CheckCryptoKeyExchange(
&crypto_type, &D3D11_DECODER_PROFILE_H264_VLD_NOFGT, i, &kex_guid);
if (FAILED(hresult)) {
DVLOG(1) << "Failed to get key exchange GUID";
return false;
}
if (kex_guid == D3D11_KEY_EXCHANGE_HW_PROTECTION)
return true;
}
DVLOG(1) << "Hardware key exchange is not supported.";
return false;
}
class D3D11CdmProxyContext : public CdmProxyContext {
public:
explicit D3D11CdmProxyContext(const GUID& key_info_guid)
: key_info_guid_(key_info_guid) {}
~D3D11CdmProxyContext() override = default;
// The pointers are owned by the caller.
void SetKey(ID3D11CryptoSession* crypto_session,
const std::vector<uint8_t>& key_id,
CdmProxy::KeyType key_type,
const std::vector<uint8_t>& key_blob) {
std::string key_id_str(key_id.begin(), key_id.end());
KeyInfo key_info(crypto_session, key_blob);
// Note that this would overwrite an entry but it is completely valid, e.g.
// updating the keyblob due to a configuration change.
key_info_map_[key_id_str][key_type] = std::move(key_info);
}
void RemoveKey(ID3D11CryptoSession* crypto_session,
const std::vector<uint8_t>& key_id) {
// There's no need for a keytype for Remove() at the moment, because it's
// used for completely removing keys associated to |key_id|.
std::string key_id_str(key_id.begin(), key_id.end());
key_info_map_.erase(key_id_str);
}
// Removes all keys from the context.
void RemoveAllKeys() { key_info_map_.clear(); }
// CdmProxyContext implementation.
base::Optional<D3D11DecryptContext> GetD3D11DecryptContext(
CdmProxy::KeyType key_type,
const std::string& key_id) override {
auto key_id_find_it = key_info_map_.find(key_id);
if (key_id_find_it == key_info_map_.end())
return base::nullopt;
auto& key_type_to_key_info = key_id_find_it->second;
auto key_type_find_it = key_type_to_key_info.find(key_type);
if (key_type_find_it == key_type_to_key_info.end())
return base::nullopt;
auto& key_info = key_type_find_it->second;
D3D11DecryptContext context = {};
context.crypto_session = key_info.crypto_session;
context.key_blob = key_info.key_blob.data();
context.key_blob_size = key_info.key_blob.size();
context.key_info_guid = key_info_guid_;
return context;
}
private:
// A structure to keep the data passed to SetKey(). See documentation for
// SetKey() for what the fields mean.
struct KeyInfo {
KeyInfo() = default;
KeyInfo(ID3D11CryptoSession* crypto_session, std::vector<uint8_t> key_blob)
: crypto_session(crypto_session), key_blob(std::move(key_blob)) {}
KeyInfo(const KeyInfo&) = default;
~KeyInfo() = default;
ID3D11CryptoSession* crypto_session;
std::vector<uint8_t> key_blob;
};
// Maps key ID -> key type -> KeyInfo.
// The key ID's type is string, which is converted from |key_id| in
// SetKey(). It's better to use string here rather than convert
// vector<uint8_t> to string every time in GetD3D11DecryptContext() because
// in most cases it would be called more often than SetKey() and RemoveKey()
// combined.
std::map<std::string, std::map<CdmProxy::KeyType, KeyInfo>> key_info_map_;
const GUID key_info_guid_;
DISALLOW_COPY_AND_ASSIGN(D3D11CdmProxyContext);
};
} // namespace
// Watches for any content protection teardown events.
// If the instance has been started for watching, the destructor will
// automatically stop watching.
class D3D11CdmProxy::HardwareEventWatcher
: public base::win::ObjectWatcher::Delegate,
public base::PowerObserver {
public:
~HardwareEventWatcher() override;
// |teardown_callback| is called on the current sequence.
// Returns an instance if it starts watching for events, otherwise returns
// nullptr.
static std::unique_ptr<HardwareEventWatcher> Create(
ComPtr<ID3D11Device> device,
base::RepeatingClosure teardown_callback);
private:
HardwareEventWatcher(ComPtr<ID3D11Device> device,
base::RepeatingClosure teardown_callback);
// Start watching for events.
bool StartWatching();
// Registers for hardware content protection teardown events.
// Return true on success.
bool RegisterHardwareContentProtectionTeardown(ComPtr<ID3D11Device> device);
// Regiesters for power events, specifically power suspend event.
// Returns true on success.
bool RegisterPowerEvents();
// base::win::ObjectWatcher::Delegate implementation.
void OnObjectSignaled(HANDLE object) override;
// base::PowerObserver implementation. Other power events are not relevant to
// this class.
void OnSuspend() override;
// Stops watching for events. Good for clean up.
void StopWatching();
// IDXGIAdapter3::RegisterHardwareContentProtectionTeardownStatusEvent
// allows watching for teardown events. It is queried thru the following
// Devices.
ComPtr<ID3D11Device> device_;
ComPtr<IDXGIDevice2> dxgi_device_;
ComPtr<IDXGIAdapter3> dxgi_adapter_;
// Cookie, event, and watcher used for watching events from
// RegisterHardwareContentProtectionTeardownStatusEvent.
DWORD teardown_event_cookie_ = 0u;
base::WaitableEvent content_protection_teardown_event_;
base::RepeatingClosure teardown_callback_;
base::win::ObjectWatcher teardown_status_watcher_;
};
class D3D11CdmContext : public CdmContext {
public:
explicit D3D11CdmContext(const GUID& key_info_guid)
: cdm_proxy_context_(key_info_guid), weak_factory_(this) {}
~D3D11CdmContext() override = default;
// The pointers are owned by the caller.
void SetKey(ID3D11CryptoSession* crypto_session,
const std::vector<uint8_t>& key_id,
CdmProxy::KeyType key_type,
const std::vector<uint8_t>& key_blob) {
cdm_proxy_context_.SetKey(crypto_session, key_id, key_type, key_blob);
event_callbacks_.Notify(Event::kHasAdditionalUsableKey);
}
void RemoveKey(ID3D11CryptoSession* crypto_session,
const std::vector<uint8_t>& key_id) {
cdm_proxy_context_.RemoveKey(crypto_session, key_id);
}
// Notifies of hardware reset.
void OnHardwareReset() {
cdm_proxy_context_.RemoveAllKeys();
event_callbacks_.Notify(Event::kHardwareContextLost);
}
base::WeakPtr<D3D11CdmContext> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
// CdmContext implementation.
std::unique_ptr<CallbackRegistration> RegisterEventCB(
EventCB event_cb) override {
return event_callbacks_.Register(std::move(event_cb));
}
CdmProxyContext* GetCdmProxyContext() override { return &cdm_proxy_context_; }
Decryptor* GetDecryptor() override {
if (!decryptor_)
decryptor_.reset(new D3D11Decryptor(&cdm_proxy_context_));
return decryptor_.get();
}
private:
D3D11CdmProxyContext cdm_proxy_context_;
std::unique_ptr<D3D11Decryptor> decryptor_;
CallbackRegistry<EventCB::RunType> event_callbacks_;
base::WeakPtrFactory<D3D11CdmContext> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(D3D11CdmContext);
};
D3D11CdmProxy::D3D11CdmProxy(const GUID& crypto_type,
CdmProxy::Protocol protocol,
const FunctionIdMap& function_id_map)
: crypto_type_(crypto_type),
protocol_(protocol),
function_id_map_(function_id_map),
cdm_context_(std::make_unique<D3D11CdmContext>(crypto_type)),
create_device_func_(base::BindRepeating(D3D11CreateDevice)),
weak_factory_(this) {}
D3D11CdmProxy::~D3D11CdmProxy() {}
base::WeakPtr<CdmContext> D3D11CdmProxy::GetCdmContext() {
return cdm_context_->GetWeakPtr();
}
void D3D11CdmProxy::Initialize(Client* client, InitializeCB init_cb) {
DCHECK(client);
auto failed = [this, &init_cb]() {
// The value doesn't matter as it shouldn't be used on a failure.
const uint32_t kFailedCryptoSessionId = 0xFF;
std::move(init_cb).Run(Status::kFail, protocol_, kFailedCryptoSessionId);
};
if (initialized_) {
failed();
NOTREACHED() << "CdmProxy should not be initialized more than once.";
return;
}
client_ = client;
const D3D_FEATURE_LEVEL feature_levels[] = {D3D_FEATURE_LEVEL_11_1};
HRESULT hresult = create_device_func_.Run(
nullptr, // No adapter.
D3D_DRIVER_TYPE_HARDWARE, nullptr, // No software rasterizer.
0, // flags, none.
feature_levels, base::size(feature_levels), D3D11_SDK_VERSION,
device_.GetAddressOf(), nullptr, device_context_.GetAddressOf());
if (FAILED(hresult)) {
DLOG(ERROR) << "Failed to create the D3D11Device:" << hresult;
failed();
return;
}
// TODO(rkuroiwa): This should be registered iff
// D3D11_CONTENT_PROTECTION_CAPS_HARDWARE_TEARDOWN is set in the capabilities.
hardware_event_watcher_ = HardwareEventWatcher::Create(
device_, base::BindRepeating(
&D3D11CdmProxy::NotifyHardwareContentProtectionTeardown,
weak_factory_.GetWeakPtr()));
if (!hardware_event_watcher_) {
DLOG(ERROR)
<< "Failed to start waching for content protection teardown events.";
failed();
return;
}
hresult = device_.CopyTo(video_device_.GetAddressOf());
if (FAILED(hresult)) {
DLOG(ERROR) << "Failed to get ID3D11VideoDevice: " << hresult;
failed();
return;
}
if (!CanDoHardwareProtectedKeyExchange(video_device_, crypto_type_)) {
DLOG(ERROR) << "Cannot do hardware protected key exchange.";
failed();
return;
}
hresult = device_context_.CopyTo(video_context_.GetAddressOf());
if (FAILED(hresult)) {
DLOG(ERROR) << "Failed to get ID3D11VideoContext: " << hresult;
failed();
return;
}
hresult = device_.CopyTo(video_device1_.GetAddressOf());
if (FAILED(hresult)) {
DLOG(ERROR) << "Failed to get ID3D11VideoDevice1: " << hresult;
failed();
return;
}
hresult = device_context_.CopyTo(video_context1_.GetAddressOf());
if (FAILED(hresult)) {
DLOG(ERROR) << "Failed to get ID3D11VideoContext1: " << hresult;
failed();
return;
}
ComPtr<ID3D11CryptoSession> csme_crypto_session;
hresult = video_device_->CreateCryptoSession(
&crypto_type_, &D3D11_DECODER_PROFILE_H264_VLD_NOFGT,
&D3D11_KEY_EXCHANGE_HW_PROTECTION, csme_crypto_session.GetAddressOf());
if (FAILED(hresult)) {
DLOG(ERROR) << "Failed to Create CryptoSession: " << hresult;
failed();
return;
}
hresult = video_device1_->GetCryptoSessionPrivateDataSize(
&crypto_type_, &D3D11_DECODER_PROFILE_H264_VLD_NOFGT,
&D3D11_KEY_EXCHANGE_HW_PROTECTION, &private_input_size_,
&private_output_size_);
if (FAILED(hresult)) {
DLOG(ERROR) << "Failed to get private data sizes: " << hresult;
failed();
return;
}
const uint32_t crypto_session_id = next_crypto_session_id_++;
crypto_session_map_[crypto_session_id] = std::move(csme_crypto_session);
initialized_ = true;
std::move(init_cb).Run(Status::kOk, protocol_, crypto_session_id);
}
void D3D11CdmProxy::Process(Function function,
uint32_t crypto_session_id,
const std::vector<uint8_t>& input_data_vec,
uint32_t expected_output_data_size,
ProcessCB process_cb) {
auto failed = [&process_cb]() {
std::move(process_cb).Run(Status::kFail, std::vector<uint8_t>());
};
if (!initialized_) {
DLOG(ERROR) << "Not initialied.";
failed();
return;
}
auto function_id_it = function_id_map_.find(function);
if (function_id_it == function_id_map_.end()) {
DLOG(ERROR) << "Unrecognized function: " << static_cast<int>(function);
failed();
return;
}
auto crypto_session_it = crypto_session_map_.find(crypto_session_id);
if (crypto_session_it == crypto_session_map_.end()) {
DLOG(ERROR) << "Cannot find crypto session with ID " << crypto_session_id;
failed();
return;
}
ComPtr<ID3D11CryptoSession>& crypto_session = crypto_session_it->second;
D3D11_KEY_EXCHANGE_HW_PROTECTION_DATA key_exchange_data = {};
key_exchange_data.HWProtectionFunctionID = function_id_it->second;
// Because D3D11_KEY_EXCHANGE_HW_PROTECTION_INPUT_DATA and
// D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA are variable size structures,
// uint8 array are allocated and casted to each type.
// -4 for the "BYTE pbInput[4]" field.
std::unique_ptr<uint8_t[]> input_data_raw(
new uint8_t[sizeof(D3D11_KEY_EXCHANGE_HW_PROTECTION_INPUT_DATA) - 4 +
input_data_vec.size()]);
std::unique_ptr<uint8_t[]> output_data_raw(
new uint8_t[sizeof(D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA) - 4 +
expected_output_data_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());
key_exchange_data.pInputData = input_data;
key_exchange_data.pOutputData = output_data;
input_data->PrivateDataSize = private_input_size_;
input_data->HWProtectionDataSize = 0;
memcpy(input_data->pbInput, input_data_vec.data(), input_data_vec.size());
output_data->PrivateDataSize = private_output_size_;
output_data->HWProtectionDataSize = 0;
output_data->TransportTime = 0;
output_data->ExecutionTime = 0;
output_data->MaxHWProtectionDataSize = expected_output_data_size;
HRESULT hresult = video_context_->NegotiateCryptoSessionKeyExchange(
crypto_session.Get(), sizeof(key_exchange_data), &key_exchange_data);
if (FAILED(hresult)) {
failed();
return;
}
std::move(process_cb)
.Run(Status::kOk, std::vector<uint8_t>(
output_data->pbOutput,
output_data->pbOutput + expected_output_data_size));
return;
}
void D3D11CdmProxy::CreateMediaCryptoSession(
const std::vector<uint8_t>& input_data,
CreateMediaCryptoSessionCB create_media_crypto_session_cb) {
auto failed = [&create_media_crypto_session_cb]() {
const uint32_t kInvalidSessionId = 0;
const uint64_t kNoOutputData = 0;
std::move(create_media_crypto_session_cb)
.Run(Status::kFail, kInvalidSessionId, kNoOutputData);
};
if (!initialized_) {
DLOG(ERROR) << "Not initialized.";
failed();
return;
}
ComPtr<ID3D11CryptoSession> media_crypto_session;
HRESULT hresult = video_device_->CreateCryptoSession(
&crypto_type_, &D3D11_DECODER_PROFILE_H264_VLD_NOFGT, &crypto_type_,
media_crypto_session.GetAddressOf());
if (FAILED(hresult)) {
DLOG(ERROR) << "Failed to create a crypto session: " << hresult;
failed();
return;
}
// Don't do CheckCryptoSessionStatus() yet. The status may be something like
// CONTEXT_LOST because GetDataForNewHardwareKey() is not called yet.
uint64_t output_data = 0;
if (!input_data.empty()) {
hresult = video_context1_->GetDataForNewHardwareKey(
media_crypto_session.Get(), input_data.size(), input_data.data(),
&output_data);
if (FAILED(hresult)) {
DLOG(ERROR) << "Failed to establish hardware session: " << hresult;
failed();
return;
}
}
D3D11_CRYPTO_SESSION_STATUS crypto_session_status = {};
hresult = video_context1_->CheckCryptoSessionStatus(
media_crypto_session.Get(), &crypto_session_status);
if (FAILED(hresult) ||
crypto_session_status != D3D11_CRYPTO_SESSION_STATUS_OK) {
DLOG(ERROR) << "Crypto session is not OK. Crypto session status "
<< crypto_session_status << ". HRESULT " << hresult;
failed();
return;
}
const uint32_t media_crypto_session_id = next_crypto_session_id_++;
crypto_session_map_[media_crypto_session_id] =
std::move(media_crypto_session);
std::move(create_media_crypto_session_cb)
.Run(Status::kOk, media_crypto_session_id, output_data);
}
void D3D11CdmProxy::SetKey(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) {
auto crypto_session_it = crypto_session_map_.find(crypto_session_id);
if (crypto_session_it == crypto_session_map_.end()) {
DLOG(WARNING) << crypto_session_id
<< " did not map to a crypto session instance.";
std::move(set_key_cb).Run(Status::kFail);
return;
}
cdm_context_->SetKey(crypto_session_it->second.Get(), key_id, key_type,
key_blob);
std::move(set_key_cb).Run(Status::kOk);
}
void D3D11CdmProxy::RemoveKey(uint32_t crypto_session_id,
const std::vector<uint8_t>& key_id,
RemoveKeyCB remove_key_cb) {
auto crypto_session_it = crypto_session_map_.find(crypto_session_id);
if (crypto_session_it == crypto_session_map_.end()) {
DLOG(WARNING) << crypto_session_id
<< " did not map to a crypto session instance.";
std::move(remove_key_cb).Run(Status::kFail);
return;
}
cdm_context_->RemoveKey(crypto_session_it->second.Get(), key_id);
std::move(remove_key_cb).Run(Status::kOk);
}
void D3D11CdmProxy::SetCreateDeviceCallbackForTesting(
D3D11CreateDeviceCB callback) {
create_device_func_ = std::move(callback);
}
void D3D11CdmProxy::NotifyHardwareContentProtectionTeardown() {
cdm_context_->OnHardwareReset();
client_->NotifyHardwareReset();
}
D3D11CdmProxy::HardwareEventWatcher::~HardwareEventWatcher() {
StopWatching();
}
std::unique_ptr<D3D11CdmProxy::HardwareEventWatcher>
D3D11CdmProxy::HardwareEventWatcher::Create(
Microsoft::WRL::ComPtr<ID3D11Device> device,
base::RepeatingClosure teardown_callback) {
std::unique_ptr<HardwareEventWatcher> event_watcher = base::WrapUnique(
new HardwareEventWatcher(device, std::move(teardown_callback)));
if (!event_watcher->StartWatching())
return nullptr;
return event_watcher;
}
D3D11CdmProxy::HardwareEventWatcher::HardwareEventWatcher(
Microsoft::WRL::ComPtr<ID3D11Device> device,
base::RepeatingClosure teardown_callback)
: device_(device), teardown_callback_(std::move(teardown_callback)) {}
bool D3D11CdmProxy::HardwareEventWatcher::StartWatching() {
if (!RegisterPowerEvents() ||
!RegisterHardwareContentProtectionTeardown(device_)) {
StopWatching();
return false;
}
return true;
}
bool D3D11CdmProxy::HardwareEventWatcher::
RegisterHardwareContentProtectionTeardown(ComPtr<ID3D11Device> device) {
device_ = device;
HRESULT hresult = device_.CopyTo(dxgi_device_.ReleaseAndGetAddressOf());
if (FAILED(hresult)) {
DVLOG(1) << "Failed to get dxgi device from device: "
<< logging::SystemErrorCodeToString(hresult);
return false;
}
hresult = dxgi_device_->GetParent(
IID_PPV_ARGS(dxgi_adapter_.ReleaseAndGetAddressOf()));
if (FAILED(hresult)) {
DVLOG(1) << "Failed to get dxgi adapter from dxgi device: "
<< logging::SystemErrorCodeToString(hresult);
return false;
}
if (!teardown_status_watcher_.StartWatchingOnce(
content_protection_teardown_event_.handle(), this)) {
DVLOG(1) << "Failed to watch tear down event.";
return false;
}
hresult = dxgi_adapter_->RegisterHardwareContentProtectionTeardownStatusEvent(
content_protection_teardown_event_.handle(), &teardown_event_cookie_);
if (FAILED(hresult)) {
DVLOG(1)
<< "Failed to register for HardwareContentProtectionTeardownStatus: "
<< logging::SystemErrorCodeToString(hresult);
return false;
}
return true;
}
bool D3D11CdmProxy::HardwareEventWatcher::RegisterPowerEvents() {
base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
if (!power_monitor) {
DVLOG(1) << "Power monitor not available.";
return false;
}
power_monitor->AddObserver(this);
return true;
}
void D3D11CdmProxy::HardwareEventWatcher::OnObjectSignaled(HANDLE object) {
DCHECK_EQ(object, content_protection_teardown_event_.handle());
teardown_callback_.Run();
}
void D3D11CdmProxy::HardwareEventWatcher::OnSuspend() {
teardown_callback_.Run();
}
void D3D11CdmProxy::HardwareEventWatcher::StopWatching() {
if (dxgi_adapter_) {
dxgi_adapter_->UnregisterHardwareContentProtectionTeardownStatus(
teardown_event_cookie_);
}
teardown_status_watcher_.StopWatching();
base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
if (power_monitor)
power_monitor->RemoveObserver(this);
}
} // namespace media