blob: d8cd838a98b005d92119258e138745cdecda8a19 [file] [log] [blame]
// Copyright 2019 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 <fidl/fuchsia.hardware.pty/cpp/wire.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/sync/completion.h>
#include <lib/zx/time.h>
#include <algorithm>
#include <iterator>
#include <zxtest/zxtest.h>
#include "pty-server.h"
namespace {
using Device = fuchsia_hardware_pty::Device;
using Connection = fidl::WireSyncClient<Device>;
class PtyTestCase : public zxtest::Test {
public:
PtyTestCase() : loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {}
void SetUp() override {
zx::result args = PtyServer::Args::Create();
ASSERT_OK(args.status_value());
std::shared_ptr server = std::make_shared<PtyServer>(std::move(args.value()), dispatcher());
zx::result endpoints = fidl::CreateEndpoints<Device>();
ASSERT_OK(endpoints.status_value());
auto& [client_end, server_end] = endpoints.value();
async::PostTask(dispatcher(),
[server = std::move(server), server_end = std::move(server_end)]() mutable {
server->AddConnection(std::move(server_end));
});
ASSERT_OK(loop_.StartThread("pty-test"));
server_ = Connection(std::move(client_end));
}
protected:
static zx::result<Connection> OpenClient(Connection& conn, uint32_t id) {
auto endpoints = fidl::CreateEndpoints<Device>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
fidl::WireResult result = conn->OpenClient(id, std::move(endpoints->server));
if (result.status() != ZX_OK) {
return zx::error(ZX_ERR_BAD_STATE);
}
if (result->s != ZX_OK) {
return zx::error(result->s);
}
return zx::ok(Connection(std::move(endpoints->client)));
}
async_dispatcher_t* dispatcher() { return loop_.dispatcher(); }
Connection take_server() { return std::move(server_); }
private:
async::Loop loop_;
Connection server_;
};
zx::eventpair GetEvent(Connection& conn) {
auto result = conn->Describe();
if (result.status() != ZX_OK) {
return {};
}
if (!result.value().has_event()) {
return {};
}
return std::move(result.value().event());
}
void WriteCtrlC(Connection& conn) {
uint8_t data[] = {0x03};
const fidl::WireResult result = conn->Write(fidl::VectorView<uint8_t>::FromExternal(data));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_EQ(response.value()->actual_count, std::size(data));
}
// Make sure the server connections describe appropriately
TEST_F(PtyTestCase, ServerDescribe) {
Connection server{take_server()};
auto result = server->Describe();
ASSERT_OK(result.status());
ASSERT_TRUE(result.value().has_event());
ASSERT_TRUE(result.value().event().is_valid());
} // namespace
TEST_F(PtyTestCase, ServerSetWindowSize) {
Connection server{take_server()};
auto result = server->SetWindowSize({.width = 80, .height = 24});
ASSERT_OK(result.status());
ASSERT_OK(result->status);
} // namespace
TEST_F(PtyTestCase, ServerClrSetFeature) {
Connection server{take_server()};
auto result = server->ClrSetFeature(0, 0);
ASSERT_OK(result.status());
// ClrSetFeature is only meaningful on clients
ASSERT_STATUS(result->status, ZX_ERR_NOT_SUPPORTED);
}
TEST_F(PtyTestCase, ServerGetWindowSize) {
Connection server{take_server()};
auto result = server->GetWindowSize();
ASSERT_OK(result.status());
// Our original implementation didn't support this, so preserve that behavior.
// It's not clear why, though. If this is causing problems, we should
// probably just implement it.
ASSERT_STATUS(result->status, ZX_ERR_NOT_SUPPORTED);
}
TEST_F(PtyTestCase, ServerMakeActive) {
Connection server{take_server()};
auto result = server->MakeActive(0);
ASSERT_OK(result.status());
// MakeActive is only meaningful on clients
ASSERT_STATUS(result->status, ZX_ERR_NOT_SUPPORTED);
}
TEST_F(PtyTestCase, ServerReadEvents) {
Connection server{take_server()};
auto result = server->ReadEvents();
ASSERT_OK(result.status());
// ReadEvents is only meaningful on clients
ASSERT_STATUS(result->status, ZX_ERR_NOT_SUPPORTED);
}
// Basic test of opening a client
TEST_F(PtyTestCase, ServerBasicOpenClient) {
Connection server{take_server()};
zx::result client = OpenClient(server, 0);
ASSERT_OK(client.status_value());
// Make sure our client connection is valid after this
ASSERT_STATUS(
client.value().client_end().channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
}
// Try opening two clients with the same id
TEST_F(PtyTestCase, ServerOpenClientTwice) {
Connection server{take_server()};
zx::result client = OpenClient(server, 0);
ASSERT_OK(client.status_value());
ASSERT_STATUS(OpenClient(server, 0).status_value(), ZX_ERR_INVALID_ARGS);
// Our original client connection should still be good.
ASSERT_STATUS(
client.value().client_end().channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
}
// Try opening two clients with different ids
TEST_F(PtyTestCase, ServerOpenClientTwoDifferent) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
zx::result client2 = OpenClient(server, 0);
ASSERT_OK(client2.status_value());
// Both connections should be good
ASSERT_STATUS(
client.value().client_end().channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
ASSERT_STATUS(
client2.value().client_end().channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
}
// Verify a server with no clients behaves as expected
TEST_F(PtyTestCase, ServerWithNoClientsInitialConditions) {
Connection server{take_server()};
zx::eventpair event = GetEvent(server);
auto check_state = [&]() {
zx_signals_t observed = 0;
ASSERT_STATUS(event.wait_one(0, zx::time{}, &observed), ZX_ERR_TIMED_OUT);
// Precisely this set of signals should be asserted
ASSERT_EQ(observed, static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable |
fuchsia_device::wire::DeviceSignal::kHangup));
// Attempts to read should get 0 bytes and ZX_OK
{
const fidl::WireResult result = server->Read(10);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const fidl::VectorView data = response.value()->data;
ASSERT_EQ(std::string_view(reinterpret_cast<const char*>(data.data()), data.count()),
std::string_view());
}
// Attempts to write should fail with ZX_ERR_PEER_CLOSED
{
uint8_t data[16] = {};
const fidl::WireResult result = server->Write(fidl::VectorView<uint8_t>::FromExternal(data));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_error());
ASSERT_STATUS(response.error_value(), ZX_ERR_PEER_CLOSED);
}
};
ASSERT_NO_FATAL_FAILURE(check_state());
// Create a client and close it, then make sure we're back in the initial
// state
{
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
}
// Wait for the server to signal that it got the client disconnect
ASSERT_OK(event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kHangup),
zx::time::infinite(), nullptr));
ASSERT_NO_FATAL_FAILURE(check_state());
}
// Verify a server with a client has the right state
TEST_F(PtyTestCase, ServerWithClientInitialConditions) {
Connection server{take_server()};
zx::result client = OpenClient(server, 0);
ASSERT_OK(client.status_value());
zx::eventpair server_event = GetEvent(server);
zx::eventpair client_event = GetEvent(client.value());
zx_signals_t observed = 0;
ASSERT_STATUS(server_event.wait_one(0, zx::time{}, &observed), ZX_ERR_TIMED_OUT);
ASSERT_EQ(observed, static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kWritable));
observed = 0;
ASSERT_STATUS(client_event.wait_one(0, zx::time{}, &observed), ZX_ERR_TIMED_OUT);
ASSERT_EQ(observed, static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kWritable));
// Attempts to read on either side should get SHOULD_WAIT
{
const fidl::WireResult result = server->Read(10);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_error());
ASSERT_STATUS(response.error_value(), ZX_ERR_SHOULD_WAIT);
}
{
const fidl::WireResult result = client->Read(10);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_error());
ASSERT_STATUS(response.error_value(), ZX_ERR_SHOULD_WAIT);
}
// Client should be in cooked mode
{
auto result = client->ClrSetFeature(0, 0);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->features, 0);
}
}
// Verify a read from a server for 0 bytes doesn't return ZX_ERR_SHOULD_WAIT
TEST_F(PtyTestCase, ServerEmpty0ByteRead) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
const fidl::WireResult result = server->Read(0);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const fidl::VectorView data = response.value()->data;
ASSERT_EQ(std::string_view(reinterpret_cast<const char*>(data.data()), data.count()),
std::string_view());
}
// Verify a write by the server for 0 bytes when the receiving client is full doesn't return
// ZX_ERR_SHOULD_WAIT
TEST_F(PtyTestCase, ClientFull0ByteServerWrite) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
// Fill up FIFO
while (true) {
uint8_t buf[256] = {};
const fidl::WireResult result = server->Write(fidl::VectorView<uint8_t>::FromExternal(buf));
ASSERT_OK(result.status());
const fit::result response = result.value();
if (response.is_error()) {
ASSERT_STATUS(response.error_value(), ZX_ERR_SHOULD_WAIT);
break;
}
ASSERT_GT(response.value()->actual_count, 0);
}
const fidl::WireResult result = server->Write({});
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_EQ(response.value()->actual_count, 0);
}
// Verify a write by a client for 0 bytes when the client isn't active returns
// ZX_ERR_SHOULD_WAIT
TEST_F(PtyTestCase, ClientInactive0ByteClientWrite) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
zx::result inactive_client = OpenClient(server, 0);
ASSERT_OK(inactive_client.status_value());
const fidl::WireResult result = inactive_client->Write({});
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_error());
ASSERT_STATUS(response.error_value(), ZX_ERR_SHOULD_WAIT);
}
// Make sure the client connections describe appropriately
TEST_F(PtyTestCase, ClientDescribe) {
Connection server{take_server()};
zx::result client = OpenClient(server, 0);
ASSERT_OK(client.status_value());
auto result = client->Describe();
ASSERT_OK(result.status());
ASSERT_TRUE(result.value().has_event());
ASSERT_TRUE(result.value().event().is_valid());
}
TEST_F(PtyTestCase, ClientWindowSize) {
Connection server{take_server()};
zx::result client = OpenClient(server, 0);
ASSERT_OK(client.status_value());
{
auto result = server->SetWindowSize({.width = 80, .height = 24});
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
auto result = client->GetWindowSize();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->size.width, 80);
ASSERT_EQ(result->size.height, 24);
}
{
auto result = client->SetWindowSize({.width = 5, .height = 32});
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
auto result = client->GetWindowSize();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->size.width, 5);
ASSERT_EQ(result->size.height, 32);
}
}
TEST_F(PtyTestCase, ClientClrSetFeature) {
Connection server{take_server()};
zx::result client = OpenClient(server, 0);
ASSERT_OK(client.status_value());
{
auto result = client->ClrSetFeature(0, 0);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->features, 0);
}
// Make sure we can set bits
{
auto result = client->ClrSetFeature(0, fuchsia_hardware_pty::wire::kFeatureRaw);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->features, fuchsia_hardware_pty::wire::kFeatureRaw);
}
// If we don't change any bits, we should see the new settings
{
auto result = client->ClrSetFeature(0, 0);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->features, fuchsia_hardware_pty::wire::kFeatureRaw);
}
// Make sure we can clear bits
{
auto result = client->ClrSetFeature(fuchsia_hardware_pty::wire::kFeatureRaw, 0);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->features, 0);
}
}
TEST_F(PtyTestCase, ClientClrSetFeatureInvalidBit) {
Connection server{take_server()};
zx::result client = OpenClient(server, 0);
ASSERT_OK(client.status_value());
{
auto result = client->ClrSetFeature(0, 0x2);
ASSERT_OK(result.status());
ASSERT_STATUS(result->status, ZX_ERR_NOT_SUPPORTED);
ASSERT_EQ(result->features, 0);
}
{
auto result = client->ClrSetFeature(0x2, 0);
ASSERT_OK(result.status());
ASSERT_STATUS(result->status, ZX_ERR_NOT_SUPPORTED);
ASSERT_EQ(result->features, 0);
}
}
TEST_F(PtyTestCase, ClientGetWindowSizeServerNeverSet) {
Connection server{take_server()};
zx::result client = OpenClient(server, 0);
ASSERT_OK(client.status_value());
auto result = client->GetWindowSize();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->size.width, 0);
ASSERT_EQ(result->size.height, 0);
}
// Each client should have its own feature flags
TEST_F(PtyTestCase, ClientIndependentFeatureFlags) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
zx::result client2 = OpenClient(server, 0);
ASSERT_OK(client2.status_value());
{
auto result = client->ClrSetFeature(0, fuchsia_hardware_pty::wire::kFeatureRaw);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->features, fuchsia_hardware_pty::wire::kFeatureRaw);
}
{
// Client 2 shouldn't see the changes
auto result = client2->ClrSetFeature(0, 0);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->features, 0);
}
}
TEST_F(PtyTestCase, ClientMakeActive) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
zx::result client2 = OpenClient(server, 0);
ASSERT_OK(client2.status_value());
{
auto result = client->MakeActive(0);
ASSERT_OK(result.status());
// This client is not the controlling client (id=0), so it cannot change the
// active client
ASSERT_STATUS(result->status, ZX_ERR_ACCESS_DENIED);
}
{
auto result = client2->MakeActive(1);
ASSERT_OK(result.status());
// This client is the controlling client (id=0), so it can.
ASSERT_OK(result->status);
}
{
// Changing the active client to the existing active client should be fine
auto result = client2->MakeActive(1);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
// Changing the active client to the control client should be fine
auto result = client2->MakeActive(0);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
// Changing the active client to a non-existent client should fail
auto result = client2->MakeActive(2);
ASSERT_OK(result.status());
ASSERT_STATUS(result->status, ZX_ERR_NOT_FOUND);
}
}
TEST_F(PtyTestCase, ClientReadEvents) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
zx::result client2 = OpenClient(server, 0);
ASSERT_OK(client2.status_value());
{
auto result = client->ReadEvents();
ASSERT_OK(result.status());
// This client is not the controlling client (id=0), so it cannot read events
ASSERT_STATUS(result->status, ZX_ERR_ACCESS_DENIED);
}
{
auto result = client2->ReadEvents();
ASSERT_OK(result.status());
// This client is the controlling client (id=0), so it can read events
ASSERT_OK(result->status);
ASSERT_EQ(result->events, 0);
}
}
// Reading events should clear the event condition
TEST_F(PtyTestCase, ClientReadEventsClears) {
Connection server{take_server()};
zx::result active_client = OpenClient(server, 1);
ASSERT_OK(active_client.status_value());
zx::result control_client = OpenClient(server, 0);
ASSERT_OK(control_client.status_value());
zx::eventpair control_event = GetEvent(control_client.value());
// No events yet
ASSERT_STATUS(
control_event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kOob),
zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
// Write a ^C byte from the server to trigger a cooked-mode event
ASSERT_NO_FATAL_FAILURE(WriteCtrlC(server));
ASSERT_OK(
control_event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kOob),
zx::time::infinite(), nullptr));
{
auto result = control_client->ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, fuchsia_hardware_pty::wire::kEventInterrupt);
}
// Signal should have cleared
ASSERT_STATUS(
control_event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kOob),
zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
// Event should have cleared
{
auto result = control_client->ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, 0);
}
}
// Events arrive even without a controlling client connected
TEST_F(PtyTestCase, EventsSentWithNoControllingClient) {
Connection server{take_server()};
zx::result active_client = OpenClient(server, 1);
ASSERT_OK(active_client.status_value());
// Write a ^C byte from the server to trigger a cooked-mode event
ASSERT_NO_FATAL_FAILURE(WriteCtrlC(server));
// Connect a control client to inspect the event
zx::result control_client = OpenClient(server, 0);
ASSERT_OK(control_client.status_value());
zx::eventpair control_event = GetEvent(control_client.value());
ASSERT_OK(control_event.wait_one(
static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kOob), zx::time{}, nullptr));
{
auto result = control_client->ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, fuchsia_hardware_pty::wire::kEventInterrupt);
}
}
TEST_F(PtyTestCase, SetWindowSizeSendsEvent) {
Connection server{take_server()};
zx::result control_client = OpenClient(server, 0);
ASSERT_OK(control_client.status_value());
zx::eventpair control_event = GetEvent(control_client.value());
// No events yet
ASSERT_STATUS(control_event.wait_one(static_cast<zx_signals_t>(static_cast<zx_signals_t>(
fuchsia_device::wire::DeviceSignal::kOob)),
zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
{
auto result = control_client->ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, 0);
}
// SetWindowSize which should trigger an event
{
auto result = server->SetWindowSize({.width = 123, .height = 45});
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
ASSERT_OK(
control_event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kOob),
zx::time::infinite(), nullptr));
{
auto result = control_client->ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, fuchsia_hardware_pty::wire::kEventWindowSize);
}
}
TEST_F(PtyTestCase, NonControllingClientOpenClient) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
// This client is not the controlling client (id=0), so it cannot create new
// clients
ASSERT_STATUS(OpenClient(client.value(), 2).status_value(), ZX_ERR_ACCESS_DENIED);
}
TEST_F(PtyTestCase, ControllingClientOpenClient) {
Connection server{take_server()};
zx::result client = OpenClient(server, 0);
ASSERT_OK(client.status_value());
zx::result client2 = OpenClient(client.value(), 1);
ASSERT_OK(client2.status_value());
}
TEST_F(PtyTestCase, ActiveClientCloses) {
Connection server{take_server()};
zx::result control_client = OpenClient(server, 0);
ASSERT_OK(control_client.status_value());
{
zx::result active_client = OpenClient(server, 1);
ASSERT_OK(active_client.status_value());
auto result = control_client->MakeActive(1);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
zx::eventpair control_event = GetEvent(control_client.value());
zx_signals_t observed = 0;
ASSERT_OK(
control_event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kOob),
zx::time::infinite(), &observed));
// Wait again with no timeout, so that observed doesn't have any transient
// signals in it.
ASSERT_OK(
control_event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kHangup),
zx::time{}, &observed));
ASSERT_EQ(observed, static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kOob |
fuchsia_device::wire::DeviceSignal::kHangup));
auto result = control_client->ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, fuchsia_hardware_pty::wire::kEventHangup);
}
// Makes sure nothing goes wrong when the active client is the controling
// client and it closes.
TEST_F(PtyTestCase, ActiveClientClosesWhenControl) {
Connection server{take_server()};
{
zx::result control_client = OpenClient(server, 0);
ASSERT_OK(control_client.status_value());
}
zx::eventpair event = GetEvent(server);
zx_signals_t observed = 0;
ASSERT_OK(event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kHangup),
zx::time::infinite(), &observed));
}
TEST_F(PtyTestCase, ServerClosesWhenClientPresent) {
Connection server{take_server()};
zx::result client = OpenClient(server, 0);
ASSERT_OK(client.status_value());
// Write some data to the client, so we can verify the client can drain the
// buffer still.
uint8_t kTestData[] = "hello world";
{
const fidl::WireResult result =
server->Write(fidl::VectorView<uint8_t>::FromExternal(kTestData));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_EQ(response.value()->actual_count, std::size(kTestData));
}
server = {};
zx::eventpair event = GetEvent(client.value());
zx_signals_t observed = 0;
ASSERT_OK(event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kHangup),
zx::time::infinite(), &observed));
// Wait again with no timeout, so that observed doesn't have any transient
// signals in it.
ASSERT_OK(event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kHangup),
zx::time{}, &observed));
ASSERT_EQ(observed, static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kHangup |
fuchsia_device::wire::DeviceSignal::kReadable));
{
auto result = client->ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, fuchsia_hardware_pty::wire::kEventHangup);
}
// Attempts to drain the buffer should succeed
{
// Request more bytes than are present
const fidl::WireResult result = client->Read(std::size(kTestData) + 10);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const fidl::VectorView data = response.value()->data;
ASSERT_EQ(std::string_view(reinterpret_cast<const char*>(data.data()), data.count()),
std::string_view(reinterpret_cast<char*>(kTestData), std::size(kTestData)));
}
// Attempts to read the empty buffer should fail with ZX_ERR_PEER_CLOSED
{
const fidl::WireResult result = client->Read(10);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_error());
ASSERT_STATUS(response.error_value(), ZX_ERR_PEER_CLOSED);
}
// Attempts to write should fail with ZX_ERR_PEER_CLOSED
{
uint8_t data[16] = {};
const fidl::WireResult result = client->Write(fidl::VectorView<uint8_t>::FromExternal(data));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_error());
ASSERT_STATUS(response.error_value(), ZX_ERR_PEER_CLOSED);
}
}
// Test writes from the client to the server when the client is cooked
TEST_F(PtyTestCase, ServerReadClientCooked) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
// In cooked mode, client writes should have \n transformed to \r\n, and
// control chars untouched.
uint8_t kTestData[] = "hello\x03 world\ntest message\n";
const uint8_t kExpectedReadback[] = "hello\x03 world\r\ntest message\r\n";
{
const fidl::WireResult result =
client->Write(fidl::VectorView<uint8_t>::FromExternal(kTestData));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_EQ(response.value()->actual_count, std::size(kTestData));
}
zx::eventpair event = GetEvent(server);
ASSERT_OK(event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time::infinite(), nullptr));
{
const fidl::WireResult result = server->Read(std::size(kExpectedReadback) + 10);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const fidl::VectorView data = response.value()->data;
ASSERT_EQ(std::string_view(reinterpret_cast<const char*>(data.data()), data.count()),
std::string_view(reinterpret_cast<const char*>(kExpectedReadback),
std::size(kExpectedReadback)));
}
// Nothing left to read
ASSERT_STATUS(
event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
}
// Test writes from the server to the client when the client is cooked
TEST_F(PtyTestCase, ServerWriteClientCooked) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
// In cooked mode, server writes should have newlines untouched and control
// chars should cause a short write
uint8_t kTestData[] = "hello world\ntest\x03 message\n";
// We expect to read this back, but without the trailing nul
const uint8_t kExpectedReadbackWithNul[] = "hello world\ntest";
{
const fidl::WireResult result =
server->Write(fidl::VectorView<uint8_t>::FromExternal(kTestData));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
// We expect to see the written count to include the ^C
ASSERT_EQ(response.value()->actual_count, std::size(kExpectedReadbackWithNul) - 1 + 1);
}
zx::eventpair event = GetEvent(client.value());
ASSERT_OK(event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time::infinite(), nullptr));
{
const fidl::WireResult result = client->Read(std::size(kExpectedReadbackWithNul) + 10);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const fidl::VectorView data = response.value()->data;
ASSERT_EQ(std::string_view(reinterpret_cast<const char*>(data.data()), data.count()),
std::string_view(reinterpret_cast<const char*>(kExpectedReadbackWithNul),
std::size(kExpectedReadbackWithNul) - 1));
}
// Nothing left to read
ASSERT_STATUS(
event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
}
// Test writes from the client to the server when the client is raw
TEST_F(PtyTestCase, ServerReadClientRaw) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
{
auto result = client->ClrSetFeature(0, fuchsia_hardware_pty::wire::kFeatureRaw);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
// In raw mode, client writes should be untouched.
uint8_t kTestData[] = "hello\x03 world\ntest message\n";
{
const fidl::WireResult result =
client->Write(fidl::VectorView<uint8_t>::FromExternal(kTestData));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_EQ(response.value()->actual_count, std::size(kTestData));
}
zx::eventpair event = GetEvent(server);
ASSERT_OK(event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time::infinite(), nullptr));
{
const fidl::WireResult result = server->Read(std::size(kTestData) + 10);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const fidl::VectorView data = response.value()->data;
ASSERT_EQ(std::string_view(reinterpret_cast<const char*>(data.data()), data.count()),
std::string_view(reinterpret_cast<char*>(kTestData), std::size(kTestData)));
}
// Nothing left to read
ASSERT_STATUS(
event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
}
// Test writes from the server to the client when the client is raw
TEST_F(PtyTestCase, ServerWriteClientRaw) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
zx::result control_client = OpenClient(server, 0);
ASSERT_OK(control_client.status_value());
{
auto result = client->ClrSetFeature(0, fuchsia_hardware_pty::wire::kFeatureRaw);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
// In raw mode, server writes should be untouched.
uint8_t kTestData[] = "hello world\ntest\x03 message\n";
{
const fidl::WireResult result =
server->Write(fidl::VectorView<uint8_t>::FromExternal(kTestData));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_EQ(response.value()->actual_count, std::size(kTestData));
}
zx::eventpair event = GetEvent(client.value());
ASSERT_OK(event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time::infinite(), nullptr));
{
const fidl::WireResult result = client->Read(std::size(kTestData) + 10);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const fidl::VectorView data = response.value()->data;
ASSERT_EQ(std::string_view(reinterpret_cast<const char*>(data.data()), data.count()),
std::string_view(reinterpret_cast<char*>(kTestData), std::size(kTestData)));
}
// Nothing left to read
ASSERT_STATUS(
event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
// Make sure we didn't see an INTERRUPT_EVENT.
{
auto result = control_client->ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, 0);
}
}
TEST_F(PtyTestCase, ServerFillsClientFifo) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
zx::eventpair server_event = GetEvent(server);
zx::eventpair client_event = GetEvent(client.value());
uint8_t kTestString[] = "abcdefghijklmnopqrstuvwxyz";
size_t total_written = 0;
while (server_event.wait_one(
static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kWritable), zx::time{},
nullptr) == ZX_OK) {
const fidl::WireResult result = server->Write(
fidl::VectorView<uint8_t>::FromExternal(kTestString, std::size(kTestString) - 1));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_GT(response.value()->actual_count, 0);
total_written += response.value()->actual_count;
}
// Trying to write when full gets SHOULD_WAIT
{
const fidl::WireResult result = server->Write(
fidl::VectorView<uint8_t>::FromExternal(kTestString, std::size(kTestString) - 1));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_error());
ASSERT_STATUS(response.error_value(), ZX_ERR_SHOULD_WAIT);
}
// Client can read FIFO contents back out
size_t total_read = 0;
while (total_read < total_written) {
ASSERT_OK(client_event.wait_one(
static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable), zx::time{},
nullptr));
const fidl::WireResult result = client->Read(std::size(kTestString) - 1);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const fidl::VectorView data = response.value()->data;
ASSERT_EQ(data.count(), std::min(std::size(kTestString) - 1, total_written - total_read));
ASSERT_EQ(std::string_view(reinterpret_cast<const char*>(data.data()), data.count()),
std::string_view(reinterpret_cast<char*>(kTestString), data.count()));
total_read += data.count();
}
ASSERT_STATUS(client_event.wait_one(
static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
}
TEST_F(PtyTestCase, ClientFillsServerFifo) {
Connection server{take_server()};
zx::result client = OpenClient(server, 1);
ASSERT_OK(client.status_value());
zx::eventpair server_event = GetEvent(server);
zx::eventpair client_event = GetEvent(client.value());
uint8_t kTestString[] = "abcdefghijklmnopqrstuvwxyz";
size_t total_written = 0;
while (client_event.wait_one(
static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kWritable), zx::time{},
nullptr) == ZX_OK) {
const fidl::WireResult result = client->Write(
fidl::VectorView<uint8_t>::FromExternal(kTestString, std::size(kTestString) - 1));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_GT(response.value()->actual_count, 0);
total_written += response.value()->actual_count;
}
// Trying to write when full gets SHOULD_WAIT
{
const fidl::WireResult result = client->Write(
fidl::VectorView<uint8_t>::FromExternal(kTestString, std::size(kTestString) - 1));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_error());
ASSERT_STATUS(response.error_value(), ZX_ERR_SHOULD_WAIT);
}
// Server can read FIFO contents back out
size_t total_read = 0;
while (total_read < total_written) {
ASSERT_OK(server_event.wait_one(
static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable), zx::time{},
nullptr));
const fidl::WireResult result = server->Read(std::size(kTestString) - 1);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const fidl::VectorView data = response.value()->data;
ASSERT_EQ(data.count(), std::min(std::size(kTestString) - 1, total_written - total_read));
ASSERT_EQ(std::string_view(reinterpret_cast<const char*>(data.data()), data.count()),
std::string_view(reinterpret_cast<char*>(kTestString), data.count()));
total_read += data.count();
}
ASSERT_STATUS(server_event.wait_one(
static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
}
TEST_F(PtyTestCase, NonActiveClientsCantWrite) {
Connection server{take_server()};
zx::result control_client = OpenClient(server, 0);
ASSERT_OK(control_client.status_value());
zx::result other_client = OpenClient(server, 1);
ASSERT_OK(other_client.status_value());
// control_client is the current active
zx::eventpair event = GetEvent(other_client.value());
zx_signals_t observed = 0;
ASSERT_STATUS(event.wait_one(0, zx::time{}, &observed), ZX_ERR_TIMED_OUT);
ASSERT_FALSE(observed & static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kWritable));
{
uint8_t byte = 0;
const fidl::WireResult result =
other_client->Write(fidl::VectorView<uint8_t>::FromExternal(&byte, 1));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_error());
ASSERT_STATUS(response.error_value(), ZX_ERR_SHOULD_WAIT);
}
}
TEST_F(PtyTestCase, ClientsHaveIndependentFifos) {
Connection server{take_server()};
zx::result control_client = OpenClient(server, 0);
ASSERT_OK(control_client.status_value());
zx::result other_client = OpenClient(server, 1);
ASSERT_OK(other_client.status_value());
uint8_t kControlClientByte = 1;
uint8_t kOtherClientByte = 2;
// control_client is the current active, so it should go to its FIFO
{
const fidl::WireResult result =
server->Write(fidl::VectorView<uint8_t>::FromExternal(&kControlClientByte, 1));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_EQ(response.value()->actual_count, 1);
}
// Switch active clients
{
auto result = control_client->MakeActive(1);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
// This should go to the other client's FIFO
{
const fidl::WireResult result =
server->Write(fidl::VectorView<uint8_t>::FromExternal(&kOtherClientByte, 1));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_EQ(response.value()->actual_count, 1);
}
auto check_client = [&](Connection& client, uint8_t expected_value) {
zx::eventpair event = GetEvent(client);
ASSERT_OK(
event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time{}, nullptr));
const fidl::WireResult result = client->Read(10);
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const fidl::VectorView data = response.value()->data;
ASSERT_EQ(data.count(), 1);
ASSERT_EQ(data.data()[0], expected_value);
ASSERT_STATUS(
event.wait_one(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable),
zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
};
ASSERT_NO_FATAL_FAILURE(check_client(other_client.value(), kOtherClientByte));
ASSERT_NO_FATAL_FAILURE(check_client(control_client.value(), kControlClientByte));
}
} // namespace