blob: 8d5e9b34fd964d70d96e76c43aaa29f14bc53639 [file] [log] [blame]
// Copyright 2022 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/media_license_storage_host.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/types/pass_key.h"
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
#include "content/browser/media/cdm_file_impl.h"
#include "content/browser/media/media_license_database.h"
#include "content/browser/media/media_license_manager.h"
#include "media/cdm/cdm_type.h"
#include "media/mojo/mojom/cdm_storage.mojom.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "storage/browser/file_system/file_system_context.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
namespace content {
// static
void MediaLicenseStorageHost::ReportDatabaseOpenError(
MediaLicenseStorageHostOpenError error,
bool is_incognito) {
DCHECK_NE(error, MediaLicenseStorageHostOpenError::kOk);
const std::string kDatabaseOpenErrorUmaName =
"Media.EME.MediaLicenseStorageHostOpenError";
base::UmaHistogramEnumeration(kDatabaseOpenErrorUmaName, error);
if (is_incognito) {
base::UmaHistogramEnumeration(kDatabaseOpenErrorUmaName + ".Incognito",
error);
} else {
base::UmaHistogramEnumeration(kDatabaseOpenErrorUmaName + ".NotIncognito",
error);
}
}
MediaLicenseStorageHost::MediaLicenseStorageHost(
MediaLicenseManager* manager,
const storage::BucketLocator& bucket_locator)
: manager_(manager),
bucket_locator_(bucket_locator),
db_(manager_->db_runner(), manager_->GetDatabasePath(bucket_locator_)) {
DCHECK(manager_);
// base::Unretained is safe here because this MediaLicenseStorageHost owns
// `receivers_`. So, the unretained MediaLicenseStorageHost is guaranteed to
// outlive `receivers_` and the closure that it uses.
receivers_.set_disconnect_handler(base::BindRepeating(
&MediaLicenseStorageHost::OnReceiverDisconnect, base::Unretained(this)));
}
MediaLicenseStorageHost::~MediaLicenseStorageHost() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void MediaLicenseStorageHost::Open(const std::string& file_name,
OpenCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (bucket_locator_.id.is_null()) {
DVLOG(1) << "Could not retrieve valid bucket.";
ReportDatabaseOpenError(MediaLicenseStorageHostOpenError::kInvalidBucket,
in_memory());
std::move(callback).Run(Status::kFailure, mojo::NullAssociatedRemote());
return;
}
if (file_name.empty()) {
DVLOG(1) << "No file specified.";
ReportDatabaseOpenError(MediaLicenseStorageHostOpenError::kNoFileSpecified,
in_memory());
std::move(callback).Run(Status::kFailure, mojo::NullAssociatedRemote());
return;
}
if (!CdmFileImpl::IsValidName(file_name)) {
ReportDatabaseOpenError(MediaLicenseStorageHostOpenError::kInvalidFileName,
in_memory());
std::move(callback).Run(Status::kFailure, mojo::NullAssociatedRemote());
return;
}
const CdmStorageBindingContext& binding_context =
receivers_.current_context();
db_.AsyncCall(&MediaLicenseDatabase::OpenFile)
.WithArgs(binding_context.cdm_type, file_name)
.Then(base::BindOnce(&MediaLicenseStorageHost::DidOpenFile,
weak_factory_.GetWeakPtr(), file_name,
binding_context, std::move(callback)));
}
void MediaLicenseStorageHost::BindReceiver(
const CdmStorageBindingContext& binding_context,
mojo::PendingReceiver<media::mojom::CdmStorage> receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(binding_context.storage_key, bucket_locator_.storage_key);
receivers_.Add(this, std::move(receiver), binding_context);
}
void MediaLicenseStorageHost::DidOpenFile(
const std::string& file_name,
CdmStorageBindingContext binding_context,
OpenCallback callback,
MediaLicenseStorageHostOpenError error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (error != MediaLicenseStorageHostOpenError::kOk) {
ReportDatabaseOpenError(error, in_memory());
std::move(callback).Run(Status::kFailure, mojo::NullAssociatedRemote());
return;
}
// Check whether this CDM file is in-use.
CdmFileId id(file_name, binding_context.cdm_type);
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;
// `this` is safe here since `cdm_file_impl` is owned by this instance.
cdm_files_.emplace(id, std::make_unique<CdmFileImpl>(
this, binding_context.cdm_type, file_name,
cdm_file.InitWithNewEndpointAndPassReceiver()));
// We don't actually touch the database here, but notify the quota system
// anyways since conceptually we're creating an empty file.
manager_->quota_manager_proxy()->NotifyBucketModified(
storage::QuotaClientType::kMediaLicense, bucket_locator_, /*delta=*/0,
/*modification_time=*/base::Time::Now(),
base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(std::move(callback), Status::kSuccess,
std::move(cdm_file)));
}
void MediaLicenseStorageHost::ReadFile(const media::CdmType& cdm_type,
const std::string& file_name,
ReadFileCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
manager_->quota_manager_proxy()->NotifyBucketAccessed(
bucket_locator_,
/*access_time=*/base::Time::Now());
db_.AsyncCall(&MediaLicenseDatabase::ReadFile)
.WithArgs(cdm_type, file_name)
.Then(std::move(callback));
}
void MediaLicenseStorageHost::WriteFile(const media::CdmType& cdm_type,
const std::string& file_name,
const std::vector<uint8_t>& data,
WriteFileCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
db_.AsyncCall(&MediaLicenseDatabase::WriteFile)
.WithArgs(cdm_type, file_name, data)
.Then(base::BindOnce(&MediaLicenseStorageHost::DidWriteFile,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void MediaLicenseStorageHost::DidWriteFile(WriteFileCallback callback,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!success) {
manager_->quota_manager_proxy()->OnClientWriteFailed(storage_key());
std::move(callback).Run(false);
return;
}
// Pass `delta`=0 since media license data does not count against quota.
// TODO(crbug.com/1305441): Consider counting this data against quota.
manager_->quota_manager_proxy()->NotifyBucketModified(
storage::QuotaClientType::kMediaLicense, bucket_locator_, /*delta=*/0,
/*modification_time=*/base::Time::Now(),
base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(std::move(callback), success));
}
void MediaLicenseStorageHost::DeleteFile(const media::CdmType& cdm_type,
const std::string& file_name,
DeleteFileCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
db_.AsyncCall(&MediaLicenseDatabase::DeleteFile)
.WithArgs(cdm_type, file_name)
.Then(base::BindOnce(&MediaLicenseStorageHost::DidWriteFile,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void MediaLicenseStorageHost::DeleteBucketData(
base::OnceCallback<void(bool)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
db_.AsyncCall(&MediaLicenseDatabase::ClearDatabase).Then(std::move(callback));
}
void MediaLicenseStorageHost::OnReceiverDisconnect() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// May delete `this`.
manager_->OnHostReceiverDisconnect(this,
base::PassKey<MediaLicenseStorageHost>());
}
void MediaLicenseStorageHost::OnFileReceiverDisconnect(
const std::string& name,
const media::CdmType& cdm_type,
base::PassKey<CdmFileImpl> /*pass_key*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto count = cdm_files_.erase(CdmFileId(name, cdm_type));
DCHECK_GT(count, 0u);
}
} // namespace content