chromium/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc

// 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/chromeos_cdm_factory.h"

#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/components/cdm_factory_daemon/cdm_storage_adapter.h"
#include "chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.h"
#include "chromeos/components/cdm_factory_daemon/mojom/content_decryption_module.mojom.h"
#include "media/base/content_decryption_module.h"
#include "media/base/decrypt_config.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"

namespace chromeos {

namespace {

// This class is used as a singleton which then allows replacing the underlying
// mojo::Remote<cdm::mojom::BrowserCdmFactory> that it will return.
class BrowserCdmFactoryRemoteHolder {
 public:
  BrowserCdmFactoryRemoteHolder() = default;
  BrowserCdmFactoryRemoteHolder(const BrowserCdmFactoryRemoteHolder&) = delete;
  BrowserCdmFactoryRemoteHolder& operator=(
      const BrowserCdmFactoryRemoteHolder&) = delete;
  ~BrowserCdmFactoryRemoteHolder() = default;

  mojo::Remote<cdm::mojom::BrowserCdmFactory>& GetBrowserCdmFactoryRemote() {
    return remote_;
  }

  void ReplaceBrowserCdmFactoryRemote(
      mojo::Remote<cdm::mojom::BrowserCdmFactory> remote) {
    remote_ = std::move(remote);
  }

 private:
  mojo::Remote<cdm::mojom::BrowserCdmFactory> remote_;
};

// This holds the global single BrowserCdmFactoryRemoteHolder object which then
// internally can hold either the Mojo connection to the browser process or the
// Mojo connection to the GPU process if we are doing OOP video decoding.
BrowserCdmFactoryRemoteHolder& GetBrowserCdmFactoryRemoteHolder() {
  static base::NoDestructor<BrowserCdmFactoryRemoteHolder> remote_holder;
  return *remote_holder;
}

// This holds the global singleton Mojo connection to the browser process.
mojo::Remote<cdm::mojom::BrowserCdmFactory>& GetBrowserCdmFactoryRemote() {
  return GetBrowserCdmFactoryRemoteHolder().GetBrowserCdmFactoryRemote();
}

// This holds the task runner we are bound to.
scoped_refptr<base::SequencedTaskRunner>& GetFactoryTaskRunner() {
  static base::NoDestructor<scoped_refptr<base::SequencedTaskRunner>> runner;
  return *runner;
}

void CreateFactoryOnTaskRunner(
    const std::string& key_system,
    cdm::mojom::BrowserCdmFactory::CreateFactoryCallback callback) {
  GetBrowserCdmFactoryRemote()->CreateFactory(key_system, std::move(callback));
}

void GetOutputProtectionOnTaskRunner(
    mojo::PendingReceiver<cdm::mojom::OutputProtection> output_protection) {
  GetBrowserCdmFactoryRemote()->GetOutputProtection(
      std::move(output_protection));
}

#if BUILDFLAG(IS_CHROMEOS_ASH)
class SingletonCdmContextRef : public media::CdmContextRef {
 public:
  explicit SingletonCdmContextRef(media::CdmContext* cdm_context)
      : cdm_context_(cdm_context) {}
  ~SingletonCdmContextRef() override = default;

  // media::CdmContextRef
  media::CdmContext* GetCdmContext() override {
    // Safe because we are a singleton for the process.
    return cdm_context_;
  }

 private:
  raw_ptr<media::CdmContext> cdm_context_;
};

void GetHwKeyDataProxy(const std::string& key_id,
                       const std::vector<uint8_t>& hw_identifier,
                       ChromeOsCdmContext::GetHwKeyDataCB callback) {
  if (!GetFactoryTaskRunner()->RunsTasksInCurrentSequence()) {
    GetFactoryTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(&GetHwKeyDataProxy, key_id, hw_identifier,
                                  std::move(callback)));
    return;
  }
  GetBrowserCdmFactoryRemote()->GetAndroidHwKeyData(
      std::vector<uint8_t>(key_id.begin(), key_id.end()), hw_identifier,
      std::move(callback));
}

class ArcCdmContext : public ChromeOsCdmContext, public media::CdmContext {
 public:
  ArcCdmContext() = default;
  ArcCdmContext(const ArcCdmContext&) = delete;
  ArcCdmContext& operator=(const ArcCdmContext&) = delete;
  ~ArcCdmContext() override = default;

  // ChromeOsCdmContext implementation.
  void GetHwKeyData(const media::DecryptConfig* decrypt_config,
                    const std::vector<uint8_t>& hw_identifier,
                    GetHwKeyDataCB callback) override {
    GetHwKeyDataProxy(decrypt_config->key_id(), hw_identifier,
                      std::move(callback));
  }
  void GetHwConfigData(
      ChromeOsCdmContext::GetHwConfigDataCB callback) override {
    ChromeOsCdmFactory::GetHwConfigData(std::move(callback));
  }
  void GetScreenResolutions(
      ChromeOsCdmContext::GetScreenResolutionsCB callback) override {
    ChromeOsCdmFactory::GetScreenResolutions(std::move(callback));
  }
  std::unique_ptr<media::CdmContextRef> GetCdmContextRef() override {
    return std::make_unique<SingletonCdmContextRef>(this);
  }
  bool UsingArcCdm() const override { return true; }
  bool IsRemoteCdm() const override { return true; }
  void AllocateSecureBuffer(uint32_t size,
                            AllocateSecureBufferCB callback) override {
    ChromeOsCdmFactory::AllocateSecureBuffer(size, std::move(callback));
  }
  void ParseEncryptedSliceHeader(
      uint64_t secure_handle,
      uint32_t offset,
      const std::vector<uint8_t>& stream_data,
      ParseEncryptedSliceHeaderCB callback) override {
    ChromeOsCdmFactory::ParseEncryptedSliceHeader(
        secure_handle, offset, stream_data, std::move(callback));
  }

  // media::CdmContext implementation.
  ChromeOsCdmContext* GetChromeOsCdmContext() override { return this; }
};
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

void OnCdmCreated(media::CdmCreatedCB callback,
                  scoped_refptr<ContentDecryptionModuleAdapter> cdm,
                  cdm::mojom::CdmFactory::CreateCdmStatus result) {
  switch (result) {
    case cdm::mojom::CdmFactory::CreateCdmStatus::kSuccess:
      std::move(callback).Run(std::move(cdm), media::CreateCdmStatus::kSuccess);
      return;
    case cdm::mojom::CdmFactory::CreateCdmStatus::kNoMoreInstances:
      std::move(callback).Run(nullptr,
                              media::CreateCdmStatus::kNoMoreInstances);
      return;
    case cdm::mojom::CdmFactory::CreateCdmStatus::kInsufficientGpuResources:
      std::move(callback).Run(
          nullptr, media::CreateCdmStatus::kInsufficientGpuResources);
      return;
  }
  std::move(callback).Run(nullptr, media::CreateCdmStatus::kUnknownError);
}
}  // namespace

ChromeOsCdmFactory::ChromeOsCdmFactory(
    media::mojom::FrameInterfaceFactory* frame_interfaces)
    : frame_interfaces_(frame_interfaces) {
  DCHECK(frame_interfaces_);
  DVLOG(1) << "Creating the ChromeOsCdmFactory";
}

ChromeOsCdmFactory::~ChromeOsCdmFactory() = default;

// static
mojo::PendingReceiver<cdm::mojom::BrowserCdmFactory>
ChromeOsCdmFactory::GetBrowserCdmFactoryReceiver() {
  mojo::PendingRemote<chromeos::cdm::mojom::BrowserCdmFactory> browser_proxy;
  auto receiver = browser_proxy.InitWithNewPipeAndPassReceiver();
  GetBrowserCdmFactoryRemote().Bind(std::move(browser_proxy));

  GetFactoryTaskRunner() = base::SequencedTaskRunner::GetCurrentDefault();
  return receiver;
}

void ChromeOsCdmFactory::Create(
    const media::CdmConfig& cdm_config,
    const media::SessionMessageCB& session_message_cb,
    const media::SessionClosedCB& session_closed_cb,
    const media::SessionKeysChangeCB& session_keys_change_cb,
    const media::SessionExpirationUpdateCB& session_expiration_update_cb,
    media::CdmCreatedCB cdm_created_cb) {
  DVLOG(1) << __func__ << " cdm_config=" << cdm_config;
  // Check that the user has Verified Access enabled in their Chrome settings
  // and if they do not then block this connection since OEMCrypto utilizes
  // remote attestation as part of verification.
  if (!cdm_document_service_) {
    frame_interfaces_->BindEmbedderReceiver(mojo::GenericPendingReceiver(
        cdm_document_service_.BindNewPipeAndPassReceiver()));
    cdm_document_service_.set_disconnect_handler(
        base::BindOnce(&ChromeOsCdmFactory::OnVerificationMojoConnectionError,
                       weak_factory_.GetWeakPtr()));
  }
  cdm_document_service_->IsVerifiedAccessEnabled(base::BindOnce(
      &ChromeOsCdmFactory::OnVerifiedAccessEnabled, weak_factory_.GetWeakPtr(),
      cdm_config, session_message_cb, session_closed_cb, session_keys_change_cb,
      session_expiration_update_cb, std::move(cdm_created_cb)));
}

// static
void ChromeOsCdmFactory::GetHwConfigData(
    ChromeOsCdmContext::GetHwConfigDataCB callback) {
  if (!GetFactoryTaskRunner()->RunsTasksInCurrentSequence()) {
    GetFactoryTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(&ChromeOsCdmFactory::GetHwConfigData,
                                  std::move(callback)));
    return;
  }
  GetBrowserCdmFactoryRemote()->GetHwConfigData(std::move(callback));
}

// static
void ChromeOsCdmFactory::GetScreenResolutions(
    ChromeOsCdmContext::GetScreenResolutionsCB callback) {
  if (!GetFactoryTaskRunner()->RunsTasksInCurrentSequence()) {
    GetFactoryTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(&ChromeOsCdmFactory::GetScreenResolutions,
                                  std::move(callback)));
    return;
  }
  GetBrowserCdmFactoryRemote()->GetScreenResolutions(std::move(callback));
}

// static
void ChromeOsCdmFactory::AllocateSecureBuffer(
    uint32_t size,
    ChromeOsCdmContext::AllocateSecureBufferCB callback) {
  if (!GetFactoryTaskRunner()->RunsTasksInCurrentSequence()) {
    GetFactoryTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(&ChromeOsCdmFactory::AllocateSecureBuffer,
                                  size, std::move(callback)));
    return;
  }
  GetBrowserCdmFactoryRemote()->AllocateSecureBuffer(size, std::move(callback));
}

// static
void ChromeOsCdmFactory::ParseEncryptedSliceHeader(
    uint64_t secure_handle,
    uint32_t offset,
    const std::vector<uint8_t>& stream_data,
    ChromeOsCdmContext::ParseEncryptedSliceHeaderCB callback) {
  if (!GetFactoryTaskRunner()->RunsTasksInCurrentSequence()) {
    GetFactoryTaskRunner()->PostTask(
        FROM_HERE,
        base::BindOnce(&ChromeOsCdmFactory::ParseEncryptedSliceHeader,
                       secure_handle, offset, stream_data,
                       std::move(callback)));
    return;
  }
  GetBrowserCdmFactoryRemote()->ParseEncryptedSliceHeader(
      secure_handle, offset, stream_data, std::move(callback));
}

#if BUILDFLAG(IS_CHROMEOS_ASH)
// static
void ChromeOsCdmFactory::SetBrowserCdmFactoryRemote(
    mojo::Remote<cdm::mojom::BrowserCdmFactory> remote) {
  GetBrowserCdmFactoryRemoteHolder().ReplaceBrowserCdmFactoryRemote(
      std::move(remote));
  GetFactoryTaskRunner() = base::SequencedTaskRunner::GetCurrentDefault();
}

// static
media::CdmContext* ChromeOsCdmFactory::GetArcCdmContext() {
  static base::NoDestructor<ArcCdmContext> arc_cdm_context;
  return arc_cdm_context.get();
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

void ChromeOsCdmFactory::OnVerifiedAccessEnabled(
    const media::CdmConfig& cdm_config,
    const media::SessionMessageCB& session_message_cb,
    const media::SessionClosedCB& session_closed_cb,
    const media::SessionKeysChangeCB& session_keys_change_cb,
    const media::SessionExpirationUpdateCB& session_expiration_update_cb,
    media::CdmCreatedCB cdm_created_cb,
    bool enabled) {
  if (!enabled) {
    DVLOG(1)
        << "Not using Chrome OS CDM factory due to Verified Access disabled";
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(cdm_created_cb), nullptr,
                       media::CreateCdmStatus::kCrOsVerifiedAccessDisabled));
    return;
  }
  // If we haven't retrieved the remote CDM factory, do that first.
  if (!remote_factory_) {
    // Now invoke the call to create the Mojo interface for the CDM factory. We
    // need to invoke the CreateFactory call on the factory task runner, but
    // we then need to process the callback on the current runner, so there's a
    // few layers of indirection here.
    GetFactoryTaskRunner()->PostTask(
        FROM_HERE,
        base::BindOnce(
            &CreateFactoryOnTaskRunner, cdm_config.key_system,
            base::BindPostTaskToCurrentDefault(base::BindOnce(
                &ChromeOsCdmFactory::OnCreateFactory,
                weak_factory_.GetWeakPtr(), cdm_config, session_message_cb,
                session_closed_cb, session_keys_change_cb,
                session_expiration_update_cb, std::move(cdm_created_cb)))));
    return;
  }

  // Create the remote CDM in the daemon and then pass that into our adapter
  // that converts the media::ContentDecryptionModule/Decryptor calls into
  // chromeos::cdm::mojom::ContentDecryptionModule calls.
  CreateCdm(cdm_config, session_message_cb, session_closed_cb,
            session_keys_change_cb, session_expiration_update_cb,
            std::move(cdm_created_cb));
}

void ChromeOsCdmFactory::OnCreateFactory(
    const media::CdmConfig& cdm_config,
    const media::SessionMessageCB& session_message_cb,
    const media::SessionClosedCB& session_closed_cb,
    const media::SessionKeysChangeCB& session_keys_change_cb,
    const media::SessionExpirationUpdateCB& session_expiration_update_cb,
    media::CdmCreatedCB cdm_created_cb,
    mojo::PendingRemote<cdm::mojom::CdmFactory> remote_factory) {
  DVLOG(1) << __func__;
  if (!remote_factory) {
    LOG(ERROR) << "Failed creating the remote CDM factory";
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(
            std::move(cdm_created_cb), nullptr,
            media::CreateCdmStatus::kCrOsRemoteFactoryCreationFailed));
    return;
  }
  // Check if this is bound already, which could happen due to asynchronous
  // calls.
  if (!remote_factory_) {
    remote_factory_.Bind(std::move(remote_factory));
    remote_factory_.set_disconnect_handler(
        base::BindOnce(&ChromeOsCdmFactory::OnFactoryMojoConnectionError,
                       weak_factory_.GetWeakPtr()));
  }

  // We have the factory bound, create the CDM.
  CreateCdm(cdm_config, session_message_cb, session_closed_cb,
            session_keys_change_cb, session_expiration_update_cb,
            std::move(cdm_created_cb));
}

void ChromeOsCdmFactory::CreateCdm(
    const media::CdmConfig& /*cdm_config*/,
    const media::SessionMessageCB& session_message_cb,
    const media::SessionClosedCB& session_closed_cb,
    const media::SessionKeysChangeCB& session_keys_change_cb,
    const media::SessionExpirationUpdateCB& session_expiration_update_cb,
    media::CdmCreatedCB cdm_created_cb) {
  DVLOG(1) << __func__;
  // Create the storage implementation we are sending to Chrome OS.
  mojo::PendingAssociatedRemote<cdm::mojom::CdmStorage> storage_remote;
  std::unique_ptr<CdmStorageAdapter> storage =
      std::make_unique<CdmStorageAdapter>(
          frame_interfaces_,
          storage_remote.InitWithNewEndpointAndPassReceiver());

  // Create the remote interface for the CDM in Chrome OS.
  mojo::AssociatedRemote<cdm::mojom::ContentDecryptionModule> cros_cdm;
  mojo::PendingAssociatedReceiver<cdm::mojom::ContentDecryptionModule>
      cros_cdm_pending_receiver = cros_cdm.BindNewEndpointAndPassReceiver();

  // Create the adapter that proxies calls between
  // media::ContentDecryptionModule and
  // chromeos::cdm::mojom::ContentDecryptionModule.
  scoped_refptr<ContentDecryptionModuleAdapter> cdm =
      base::WrapRefCounted<ContentDecryptionModuleAdapter>(
          new ContentDecryptionModuleAdapter(
              std::move(storage), std::move(cros_cdm), session_message_cb,
              session_closed_cb, session_keys_change_cb,
              session_expiration_update_cb));

  // Create the OutputProtection interface to pass to the CDM.
  mojo::PendingRemote<cdm::mojom::OutputProtection> output_protection_remote;
  GetFactoryTaskRunner()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &GetOutputProtectionOnTaskRunner,
          output_protection_remote.InitWithNewPipeAndPassReceiver()));

  url::Origin cdm_origin;
  frame_interfaces_->GetCdmOrigin(&cdm_origin);

  // Now create the remote CDM instance that links everything up.
  remote_factory_->CreateCdm(
      cdm->GetClientInterface(), std::move(storage_remote),
      std::move(output_protection_remote), cdm_origin.host(),
      std::move(cros_cdm_pending_receiver),
      base::BindPostTaskToCurrentDefault(base::BindOnce(
          &OnCdmCreated, std::move(cdm_created_cb), std::move(cdm))));
}

void ChromeOsCdmFactory::OnFactoryMojoConnectionError() {
  DVLOG(1) << __func__;
  remote_factory_.reset();
}

void ChromeOsCdmFactory::OnVerificationMojoConnectionError() {
  DVLOG(1) << __func__;
  cdm_document_service_.reset();
}

}  // namespace chromeos