blob: f32204e342fa3225549ea68b13208e718361ce94 [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 "remoting/host/file_transfer/file_transfer_message_handler.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/containers/queue.h"
#include "base/memory/ptr_util.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "net/base/io_buffer.h"
#include "remoting/base/compound_buffer.h"
#include "remoting/host/file_transfer/fake_file_operations.h"
#include "remoting/protocol/fake_message_pipe.h"
#include "remoting/protocol/fake_message_pipe_wrapper.h"
#include "remoting/protocol/file_transfer_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
constexpr char kTestDatachannelName[] = "filetransfer-test";
constexpr char kTestFilename[] = "test-file.txt";
std::unique_ptr<remoting::CompoundBuffer> StringToBuffer(
const std::string& data) {
std::unique_ptr<remoting::CompoundBuffer> buffer =
std::make_unique<remoting::CompoundBuffer>();
buffer->Append(base::MakeRefCounted<net::StringIOBuffer>(data.data()),
data.size());
return buffer;
}
std::unique_ptr<remoting::CompoundBuffer> MessageToBuffer(
const remoting::protocol::FileTransfer& message) {
return StringToBuffer(message.SerializeAsString());
}
std::unique_ptr<remoting::CompoundBuffer> DataToBuffer(
const std::string& data) {
remoting::protocol::FileTransfer message;
message.mutable_data()->set_data(data);
return MessageToBuffer(message);
}
// base::queue doesn't provide operator==.
template <typename T>
bool QueuesEqual(const base::queue<T>& a, const base::queue<T>& b) {
if (a.size() != b.size())
return false;
auto a_copy = a;
auto b_copy = b;
while (!a_copy.empty()) {
if (a_copy.front() != b_copy.front())
return false;
a_copy.pop();
b_copy.pop();
}
return true;
}
} // namespace
namespace remoting {
class FileTransferMessageHandlerTest : public testing::Test {
public:
FileTransferMessageHandlerTest();
~FileTransferMessageHandlerTest() override;
// testing::Test implementation.
void SetUp() override;
void TearDown() override;
protected:
const std::string kTestDataOne = "this is the first test string";
const std::string kTestDataTwo = "this is the second test string";
const std::string kTestDataThree = "this is the third test string";
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<protocol::FakeMessagePipe> fake_pipe_;
protocol::FileTransfer fake_metadata_;
protocol::FileTransfer fake_end_;
};
FileTransferMessageHandlerTest::FileTransferMessageHandlerTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::DEFAULT,
base::test::ScopedTaskEnvironment::ExecutionMode::QUEUED) {}
FileTransferMessageHandlerTest::~FileTransferMessageHandlerTest() = default;
void FileTransferMessageHandlerTest::SetUp() {
fake_pipe_ =
base::WrapUnique(new protocol::FakeMessagePipe(false /* asynchronous */));
fake_metadata_.Clear();
fake_metadata_.mutable_metadata()->set_filename(kTestFilename);
fake_metadata_.mutable_metadata()->set_size(
kTestDataOne.size() + kTestDataTwo.size() + kTestDataThree.size());
fake_end_.Clear();
fake_end_.mutable_end();
}
void FileTransferMessageHandlerTest::TearDown() {}
// Verify that the message handler creates, writes to, and closes a
// FileProxyWrapper without errors when given valid input.
TEST_F(FileTransferMessageHandlerTest, WritesThreeChunks) {
FakeFileOperations::TestIo test_io;
auto file_operations = std::make_unique<FakeFileOperations>(&test_io);
// This will delete itself when fake_pipe_->ClosePipe() is called.
new FileTransferMessageHandler(kTestDatachannelName, fake_pipe_->Wrap(),
std::move(file_operations));
fake_pipe_->OpenPipe();
fake_pipe_->Receive(MessageToBuffer(fake_metadata_));
fake_pipe_->Receive(DataToBuffer(kTestDataOne));
fake_pipe_->Receive(DataToBuffer(kTestDataTwo));
fake_pipe_->Receive(DataToBuffer(kTestDataThree));
fake_pipe_->Receive(MessageToBuffer(fake_end_));
scoped_task_environment_.RunUntilIdle();
fake_pipe_->ClosePipe();
ASSERT_EQ(1ul, test_io.files_written.size());
ASSERT_EQ(false, test_io.files_written[0].failed);
std::vector<std::string> expected_chunks = {kTestDataOne, kTestDataTwo,
kTestDataThree};
ASSERT_EQ(expected_chunks, test_io.files_written[0].chunks);
const base::queue<std::string>& actual_sent_messages =
fake_pipe_->sent_messages();
protocol::FileTransfer expected_response;
expected_response.mutable_success();
base::queue<std::string> expected_sent_messages;
expected_sent_messages.push(expected_response.SerializeAsString());
ASSERT_TRUE(QueuesEqual(expected_sent_messages, actual_sent_messages));
}
// Verifies that the message handler sends an error protobuf when
// FileProxyWrapper returns an error.
TEST_F(FileTransferMessageHandlerTest, HandlesWriteError) {
FakeFileOperations::TestIo test_io;
auto file_operations = std::make_unique<FakeFileOperations>(&test_io);
protocol::FileTransfer_Error fake_error = protocol::MakeFileTransferError(
FROM_HERE, protocol::FileTransfer_Error_Type_IO_ERROR);
// This will delete itself when fake_pipe_->ClosePipe() is called.
new FileTransferMessageHandler(kTestDatachannelName, fake_pipe_->Wrap(),
std::move(file_operations));
fake_pipe_->OpenPipe();
fake_pipe_->Receive(MessageToBuffer(fake_metadata_));
fake_pipe_->Receive(DataToBuffer(kTestDataOne));
fake_pipe_->Receive(DataToBuffer(kTestDataTwo));
scoped_task_environment_.RunUntilIdle();
test_io.io_error = fake_error;
fake_pipe_->Receive(DataToBuffer(kTestDataTwo));
fake_pipe_->Receive(MessageToBuffer(fake_end_));
scoped_task_environment_.RunUntilIdle();
fake_pipe_->ClosePipe();
const base::queue<std::string>& actual_sent_messages =
fake_pipe_->sent_messages();
protocol::FileTransfer expected_response;
*expected_response.mutable_error() = fake_error;
base::queue<std::string> expected_sent_messages;
expected_sent_messages.push(expected_response.SerializeAsString());
ASSERT_TRUE(QueuesEqual(expected_sent_messages, actual_sent_messages));
}
// Verifies that the message handler cancels the write if an error is received
// from the sender.
TEST_F(FileTransferMessageHandlerTest, HandlesErrorMessage) {
FakeFileOperations::TestIo test_io;
auto file_operations = std::make_unique<FakeFileOperations>(&test_io);
// This will delete itself when fake_pipe_->ClosePipe() is called.
new FileTransferMessageHandler(kTestDatachannelName, fake_pipe_->Wrap(),
std::move(file_operations));
protocol::FileTransfer fake_error_message;
*fake_error_message.mutable_error() = protocol::MakeFileTransferError(
FROM_HERE, protocol::FileTransfer_Error_Type_IO_ERROR);
fake_pipe_->OpenPipe();
fake_pipe_->Receive(MessageToBuffer(fake_metadata_));
fake_pipe_->Receive(DataToBuffer(kTestDataOne));
fake_pipe_->Receive(DataToBuffer(kTestDataTwo));
scoped_task_environment_.RunUntilIdle();
fake_pipe_->Receive(MessageToBuffer(fake_error_message));
scoped_task_environment_.RunUntilIdle();
fake_pipe_->ClosePipe();
ASSERT_EQ(1ul, test_io.files_written.size());
ASSERT_EQ(true, test_io.files_written[0].failed);
std::vector<std::string> expected_chunks = {kTestDataOne, kTestDataTwo};
ASSERT_EQ(expected_chunks, test_io.files_written[0].chunks);
const base::queue<std::string>& actual_sent_messages =
fake_pipe_->sent_messages();
// No messages
base::queue<std::string> expected_sent_messages;
ASSERT_TRUE(QueuesEqual(expected_sent_messages, actual_sent_messages));
}
// Verifies that the message handler cancels the write if the connection is
// closed prematurely.
TEST_F(FileTransferMessageHandlerTest, HandlesPrematureClose) {
FakeFileOperations::TestIo test_io;
auto file_operations = std::make_unique<FakeFileOperations>(&test_io);
// This will delete itself when fake_pipe_->ClosePipe() is called.
new FileTransferMessageHandler(kTestDatachannelName, fake_pipe_->Wrap(),
std::move(file_operations));
fake_pipe_->OpenPipe();
fake_pipe_->Receive(MessageToBuffer(fake_metadata_));
fake_pipe_->Receive(DataToBuffer(kTestDataOne));
fake_pipe_->Receive(DataToBuffer(kTestDataTwo));
scoped_task_environment_.RunUntilIdle();
fake_pipe_->ClosePipe();
ASSERT_EQ(1ul, test_io.files_written.size());
ASSERT_EQ(true, test_io.files_written[0].failed);
std::vector<std::string> expected_chunks = {kTestDataOne, kTestDataTwo};
ASSERT_EQ(expected_chunks, test_io.files_written[0].chunks);
}
// Verifies that an error is sent if data is sent before/without metadata.
TEST_F(FileTransferMessageHandlerTest, ErrorsOnMissingMetadata) {
FakeFileOperations::TestIo test_io;
auto file_operations = std::make_unique<FakeFileOperations>(&test_io);
// This will delete itself when fake_pipe_->ClosePipe() is called.
new FileTransferMessageHandler(kTestDatachannelName, fake_pipe_->Wrap(),
std::move(file_operations));
fake_pipe_->OpenPipe();
fake_pipe_->Receive(DataToBuffer(kTestDataOne));
fake_pipe_->Receive(DataToBuffer(kTestDataTwo));
fake_pipe_->Receive(DataToBuffer(kTestDataThree));
fake_pipe_->Receive(MessageToBuffer(fake_end_));
scoped_task_environment_.RunUntilIdle();
fake_pipe_->ClosePipe();
ASSERT_EQ(0ul, test_io.files_written.size());
const base::queue<std::string>& sent_messages = fake_pipe_->sent_messages();
ASSERT_EQ(1ul, sent_messages.size());
protocol::FileTransfer response;
response.ParseFromString(sent_messages.front());
ASSERT_EQ(protocol::FileTransfer::kError, response.message_case());
ASSERT_EQ(protocol::FileTransfer_Error_Type_PROTOCOL_ERROR,
response.error().type());
}
// Verifies that an error is sent if another metadata message is sent.
TEST_F(FileTransferMessageHandlerTest, ErrorsOnNewMetadata) {
FakeFileOperations::TestIo test_io;
auto file_operations = std::make_unique<FakeFileOperations>(&test_io);
// This will delete itself when fake_pipe_->ClosePipe() is called.
new FileTransferMessageHandler(kTestDatachannelName, fake_pipe_->Wrap(),
std::move(file_operations));
fake_pipe_->OpenPipe();
fake_pipe_->Receive(MessageToBuffer(fake_metadata_));
fake_pipe_->Receive(DataToBuffer(kTestDataOne));
fake_pipe_->Receive(DataToBuffer(kTestDataTwo));
fake_pipe_->Receive(DataToBuffer(kTestDataThree));
fake_pipe_->Receive(MessageToBuffer(fake_end_));
scoped_task_environment_.RunUntilIdle();
fake_pipe_->Receive(MessageToBuffer(fake_metadata_));
fake_pipe_->ClosePipe();
const base::queue<std::string>& sent_messages = fake_pipe_->sent_messages();
// First is the sucess message, second should be a protocol error.
ASSERT_EQ(2ul, sent_messages.size());
protocol::FileTransfer response;
response.ParseFromString(sent_messages.back());
ASSERT_EQ(protocol::FileTransfer::kError, response.message_case());
ASSERT_EQ(protocol::FileTransfer_Error_Type_PROTOCOL_ERROR,
response.error().type());
}
// Verifies that an error is sent if more data is sent after Close.
TEST_F(FileTransferMessageHandlerTest, ErrorsOnDataAfterClose) {
FakeFileOperations::TestIo test_io;
auto file_operations = std::make_unique<FakeFileOperations>(&test_io);
// This will delete itself when fake_pipe_->ClosePipe() is called.
new FileTransferMessageHandler(kTestDatachannelName, fake_pipe_->Wrap(),
std::move(file_operations));
fake_pipe_->OpenPipe();
fake_pipe_->Receive(MessageToBuffer(fake_metadata_));
fake_pipe_->Receive(DataToBuffer(kTestDataOne));
fake_pipe_->Receive(DataToBuffer(kTestDataTwo));
fake_pipe_->Receive(DataToBuffer(kTestDataThree));
fake_pipe_->Receive(MessageToBuffer(fake_end_));
fake_pipe_->Receive(DataToBuffer(kTestDataOne));
scoped_task_environment_.RunUntilIdle();
fake_pipe_->ClosePipe();
ASSERT_EQ(1ul, test_io.files_written.size());
ASSERT_EQ(true, test_io.files_written[0].failed);
const base::queue<std::string>& sent_messages = fake_pipe_->sent_messages();
// Because the error is triggered before RunUntilIdle is called, there should
// be no complete message this time.
ASSERT_EQ(1ul, sent_messages.size());
protocol::FileTransfer response;
response.ParseFromString(sent_messages.front());
ASSERT_EQ(protocol::FileTransfer::kError, response.message_case());
ASSERT_EQ(protocol::FileTransfer_Error_Type_PROTOCOL_ERROR,
response.error().type());
}
} // namespace remoting