Commit 4512f77b authored by Lambros Lambrou's avatar Lambros Lambrou Committed by Commit Bot

[remoting] Add FileOperations subclass for sending RTC event logs.

This adds a FileOperations implementation which can send the host's
RTC event log as if it were a regular file. It runs on the same network
thread that gathers the RTC event log and sends file-transfer data, so
on Windows it should work inside the network process without any need
for IPC.

Bug: 1122798
Change-Id: I6b4484caa70804e0091e7a747ebf9ff60fe71b14
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2441449
Commit-Queue: Lambros Lambrou <lambroslambrou@chromium.org>
Commit-Queue: Erik Jensen <rkjnsn@chromium.org>
Auto-Submit: Lambros Lambrou <lambroslambrou@chromium.org>
Reviewed-by: default avatarErik Jensen <rkjnsn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812839}
parent d59bb7ac
......@@ -57,6 +57,7 @@ source_set("common") {
"file_transfer_message_handler.h",
"ipc_file_operations.h",
"local_file_operations.h",
"rtc_log_file_operations.h",
"session_file_operations_handler.h",
]
......@@ -68,6 +69,7 @@ source_set("common") {
"file_transfer_message_handler.cc",
"ipc_file_operations.cc",
"local_file_operations.cc",
"rtc_log_file_operations.cc",
"session_file_operations_handler.cc",
]
......@@ -103,6 +105,7 @@ source_set("unit_tests") {
"file_transfer_message_handler_unittest.cc",
"ipc_file_operations_unittest.cc",
"local_file_operations_unittest.cc",
"rtc_log_file_operations_unittest.cc",
]
deps = [
......
// Copyright 2020 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 "remoting/host/file_transfer/rtc_log_file_operations.h"
#include <algorithm>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "remoting/protocol/connection_to_client.h"
#include "remoting/protocol/file_transfer_helpers.h"
#include "remoting/protocol/webrtc_event_log_data.h"
namespace remoting {
namespace {
class RtcLogFileReader : public FileOperations::Reader {
public:
explicit RtcLogFileReader(protocol::ConnectionToClient* connection);
~RtcLogFileReader() override;
RtcLogFileReader(const RtcLogFileReader&) = delete;
RtcLogFileReader& operator=(const RtcLogFileReader&) = delete;
// FileOperations::Reader interface.
void Open(OpenCallback callback) override;
void ReadChunk(std::size_t size, ReadCallback callback) override;
const base::FilePath& filename() const override;
std::uint64_t size() const override;
FileOperations::State state() const override;
private:
using LogSection = protocol::WebrtcEventLogData::LogSection;
void DoOpen(OpenCallback callback);
void DoReadChunk(std::size_t size, ReadCallback callback);
// Reads up to |maximum_to_read| bytes from the event log, and appends them
// to |output| and returns the number of bytes appended. This only reads from
// a single LogSection, and it takes care of advancing to the next LogSection
// if the end is reached. Returns 0 if there is no more data to be read.
int ReadPartially(int maximum_to_read, std::vector<std::uint8_t>& output);
protocol::ConnectionToClient* connection_;
base::FilePath filename_;
base::circular_deque<LogSection> data_;
FileOperations::State state_ = FileOperations::kCreated;
// Points to the current LogSection being read from, or data_.end() if
// reading is finished.
base::circular_deque<LogSection>::const_iterator current_log_section_;
// Points to the current read position inside |current_log_section_| or is
// undefined if current_log_section_ == data_.end(). Note that each
// LogSection of |data_| is always non-empty. If the end of a LogSection is
// reached, |current_log_section_| will advance to the next section, and this
// position will be reset to the beginning of the new section.
LogSection::const_iterator current_position_;
base::WeakPtrFactory<RtcLogFileReader> weak_factory_{this};
};
RtcLogFileReader::RtcLogFileReader(protocol::ConnectionToClient* connection)
: connection_(connection) {}
RtcLogFileReader::~RtcLogFileReader() = default;
void RtcLogFileReader::Open(OpenCallback callback) {
state_ = FileOperations::kBusy;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&RtcLogFileReader::DoOpen, weak_factory_.GetWeakPtr(),
std::move(callback)));
}
void RtcLogFileReader::ReadChunk(std::size_t size, ReadCallback callback) {
state_ = FileOperations::kBusy;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&RtcLogFileReader::DoReadChunk, weak_factory_.GetWeakPtr(),
size, std::move(callback)));
}
const base::FilePath& RtcLogFileReader::filename() const {
return filename_;
}
std::uint64_t RtcLogFileReader::size() const {
std::uint64_t result = 0;
for (const auto& section : data_) {
result += section.size();
}
return result;
}
FileOperations::State RtcLogFileReader::state() const {
return state_;
}
void RtcLogFileReader::DoOpen(OpenCallback callback) {
protocol::WebrtcEventLogData* rtc_log = connection_->rtc_event_log();
if (!rtc_log) {
// This is a protocol error because RTC log is only supported for WebRTC
// connections.
state_ = FileOperations::kFailed;
std::move(callback).Run(protocol::MakeFileTransferError(
FROM_HERE, protocol::FileTransfer_Error_Type_PROTOCOL_ERROR));
return;
}
base::Time::Exploded exploded;
base::Time::NowFromSystemTime().LocalExplode(&exploded);
std::string filename = base::StringPrintf(
"host-rtc-log-%d-%d-%d_%d-%d-%d", exploded.year, exploded.month,
exploded.day_of_month, exploded.hour, exploded.minute, exploded.second);
filename_ = base::FilePath::FromUTF8Unsafe(filename);
data_ = rtc_log->TakeLogData();
current_log_section_ = data_.begin();
if (!data_.empty()) {
current_position_ = (*current_log_section_).begin();
}
state_ = FileOperations::kReady;
std::move(callback).Run(kSuccessTag);
}
void RtcLogFileReader::DoReadChunk(std::size_t size, ReadCallback callback) {
std::vector<std::uint8_t> result;
int bytes_read;
int bytes_remaining = static_cast<int>(size);
while (bytes_remaining &&
(bytes_read = ReadPartially(bytes_remaining, result)) > 0) {
bytes_remaining -= bytes_read;
}
state_ = result.empty() ? FileOperations::kComplete : FileOperations::kReady;
std::move(callback).Run(result);
}
int RtcLogFileReader::ReadPartially(int maximum_to_read,
std::vector<std::uint8_t>& output) {
if (data_.empty()) {
return 0;
}
if (current_log_section_ == data_.end()) {
return 0;
}
const auto& section = *current_log_section_;
DCHECK(section.begin() <= current_position_);
DCHECK(current_position_ < section.end());
int remaining_in_section = section.end() - current_position_;
int read_amount = std::min(remaining_in_section, maximum_to_read);
output.insert(output.end(), current_position_,
current_position_ + read_amount);
current_position_ += read_amount;
if (current_position_ == section.end()) {
// Advance to beginning of next LogSection.
current_log_section_++;
if (current_log_section_ != data_.end()) {
current_position_ = (*current_log_section_).begin();
}
}
return read_amount;
}
} // namespace
RtcLogFileOperations::RtcLogFileOperations(
protocol::ConnectionToClient* connection)
: connection_(connection) {}
RtcLogFileOperations::~RtcLogFileOperations() = default;
std::unique_ptr<FileOperations::Reader> RtcLogFileOperations::CreateReader() {
return std::make_unique<RtcLogFileReader>(connection_);
}
std::unique_ptr<FileOperations::Writer> RtcLogFileOperations::CreateWriter() {
NOTREACHED() << "RTC event log is read-only.";
return nullptr;
}
} // namespace remoting
// Copyright 2020 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.
#ifndef REMOTING_HOST_FILE_TRANSFER_RTC_LOG_FILE_OPERATIONS_H_
#define REMOTING_HOST_FILE_TRANSFER_RTC_LOG_FILE_OPERATIONS_H_
#include "remoting/host/file_transfer/file_operations.h"
namespace remoting {
namespace protocol {
class ConnectionToClient;
} // namespace protocol
// Implementation of FileOperations that sends the RTC event log to the client.
// As the event log is held in memory, there is no need for blocking IO and
// this class can run on the network thread.
class RtcLogFileOperations : public FileOperations {
public:
explicit RtcLogFileOperations(protocol::ConnectionToClient* connection);
~RtcLogFileOperations() override;
RtcLogFileOperations(const RtcLogFileOperations&) = delete;
RtcLogFileOperations& operator=(const RtcLogFileOperations&) = delete;
// FileOperations interface.
std::unique_ptr<Reader> CreateReader() override;
std::unique_ptr<Writer> CreateWriter() override;
private:
protocol::ConnectionToClient* connection_;
};
} // namespace remoting
#endif // REMOTING_HOST_FILE_TRANSFER_RTC_LOG_FILE_OPERATIONS_H_
// Copyright 2020 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 "remoting/host/file_transfer/rtc_log_file_operations.h"
#include "base/test/task_environment.h"
#include "remoting/protocol/fake_connection_to_client.h"
#include "remoting/protocol/session.h"
#include "remoting/protocol/webrtc_event_log_data.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace {
std::string ToString(const std::vector<std::uint8_t>& data) {
return std::string(data.begin(), data.end());
}
class FakeConnectionWithRtcLog : public protocol::FakeConnectionToClient {
public:
explicit FakeConnectionWithRtcLog(protocol::WebrtcEventLogData* event_log)
: protocol::FakeConnectionToClient(nullptr), event_log_(event_log) {}
~FakeConnectionWithRtcLog() override = default;
FakeConnectionWithRtcLog(const FakeConnectionWithRtcLog&) = delete;
FakeConnectionWithRtcLog& operator=(const FakeConnectionWithRtcLog&) = delete;
protocol::WebrtcEventLogData* rtc_event_log() override { return event_log_; }
void set_event_log(protocol::WebrtcEventLogData* log) { event_log_ = log; }
private:
protocol::WebrtcEventLogData* event_log_;
};
} // namespace
class RtcLogFileOperationsTest : public testing::Test {
public:
RtcLogFileOperationsTest()
: connection_(&event_log_), file_operations_(&connection_) {}
void SetUp() override { reader_ = file_operations_.CreateReader(); }
protected:
base::test::SingleThreadTaskEnvironment task_environment_;
protocol::WebrtcEventLogData event_log_;
FakeConnectionWithRtcLog connection_;
RtcLogFileOperations file_operations_;
std::unique_ptr<FileOperations::Reader> reader_;
// These are the most-recent results from the callbacks.
base::Optional<FileOperations::Reader::OpenResult> open_result_;
base::Optional<FileOperations::Reader::ReadResult> read_result_;
FileOperations::Reader::OpenCallback MakeOpenCallback() {
return base::BindOnce(&RtcLogFileOperationsTest::OnOpenResult,
base::Unretained(this));
}
FileOperations::Reader::ReadCallback MakeReadCallback() {
return base::BindOnce(&RtcLogFileOperationsTest::OnReadResult,
base::Unretained(this));
}
private:
void OnOpenResult(FileOperations::Reader::OpenResult result) {
open_result_ = result;
}
void OnReadResult(FileOperations::Reader::ReadResult result) {
read_result_ = result;
}
};
TEST_F(RtcLogFileOperationsTest, InitialState_IsCreated) {
EXPECT_EQ(reader_->state(), FileOperations::State::kCreated);
}
TEST_F(RtcLogFileOperationsTest, NonWebrtcConnection_RaisesProtocolError) {
connection_.set_event_log(nullptr);
reader_->Open(MakeOpenCallback());
task_environment_.RunUntilIdle();
ASSERT_TRUE(open_result_);
ASSERT_FALSE(*open_result_);
EXPECT_EQ(open_result_->error().type(),
protocol::FileTransfer_Error_Type_PROTOCOL_ERROR);
}
TEST_F(RtcLogFileOperationsTest, EmptyLog_ZeroBytesSent) {
// No setup needed, the RTC log is initially empty.
reader_->Open(MakeOpenCallback());
task_environment_.RunUntilIdle();
reader_->ReadChunk(1U, MakeReadCallback());
task_environment_.RunUntilIdle();
ASSERT_TRUE(open_result_);
ASSERT_TRUE(*open_result_);
ASSERT_TRUE(read_result_);
ASSERT_TRUE(*read_result_);
EXPECT_TRUE((*read_result_)->empty());
EXPECT_EQ(reader_->state(), FileOperations::State::kComplete);
}
TEST_F(RtcLogFileOperationsTest, FileSizeOfMultipleLogSections_IsCorrect) {
event_log_.SetMaxSectionSizeForTest(3);
event_log_.Write("aaa");
event_log_.Write("bb");
event_log_.Write("cc");
reader_->Open(MakeOpenCallback());
task_environment_.RunUntilIdle();
EXPECT_EQ(reader_->size(), 7U);
}
TEST_F(RtcLogFileOperationsTest, PartialTransfer_IsReady) {
event_log_.Write("aaa");
reader_->Open(MakeOpenCallback());
task_environment_.RunUntilIdle();
reader_->ReadChunk(2U, MakeReadCallback());
task_environment_.RunUntilIdle();
EXPECT_EQ(reader_->state(), FileOperations::State::kReady);
}
TEST_F(RtcLogFileOperationsTest, CompleteTransfer_IsComplete) {
event_log_.Write("aaa");
reader_->Open(MakeOpenCallback());
task_environment_.RunUntilIdle();
reader_->ReadChunk(3U, MakeReadCallback());
task_environment_.RunUntilIdle();
// State becomes Complete only after a final read has returned 0 bytes.
reader_->ReadChunk(3U, MakeReadCallback());
task_environment_.RunUntilIdle();
ASSERT_TRUE(read_result_);
ASSERT_TRUE(*read_result_);
EXPECT_TRUE((*read_result_)->empty());
EXPECT_EQ(reader_->state(), FileOperations::State::kComplete);
}
TEST_F(RtcLogFileOperationsTest,
MultipleReadsOverMultipleSections_CorrectDataIsSent) {
event_log_.SetMaxSectionSizeForTest(3);
event_log_.Write("aaa");
event_log_.Write("bbb");
event_log_.Write("ccc");
reader_->Open(MakeOpenCallback());
task_environment_.RunUntilIdle();
reader_->ReadChunk(2U, MakeReadCallback());
task_environment_.RunUntilIdle();
ASSERT_TRUE(read_result_);
ASSERT_TRUE(*read_result_);
EXPECT_EQ(ToString(**read_result_), "aa");
reader_->ReadChunk(5U, MakeReadCallback());
task_environment_.RunUntilIdle();
EXPECT_EQ(ToString(**read_result_), "abbbc");
reader_->ReadChunk(100U, MakeReadCallback());
task_environment_.RunUntilIdle();
EXPECT_EQ(ToString(**read_result_), "cc");
}
} // namespace remoting
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment