910e62b5创建于 1月15日历史提交
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/media/cdm_storage_manager.h"

#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/task/thread_pool.h"
#include "base/types/pass_key.h"
#include "content/browser/media/cdm_storage_common.h"
#include "content/public/common/content_features.h"
#include "media/cdm/cdm_type.h"
#include "media/mojo/mojom/cdm_storage.mojom.h"

namespace content {

namespace {

const char kUmaPrefix[] = "Media.EME.CdmStorageManager.";

const char kGetSizeForFileError[] = "GetSizeForFileError.";
const char kGetSizeForStorageKeyError[] = "GetSizeForStorageKeyError.";
const char kGetSizeForTimeFrameError[] = "GetSizeForTimeFrameError.";
const char kWriteFileError[] = "WriteFileError.";
const char kReadFileError[] = "ReadFileError.";
const char kDatabaseOpenErrorNoPeriod[] = "DatabaseOpenError";
const char kDatabaseOpenError[] = "DatabaseOpenError.";
const char kDatabaseSizeName[] = "CurrentDatabaseUsageKB.";

constexpr uint64_t kBytesPerKB = 1024;
constexpr int kMinDatabaseSizeKB = 0;
// Used for histogram reporting, the max size of the database we expect in KB.
constexpr uint64_t kMaxDatabaseSizeKB = 512000 * 10;
constexpr int kSizeKBBuckets = 1000;

// Creates a task runner suitable for running SQLite database operations.
scoped_refptr<base::SequencedTaskRunner> CreateDatabaseTaskRunner() {
  // We use a SequencedTaskRunner so that there is a global ordering to a
  // storage key's directory operations.
  return base::ThreadPool::CreateSequencedTaskRunner({
      // Needed for file I/O.
      base::MayBlock(),

      // Reasonable compromise, given that a few database operations are
      // blocking, while most operations are not. We should be able to do better
      // when we get scheduling APIs on the Web Platform.
      base::TaskPriority::USER_VISIBLE,

      // Needed to allow for clearing site data on shutdown.
      base::TaskShutdownBehavior::BLOCK_SHUTDOWN,
  });
}

}  // namespace

CdmStorageManager::CdmStorageManager(const base::FilePath& path)
    : path_(path), db_(CreateDatabaseTaskRunner(), path_) {}

CdmStorageManager::~CdmStorageManager() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void CdmStorageManager::Open(const std::string& file_name,
                             OpenCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (file_name.empty()) {
    DVLOG(1) << "No file specified.";
    ReportDatabaseOpenError(CdmStorageOpenError::kNoFileSpecified);
    std::move(callback).Run(Status::kFailure, mojo::NullAssociatedRemote());
    return;
  }

  if (!CdmFileImpl::IsValidName(file_name)) {
    DVLOG(1) << "Invalid name of file.";
    ReportDatabaseOpenError(CdmStorageOpenError::kInvalidFileName);
    std::move(callback).Run(Status::kFailure, mojo::NullAssociatedRemote());
    return;
  }

  db_.AsyncCall(&CdmStorageDatabase::EnsureOpen)
      .Then(base::BindOnce(&CdmStorageManager::DidOpenFile,
                           weak_factory_.GetWeakPtr(),
                           receivers_.current_context().storage_key,
                           receivers_.current_context().cdm_type, file_name,
                           std::move(callback)));
}

void CdmStorageManager::GetUsagePerAllStorageKeys(
    base::OnceCallback<void(const CdmStorageKeyUsageSize&)> callback,
    base::Time begin,
    base::Time end) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  db_.AsyncCall(&CdmStorageDatabase::GetUsagePerAllStorageKeys)
      .WithArgs(begin, end)
      .Then(std::move(callback));
}

void CdmStorageManager::DeleteDataForStorageKey(
    const blink::StorageKey& storage_key,
    base::OnceCallback<void(bool)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  DeleteData(base::NullCallback(), storage_key, base::Time::Min(),
             base::Time::Max(), std::move(callback));
}

void CdmStorageManager::OpenCdmStorage(
    const CdmStorageBindingContext& binding_context,
    mojo::PendingReceiver<media::mojom::CdmStorage> receiver) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  receivers_.Add(this, std::move(receiver), binding_context);
}

void CdmStorageManager::ReadFile(
    const blink::StorageKey& storage_key,
    const media::CdmType& cdm_type,
    const std::string& file_name,
    base::OnceCallback<void(std::optional<std::vector<uint8_t>>)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  db_.AsyncCall(&CdmStorageDatabase::ReadFile)
      .WithArgs(storage_key, cdm_type, file_name)
      .Then(base::BindOnce(&CdmStorageManager::DidReadFile,
                           weak_factory_.GetWeakPtr(), std::move(callback)));
}

void CdmStorageManager::WriteFile(const blink::StorageKey& storage_key,
                                  const media::CdmType& cdm_type,
                                  const std::string& file_name,
                                  const std::vector<uint8_t>& data,
                                  base::OnceCallback<void(bool)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  db_.AsyncCall(&CdmStorageDatabase::WriteFile)
      .WithArgs(storage_key, cdm_type, file_name, data)
      .Then(base::BindOnce(&CdmStorageManager::DidWriteFile,
                           weak_factory_.GetWeakPtr(), std::move(callback)));
}

void CdmStorageManager::GetSizeForFile(
    const blink::StorageKey& storage_key,
    const media::CdmType& cdm_type,
    const std::string& file_name,
    base::OnceCallback<void(uint64_t)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  db_.AsyncCall(&CdmStorageDatabase::GetSizeForFile)
      .WithArgs(storage_key, cdm_type, file_name)
      .Then(base::BindOnce(&CdmStorageManager::DidGetSize,
                           weak_factory_.GetWeakPtr(), std::move(callback),
                           kGetSizeForFileError));
}

void CdmStorageManager::GetSizeForStorageKey(
    const blink::StorageKey& storage_key,
    const base::Time begin,
    const base::Time end,
    base::OnceCallback<void(uint64_t)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  db_.AsyncCall(&CdmStorageDatabase::GetSizeForStorageKey)
      .WithArgs(storage_key, begin, end)
      .Then(base::BindOnce(&CdmStorageManager::DidGetSize,
                           weak_factory_.GetWeakPtr(), std::move(callback),
                           kGetSizeForStorageKeyError));
}

void CdmStorageManager::GetSizeForTimeFrame(
    const base::Time begin,
    const base::Time end,
    base::OnceCallback<void(uint64_t)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  db_.AsyncCall(&CdmStorageDatabase::GetSizeForTimeFrame)
      .WithArgs(begin, end)
      .Then(base::BindOnce(&CdmStorageManager::DidGetSize,
                           weak_factory_.GetWeakPtr(), std::move(callback),
                           kGetSizeForTimeFrameError));
}

void CdmStorageManager::DeleteFile(const blink::StorageKey& storage_key,
                                   const media::CdmType& cdm_type,
                                   const std::string& file_name,
                                   base::OnceCallback<void(bool)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  db_.AsyncCall(&CdmStorageDatabase::DeleteFile)
      .WithArgs(storage_key, cdm_type, file_name)
      .Then(std::move(callback));
}

void CdmStorageManager::DeleteData(
    const StoragePartition::StorageKeyMatcherFunction& storage_key_matcher,
    const blink::StorageKey& storage_key,
    const base::Time begin,
    const base::Time end,
    base::OnceCallback<void(bool)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  db_.AsyncCall(&CdmStorageDatabase::DeleteData)
      .WithArgs(storage_key_matcher, storage_key, begin, end)
      .Then(std::move(callback));
}

void CdmStorageManager::OnFileReceiverDisconnect(
    const std::string& name,
    const media::CdmType& cdm_type,
    const blink::StorageKey& storage_key,
    base::PassKey<CdmFileImpl> pass_key) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto count = cdm_files_.erase(CdmFileId(name, cdm_type, storage_key));
  DCHECK_GT(count, 0u);
}

void CdmStorageManager::DidOpenFile(const blink::StorageKey& storage_key,
                                    const media::CdmType& cdm_type,
                                    const std::string& file_name,
                                    OpenCallback callback,
                                    CdmStorageOpenError error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (error != CdmStorageOpenError::kOk) {
    ReportDatabaseOpenError(error);
    std::move(callback).Run(Status::kFailure, mojo::NullAssociatedRemote());
    return;
  }

  // Check whether this CDM file is in-use.
  CdmFileId id(file_name, cdm_type, storage_key);
  if (base::Contains(cdm_files_, id)) {
    std::move(callback).Run(Status::kInUse, mojo::NullAssociatedRemote());
    return;
  }

  // File was opened successfully, so create the binding and return success.
  mojo::PendingAssociatedRemote<media::mojom::CdmFile> cdm_file;

  cdm_files_.emplace(id, std::make_unique<CdmFileImpl>(
                             this, storage_key, cdm_type, file_name,
                             cdm_file.InitWithNewEndpointAndPassReceiver()));

  std::move(callback).Run(Status::kSuccess, std::move(cdm_file));
}

void CdmStorageManager::DidReadFile(
    base::OnceCallback<void(std::optional<std::vector<uint8_t>>)> callback,
    std::optional<std::vector<uint8_t>> data) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  base::UmaHistogramBoolean(
      GetCdmStorageManagerHistogramName(kReadFileError, in_memory()),
      !data.has_value());

  if (!database_size_reported_) {
    db_.AsyncCall(&CdmStorageDatabase::GetDatabaseSize)
        .Then(base::BindOnce(&CdmStorageManager::DidGetDatabaseSize,
                             weak_factory_.GetWeakPtr()));
    database_size_reported_ = true;
  }

  std::move(callback).Run(data);
}

void CdmStorageManager::DidWriteFile(base::OnceCallback<void(bool)> callback,
                                     bool success) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  base::UmaHistogramBoolean(
      GetCdmStorageManagerHistogramName(kWriteFileError, in_memory()),
      !success);

  std::move(callback).Run(success);
}

void CdmStorageManager::DidGetSize(base::OnceCallback<void(uint64_t)> callback,
                                   const std::string& operation,
                                   std::optional<uint64_t> size) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  base::UmaHistogramBoolean(
      GetCdmStorageManagerHistogramName(operation, in_memory()),
      !size.has_value());

  std::move(callback).Run(size.value_or(0));
}

void CdmStorageManager::DidGetDatabaseSize(const uint64_t size) {
  // One time report DatabaseSize.
  base::UmaHistogramCustomCounts(
      GetCdmStorageManagerHistogramName(kDatabaseSizeName, in_memory()),
      size / kBytesPerKB, kMinDatabaseSizeKB, kMaxDatabaseSizeKB,
      kSizeKBBuckets);
}

// TODO(crbug.com/40272342) Investigate if we can propagate the SQL errors.
// Investigate adding delete functionality to 'MojoCdmHelper::CloseCdmFileIO' to
// close database on CdmFileIO closure.

void CdmStorageManager::ReportDatabaseOpenError(CdmStorageOpenError error) {
  // General Errors without distinguishing incognito or not.
  base::UmaHistogramEnumeration(
      std::string{kUmaPrefix} + std::string{kDatabaseOpenErrorNoPeriod}, error);

  // Histogram split by incognito and non-incognito.
  base::UmaHistogramEnumeration(
      GetCdmStorageManagerHistogramName(kDatabaseOpenError, in_memory()),
      error);
}

}  // namespace content