blob: 979077e45646bec97ec25c7a38ae3d4367918958 [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/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "content/browser/media/media_license_storage_host.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"
#include "third_party/blink/public/common/storage_key/storage_key.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 = '_';
// File size limit is 512KB. Licenses saved by the CDM are typically several
// hundreds of bytes.
const int64_t kMaxFileSizeBytes = 512 * 1024;
// Maximum length of a file name.
const size_t kFileNameMaxLength = 256;
const char kReadTimeUmaName[] = "Media.EME.CdmFileIO.TimeTo.ReadFile";
const char kWriteTimeUmaName[] = "Media.EME.CdmFileIO.TimeTo.WriteFile";
const char kDeleteTimeUmaName[] = "Media.EME.CdmFileIO.TimeTo.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(
MediaLicenseStorageHost* host,
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), host_(host) {
DVLOG(3) << __func__ << " " << file_name_;
DCHECK(IsValidName(file_name_));
DCHECK(host_);
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(host_);
// 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();
host_->ReadFile(
cdm_type_, file_name_,
base::BindOnce(&CdmFileImpl::DidRead, weak_factory_.GetWeakPtr()));
}
void CdmFileImpl::DidRead(absl::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(host_);
if (!data.has_value()) {
// Unable to read the contents of the file.
std::move(read_callback_).Run(Status::kFailure, {});
return;
}
// Only report reading time for successful reads.
ReportFileOperationTimeUMA(kReadTimeUmaName);
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(host_);
// 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() > 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;
}
host_->WriteFile(
cdm_type_, file_name_, data,
base::BindOnce(&CdmFileImpl::DidWrite, weak_factory_.GetWeakPtr()));
}
void CdmFileImpl::ReportFileOperationTimeUMA(const std::string& uma_name) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(host_);
static const char kIncognito[] = ".Incognito";
static const char kNormal[] = ".Normal";
bool is_incognito = host_->in_memory();
// This records the time taken to the base histogram as well as splitting it
// out by incognito or normal mode.
auto time_taken = base::TimeTicks::Now() - start_time_;
base::UmaHistogramTimes(uma_name, time_taken);
base::UmaHistogramTimes(
base::StrCat({uma_name, is_incognito ? kIncognito : kNormal}),
time_taken);
}
void CdmFileImpl::DidWrite(bool success) {
DVLOG(3) << __func__ << " file: " << file_name_;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(write_callback_);
DCHECK(host_);
if (!success) {
DLOG(WARNING) << "Unable to write to file " << file_name_;
std::move(write_callback_).Run(Status::kFailure);
return;
}
// Only report writing time for successful writes.
ReportFileOperationTimeUMA(kWriteTimeUmaName);
std::move(write_callback_).Run(Status::kSuccess);
}
void CdmFileImpl::DeleteFile() {
DVLOG(3) << __func__;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(write_callback_);
DCHECK(host_);
DVLOG(3) << "Deleting " << file_name_;
host_->DeleteFile(
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(host_);
if (!success) {
DLOG(WARNING) << "Unable to delete file " << file_name_;
std::move(write_callback_).Run(Status::kFailure);
return;
}
// Only report writing time for successful deletions.
ReportFileOperationTimeUMA(kDeleteTimeUmaName);
std::move(write_callback_).Run(Status::kSuccess);
}
void CdmFileImpl::OnReceiverDisconnect() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(host_);
// May delete `this`.
host_->OnFileReceiverDisconnect(file_name_, cdm_type_,
base::PassKey<CdmFileImpl>());
}
} // namespace content