blob: df04f56e77112300b0daae0ca730a058240c9b4f [file] [log] [blame]
// Copyright 2017 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_file_impl.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_util.h"
#include "content/browser/media/cdm_storage_manager.h"
#include "media/cdm/cdm_helpers.h"
#include "media/cdm/cdm_type.h"
#include "media/mojo/mojom/cdm_storage.mojom.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "storage/browser/quota/quota_manager.h"
#include "storage/common/file_system/file_system_types.h"
namespace content {
namespace {
// The CDM interface has a restriction that file names can not begin with _,
// so use it to prefix temporary files.
const char kTemporaryFilePrefix = '_';
// Maximum length of a file name.
const size_t kFileNameMaxLength = 256;
// UMA suffices for CDM File IO operations.
const char kReadFile[] = "ReadFile";
const char kWriteFile[] = "WriteFile";
const char kDeleteFile[] = "DeleteFile";
} // namespace
// static
bool CdmFileImpl::IsValidName(const std::string& name) {
// File names must only contain letters (A-Za-z), digits(0-9), or "._-",
// and not start with "_". It must contain at least 1 character, and not
// more then |kFileNameMaxLength| characters.
if (name.empty() || name.length() > kFileNameMaxLength ||
name[0] == kTemporaryFilePrefix) {
return false;
}
for (const auto ch : name) {
if (!base::IsAsciiAlpha(ch) && !base::IsAsciiDigit(ch) && ch != '.' &&
ch != '_' && ch != '-') {
return false;
}
}
return true;
}
CdmFileImpl::CdmFileImpl(
CdmStorageManager* manager,
const blink::StorageKey& storage_key,
const media::CdmType& cdm_type,
const std::string& file_name,
mojo::PendingAssociatedReceiver<media::mojom::CdmFile> pending_receiver)
: file_name_(file_name),
cdm_type_(cdm_type),
storage_key_(storage_key),
cdm_storage_manager_(manager) {
DVLOG(3) << __func__ << " " << file_name_;
DCHECK(IsValidName(file_name_));
DCHECK(cdm_storage_manager_);
receiver_.Bind(std::move(pending_receiver));
receiver_.set_disconnect_handler(base::BindOnce(
&CdmFileImpl::OnReceiverDisconnect, weak_factory_.GetWeakPtr()));
}
CdmFileImpl::~CdmFileImpl() {
DVLOG(3) << __func__ << " " << file_name_;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (read_callback_)
std::move(read_callback_).Run(Status::kFailure, {});
if (write_callback_)
std::move(write_callback_).Run(Status::kFailure);
}
void CdmFileImpl::Read(ReadCallback callback) {
DVLOG(3) << __func__ << " file: " << file_name_;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(cdm_storage_manager_);
// Only 1 Read() or Write() is allowed at any time.
if (read_callback_ || write_callback_) {
std::move(callback).Run(Status::kFailure, {});
return;
}
// Save |callback| for later use.
read_callback_ = std::move(callback);
start_time_ = base::TimeTicks::Now();
cdm_storage_manager_->ReadFile(
storage_key_, cdm_type_, file_name_,
base::BindOnce(&CdmFileImpl::DidRead, weak_factory_.GetWeakPtr()));
}
void CdmFileImpl::DidRead(std::optional<std::vector<uint8_t>> data) {
DVLOG(3) << __func__ << " file: " << file_name_
<< ", success: " << (data.has_value() ? "yes" : "no");
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(read_callback_);
DCHECK(cdm_storage_manager_);
bool success = data.has_value();
ReportFileOperationUMA(success, kReadFile);
if (!success) {
// Unable to read the contents of the file.
std::move(read_callback_).Run(Status::kFailure, {});
return;
}
std::move(read_callback_).Run(Status::kSuccess, std::move(data.value()));
}
void CdmFileImpl::Write(const std::vector<uint8_t>& data,
WriteCallback callback) {
DVLOG(3) << __func__ << " file: " << file_name_ << ", size: " << data.size();
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(cdm_storage_manager_);
// Only 1 Read() or Write() is allowed at any time.
if (read_callback_ || write_callback_) {
std::move(callback).Run(Status::kFailure);
return;
}
// Files are limited in size, so fail if file too big. This should have been
// checked by the caller, but we don't fully trust IPC.
if (data.size() > media::kMaxFileSizeBytes) {
DLOG(WARNING) << __func__
<< " Too much data to write. #bytes = " << data.size();
std::move(callback).Run(Status::kFailure);
return;
}
// Save |callback| for later use.
write_callback_ = std::move(callback);
start_time_ = base::TimeTicks::Now();
// If there is no data to write, delete the file to save space.
// |write_callback_| will be called after the file is deleted.
if (data.empty()) {
DeleteFile();
return;
}
cdm_storage_manager_->WriteFile(
storage_key_, cdm_type_, file_name_, data,
base::BindOnce(&CdmFileImpl::DidWrite, weak_factory_.GetWeakPtr()));
}
void CdmFileImpl::ReportFileOperationUMA(bool success,
const std::string& operation) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(cdm_storage_manager_);
// Strings for UMA names.
static const char kUmaPrefix[] = "Media.EME.CdmFileIO";
static const char kTimeTo[] = "TimeTo";
const std::string mode_suffix =
cdm_storage_manager_->in_memory() ? "Incognito" : "Normal";
// Records the result to the base histogram as well as splitting it out by
// incognito or normal mode.
auto result_uma_name = base::JoinString({kUmaPrefix, operation}, ".");
base::UmaHistogramBoolean(result_uma_name, success);
base::UmaHistogramBoolean(
base::JoinString({result_uma_name, mode_suffix}, "."), success);
// Records the time taken to the base histogram as well as splitting it out by
// incognito or normal mode. Only reported for successful operation.
if (success) {
auto time_taken = base::TimeTicks::Now() - start_time_;
auto time_taken_uma_name =
base::JoinString({kUmaPrefix, kTimeTo, operation}, ".");
base::UmaHistogramTimes(time_taken_uma_name, time_taken);
base::UmaHistogramTimes(
base::JoinString({time_taken_uma_name, mode_suffix}, "."), time_taken);
}
}
void CdmFileImpl::DidWrite(bool success) {
DVLOG(3) << __func__ << " file: " << file_name_;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(write_callback_);
DCHECK(cdm_storage_manager_);
ReportFileOperationUMA(success, kWriteFile);
if (!success) {
DLOG(WARNING) << "Unable to write to file " << file_name_;
std::move(write_callback_).Run(Status::kFailure);
return;
}
std::move(write_callback_).Run(Status::kSuccess);
}
void CdmFileImpl::DeleteFile() {
DVLOG(3) << __func__;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(write_callback_);
DCHECK(cdm_storage_manager_);
DVLOG(3) << "Deleting " << file_name_;
cdm_storage_manager_->DeleteFile(
storage_key_, cdm_type_, file_name_,
base::BindOnce(&CdmFileImpl::DidDeleteFile, weak_factory_.GetWeakPtr()));
}
void CdmFileImpl::DidDeleteFile(bool success) {
DVLOG(3) << __func__ << " file: " << file_name_;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(write_callback_);
DCHECK(cdm_storage_manager_);
ReportFileOperationUMA(success, kDeleteFile);
if (!success) {
DLOG(WARNING) << "Unable to delete file " << file_name_;
std::move(write_callback_).Run(Status::kFailure);
return;
}
std::move(write_callback_).Run(Status::kSuccess);
}
void CdmFileImpl::OnReceiverDisconnect() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(cdm_storage_manager_);
// May delete `this`.
cdm_storage_manager_->OnFileReceiverDisconnect(
file_name_, cdm_type_, storage_key_, base::PassKey<CdmFileImpl>());
}
} // namespace content