blob: dc07c1197b83e82b5a64fffc82e25ad73858acf9 [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 "media/mojo/services/mojo_cdm_file_io.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.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 FileStatus = media::mojom::CdmFile::Status;
using StorageStatus = media::mojom::CdmStorage::Status;
// File size limit is 512KB. Licenses saved by the CDM are typically several
// hundreds of bytes. This value should match what is in CdmFileImpl.
const int64_t kMaxFileSizeBytes = 512 * 1024;
const char* ConvertStorageStatus(StorageStatus status) {
switch (status) {
case StorageStatus::kSuccess:
return "kSuccess";
case StorageStatus::kInUse:
return "kInUse";
case StorageStatus::kFailure:
return "kFailure";
}
return "unknown";
}
const char* ConvertFileStatus(FileStatus status) {
return status == FileStatus::kSuccess ? "kSuccess" : "kFailure";
}
} // namespace
MojoCdmFileIO::MojoCdmFileIO(Delegate* delegate,
cdm::FileIOClient* client,
mojo::Remote<mojom::CdmStorage> cdm_storage)
: delegate_(delegate),
client_(client),
cdm_storage_(std::move(cdm_storage)) {
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;
// Open is only allowed if the current state is kUnopened and the file name
// is valid.
if (state_ != State::kUnopened) {
OnError(ErrorType::kOpenError);
return;
}
state_ = State::kOpening;
file_name_ = file_name_string;
TRACE_EVENT_ASYNC_BEGIN1("media", "MojoCdmFileIO::Open", this, "file_name",
file_name_);
// Wrap the callback to detect the case when the mojo connection is
// terminated prior to receiving the response. This avoids problems if the
// service is destroyed before the CDM. If that happens let the CDM know that
// Open() failed.
auto callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(&MojoCdmFileIO::OnFileOpened, weak_factory_.GetWeakPtr()),
StorageStatus::kFailure, mojo::NullAssociatedRemote());
cdm_storage_->Open(file_name_string, std::move(callback));
}
void MojoCdmFileIO::OnFileOpened(
StorageStatus status,
mojo::PendingAssociatedRemote<mojom::CdmFile> cdm_file) {
DVLOG(3) << __func__ << " file: " << file_name_ << ", status: " << status;
UMA_HISTOGRAM_ENUMERATION("Media.EME.CdmFileIO::OpenFile", status);
// This logs the end of the async Open() request, and separately logs
// how long the client takes in OnOpenComplete().
TRACE_EVENT_ASYNC_END1("media", "MojoCdmFileIO::Open", this, "status",
ConvertStorageStatus(status));
switch (status) {
case StorageStatus::kSuccess:
// File was successfully opened.
state_ = State::kOpened;
cdm_file_.Bind(std::move(cdm_file));
{
TRACE_EVENT0("media", "FileIOClient::OnOpenComplete");
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_;
// 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;
}
TRACE_EVENT_ASYNC_BEGIN1("media", "MojoCdmFileIO::Read", this, "file_name",
file_name_);
state_ = State::kReading;
// Wrap the callback to detect the case when the mojo connection is
// terminated prior to receiving the response. This avoids problems if the
// service is destroyed before the CDM. If that happens let the CDM know that
// Read() failed.
auto callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(&MojoCdmFileIO::OnFileRead, weak_factory_.GetWeakPtr()),
FileStatus::kFailure, std::vector<uint8_t>());
cdm_file_->Read(std::move(callback));
}
void MojoCdmFileIO::OnFileRead(FileStatus status,
const std::vector<uint8_t>& data) {
DVLOG(3) << __func__ << " file: " << file_name_;
DCHECK_EQ(State::kReading, state_);
// This logs the end of the async Read() request, and separately logs
// how long the client takes in OnReadComplete().
TRACE_EVENT_ASYNC_END2("media", "MojoCdmFileIO::Read", this, "bytes_read",
data.size(), "status", ConvertFileStatus(status));
if (status != FileStatus::kSuccess) {
DVLOG(1) << "Failed to read file " << file_name_;
state_ = State::kOpened;
OnError(ErrorType::kReadError);
return;
}
// Call this before OnReadComplete() so that we always have the latest file
// size before CDM fires errors.
delegate_->ReportFileReadSize(data.size());
state_ = State::kOpened;
TRACE_EVENT0("media", "FileIOClient::OnReadComplete");
client_->OnReadComplete(ClientStatus::kSuccess, data.data(), data.size());
}
void MojoCdmFileIO::Write(const uint8_t* data, uint32_t data_size) {
DVLOG(3) << __func__ << " file: " << file_name_ << ", bytes: " << data_size;
// 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 in size, 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;
}
TRACE_EVENT_ASYNC_BEGIN2("media", "MojoCdmFileIO::Write", this, "file_name",
file_name_, "bytes_to_write", data_size);
state_ = State::kWriting;
// Wrap the callback to detect the case when the mojo connection is
// terminated prior to receiving the response. This avoids problems if the
// service is destroyed before the CDM. If that happens let the CDM know that
// Write() failed.
auto callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(&MojoCdmFileIO::OnFileWritten, weak_factory_.GetWeakPtr()),
FileStatus::kFailure);
cdm_file_->Write(std::vector<uint8_t>(data, data + data_size),
std::move(callback));
}
void MojoCdmFileIO::OnFileWritten(FileStatus status) {
DVLOG(3) << __func__ << " file: " << file_name_;
DCHECK_EQ(State::kWriting, state_);
// This logs the end of the async Write() request, and separately logs
// how long the client takes in OnWriteComplete().
TRACE_EVENT_ASYNC_END1("media", "MojoCdmFileIO::Write", this, "status",
ConvertFileStatus(status));
if (status != FileStatus::kSuccess) {
DVLOG(1) << "Failed to write file " << file_name_;
state_ = State::kError;
OnError(ErrorType::kWriteError);
return;
}
state_ = State::kOpened;
TRACE_EVENT0("media", "FileIOClient::OnWriteComplete");
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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&MojoCdmFileIO::NotifyClientOfError,
weak_factory_.GetWeakPtr(), error));
}
void MojoCdmFileIO::NotifyClientOfError(ErrorType error) {
// Note that no event tracing is done for error conditions.
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