blob: d5df66083ce595fc6933ce510121f99bbee507b6 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/mojo/services/mojo_cdm_file_io.h"
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
namespace media {
namespace {
using ClientStatus = cdm::FileIOClient::Status;
using StorageStatus = media::mojom::CdmStorage::Status;
// File size limit is 32MB. Licenses saved by the CDM are typically several
// hundreds of bytes.
const int64_t kMaxFileSizeBytes = 32 * 1024 * 1024;
// Maximum length of a file name.
const size_t kFileNameMaxLength = 256;
// 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.
bool IsValidFileName(const std::string& name) {
if (name.empty() || name.length() > kFileNameMaxLength || name[0] == '_')
return false;
for (auto ch : name) {
if (!base::IsAsciiAlpha(ch) && !base::IsAsciiDigit(ch) && ch != '.' &&
ch != '_' && ch != '-') {
return false;
}
}
return true;
}
} // namespace
MojoCdmFileIO::MojoCdmFileIO(Delegate* delegate,
cdm::FileIOClient* client,
mojom::CdmStorage* cdm_storage)
: delegate_(delegate),
client_(client),
cdm_storage_(cdm_storage),
weak_factory_(this) {
DVLOG(1) << __func__;
DCHECK(delegate_);
DCHECK(client_);
DCHECK(cdm_storage_);
}
MojoCdmFileIO::~MojoCdmFileIO() {
DVLOG(1) << __func__;
}
void MojoCdmFileIO::Open(const char* file_name, uint32_t file_name_size) {
std::string file_name_string(file_name, file_name_size);
DVLOG(3) << __func__ << " file: " << file_name_string;
TRACE_EVENT1("media", "MojoCdmFileIO::Open", "file_name", file_name_string);
// Open is only allowed if the current state is kUnopened and the file name
// is valid.
if (state_ != State::kUnopened) {
OnError(ErrorType::kOpenError);
return;
}
if (!IsValidFileName(file_name_string)) {
OnError(ErrorType::kOpenError);
return;
}
state_ = State::kOpening;
file_name_ = file_name_string;
auto callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(&MojoCdmFileIO::OnFileOpened, weak_factory_.GetWeakPtr()),
StorageStatus::kFailure, base::File(), nullptr);
cdm_storage_->Open(file_name_string, std::move(callback));
}
void MojoCdmFileIO::OnFileOpened(StorageStatus status,
base::File file,
mojom::CdmFileAssociatedPtrInfo cdm_file) {
DVLOG(3) << __func__ << " file: " << file_name_ << ", status: " << status;
TRACE_EVENT2("media", "MojoCdmFileIO::FileOpened", "file_name", file_name_,
"status", static_cast<int32_t>(status));
switch (status) {
case StorageStatus::kSuccess:
if (!file.IsValid()) {
NOTREACHED() << "File received is invalid.";
state_ = State::kError;
OnError(ErrorType::kOpenError);
return;
}
// File was successfully opened.
state_ = State::kOpened;
file_for_reading_ = std::move(file);
cdm_file_.Bind(std::move(cdm_file));
client_->OnOpenComplete(ClientStatus::kSuccess);
return;
case StorageStatus::kInUse:
// File already open by somebody else.
state_ = State::kUnopened;
OnError(ErrorType::kOpenInUse);
return;
case StorageStatus::kFailure:
// Something went wrong.
state_ = State::kError;
OnError(ErrorType::kOpenError);
return;
}
NOTREACHED();
}
void MojoCdmFileIO::Read() {
DVLOG(3) << __func__ << " file: " << file_name_;
TRACE_EVENT1("media", "MojoCdmFileIO::Read", "file_name", file_name_);
// If another operation is in progress, fail.
if (state_ == State::kReading || state_ == State::kWriting) {
OnError(ErrorType::kReadInUse);
return;
}
// If the file is not open, fail.
if (state_ != State::kOpened) {
OnError(ErrorType::kReadError);
return;
}
// Determine the size of the file (so we know how many bytes to read).
int64_t num_bytes = file_for_reading_.GetLength();
if (num_bytes < 0) {
// Negative bytes mean failure, so fail. This error is not recoverable,
// so don't allow any more operations other than close on the file.
state_ = State::kError;
OnError(ErrorType::kReadError);
return;
}
// Files are limited to 32MB, so fail if file too big. Setting |state_|
// back to Opened so that the CDM could write something valid to this file.
if (num_bytes > kMaxFileSizeBytes) {
DLOG(WARNING) << __func__
<< " Too much data to read. #bytes = " << num_bytes;
OnError(ErrorType::kReadError);
return;
}
// Do the actual read asynchronously so that we don't need to copy the
// data when calling |client_|.
state_ = State::kReading;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MojoCdmFileIO::DoRead,
weak_factory_.GetWeakPtr(), num_bytes));
}
void MojoCdmFileIO::DoRead(int64_t num_bytes) {
DVLOG(3) << __func__ << " file: " << file_name_;
DCHECK_EQ(State::kReading, state_);
TRACE_EVENT2("media", "MojoCdmFileIO::DoRead", "file_name", file_name_,
"bytes_to_read", num_bytes);
// We know how much data is available, so read the complete contents of the
// file into a buffer and passing it back to |client_|. As these should be
// small files, we don't worry about breaking it up into chunks to read it.
// Read the contents of the file. Read() sizes (provided and returned) are
// type int, so cast appropriately.
int bytes_to_read = base::checked_cast<int>(num_bytes);
std::vector<uint8_t> buffer(bytes_to_read);
// If the file has 0 bytes, no need to read anything.
if (bytes_to_read != 0) {
TRACE_EVENT0("media", "MojoCdmFileIO::ActualRead");
base::TimeTicks start = base::TimeTicks::Now();
int bytes_read = file_for_reading_.Read(
0, reinterpret_cast<char*>(buffer.data()), bytes_to_read);
base::TimeDelta read_time = base::TimeTicks::Now() - start;
if (bytes_to_read != bytes_read) {
// Unable to read the contents of the file. Setting |state_| to kOpened
// so that the CDM can write something valid to this file.
DVLOG(1) << "Failed to read file " << file_name_ << ". Requested "
<< bytes_to_read << " bytes, got " << bytes_read;
state_ = State::kOpened;
OnError(ErrorType::kReadError);
return;
}
// Only report reading time for successful reads.
UMA_HISTOGRAM_TIMES("Media.EME.CdmFileIO.ReadTime", read_time);
}
// Call this before OnReadComplete() so that we always have the latest file
// size before CDM fires errors.
delegate_->ReportFileReadSize(bytes_to_read);
state_ = State::kOpened;
client_->OnReadComplete(ClientStatus::kSuccess, buffer.data(), buffer.size());
}
void MojoCdmFileIO::Write(const uint8_t* data, uint32_t data_size) {
DVLOG(3) << __func__ << " file: " << file_name_ << ", bytes: " << data_size;
TRACE_EVENT1("media", "MojoCdmFileIO::Write", "file_name", file_name_);
// If another operation is in progress, fail.
if (state_ == State::kReading || state_ == State::kWriting) {
OnError(ErrorType::kWriteInUse);
return;
}
// If the file is not open, fail.
if (state_ != State::kOpened) {
OnError(ErrorType::kWriteError);
return;
}
// Files are limited to 32MB, so fail if file too big.
if (data_size > kMaxFileSizeBytes) {
DLOG(WARNING) << __func__
<< " Too much data to write. #bytes = " << data_size;
OnError(ErrorType::kWriteError);
return;
}
// Now open a temporary file for writing. Close |file_for_reading_| as it
// won't be used again.
state_ = State::kWriting;
file_for_reading_.Close();
cdm_file_->OpenFileForWriting(
base::BindOnce(&MojoCdmFileIO::DoWrite, weak_factory_.GetWeakPtr(),
std::vector<uint8_t>(data, data + data_size)));
}
void MojoCdmFileIO::DoWrite(const std::vector<uint8_t>& data,
base::File temporary_file) {
DVLOG(3) << __func__ << " file: " << file_name_ << ", result: "
<< base::File::ErrorToString(temporary_file.error_details());
DCHECK_EQ(State::kWriting, state_);
TRACE_EVENT2("media", "MojoCdmFileIO::DoWrite", "file_name", file_name_,
"bytes_to_write", data.size());
if (!temporary_file.IsValid()) {
// Failed to open temporary file.
state_ = State::kError;
OnError(ErrorType::kWriteError);
return;
}
// As the temporary file should have been newly created, it should be empty.
// No need to call write() if there is no data.
CHECK_EQ(0u, temporary_file.GetLength()) << "Temporary file is not empty.";
int bytes_to_write = base::checked_cast<int>(data.size());
if (bytes_to_write > 0) {
TRACE_EVENT0("media", "MojoCdmFileIO::ActualWrite");
base::TimeTicks start = base::TimeTicks::Now();
int bytes_written = temporary_file.Write(
0, reinterpret_cast<const char*>(data.data()), bytes_to_write);
base::TimeDelta write_time = base::TimeTicks::Now() - start;
if (bytes_written != bytes_to_write) {
// Failed to write to the temporary file.
state_ = State::kError;
OnError(ErrorType::kWriteError);
return;
}
// Only report writing time for successful writes.
UMA_HISTOGRAM_TIMES("Media.EME.CdmFileIO.WriteTime", write_time);
}
// Close the temporary file returned before renaming. Original file was
// closed previously.
temporary_file.Close();
DCHECK(!file_for_reading_.IsValid()) << "Original file was not closed.";
cdm_file_->CommitWrite(base::BindOnce(&MojoCdmFileIO::OnWriteCommitted,
weak_factory_.GetWeakPtr()));
}
void MojoCdmFileIO::OnWriteCommitted(base::File reopened_file) {
DVLOG(3) << __func__ << " file: " << file_name_;
DCHECK_EQ(State::kWriting, state_);
DCHECK(!file_for_reading_.IsValid()) << "Original file was not closed.";
TRACE_EVENT1("media", "MojoCdmFileIO::WriteDone", "file_name", file_name_);
if (!reopened_file.IsValid()) {
// Rename failed, and no file to use.
state_ = State::kError;
OnError(ErrorType::kWriteError);
return;
}
state_ = State::kOpened;
file_for_reading_ = std::move(reopened_file);
client_->OnWriteComplete(ClientStatus::kSuccess);
}
void MojoCdmFileIO::Close() {
DVLOG(3) << __func__ << " file: " << file_name_;
// Note: |this| could be deleted as part of this call.
delegate_->CloseCdmFileIO(this);
}
void MojoCdmFileIO::OnError(ErrorType error) {
DVLOG(3) << __func__ << " file: " << file_name_ << ", error: " << (int)error;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MojoCdmFileIO::NotifyClientOfError,
weak_factory_.GetWeakPtr(), error));
}
void MojoCdmFileIO::NotifyClientOfError(ErrorType error) {
switch (error) {
case ErrorType::kOpenError:
client_->OnOpenComplete(ClientStatus::kError);
break;
case ErrorType::kOpenInUse:
client_->OnOpenComplete(ClientStatus::kInUse);
break;
case ErrorType::kReadError:
client_->OnReadComplete(ClientStatus::kError, nullptr, 0);
break;
case ErrorType::kReadInUse:
client_->OnReadComplete(ClientStatus::kInUse, nullptr, 0);
break;
case ErrorType::kWriteError:
client_->OnWriteComplete(ClientStatus::kError);
break;
case ErrorType::kWriteInUse:
client_->OnWriteComplete(ClientStatus::kInUse);
break;
}
}
} // namespace media