blob: 1a72a60a46cc3182c481ad95dc994e99a867e59f [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/media/webrtc/webrtc_event_log_history.h"
#include <limits>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
namespace webrtc_event_logging {
const size_t kWebRtcEventLogMaxUploadIdBytes = 100;
namespace {
// Compactness is not important for these few and small files; we therefore
// go with a human-readable format.
const char kCaptureTimeLinePrefix[] =
"Capture time (seconds since UNIX epoch): ";
const char kUploadTimeLinePrefix[] = "Upload time (seconds since UNIX epoch): ";
const char kUploadIdLinePrefix[] = "Upload ID: ";
// No need to use \r\n for Windows; better have a consistent file format
// between platforms.
const char kEOL[] = "\n";
static_assert(std::size(kEOL) == 1 + 1 /* +1 for the implicit \0. */,
"SplitString relies on this being a single character.");
// |time| must *not* be earlier than UNIX epoch start. If it is, the empty
// string is returned.
std::string DeltaFromEpochSeconds(base::Time time) {
if (time.is_null() || time.is_min() || time.is_max()) {
LOG(ERROR) << "Not a valid time (" << time << ").";
return std::string();
}
const base::Time epoch = base::Time::UnixEpoch();
if (time < epoch) {
LOG(WARNING) << "Time to go back to the future.";
return std::string();
}
return base::NumberToString((time - epoch).InSeconds());
}
// Helper for ParseTime; see its documentation for details.
base::Time StringToTime(const std::string& time) {
int64_t seconds_from_epoch;
if (!base::StringToInt64(time, &seconds_from_epoch) ||
seconds_from_epoch < 0) {
LOG(WARNING) << "Error encountered while reading time.";
return base::Time();
}
return base::Time::UnixEpoch() + base::Seconds(seconds_from_epoch);
}
// Convert a history file's timestamp, which is the number of seconds since
// UNIX epoch, into a base::Time object.
// This function errors on timestamps from UNIX epoch or before it.
bool ParseTime(const std::string& line,
const std::string& prefix,
base::Time* out) {
DCHECK(line.find(prefix) == 0);
DCHECK(out);
if (!out->is_null()) {
LOG(WARNING) << "Repeated line.";
return false;
}
const base::Time time = StringToTime(line.substr(prefix.length()));
if (time.is_null()) {
LOG(WARNING) << "Null time.";
return false;
}
*out = time;
return true;
}
bool ParseString(const std::string& line,
const std::string& prefix,
std::string* out) {
DCHECK(line.find(prefix) == 0);
DCHECK(out);
if (!out->empty()) {
LOG(WARNING) << "Repeated line.";
return false;
}
*out = line.substr(prefix.length());
if (out->empty()) {
LOG(WARNING) << "Empty string.";
return false;
}
return true;
}
} // namespace
std::unique_ptr<WebRtcEventLogHistoryFileWriter>
WebRtcEventLogHistoryFileWriter::Create(const base::FilePath& path) {
auto history_file_writer =
base::WrapUnique(new WebRtcEventLogHistoryFileWriter(path));
if (!history_file_writer->Init()) {
LOG(WARNING) << "Initialization of history file writer failed.";
return nullptr;
}
return history_file_writer;
}
WebRtcEventLogHistoryFileWriter::WebRtcEventLogHistoryFileWriter(
const base::FilePath& path)
: path_(path), valid_(false) {}
bool WebRtcEventLogHistoryFileWriter::Init() {
DCHECK(!valid_);
if (base::PathExists(path_)) {
if (!base::DeleteFile(path_)) {
LOG(ERROR) << "History file already exists, and could not be deleted.";
return false;
}
LOG(WARNING) << "History file already existed; deleted.";
}
// Attempt to create the file.
constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
base::File::FLAG_WIN_EXCLUSIVE_WRITE;
file_.Initialize(path_, file_flags);
if (!file_.IsValid() || !file_.created()) {
LOG(WARNING) << "Couldn't create history file.";
if (!base::DeleteFile(path_)) {
LOG(ERROR) << "Failed to delete " << path_ << ".";
}
return false;
}
valid_ = true;
return true;
}
bool WebRtcEventLogHistoryFileWriter::WriteCaptureTime(
base::Time capture_time) {
DCHECK(valid_);
if (capture_time.is_null()) {
valid_ = false;
return false;
}
const std::string delta_seconds = DeltaFromEpochSeconds(capture_time);
if (delta_seconds.empty()) {
valid_ = false;
return false;
}
const bool written = Write(kCaptureTimeLinePrefix + delta_seconds + kEOL);
if (!written) {
// Error logged by Write().
valid_ = false;
return false;
}
return true;
}
bool WebRtcEventLogHistoryFileWriter::WriteUploadTime(base::Time upload_time) {
DCHECK(valid_);
if (upload_time.is_null()) {
valid_ = false;
return false;
}
const std::string delta_seconds = DeltaFromEpochSeconds(upload_time);
if (delta_seconds.empty()) {
valid_ = false;
return false;
}
const bool written = Write(kUploadTimeLinePrefix + delta_seconds + kEOL);
if (!written) {
valid_ = false;
return false;
}
return true;
}
bool WebRtcEventLogHistoryFileWriter::WriteUploadId(
const std::string& upload_id) {
DCHECK(valid_);
DCHECK(!upload_id.empty());
DCHECK_LE(upload_id.length(), kWebRtcEventLogMaxUploadIdBytes);
const bool written = Write(kUploadIdLinePrefix + upload_id + kEOL);
if (!written) {
valid_ = false;
return false;
}
return true;
}
void WebRtcEventLogHistoryFileWriter::Delete() {
if (!base::DeleteFile(path_)) {
LOG(ERROR) << "History file could not be deleted.";
}
valid_ = false; // Like was already false.
}
base::FilePath WebRtcEventLogHistoryFileWriter::path() const {
DCHECK(valid_); // Can be performed on invalid objects, but likely shouldn't.
return path_;
}
bool WebRtcEventLogHistoryFileWriter::Write(const std::string& str) {
DCHECK(valid_);
DCHECK(!str.empty());
DCHECK_LE(str.length(), static_cast<size_t>(std::numeric_limits<int>::max()));
if (!file_.WriteAtCurrentPosAndCheck(base::as_byte_span(str))) {
LOG(WARNING) << "Writing to history file failed.";
valid_ = false;
return false;
}
// Writes to the history file are infrequent, and happen on a |task_runner_|
// dedicated to event logs. We can therefore afford to Flush() after every
// write, giving us greater confidence that information would not get lost if,
// e.g., Chrome crashes.
file_.Flush();
return true;
}
std::unique_ptr<WebRtcEventLogHistoryFileReader>
WebRtcEventLogHistoryFileReader::Create(const base::FilePath& path) {
auto history_file_reader =
base::WrapUnique(new WebRtcEventLogHistoryFileReader(path));
if (!history_file_reader->Init()) {
LOG(WARNING) << "Initialization of history file reader failed.";
return nullptr;
}
return history_file_reader;
}
WebRtcEventLogHistoryFileReader::WebRtcEventLogHistoryFileReader(
const base::FilePath& path)
: path_(path),
local_id_(ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(path_)),
valid_(false) {}
WebRtcEventLogHistoryFileReader::WebRtcEventLogHistoryFileReader(
WebRtcEventLogHistoryFileReader&& other)
: path_(other.path_),
local_id_(other.local_id_),
capture_time_(other.capture_time_),
upload_time_(other.upload_time_),
upload_id_(other.upload_id_),
valid_(other.valid_) {
other.valid_ = false;
}
bool WebRtcEventLogHistoryFileReader::Init() {
DCHECK(!valid_);
if (local_id_.empty()) {
LOG(WARNING) << "Unknown local ID.";
return false;
}
if (local_id_.length() > kWebRtcEventLogMaxUploadIdBytes) {
LOG(WARNING) << "Excessively long local ID.";
return false;
}
if (!base::PathExists(path_)) {
LOG(WARNING) << "File does not exist.";
return false;
}
constexpr int file_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
base::File file(path_, file_flags);
if (!file.IsValid()) {
LOG(WARNING) << "Couldn't read history file.";
if (!base::DeleteFile(path_)) {
LOG(ERROR) << "Failed to delete " << path_ << ".";
}
return false;
}
constexpr size_t kMaxHistoryFileSizeBytes = 1024;
static_assert(kWebRtcEventLogMaxUploadIdBytes < kMaxHistoryFileSizeBytes, "");
std::string file_contents;
file_contents.resize(kMaxHistoryFileSizeBytes);
const int read_bytes =
UNSAFE_TODO(file.Read(0, &file_contents[0], file_contents.size()));
if (read_bytes < 0) {
LOG(WARNING) << "Couldn't read contents of history file.";
return false;
}
DCHECK_LE(static_cast<size_t>(read_bytes), file_contents.size());
file_contents.resize(static_cast<size_t>(read_bytes));
// Note: In excessively long files, the rest of the file will be ignored; the
// beginning of the file will encounter a parse error.
if (!Parse(file_contents)) {
LOG(WARNING) << "Parsing of history file failed.";
return false;
}
valid_ = true;
return true;
}
std::string WebRtcEventLogHistoryFileReader::LocalId() const {
DCHECK(valid_);
DCHECK(!local_id_.empty());
return local_id_;
}
base::Time WebRtcEventLogHistoryFileReader::CaptureTime() const {
DCHECK(valid_);
DCHECK(!capture_time_.is_null());
return capture_time_;
}
base::Time WebRtcEventLogHistoryFileReader::UploadTime() const {
DCHECK(valid_);
return upload_time_; // May be null (which indicates "unset").
}
std::string WebRtcEventLogHistoryFileReader::UploadId() const {
DCHECK(valid_);
return upload_id_;
}
base::FilePath WebRtcEventLogHistoryFileReader::path() const {
DCHECK(valid_);
return path_;
}
bool WebRtcEventLogHistoryFileReader::operator<(
const WebRtcEventLogHistoryFileReader& other) const {
DCHECK(valid_);
DCHECK(!capture_time_.is_null());
DCHECK(other.valid_);
DCHECK(!other.capture_time_.is_null());
if (capture_time_ == other.capture_time_) {
// Resolve ties arbitrarily, but consistently (Local IDs are unique).
return LocalId() < other.LocalId();
}
return (capture_time_ < other.capture_time_);
}
bool WebRtcEventLogHistoryFileReader::Parse(const std::string& file_contents) {
DCHECK(!valid_);
DCHECK(capture_time_.is_null());
DCHECK(upload_time_.is_null());
DCHECK(upload_id_.empty());
const std::vector<std::string> lines =
base::SplitString(file_contents, kEOL, base::TRIM_WHITESPACE,
base::SplitResult::SPLIT_WANT_NONEMPTY);
for (const std::string& line : lines) {
if (line.find(kCaptureTimeLinePrefix) == 0) {
if (!ParseTime(line, kCaptureTimeLinePrefix, &capture_time_)) {
return false;
}
} else if (line.find(kUploadTimeLinePrefix) == 0) {
if (!ParseTime(line, kUploadTimeLinePrefix, &upload_time_)) {
return false;
}
} else if (line.find(kUploadIdLinePrefix) == 0) {
if (!ParseString(line, kUploadIdLinePrefix, &upload_id_)) {
return false;
}
} else {
LOG(WARNING) << "Unrecognized line in history file.";
return false;
}
}
if (capture_time_.is_null()) {
LOG(WARNING) << "Incomplete history file; capture time unknown.";
return false;
}
if (!upload_id_.empty() && upload_time_.is_null()) {
LOG(WARNING) << "Incomplete history file; upload time known, "
<< "but ID unknown.";
return false;
}
if (!upload_time_.is_null() && upload_time_ < capture_time_) {
LOG(WARNING) << "Defective history file; claims to have been uploaded "
<< "before being captured.";
return false;
}
return true;
}
} // namespace webrtc_event_logging