blob: 590184e9236100cf311446a292cad5a5da722b40 [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 "arc/vm/mojo_proxy/mojo_proxy.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/files/scoped_temp_dir.h>
#include <base/macros.h>
#include <base/optional.h>
#include <base/posix/unix_domain_socket.h>
#include <base/run_loop.h>
#include <base/test/task_environment.h>
#include <gtest/gtest.h>
#include "arc/vm/mojo_proxy/file_descriptor_util.h"
#include "arc/vm/mojo_proxy/message.pb.h"
#include "arc/vm/mojo_proxy/message_stream.h"
namespace arc {
namespace {
class TestDelegate : public MojoProxy::Delegate {
public:
TestDelegate(MojoProxy::Type type, base::ScopedFD fd)
: type_(type), stream_(std::make_unique<MessageStream>(std::move(fd))) {}
~TestDelegate() override = default;
bool is_stopped() const { return is_stopped_; }
void ResetStream() { stream_.reset(); }
MojoProxy::Type GetType() const override { return type_; }
int GetPollFd() override { return stream_->Get(); }
base::ScopedFD CreateProxiedRegularFile(int64_t handle,
int32_t flags) override {
return {};
}
bool SendMessage(const arc_proxy::MojoMessage& message,
const std::vector<base::ScopedFD>& fds) override {
return stream_->Write(message);
}
bool ReceiveMessage(arc_proxy::MojoMessage* message,
std::vector<base::ScopedFD>* fds) override {
return stream_->Read(message, fds);
}
void OnStopped() override { is_stopped_ = true; }
private:
const MojoProxy::Type type_;
std::unique_ptr<MessageStream> stream_;
bool is_stopped_ = false;
};
class MojoProxyTest : public testing::Test {
public:
MojoProxyTest() = default;
MojoProxyTest(const MojoProxyTest&) = delete;
MojoProxyTest& operator=(const MojoProxyTest&) = delete;
~MojoProxyTest() override = default;
void SetUp() override {
// Use a blocking socket pair instead of virtio-wl for testing.
auto socket_pair = CreateSocketPair(SOCK_STREAM);
ASSERT_TRUE(socket_pair.has_value());
server_delegate_ = std::make_unique<TestDelegate>(
MojoProxy::Type::SERVER, std::move(socket_pair->first));
client_delegate_ = std::make_unique<TestDelegate>(
MojoProxy::Type::CLIENT, std::move(socket_pair->second));
server_ = std::make_unique<MojoProxy>(server_delegate_.get());
client_ = std::make_unique<MojoProxy>(client_delegate_.get());
// Register initial socket pairs.
auto server_socket_pair = CreateSocketPair(SOCK_STREAM | SOCK_NONBLOCK);
ASSERT_TRUE(server_socket_pair.has_value());
auto client_socket_pair = CreateSocketPair(SOCK_STREAM | SOCK_NONBLOCK);
ASSERT_TRUE(client_socket_pair.has_value());
int64_t handle = server_->RegisterFileDescriptor(
std::move(server_socket_pair->first),
arc_proxy::FileDescriptor::SOCKET_STREAM, 0 /* handle */);
server_fd_ = std::move(server_socket_pair->second);
client_->RegisterFileDescriptor(std::move(client_socket_pair->first),
arc_proxy::FileDescriptor::SOCKET_STREAM,
handle);
client_fd_ = std::move(client_socket_pair->second);
}
void TearDown() override {
client_fd_.reset();
server_fd_.reset();
ResetClient();
ResetServer();
}
MojoProxy* server() { return server_.get(); }
MojoProxy* client() { return client_.get(); }
TestDelegate& server_delegate() { return *server_delegate_; }
TestDelegate& client_delegate() { return *client_delegate_; }
int server_fd() const { return server_fd_.get(); }
int client_fd() const { return client_fd_.get(); }
void ResetServerFD() { server_fd_.reset(); }
void ResetClientFD() { client_fd_.reset(); }
void ResetServer() {
server_.reset();
server_delegate_->ResetStream();
}
void ResetClient() {
client_.reset();
client_delegate_->ResetStream();
}
private:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
base::test::TaskEnvironment::MainThreadType::IO};
std::unique_ptr<TestDelegate> server_delegate_;
std::unique_ptr<TestDelegate> client_delegate_;
std::unique_ptr<MojoProxy> server_;
std::unique_ptr<MojoProxy> client_;
base::ScopedFD server_fd_;
base::ScopedFD client_fd_;
};
// Runs the message loop until the given |fd| gets read ready.
void WaitUntilReadable(int fd) {
base::RunLoop run_loop;
auto controller =
base::FileDescriptorWatcher::WatchReadable(fd, run_loop.QuitClosure());
run_loop.Run();
}
// Exercises if simple data tranferring from |write_fd| to |read_fd| works.
void TestDataTransfer(int write_fd, int read_fd) {
constexpr char kData[] = "abcdefg";
if (Sendmsg(write_fd, kData, sizeof(kData), {}) != sizeof(kData)) {
ADD_FAILURE() << "Failed to send message.";
return;
}
WaitUntilReadable(read_fd);
char buf[256];
std::vector<base::ScopedFD> fds;
ssize_t size = Recvmsg(read_fd, buf, sizeof(buf), &fds);
EXPECT_EQ(size, sizeof(kData));
EXPECT_STREQ(buf, kData);
EXPECT_TRUE(fds.empty());
}
// Checks if EOF is read from the give socket |fd|.
void ExpectSocketEof(int fd) {
char buf[256];
std::vector<base::ScopedFD> fds;
ssize_t size = Recvmsg(fd, buf, sizeof(buf), &fds);
EXPECT_EQ(size, 0);
EXPECT_TRUE(fds.empty());
}
TEST_F(MojoProxyTest, ServerToClient) {
TestDataTransfer(server_fd(), client_fd());
}
TEST_F(MojoProxyTest, ClientToServer) {
TestDataTransfer(client_fd(), server_fd());
}
TEST_F(MojoProxyTest, CloseServer) {
ResetServerFD();
WaitUntilReadable(client_fd());
ExpectSocketEof(client_fd());
}
TEST_F(MojoProxyTest, CloseClient) {
ResetClientFD();
WaitUntilReadable(server_fd());
ExpectSocketEof(server_fd());
}
TEST_F(MojoProxyTest, ResetServer) {
ResetServer();
EXPECT_TRUE(server_delegate().is_stopped());
WaitUntilReadable(client_fd());
ExpectSocketEof(client_fd());
EXPECT_TRUE(client_delegate().is_stopped());
}
TEST_F(MojoProxyTest, ResetClient) {
ResetClient();
EXPECT_TRUE(client_delegate().is_stopped());
WaitUntilReadable(server_fd());
ExpectSocketEof(server_fd());
EXPECT_TRUE(server_delegate().is_stopped());
}
TEST_F(MojoProxyTest, FileWriteError) {
// Register a socket pair to the server.
auto server_socket_pair = CreateSocketPair(SOCK_STREAM | SOCK_NONBLOCK);
ASSERT_TRUE(server_socket_pair.has_value());
int64_t handle = server()->RegisterFileDescriptor(
std::move(server_socket_pair->first),
arc_proxy::FileDescriptor::SOCKET_STREAM, 0 /* handle */);
auto server_fd = std::move(server_socket_pair->second);
// Register a read only FD to the client. This will cause a write error.
base::ScopedFD client_fd_read, client_fd_write;
base::CreatePipe(&client_fd_read, &client_fd_write, true);
ASSERT_TRUE(client_fd_read.is_valid());
client()->RegisterFileDescriptor(
std::move(client_fd_read), arc_proxy::FileDescriptor::FIFO_READ, handle);
// Try to send data from the server to the client, but it fails because of a
// write error in the client.
constexpr char kData[] = "abcdefg";
ASSERT_TRUE(base::WriteFileDescriptor(server_fd.get(), kData, sizeof(kData)));
// Write error on the client results in closing the server socket.
WaitUntilReadable(server_fd.get());
ExpectSocketEof(server_fd.get());
}
TEST_F(MojoProxyTest, PassStreamSocketFromServer) {
auto sockpair = CreateSocketPair(SOCK_STREAM | SOCK_NONBLOCK);
ASSERT_TRUE(sockpair.has_value());
constexpr char kData[] = "testdata";
{
std::vector<base::ScopedFD> fds;
fds.push_back(std::move(sockpair->second));
ASSERT_EQ(Sendmsg(server_fd(), kData, sizeof(kData), fds), sizeof(kData));
}
base::ScopedFD received_fd;
{
WaitUntilReadable(client_fd());
char buf[256];
std::vector<base::ScopedFD> fds;
ssize_t size = Recvmsg(client_fd(), buf, sizeof(buf), &fds);
EXPECT_EQ(sizeof(kData), size);
EXPECT_STREQ(kData, buf);
EXPECT_EQ(1, fds.size());
received_fd = std::move(fds[0]);
}
EXPECT_EQ(SOCK_STREAM, GetSocketType(received_fd.get()));
TestDataTransfer(sockpair->first.get(), received_fd.get());
TestDataTransfer(received_fd.get(), sockpair->first.get());
}
TEST_F(MojoProxyTest, PassStreamSocketSocketFromClient) {
auto sockpair = CreateSocketPair(SOCK_STREAM | SOCK_NONBLOCK);
ASSERT_TRUE(sockpair.has_value());
constexpr char kData[] = "testdata";
{
std::vector<base::ScopedFD> fds;
fds.push_back(std::move(sockpair->second));
ASSERT_EQ(Sendmsg(client_fd(), kData, sizeof(kData), fds), sizeof(kData));
}
base::ScopedFD received_fd;
{
WaitUntilReadable(server_fd());
char buf[256];
std::vector<base::ScopedFD> fds;
ssize_t size = Recvmsg(server_fd(), buf, sizeof(buf), &fds);
EXPECT_EQ(sizeof(kData), size);
EXPECT_STREQ(kData, buf);
EXPECT_EQ(1, fds.size());
received_fd = std::move(fds[0]);
}
EXPECT_EQ(SOCK_STREAM, GetSocketType(received_fd.get()));
TestDataTransfer(sockpair->first.get(), received_fd.get());
TestDataTransfer(received_fd.get(), sockpair->first.get());
}
TEST_F(MojoProxyTest, PassDgramSocketFromServer) {
auto sockpair = CreateSocketPair(SOCK_DGRAM | SOCK_NONBLOCK);
ASSERT_TRUE(sockpair.has_value());
constexpr char kData[] = "testdata";
{
std::vector<base::ScopedFD> fds;
fds.push_back(std::move(sockpair->second));
ASSERT_EQ(Sendmsg(server_fd(), kData, sizeof(kData), fds), sizeof(kData));
}
base::ScopedFD received_fd;
{
WaitUntilReadable(client_fd());
char buf[256];
std::vector<base::ScopedFD> fds;
ssize_t size = Recvmsg(client_fd(), buf, sizeof(buf), &fds);
EXPECT_EQ(sizeof(kData), size);
EXPECT_STREQ(kData, buf);
EXPECT_EQ(1, fds.size());
received_fd = std::move(fds[0]);
}
EXPECT_EQ(SOCK_DGRAM, GetSocketType(received_fd.get()));
TestDataTransfer(sockpair->first.get(), received_fd.get());
TestDataTransfer(received_fd.get(), sockpair->first.get());
}
TEST_F(MojoProxyTest, PassSeqpacketSocketFromServer) {
auto sockpair = CreateSocketPair(SOCK_SEQPACKET | SOCK_NONBLOCK);
ASSERT_TRUE(sockpair.has_value());
constexpr char kData[] = "testdata";
{
std::vector<base::ScopedFD> fds;
fds.push_back(std::move(sockpair->second));
ASSERT_EQ(Sendmsg(server_fd(), kData, sizeof(kData), fds), sizeof(kData));
}
base::ScopedFD received_fd;
{
WaitUntilReadable(client_fd());
char buf[256];
std::vector<base::ScopedFD> fds;
ssize_t size = Recvmsg(client_fd(), buf, sizeof(buf), &fds);
EXPECT_EQ(sizeof(kData), size);
EXPECT_STREQ(kData, buf);
EXPECT_EQ(1, fds.size());
received_fd = std::move(fds[0]);
}
EXPECT_EQ(SOCK_SEQPACKET, GetSocketType(received_fd.get()));
TestDataTransfer(sockpair->first.get(), received_fd.get());
TestDataTransfer(received_fd.get(), sockpair->first.get());
}
TEST_F(MojoProxyTest, Connect) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath socket_path = temp_dir.GetPath().Append("test.sock");
// Create unix domain socket for testing, which is connected by the following
// Connect() invocation from client side.
auto server_sock = CreateUnixDomainSocket(socket_path);
// Try to follow the actual initial connection procedure.
base::RunLoop run_loop;
base::Optional<int> error_code;
base::Optional<int64_t> handle;
client()->Connect(socket_path, base::BindOnce(
[](base::RunLoop* run_loop,
base::Optional<int>* error_code_out,
base::Optional<int64_t>* handle_out,
int error_code, int64_t handle) {
*error_code_out = error_code;
*handle_out = handle;
run_loop->Quit();
},
&run_loop, &error_code, &handle));
run_loop.Run();
ASSERT_EQ(0, error_code);
ASSERT_TRUE(handle.has_value());
// TODO(hidehiko): Remove the cast on next libchrome uprev.
ASSERT_TRUE(handle != static_cast<int64_t>(0));
// Register client side socket.
auto client_sock_pair = CreateSocketPair(SOCK_STREAM | SOCK_NONBLOCK);
ASSERT_TRUE(client_sock_pair.has_value());
client()->RegisterFileDescriptor(std::move(client_sock_pair->first),
arc_proxy::FileDescriptor::SOCKET_STREAM,
handle.value());
auto client_fd = std::move(client_sock_pair->second);
auto server_fd = AcceptSocket(server_sock.get());
ASSERT_TRUE(server_fd.is_valid());
TestDataTransfer(client_fd.get(), server_fd.get());
TestDataTransfer(server_fd.get(), client_fd.get());
}
TEST_F(MojoProxyTest, Pread) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath file_path = temp_dir.GetPath().Append("test.txt");
constexpr char kFileContent[] = "abcdefghijklmnopqrstuvwxyz";
// Trim trailing '\0'.
ASSERT_EQ(sizeof(kFileContent) - 1,
base::WriteFile(file_path, kFileContent, sizeof(kFileContent) - 1));
base::ScopedFD fd(HANDLE_EINTR(open(file_path.value().c_str(), O_RDONLY)));
ASSERT_TRUE(fd.is_valid());
const int64_t handle = client()->RegisterFileDescriptor(
std::move(fd), arc_proxy::FileDescriptor::REGULAR_FILE, 0);
base::RunLoop run_loop;
server()->Pread(
handle, 10, 10,
base::BindOnce(
[](base::RunLoop* run_loop, int error_code, const std::string& blob) {
run_loop->Quit();
EXPECT_EQ(0, error_code);
EXPECT_EQ("klmnopqrst", blob);
},
&run_loop));
run_loop.Run();
}
TEST_F(MojoProxyTest, Pread_UnknownHandle) {
constexpr int64_t kUnknownHandle = 100;
base::RunLoop run_loop;
server()->Pread(
kUnknownHandle, 10, 10,
base::BindOnce(
[](base::RunLoop* run_loop, int error_code, const std::string& blob) {
run_loop->Quit();
EXPECT_EQ(EBADF, error_code);
},
&run_loop));
run_loop.Run();
}
TEST_F(MojoProxyTest, Fstat) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath file_path = temp_dir.GetPath().Append("test.txt");
constexpr char kFileContent[] = "abcdefghijklmnopqrstuvwxyz";
// Trim trailing '\0'.
constexpr size_t kContentSize = sizeof(kFileContent) - 1;
ASSERT_EQ(kContentSize,
base::WriteFile(file_path, kFileContent, kContentSize));
base::ScopedFD fd(HANDLE_EINTR(open(file_path.value().c_str(), O_RDONLY)));
ASSERT_TRUE(fd.is_valid());
const int64_t handle = client()->RegisterFileDescriptor(
std::move(fd), arc_proxy::FileDescriptor::REGULAR_FILE, 0);
base::RunLoop run_loop;
server()->Fstat(
handle, base::BindOnce(
[](base::RunLoop* run_loop, int error_code, int64_t size) {
run_loop->Quit();
EXPECT_EQ(0, error_code);
EXPECT_EQ(26, size);
},
&run_loop));
run_loop.Run();
}
TEST_F(MojoProxyTest, Fstat_UnknownHandle) {
constexpr int64_t kUnknownHandle = 100;
base::RunLoop run_loop;
server()->Fstat(kUnknownHandle, base::BindOnce(
[](base::RunLoop* run_loop,
int error_code, int64_t size) {
run_loop->Quit();
EXPECT_EQ(EBADF, error_code);
},
&run_loop));
run_loop.Run();
}
} // namespace
} // namespace arc