// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/cdm_factory_daemon/cdm_factory_daemon_proxy_ash.h"
#include "ash/constants/ash_switches.h"
#include "ash/shell.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/ash/components/dbus/cdm_factory_daemon/cdm_factory_daemon_client.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/invitation.h"
#include "ui/display/manager/display_manager.h"
namespace chromeos {
namespace {
constexpr char kCdmFactoryDaemonPipeName[] = "cdm-factory-daemon-pipe";
// This is used as an implementation of cdm::mojom::BrowserCdmFactory when we
// pass a mojo::PendingRemote to an OOP video
// decoder so it can proxy the calls back to the browser process like the
// corresponding methods do in ChromeOsCdmFactory and ArcCdmContext. The
// methods marked as NOTREACHED() are never used during video decoding.
class BrowserCdmFactoryProxy : public cdm::mojom::BrowserCdmFactory {
public:
BrowserCdmFactoryProxy()
: task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}
BrowserCdmFactoryProxy(const BrowserCdmFactoryProxy&) = delete;
BrowserCdmFactoryProxy& operator=(const BrowserCdmFactoryProxy&) = delete;
~BrowserCdmFactoryProxy() override = default;
// chromeos::cdm::mojom::BrowserCdmFactoryDaemon:
void CreateFactory(const std::string& key_system,
CreateFactoryCallback callback) override {
NOTREACHED_IN_MIGRATION();
}
void GetHwConfigData(GetHwConfigDataCallback callback) override {
if (!task_runner_->RunsTasksInCurrentSequence()) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&BrowserCdmFactoryProxy::GetHwConfigData,
weak_factory_.GetWeakPtr(), std::move(callback)));
return;
}
CdmFactoryDaemonProxyAsh::GetInstance().GetHwConfigData(
std::move(callback));
}
void GetOutputProtection(mojo::PendingReceiver<cdm::mojom::OutputProtection>
output_protection) override {
NOTREACHED_IN_MIGRATION();
}
void GetScreenResolutions(GetScreenResolutionsCallback callback) override {
if (!task_runner_->RunsTasksInCurrentSequence()) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&BrowserCdmFactoryProxy::GetScreenResolutions,
weak_factory_.GetWeakPtr(), std::move(callback)));
return;
}
CdmFactoryDaemonProxyAsh::GetInstance().GetScreenResolutions(
std::move(callback));
}
void GetAndroidHwKeyData(const std::vector<uint8_t>& key_id,
const std::vector<uint8_t>& hw_identifier,
GetAndroidHwKeyDataCallback callback) override {
if (!task_runner_->RunsTasksInCurrentSequence()) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&BrowserCdmFactoryProxy::GetAndroidHwKeyData,
weak_factory_.GetWeakPtr(), key_id, hw_identifier,
std::move(callback)));
return;
}
CdmFactoryDaemonProxyAsh::GetInstance().GetAndroidHwKeyData(
key_id, hw_identifier, std::move(callback));
}
void AllocateSecureBuffer(uint32_t size,
AllocateSecureBufferCallback callback) override {
if (!task_runner_->RunsTasksInCurrentSequence()) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&BrowserCdmFactoryProxy::AllocateSecureBuffer,
weak_factory_.GetWeakPtr(), size,
std::move(callback)));
return;
}
CdmFactoryDaemonProxyAsh::GetInstance().AllocateSecureBuffer(
size, std::move(callback));
}
void ParseEncryptedSliceHeader(
uint64_t secure_handle,
uint32_t offset,
const std::vector<uint8_t>& stream_data,
ParseEncryptedSliceHeaderCallback callback) override {
if (!task_runner_->RunsTasksInCurrentSequence()) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&BrowserCdmFactoryProxy::ParseEncryptedSliceHeader,
weak_factory_.GetWeakPtr(), secure_handle, offset,
stream_data, std::move(callback)));
return;
}
CdmFactoryDaemonProxyAsh::GetInstance().ParseEncryptedSliceHeader(
secure_handle, offset, stream_data, std::move(callback));
}
private:
scoped_refptr<base::SequencedTaskRunner> task_runner_;
base::WeakPtrFactory<BrowserCdmFactoryProxy> weak_factory_{this};
};
} // namespace
CdmFactoryDaemonProxyAsh::CdmFactoryDaemonProxyAsh() : CdmFactoryDaemonProxy() {
// Check if there's a Chrome flag set to force a specific HDCP mode. We do
// that from here because this is a singleton we use in ash chrome and this is
// related to the OutputProtection class we have which is able to manage HDCP
// state across all displays easily.
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(ash::switches::kAlwaysEnableHdcp)) {
std::string hdcp_mode =
command_line->GetSwitchValueASCII(ash::switches::kAlwaysEnableHdcp);
if (!hdcp_mode.empty()) {
forced_output_protection_ =
std::make_unique<OutputProtectionImpl>(nullptr);
if (hdcp_mode == "type0") {
forced_output_protection_->EnableProtection(
cdm::mojom::OutputProtection::ProtectionType::HDCP_TYPE_0,
base::DoNothing());
} else if (hdcp_mode == "type1") {
forced_output_protection_->EnableProtection(
cdm::mojom::OutputProtection::ProtectionType::HDCP_TYPE_1,
base::DoNothing());
} else {
LOG(ERROR) << "Invalid HDCP mode of: " << hdcp_mode;
}
} else {
LOG(ERROR) << "Empty HDCP mode for: " << ash::switches::kAlwaysEnableHdcp;
}
}
ash::Shell::Get()
->display_configurator()
->content_protection_manager()
->SetProvisionedKeyRequest(base::BindRepeating(
&CdmFactoryDaemonProxyAsh::GetHdcp14Key, base::Unretained(this)));
}
CdmFactoryDaemonProxyAsh::~CdmFactoryDaemonProxyAsh() = default;
void CdmFactoryDaemonProxyAsh::Create(
mojo::PendingReceiver<BrowserCdmFactory> receiver) {
// We do not want to use a SelfOwnedReceiver for the main implementation here
// because if the GPU process or Lacros goes down, we don't want to destruct
// and drop our connection to the daemon. It's not possible to reconnect to
// the daemon from the browser process w/out restarting both processes (which
// happens if the browser goes down). However, the connection between ash-GPU
// and ash-browser uses a ReceiverSet, which is self-destructing on
// disconnect.
GetInstance().BindReceiver(std::move(receiver));
}
// static
CdmFactoryDaemonProxyAsh& CdmFactoryDaemonProxyAsh::GetInstance() {
static base::NoDestructor<CdmFactoryDaemonProxyAsh> instance;
return *instance;
}
// static
std::unique_ptr<cdm::mojom::BrowserCdmFactory>
CdmFactoryDaemonProxyAsh::CreateBrowserCdmFactoryProxy() {
return std::make_unique<BrowserCdmFactoryProxy>();
}
void CdmFactoryDaemonProxyAsh::ConnectOemCrypto(
mojo::PendingReceiver<arc::mojom::OemCryptoService> oemcryptor,
mojo::PendingRemote<arc::mojom::ProtectedBufferManager>
protected_buffer_manager,
mojo::PendingRemote<cdm::mojom::OutputProtection> output_protection) {
// This gets invoked from ArcBridge which uses a different thread.
if (!mojo_task_runner_->RunsTasksInCurrentSequence()) {
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CdmFactoryDaemonProxyAsh::ConnectOemCrypto,
base::Unretained(this), std::move(oemcryptor),
std::move(protected_buffer_manager),
std::move(output_protection)));
return;
}
DVLOG(1) << "CdmFactoryDaemonProxyAsh::ConnectOemCrypto called";
if (daemon_remote_) {
DVLOG(1) << "CdmFactoryDaemon mojo connection already exists, re-use it";
CompleteOemCryptoConnection(std::move(oemcryptor),
std::move(protected_buffer_manager),
std::move(output_protection));
return;
}
EstablishDaemonConnection(base::BindOnce(
&CdmFactoryDaemonProxyAsh::CompleteOemCryptoConnection,
base::Unretained(this), std::move(oemcryptor),
std::move(protected_buffer_manager), std::move(output_protection)));
}
void CdmFactoryDaemonProxyAsh::CreateFactory(const std::string& key_system,
CreateFactoryCallback callback) {
DCHECK(mojo_task_runner_->RunsTasksInCurrentSequence());
DVLOG(1) << "CdmFactoryDaemonProxyAsh::CreateFactory called";
if (daemon_remote_) {
DVLOG(1) << "CdmFactoryDaemon mojo connection already exists, re-use it";
GetFactoryInterface(key_system, std::move(callback));
return;
}
EstablishDaemonConnection(
base::BindOnce(&CdmFactoryDaemonProxyAsh::GetFactoryInterface,
base::Unretained(this), key_system, std::move(callback)));
}
void CdmFactoryDaemonProxyAsh::GetHwConfigData(
GetHwConfigDataCallback callback) {
DCHECK(mojo_task_runner_->RunsTasksInCurrentSequence());
DVLOG(1) << "CdmFactoryDaemonProxyAsh::GetHwConfigData called";
if (daemon_remote_) {
DVLOG(1) << "CdmFactoryDaemon mojo connection already exists, re-use it";
ProxyGetHwConfigData(std::move(callback));
return;
}
EstablishDaemonConnection(
base::BindOnce(&CdmFactoryDaemonProxyAsh::ProxyGetHwConfigData,
base::Unretained(this), std::move(callback)));
}
void CdmFactoryDaemonProxyAsh::GetOutputProtection(
mojo::PendingReceiver<cdm::mojom::OutputProtection> output_protection) {
OutputProtectionImpl::Create(std::move(output_protection));
}
void CdmFactoryDaemonProxyAsh::GetScreenResolutions(
GetScreenResolutionsCallback callback) {
std::vector<gfx::Size> resolutions;
const std::vector<
raw_ptr<display::DisplaySnapshot, VectorExperimental>>& displays =
ash::Shell::Get()->display_manager()->configurator()->cached_displays();
for (display::DisplaySnapshot* display : displays)
resolutions.emplace_back(display->native_mode()->size());
std::move(callback).Run(std::move(resolutions));
}
void CdmFactoryDaemonProxyAsh::GetAndroidHwKeyData(
const std::vector<uint8_t>& key_id,
const std::vector<uint8_t>& hw_identifier,
GetAndroidHwKeyDataCallback callback) {
DCHECK(mojo_task_runner_->RunsTasksInCurrentSequence());
DVLOG(1) << "CdmFactoryDaemonProxyAsh::GetAndroidHwKeyData called";
if (daemon_remote_.is_bound()) {
DVLOG(1) << "CdmFactoryDaemon mojo connection already exists, re-use it";
ProxyGetAndroidHwKeyData(key_id, hw_identifier, std::move(callback));
return;
}
// base::Unretained is safe below because this class is a singleton.
EstablishDaemonConnection(base::BindOnce(
&CdmFactoryDaemonProxyAsh::ProxyGetAndroidHwKeyData,
base::Unretained(this), key_id, hw_identifier, std::move(callback)));
}
void CdmFactoryDaemonProxyAsh::AllocateSecureBuffer(
uint32_t size,
AllocateSecureBufferCallback callback) {
DCHECK(mojo_task_runner_->RunsTasksInCurrentSequence());
DVLOG(1) << "CdmFactoryDaemonProxyAsh::AllocateSecureBuffer called";
if (daemon_remote_.is_bound()) {
DVLOG(1) << "CdmFactoryDaemon mojo connection already exists, re-use it";
ProxyAllocateSecureBuffer(size, std::move(callback));
return;
}
// base::Unretained is safe below because this class is a singleton.
EstablishDaemonConnection(
base::BindOnce(&CdmFactoryDaemonProxyAsh::ProxyAllocateSecureBuffer,
base::Unretained(this), size, std::move(callback)));
}
void CdmFactoryDaemonProxyAsh::ParseEncryptedSliceHeader(
uint64_t secure_handle,
uint32_t offset,
const std::vector<uint8_t>& stream_data,
ParseEncryptedSliceHeaderCallback callback) {
DCHECK(mojo_task_runner_->RunsTasksInCurrentSequence());
DVLOG(1) << "CdmFactoryDaemonProxyAsh::ParseEncryptedSliceHeader called";
if (daemon_remote_.is_bound()) {
DVLOG(1) << "CdmFactoryDaemon mojo connection already exists, re-use it";
ProxyParseEncryptedSliceHeader(secure_handle, offset, stream_data,
std::move(callback));
return;
}
// base::Unretained is safe below because this class is a singleton.
EstablishDaemonConnection(
base::BindOnce(&CdmFactoryDaemonProxyAsh::ProxyParseEncryptedSliceHeader,
base::Unretained(this), secure_handle, offset, stream_data,
std::move(callback)));
}
void CdmFactoryDaemonProxyAsh::EstablishDaemonConnection(
base::OnceClosure callback) {
// This may have happened already.
if (daemon_remote_) {
std::move(callback).Run();
return;
}
// Bootstrap the Mojo connection to the daemon.
mojo::OutgoingInvitation invitation;
mojo::PlatformChannel channel;
mojo::ScopedMessagePipeHandle server_pipe =
invitation.AttachMessagePipe(kCdmFactoryDaemonPipeName);
mojo::OutgoingInvitation::Send(std::move(invitation),
base::kNullProcessHandle,
channel.TakeLocalEndpoint());
base::ScopedFD fd =
channel.TakeRemoteEndpoint().TakePlatformHandle().TakeFD();
// Bind the Mojo pipe to the interface before we send the D-Bus message
// to avoid any kind of race condition with detecting it's been bound.
// It's safe to do this before the other end binds anyways.
daemon_remote_.Bind(mojo::PendingRemote<cdm::mojom::CdmFactoryDaemon>(
std::move(server_pipe), 0u));
// Disconnect handler is setup for when the daemon crashes so we can drop our
// connection to it and signal it needs to be reconnected on next entry.
daemon_remote_.set_disconnect_handler(
base::BindOnce(&CdmFactoryDaemonProxyAsh::OnDaemonMojoConnectionError,
base::Unretained(this)));
// We need to invoke this call on the D-Bus (UI) thread.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&CdmFactoryDaemonProxyAsh::SendDBusRequest,
base::Unretained(this), std::move(fd),
std::move(callback)));
}
void CdmFactoryDaemonProxyAsh::GetFactoryInterface(
const std::string& key_system,
CreateFactoryCallback callback) {
if (!daemon_remote_) {
LOG(ERROR) << "daemon_remote_ interface is not connected";
std::move(callback).Run(mojo::PendingRemote<cdm::mojom::CdmFactory>());
return;
}
daemon_remote_->CreateFactory(key_system, std::move(callback));
}
void CdmFactoryDaemonProxyAsh::ProxyGetHwConfigData(
GetHwConfigDataCallback callback) {
if (!daemon_remote_) {
LOG(ERROR) << "daemon_remote_ interface is not connected";
std::move(callback).Run(false, std::vector<uint8_t>());
return;
}
daemon_remote_->GetHwConfigData(std::move(callback));
}
void CdmFactoryDaemonProxyAsh::ProxyGetAndroidHwKeyData(
const std::vector<uint8_t>& key_id,
const std::vector<uint8_t>& hw_identifier,
GetAndroidHwKeyDataCallback callback) {
if (!daemon_remote_) {
LOG(ERROR) << "daemon_remote_ interface is not connected";
std::move(callback).Run(media::Decryptor::Status::kError, {});
return;
}
daemon_remote_->GetAndroidHwKeyData(key_id, hw_identifier,
std::move(callback));
}
void CdmFactoryDaemonProxyAsh::ProxyAllocateSecureBuffer(
uint32_t size,
AllocateSecureBufferCallback callback) {
if (!daemon_remote_) {
LOG(ERROR) << "daemon_remote_ interface is not connected";
std::move(callback).Run(mojo::PlatformHandle());
return;
}
daemon_remote_->AllocateSecureBuffer(size, std::move(callback));
}
void CdmFactoryDaemonProxyAsh::ProxyParseEncryptedSliceHeader(
uint64_t secure_handle,
uint32_t offset,
const std::vector<uint8_t>& stream_data,
ParseEncryptedSliceHeaderCallback callback) {
if (!daemon_remote_) {
LOG(ERROR) << "daemon_remote_ interface is not connected";
std::move(callback).Run(false, {});
return;
}
daemon_remote_->ParseEncryptedSliceHeader(secure_handle, offset, stream_data,
std::move(callback));
}
void CdmFactoryDaemonProxyAsh::SendDBusRequest(base::ScopedFD fd,
base::OnceClosure callback) {
ash::CdmFactoryDaemonClient::Get()->BootstrapMojoConnection(
std::move(fd),
base::BindOnce(&CdmFactoryDaemonProxyAsh::OnBootstrapMojoConnection,
base::Unretained(this), std::move(callback)));
}
void CdmFactoryDaemonProxyAsh::OnBootstrapMojoConnection(
base::OnceClosure callback,
bool result) {
if (!mojo_task_runner_->RunsTasksInCurrentSequence()) {
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CdmFactoryDaemonProxyAsh::OnBootstrapMojoConnection,
base::Unretained(this), std::move(callback), result));
return;
}
if (!result) {
LOG(ERROR) << "CdmFactoryDaemon had a failure in D-Bus with the daemon";
daemon_remote_.reset();
} else {
DVLOG(1) << "Succeeded with CdmFactoryDaemon bootstrapping";
}
std::move(callback).Run();
}
void CdmFactoryDaemonProxyAsh::CompleteOemCryptoConnection(
mojo::PendingReceiver<arc::mojom::OemCryptoService> oemcryptor,
mojo::PendingRemote<arc::mojom::ProtectedBufferManager>
protected_buffer_manager,
mojo::PendingRemote<cdm::mojom::OutputProtection> output_protection) {
if (!daemon_remote_) {
LOG(ERROR) << "daemon_remote_ interface is not connected";
// Just let the mojo objects go out of scope and be destructed to signal
// failure.
return;
}
daemon_remote_->ConnectOemCrypto(std::move(oemcryptor),
std::move(protected_buffer_manager),
std::move(output_protection));
}
void CdmFactoryDaemonProxyAsh::OnDaemonMojoConnectionError() {
DVLOG(1) << "CdmFactoryDaemon daemon Mojo connection lost.";
// Reset the remote here to trigger reconnection to the daemon on the next
// call to CreateFactory.
daemon_remote_.reset();
}
void CdmFactoryDaemonProxyAsh::GetHdcp14Key(
cdm::mojom::CdmFactoryDaemon::GetHdcp14KeyCallback callback) {
if (!mojo_task_runner_->RunsTasksInCurrentSequence()) {
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CdmFactoryDaemonProxyAsh::GetHdcp14Key,
base::Unretained(this), std::move(callback)));
return;
}
DVLOG(1) << "CdmFactoryDaemonProxyAsh::GetHdcp14Key called";
if (daemon_remote_) {
DVLOG(1) << "CdmFactoryDaemon mojo connection already exists, re-use it";
daemon_remote_->GetHdcp14Key(std::move(callback));
return;
}
EstablishDaemonConnection(
base::BindOnce(&CdmFactoryDaemonProxyAsh::GetHdcp14Key,
base::Unretained(this), std::move(callback)));
}
} // namespace chromeos