blob: cec159b22aef16603fa676d4617c60aa3044d27d [file] [log] [blame]
// Copyright 2021 The Fuchsia 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 <fuchsia/hardware/pty/llcpp/fidl_test_base.h>
#include <fuchsia/io/llcpp/fidl_test_base.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/zx/vmo.h>
#include <lib/zxio/inception.h>
#include <string.h>
#include <zxtest/zxtest.h>
#include "sdk/lib/zxio/tests/test_directory_server_base.h"
#include "sdk/lib/zxio/tests/test_file_server_base.h"
TEST(CreateWithAllocator, ErrorAllocator) {
auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
return ZX_ERR_INVALID_ARGS;
};
zx::channel channel0, channel1;
ASSERT_OK(zx::channel::create(0u, &channel0, &channel1));
void* context;
ASSERT_EQ(zxio_create_with_allocator(std::move(channel0), allocator, &context), ZX_ERR_NO_MEMORY);
// Make sure that the handle is closed.
zx_signals_t pending = 0;
ASSERT_EQ(channel1.wait_one(0u, zx::time::infinite_past(), &pending), ZX_ERR_TIMED_OUT);
EXPECT_EQ(pending & ZX_CHANNEL_PEER_CLOSED, ZX_CHANNEL_PEER_CLOSED, "pending is %u", pending);
}
TEST(CreateWithAllocator, BadAllocator) {
auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
*out_storage = nullptr;
return ZX_OK;
};
zx::channel channel0, channel1;
ASSERT_OK(zx::channel::create(0u, &channel0, &channel1));
void* context;
ASSERT_EQ(zxio_create_with_allocator(std::move(channel0), allocator, &context), ZX_ERR_NO_MEMORY);
// Make sure that the handle is closed.
zx_signals_t pending = 0;
ASSERT_EQ(channel1.wait_one(0u, zx::time::infinite_past(), &pending), ZX_ERR_TIMED_OUT);
EXPECT_EQ(pending & ZX_CHANNEL_PEER_CLOSED, ZX_CHANNEL_PEER_CLOSED, "pending is %u", pending);
}
struct VmoWrapper {
int32_t tag;
zxio_storage_t storage;
};
TEST(CreateWithAllocator, Vmo) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(1024, 0u, &vmo));
uint32_t data = 0x1a2a3a4a;
ASSERT_OK(vmo.write(&data, 0u, sizeof(data)));
auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
if (type != ZXIO_OBJECT_TYPE_VMO) {
return ZX_ERR_NOT_SUPPORTED;
}
auto* wrapper = new VmoWrapper{
.tag = 0x42,
.storage = {},
};
*out_storage = &(wrapper->storage);
*out_context = wrapper;
return ZX_OK;
};
void* context = nullptr;
ASSERT_OK(zxio_create_with_allocator(std::move(vmo), allocator, &context));
ASSERT_NE(nullptr, context);
std::unique_ptr<VmoWrapper> wrapper(static_cast<VmoWrapper*>(context));
ASSERT_EQ(wrapper->tag, 0x42);
zxio_t* io = &(wrapper->storage.io);
uint32_t buffer = 0;
size_t actual = 0;
zxio_read(io, &buffer, sizeof(buffer), 0u, &actual);
ASSERT_EQ(actual, sizeof(buffer));
ASSERT_EQ(buffer, data);
ASSERT_OK(zxio_close(io));
}
TEST(CreateWithInfo, Unsupported) {
auto node_ends = fidl::CreateEndpoints<fuchsia_io::Node>();
ASSERT_OK(node_ends.status_value());
auto [node_client, node_server] = std::move(node_ends.value());
zx::socket socket0, socket1;
ASSERT_OK(zx::socket::create(0u, &socket0, &socket1));
fuchsia_io::wire::StreamSocket stream_socket = {.socket = std::move(socket0)};
fidl::FidlAllocator fidl_allocator;
auto node_info =
fuchsia_io::wire::NodeInfo::WithStreamSocket(fidl_allocator, std::move(stream_socket));
auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
*out_storage = new zxio_storage_t;
*out_context = *out_storage;
return ZX_OK;
};
void* context = nullptr;
zx_status_t status =
zxio_create_with_allocator(std::move(node_client), node_info, allocator, &context);
EXPECT_EQ(status, ZX_ERR_NOT_SUPPORTED);
ASSERT_NE(context, nullptr);
// The socket in node_info should be preserved.
EXPECT_EQ(node_info.which(), fuchsia_io::wire::NodeInfo::Tag::kStreamSocket);
EXPECT_TRUE(node_info.stream_socket().socket.is_valid());
std::unique_ptr<zxio_storage_t> storage(static_cast<zxio_storage_t*>(context));
zxio_t* zxio = &(storage->io);
zx::channel recaptured_handle;
ASSERT_OK(zxio_release(zxio, recaptured_handle.reset_and_get_address()));
EXPECT_TRUE(recaptured_handle.is_valid());
ASSERT_OK(zxio_close(zxio));
}
class TestDeviceNodeServer : public fuchsia_io::testing::Node_TestBase {
public:
void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) final {
ADD_FAILURE("unexpected message received: %s", name.c_str());
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Close(CloseRequestView request, CloseCompleter::Sync& completer) final {
completer.Reply(ZX_OK);
// After the reply, we should close the connection.
completer.Close(ZX_OK);
}
};
TEST(CreateWithInfo, Device) {
auto node_ends = fidl::CreateEndpoints<fuchsia_io::Node>();
ASSERT_OK(node_ends.status_value());
auto [node_client, node_server] = std::move(node_ends.value());
zx::eventpair event0, event1;
ASSERT_OK(zx::eventpair::create(0, &event0, &event1));
fuchsia_io::wire::Device device = {.event = std::move(event1)};
fidl::FidlAllocator fidl_allocator;
auto node_info = fuchsia_io::wire::NodeInfo::WithDevice(fidl_allocator, std::move(device));
auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
if (type != ZXIO_OBJECT_TYPE_DEVICE) {
return ZX_ERR_NOT_SUPPORTED;
}
*out_storage = new zxio_storage_t;
*out_context = *out_storage;
return ZX_OK;
};
async::Loop device_control_loop(&kAsyncLoopConfigNoAttachToCurrentThread);
TestDeviceNodeServer server;
fidl::BindServer(device_control_loop.dispatcher(), std::move(node_server), &server);
device_control_loop.StartThread("device_control_thread");
void* context = nullptr;
ASSERT_OK(zxio_create_with_allocator(std::move(node_client), node_info, allocator, &context));
ASSERT_NE(context, nullptr);
// The event in node_info should be consumed by zxio.
EXPECT_FALSE(node_info.device().event.is_valid());
std::unique_ptr<zxio_storage_t> storage(static_cast<zxio_storage_t*>(context));
zxio_t* zxio = &(storage->io);
// Closing the zxio object should close our eventpair's peer event.
zx_signals_t pending = 0;
ASSERT_EQ(event0.wait_one(0u, zx::time::infinite_past(), &pending), ZX_ERR_TIMED_OUT);
EXPECT_NE(pending & ZX_EVENTPAIR_PEER_CLOSED, ZX_EVENTPAIR_PEER_CLOSED, "pending is %u", pending);
ASSERT_OK(zxio_close(zxio));
ASSERT_EQ(event0.wait_one(0u, zx::time::infinite_past(), &pending), ZX_ERR_TIMED_OUT);
EXPECT_EQ(pending & ZX_EVENTPAIR_PEER_CLOSED, ZX_EVENTPAIR_PEER_CLOSED, "pending is %u", pending);
device_control_loop.Shutdown();
}
class TestDirectoryServer final : public zxio_tests::TestDirectoryServerBase {
void Sync(SyncRequestView request, SyncCompleter::Sync& completer) final {
completer.Reply(ZX_OK);
}
};
TEST(CreateWithInfo, Directory) {
auto dir_ends = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_OK(dir_ends.status_value());
auto [dir_client, dir_server] = std::move(dir_ends.value());
fidl::FidlAllocator fidl_allocator;
auto node_info = fuchsia_io::wire::NodeInfo::WithDirectory(fidl_allocator);
auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
if (type != ZXIO_OBJECT_TYPE_DIR) {
return ZX_ERR_NOT_SUPPORTED;
}
*out_storage = new zxio_storage_t;
*out_context = *out_storage;
return ZX_OK;
};
async::Loop dir_control_loop(&kAsyncLoopConfigNoAttachToCurrentThread);
TestDirectoryServer server;
fidl::BindServer(dir_control_loop.dispatcher(), std::move(dir_server), &server);
dir_control_loop.StartThread("dir_control_thread");
void* context = nullptr;
fidl::ClientEnd<fuchsia_io::Node> node_client(dir_client.TakeChannel());
ASSERT_OK(zxio_create_with_allocator(std::move(node_client), node_info, allocator, &context));
ASSERT_NE(context, nullptr);
std::unique_ptr<zxio_storage_t> storage(static_cast<zxio_storage_t*>(context));
zxio_t* zxio = &(storage->io);
// Sanity check the zxio by sending a sync operation to the server.
EXPECT_OK(zxio_sync(zxio));
ASSERT_OK(zxio_close(zxio));
dir_control_loop.Shutdown();
}
class TestFileServer final : public zxio_tests::TestFileServerBase {
public:
void Read(ReadRequestView request, ReadCompleter::Sync& completer) final {
fidl::FidlAllocator fidl_allocator;
fidl::VectorView<uint8_t> read_data(fidl_allocator, sizeof(kTestData));
memcpy(read_data.mutable_data(), kTestData, sizeof(kTestData));
completer.Reply(ZX_OK, read_data);
}
static constexpr char kTestData[] = "abcdef";
};
TEST(CreateWithInfo, File) {
auto file_ends = fidl::CreateEndpoints<fuchsia_io::File>();
ASSERT_OK(file_ends.status_value());
auto [file_client, file_server] = std::move(file_ends.value());
zx::event file_event;
ASSERT_OK(zx::event::create(0u, &file_event));
fuchsia_io::wire::FileObject file = {.event = std::move(file_event)};
fidl::FidlAllocator fidl_allocator;
auto node_info = fuchsia_io::wire::NodeInfo::WithFile(fidl_allocator, std::move(file));
auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
if (type != ZXIO_OBJECT_TYPE_FILE) {
return ZX_ERR_NOT_SUPPORTED;
}
*out_storage = new zxio_storage_t;
*out_context = *out_storage;
return ZX_OK;
};
async::Loop file_control_loop(&kAsyncLoopConfigNoAttachToCurrentThread);
TestFileServer server;
fidl::BindServer(file_control_loop.dispatcher(), std::move(file_server), &server);
file_control_loop.StartThread("file_control_thread");
void* context = nullptr;
fidl::ClientEnd<fuchsia_io::Node> node_client(file_client.TakeChannel());
ASSERT_OK(zxio_create_with_allocator(std::move(node_client), node_info, allocator, &context));
ASSERT_NE(context, nullptr);
// The event in node_info should be consumed by zxio.
EXPECT_FALSE(node_info.file().event.is_valid());
std::unique_ptr<zxio_storage_t> storage(static_cast<zxio_storage_t*>(context));
zxio_t* zxio = &(storage->io);
// Sanity check the zxio by reading some test data from the server.
char buffer[sizeof(TestFileServer::kTestData)];
size_t actual = 0u;
ASSERT_OK(zxio_read(zxio, buffer, sizeof(buffer), 0u, &actual));
EXPECT_EQ(sizeof(buffer), actual);
EXPECT_BYTES_EQ(buffer, TestFileServer::kTestData, sizeof(buffer));
ASSERT_OK(zxio_close(zxio));
file_control_loop.Shutdown();
}
TEST(CreateWithInfo, Pipe) {
auto node_ends = fidl::CreateEndpoints<fuchsia_io::Node>();
ASSERT_OK(node_ends.status_value());
auto [node_client, node_server] = std::move(node_ends.value());
zx::socket socket0, socket1;
ASSERT_OK(zx::socket::create(0u, &socket0, &socket1));
fuchsia_io::wire::Pipe pipe = {.socket = std::move(socket0)};
fidl::FidlAllocator fidl_allocator;
auto node_info = fuchsia_io::wire::NodeInfo::WithPipe(fidl_allocator, std::move(pipe));
auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
if (type != ZXIO_OBJECT_TYPE_PIPE) {
return ZX_ERR_NOT_SUPPORTED;
}
*out_storage = new zxio_storage_t;
*out_context = *out_storage;
return ZX_OK;
};
void* context = nullptr;
ASSERT_OK(zxio_create_with_allocator(std::move(node_client), node_info, allocator, &context));
ASSERT_NE(context, nullptr);
// The socket in node_info should be consumed by zxio.
EXPECT_FALSE(node_info.pipe().socket.is_valid());
std::unique_ptr<zxio_storage_t> storage(static_cast<zxio_storage_t*>(context));
zxio_t* zxio = &(storage->io);
// Send some data through the kernel socket object and read it through zxio to
// sanity check that the pipe is functional.
int32_t data = 0x1a2a3a4a;
size_t actual = 0u;
ASSERT_OK(socket1.write(0u, &data, sizeof(data), &actual));
EXPECT_EQ(actual, sizeof(data));
int32_t buffer = 0;
ASSERT_OK(zxio_read(zxio, &buffer, sizeof(buffer), 0u, &actual));
EXPECT_EQ(actual, sizeof(buffer));
EXPECT_EQ(buffer, data);
ASSERT_OK(zxio_close(zxio));
}
class TestServiceNodeServer : public fuchsia_io::testing::Node_TestBase {
public:
void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) final {
ADD_FAILURE("unexpected message received: %s", name.c_str());
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Close(CloseRequestView request, CloseCompleter::Sync& completer) final {
completer.Reply(ZX_OK);
// After the reply, we should close the connection.
completer.Close(ZX_OK);
}
};
TEST(CreateWithInfo, Service) {
auto node_ends = fidl::CreateEndpoints<fuchsia_io::Node>();
ASSERT_OK(node_ends.status_value());
auto [node_client, node_server] = std::move(node_ends.value());
fidl::FidlAllocator fidl_allocator;
auto node_info = fuchsia_io::wire::NodeInfo::WithService(fidl_allocator);
auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
if (type != ZXIO_OBJECT_TYPE_SERVICE) {
return ZX_ERR_NOT_SUPPORTED;
}
*out_storage = new zxio_storage_t;
*out_context = *out_storage;
return ZX_OK;
};
async::Loop service_control_loop(&kAsyncLoopConfigNoAttachToCurrentThread);
TestServiceNodeServer server;
fidl::BindServer(service_control_loop.dispatcher(), std::move(node_server), &server);
service_control_loop.StartThread("service_control_thread");
void* context = nullptr;
ASSERT_OK(zxio_create_with_allocator(std::move(node_client), node_info, allocator, &context));
ASSERT_NE(context, nullptr);
std::unique_ptr<zxio_storage_t> storage(static_cast<zxio_storage_t*>(context));
zxio_t* zxio = &(storage->io);
ASSERT_OK(zxio_close(zxio));
service_control_loop.Shutdown();
}
class TestTtyServer : public fuchsia_hardware_pty::testing::Device_TestBase {
public:
void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) final {
ADD_FAILURE("unexpected message received: %s", name.c_str());
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Close(CloseRequestView request, CloseCompleter::Sync& completer) final {
completer.Reply(ZX_OK);
// After the reply, we should close the connection.
completer.Close(ZX_OK);
}
};
TEST(CreateWithInfo, Tty) {
auto node_ends = fidl::CreateEndpoints<fuchsia_io::Node>();
ASSERT_OK(node_ends.status_value());
auto [node_client, node_server] = std::move(node_ends.value());
zx::eventpair event0, event1;
ASSERT_OK(zx::eventpair::create(0, &event0, &event1));
fuchsia_io::wire::Tty tty = {.event = std::move(event1)};
fidl::FidlAllocator fidl_allocator;
auto node_info = fuchsia_io::wire::NodeInfo::WithTty(fidl_allocator, std::move(tty));
auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
if (type != ZXIO_OBJECT_TYPE_TTY) {
return ZX_ERR_NOT_SUPPORTED;
}
*out_storage = new zxio_storage_t;
*out_context = *out_storage;
return ZX_OK;
};
async::Loop tty_control_loop(&kAsyncLoopConfigNoAttachToCurrentThread);
TestTtyServer server;
fidl::ServerEnd<fuchsia_hardware_pty::Device> tty_server(node_server.TakeChannel());
fidl::BindServer(tty_control_loop.dispatcher(), std::move(tty_server), &server);
tty_control_loop.StartThread("tty_control_thread");
void* context = nullptr;
ASSERT_OK(zxio_create_with_allocator(std::move(node_client), node_info, allocator, &context));
ASSERT_NE(context, nullptr);
// The event in node_info should be consumed by zxio.
EXPECT_FALSE(node_info.tty().event.is_valid());
std::unique_ptr<zxio_storage_t> storage(static_cast<zxio_storage_t*>(context));
zxio_t* zxio = &(storage->io);
// Closing the zxio object should close our eventpair's peer event.
zx_signals_t pending = 0;
ASSERT_EQ(event0.wait_one(0u, zx::time::infinite_past(), &pending), ZX_ERR_TIMED_OUT);
EXPECT_NE(pending & ZX_EVENTPAIR_PEER_CLOSED, ZX_EVENTPAIR_PEER_CLOSED, "pending is %u", pending);
ASSERT_OK(zxio_close(zxio));
ASSERT_EQ(event0.wait_one(0u, zx::time::infinite_past(), &pending), ZX_ERR_TIMED_OUT);
EXPECT_EQ(pending & ZX_EVENTPAIR_PEER_CLOSED, ZX_EVENTPAIR_PEER_CLOSED, "pending is %u", pending);
tty_control_loop.Shutdown();
}
class TestVmofileServer : public zxio_tests::TestFileServerBase {
public:
explicit TestVmofileServer(uint64_t seek_start_offset) : seek_start_offset_(seek_start_offset) {}
private:
// Constructing a Vmofile instance requires a server responding to a seek call
// over the fuchsia.io.File protocol. This is a test implementation smart enough
// to respond to this call.
void Seek(SeekRequestView request, SeekCompleter::Sync& completer) final {
if (request->start != fuchsia_io::wire::SeekOrigin::kStart || request->offset != 0) {
ADD_FAILURE("unsupported Seek received start %d offset %ld", request->start, request->offset);
completer.Close(ZX_ERR_NOT_SUPPORTED);
return;
}
completer.Reply(ZX_OK, seek_start_offset_);
}
const uint64_t seek_start_offset_;
};
TEST(CreateWithInfo, Vmofile) {
auto file_ends = fidl::CreateEndpoints<fuchsia_io::File>();
ASSERT_OK(file_ends.status_value());
auto [file_client, file_server] = std::move(file_ends.value());
const uint64_t vmo_size = 5678;
const uint64_t file_start_offset = 1234;
const uint64_t file_length = 345;
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(vmo_size, 0u, &vmo));
fuchsia_io::wire::Vmofile vmofile = {
.vmo = std::move(vmo),
.offset = file_start_offset,
.length = file_length,
};
fidl::FidlAllocator fidl_allocator;
auto node_info = fuchsia_io::wire::NodeInfo::WithVmofile(fidl_allocator, std::move(vmofile));
auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
if (type != ZXIO_OBJECT_TYPE_VMOFILE) {
return ZX_ERR_NOT_SUPPORTED;
}
*out_storage = new zxio_storage_t;
*out_context = *out_storage;
return ZX_OK;
};
const uint64_t offset_within_file = 234;
async::Loop vmofile_control_loop(&kAsyncLoopConfigNoAttachToCurrentThread);
TestVmofileServer server(offset_within_file);
fidl::BindServer(vmofile_control_loop.dispatcher(), std::move(file_server), &server);
vmofile_control_loop.StartThread("vmofile_control_thread");
void* context = nullptr;
fidl::ClientEnd<fuchsia_io::Node> node_client(file_client.TakeChannel());
ASSERT_OK(zxio_create_with_allocator(std::move(node_client), node_info, allocator, &context));
ASSERT_NE(context, nullptr);
// The vmo in node_info should be consumed by zxio.
EXPECT_FALSE(node_info.vmofile().vmo.is_valid());
std::unique_ptr<zxio_storage_t> storage(static_cast<zxio_storage_t*>(context));
zxio_t* zxio = &(storage->io);
// Sanity check the zxio object.
zxio_node_attributes_t attr;
EXPECT_OK(zxio_attr_get(zxio, &attr));
EXPECT_TRUE(attr.has.content_size);
EXPECT_EQ(attr.content_size, file_length);
size_t seek_current_offset = 0u;
EXPECT_OK(zxio_seek(zxio, ZXIO_SEEK_ORIGIN_CURRENT, 0, &seek_current_offset));
EXPECT_EQ(static_cast<size_t>(offset_within_file), seek_current_offset);
size_t seek_start_offset = 0u;
EXPECT_OK(zxio_seek(zxio, ZXIO_SEEK_ORIGIN_START, 0, &seek_start_offset));
EXPECT_EQ(0, seek_start_offset);
size_t seek_end_offset = 0u;
EXPECT_OK(zxio_seek(zxio, ZXIO_SEEK_ORIGIN_END, 0, &seek_end_offset));
EXPECT_EQ(static_cast<size_t>(file_length), seek_end_offset);
ASSERT_OK(zxio_close(zxio));
vmofile_control_loop.Shutdown();
}