blob: 0a2d8413ed0eb5b32b187078393486e58b3d8c73 [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 <fuchsia/hardware/pty/llcpp/fidl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/sync/completion.h>
#include <lib/zx/time.h>
#include <algorithm>
#include <iterator>
#include <fs/managed_vfs.h>
#include <fs/vfs_types.h>
#include <zxtest/zxtest.h>
#include "pty-server-vnode.h"
#include "pty-server.h"
namespace {
using Connection = ::llcpp::fuchsia::hardware::pty::Device::SyncClient;
class PtyTestCase : public zxtest::Test {
public:
PtyTestCase()
: loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
vfs_(loop_.dispatcher()),
server_(zx::channel()) {}
void SetUp() override {
ASSERT_OK(loop_.StartThread("pty-test"));
ASSERT_NO_FATAL_FAILURES(CreateNewServer(&server_));
}
void TearDown() override {
sync_completion_t completion;
vfs_.Shutdown([&completion](zx_status_t) { sync_completion_signal(&completion); });
ASSERT_OK(sync_completion_wait_deadline(&completion, zx::time::infinite().get()));
}
protected:
zx_status_t OpenClient(Connection* conn, uint32_t id, Connection* client) {
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return status;
}
auto result = conn->OpenClient(id, std::move(remote));
if (result.status() != ZX_OK) {
return ZX_ERR_BAD_STATE;
}
if (result->s != ZX_OK) {
return result->s;
}
*client = Connection(std::move(local));
return ZX_OK;
}
async_dispatcher_t* dispatcher() { return loop_.dispatcher(); }
fs::Vfs* vfs() { return &vfs_; }
Connection take_server() { return std::move(server_); }
private:
void CreateNewServer(Connection* conn) {
fbl::RefPtr<PtyServer> server;
ASSERT_OK(PtyServer::Create(&server, &vfs_));
auto vnode = fbl::MakeRefCounted<PtyServerVnode>(std::move(server));
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
ASSERT_OK(
vfs()->Serve(std::move(vnode), std::move(remote), fs::VnodeConnectionOptions::ReadWrite()));
*conn = Connection(std::move(local));
}
async::Loop loop_;
fs::ManagedVfs vfs_;
Connection server_;
};
zx::eventpair GetEvent(Connection* conn) {
auto result = conn->Describe();
if (result.status() != ZX_OK) {
return {};
}
zx::eventpair event = std::move(result->info.mutable_tty().event);
return event;
}
void WriteCtrlC(Connection* conn) {
uint8_t data[] = {0x03};
auto result = conn->Write(fidl::VectorView<uint8_t>{fidl::unowned_ptr(data), std::size(data)});
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual, 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->info.is_tty());
ASSERT_TRUE(result->info.tty().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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client));
// Make sure our client connection is valid after this
ASSERT_STATUS(client.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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client));
Connection client2{zx::channel{}};
ASSERT_STATUS(OpenClient(&server, 0, &client2), ZX_ERR_INVALID_ARGS);
// Our original client connection should still be good.
ASSERT_STATUS(client.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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
Connection client2{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client2));
// Both connections should be good
ASSERT_STATUS(client.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
ASSERT_STATUS(client2.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, ::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE |
::llcpp::fuchsia::device::DEVICE_SIGNAL_HANGUP);
// Attempts to read should get 0 bytes and ZX_OK
{
auto result = server.Read(10);
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->data.count(), 0);
}
// Attempts to write should fail with ZX_ERR_PEER_CLOSED
{
uint8_t data[16] = {};
auto result =
server.Write(fidl::VectorView<uint8_t>{fidl::unowned_ptr(data), std::size(data)});
ASSERT_OK(result.status());
ASSERT_STATUS(result->s, ZX_ERR_PEER_CLOSED);
}
};
ASSERT_NO_FATAL_FAILURES(check_state());
// Create a client and close it, then make sure we're back in the initial
// state
{
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
}
// Wait for the server to signal that it got the client disconnect
ASSERT_OK(event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_HANGUP, zx::time::infinite(),
nullptr));
ASSERT_NO_FATAL_FAILURES(check_state());
}
// Verify a server with a client has the right state
TEST_F(PtyTestCase, ServerWithClientInitialConditions) {
Connection server{take_server()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client));
zx::eventpair server_event = GetEvent(&server);
zx::eventpair client_event = GetEvent(&client);
zx_signals_t observed = 0;
ASSERT_STATUS(server_event.wait_one(0, zx::time{}, &observed), ZX_ERR_TIMED_OUT);
ASSERT_EQ(observed, ::llcpp::fuchsia::device::DEVICE_SIGNAL_WRITABLE);
observed = 0;
ASSERT_STATUS(client_event.wait_one(0, zx::time{}, &observed), ZX_ERR_TIMED_OUT);
ASSERT_EQ(observed, ::llcpp::fuchsia::device::DEVICE_SIGNAL_WRITABLE);
// Attempts to read on either side should get SHOULD_WAIT
{
auto result = server.Read(10);
ASSERT_OK(result.status());
ASSERT_STATUS(result->s, ZX_ERR_SHOULD_WAIT);
}
{
auto result = client.Read(10);
ASSERT_OK(result.status());
ASSERT_STATUS(result->s, 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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
auto result = server.Read(0);
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->data.count(), 0);
}
// 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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
// Fill up FIFO
while (true) {
uint8_t buf[256] = {};
auto result = server.Write({fidl::unowned_ptr(buf), std::size(buf)});
ASSERT_OK(result.status());
if (result->s == ZX_ERR_SHOULD_WAIT) {
break;
}
ASSERT_OK(result->s);
ASSERT_GT(result->actual, 0);
}
auto result = server.Write({});
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual, 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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
Connection inactive_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &inactive_client));
auto result = inactive_client.Write({});
ASSERT_OK(result.status());
ASSERT_STATUS(result->s, ZX_ERR_SHOULD_WAIT);
}
// Make sure the client connections describe appropriately
TEST_F(PtyTestCase, ClientDescribe) {
Connection server{take_server()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client));
auto result = client.Describe();
ASSERT_OK(result.status());
ASSERT_TRUE(result->info.is_tty());
ASSERT_TRUE(result->info.tty().event.is_valid());
}
TEST_F(PtyTestCase, ClientWindowSize) {
Connection server{take_server()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client));
{
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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client));
{
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, ::llcpp::fuchsia::hardware::pty::FEATURE_RAW);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->features, ::llcpp::fuchsia::hardware::pty::FEATURE_RAW);
}
// 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, ::llcpp::fuchsia::hardware::pty::FEATURE_RAW);
}
// Make sure we can clear bits
{
auto result = client.ClrSetFeature(::llcpp::fuchsia::hardware::pty::FEATURE_RAW, 0);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->features, 0);
}
}
TEST_F(PtyTestCase, ClientClrSetFeatureInvalidBit) {
Connection server{take_server()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client));
{
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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client));
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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
Connection client2{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client2));
{
auto result = client.ClrSetFeature(0, ::llcpp::fuchsia::hardware::pty::FEATURE_RAW);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->features, ::llcpp::fuchsia::hardware::pty::FEATURE_RAW);
}
{
// 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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
Connection client2{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client2));
{
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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
Connection client2{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client2));
{
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()};
Connection active_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &active_client));
Connection control_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &control_client));
zx::eventpair control_event = GetEvent(&control_client);
// No events yet
ASSERT_STATUS(
control_event.wait_one(::llcpp::fuchsia::hardware::pty::SIGNAL_EVENT, zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
// Write a ^C byte from the server to trigger a cooked-mode event
ASSERT_NO_FATAL_FAILURES(WriteCtrlC(&server));
ASSERT_OK(control_event.wait_one(::llcpp::fuchsia::hardware::pty::SIGNAL_EVENT,
zx::time::infinite(), nullptr));
{
auto result = control_client.ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, ::llcpp::fuchsia::hardware::pty::EVENT_INTERRUPT);
}
// Signal should have cleared
ASSERT_STATUS(
control_event.wait_one(::llcpp::fuchsia::hardware::pty::SIGNAL_EVENT, 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()};
Connection active_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &active_client));
// Write a ^C byte from the server to trigger a cooked-mode event
ASSERT_NO_FATAL_FAILURES(WriteCtrlC(&server));
// Connect a control client to inspect the event
Connection control_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &control_client));
zx::eventpair control_event = GetEvent(&control_client);
ASSERT_OK(
control_event.wait_one(::llcpp::fuchsia::hardware::pty::SIGNAL_EVENT, zx::time{}, nullptr));
{
auto result = control_client.ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, ::llcpp::fuchsia::hardware::pty::EVENT_INTERRUPT);
}
}
TEST_F(PtyTestCase, NonControllingClientOpenClient) {
Connection server{take_server()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
Connection client2{zx::channel{}};
// This client is not the controlling client (id=0), so it cannot create new
// clients
ASSERT_STATUS(OpenClient(&client, 2, &client2), ZX_ERR_ACCESS_DENIED);
}
TEST_F(PtyTestCase, ControllingClientOpenClient) {
Connection server{take_server()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client));
Connection client2{zx::channel{}};
ASSERT_OK(OpenClient(&client, 1, &client2));
}
TEST_F(PtyTestCase, ActiveClientCloses) {
Connection server{take_server()};
Connection control_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &control_client));
{
Connection active_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &active_client));
auto result = control_client.MakeActive(1);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
zx::eventpair control_event = GetEvent(&control_client);
zx_signals_t observed = 0;
ASSERT_OK(control_event.wait_one(::llcpp::fuchsia::hardware::pty::SIGNAL_EVENT,
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(::llcpp::fuchsia::device::DEVICE_SIGNAL_HANGUP, zx::time{},
&observed));
ASSERT_EQ(observed, ::llcpp::fuchsia::hardware::pty::SIGNAL_EVENT |
::llcpp::fuchsia::device::DEVICE_SIGNAL_HANGUP);
auto result = control_client.ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, ::llcpp::fuchsia::hardware::pty::EVENT_HANGUP);
}
// Makes sure nothing goes wrong when the active client is the controling
// client and it closes.
TEST_F(PtyTestCase, ActiveClientClosesWhenControl) {
Connection server{take_server()};
{
Connection control_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &control_client));
}
zx::eventpair event = GetEvent(&server);
zx_signals_t observed = 0;
ASSERT_OK(event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_HANGUP, zx::time::infinite(),
&observed));
}
TEST_F(PtyTestCase, ServerClosesWhenClientPresent) {
Connection server{take_server()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &client));
// Write some data to the client, so we can verify the client can drain the
// buffer still.
uint8_t kTestData[] = u8"hello world";
{
auto result =
server.Write(fidl::VectorView<uint8_t>{fidl::unowned_ptr(kTestData), std::size(kTestData)});
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual, std::size(kTestData));
}
server.mutable_channel()->reset();
zx::eventpair event = GetEvent(&client);
zx_signals_t observed = 0;
ASSERT_OK(event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_HANGUP, 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(::llcpp::fuchsia::device::DEVICE_SIGNAL_HANGUP, zx::time{}, &observed));
ASSERT_EQ(observed, ::llcpp::fuchsia::device::DEVICE_SIGNAL_HANGUP |
::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE);
{
auto result = client.ReadEvents();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ASSERT_EQ(result->events, ::llcpp::fuchsia::hardware::pty::EVENT_HANGUP);
}
// Attempts to drain the buffer should succeed
{
// Request more bytes than are present
auto result = client.Read(std::size(kTestData) + 10);
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->data.count(), std::size(kTestData));
ASSERT_BYTES_EQ(result->data.data(), kTestData, std::size(kTestData));
}
// Attempts to read the empty buffer should fail with ZX_ERR_PEER_CLOSED
{
auto result = client.Read(10);
ASSERT_OK(result.status());
ASSERT_STATUS(result->s, ZX_ERR_PEER_CLOSED);
}
// Attempts to write should fail with ZX_ERR_PEER_CLOSED
{
uint8_t data[16] = {};
auto result = client.Write(fidl::VectorView<uint8_t>{fidl::unowned_ptr(data), std::size(data)});
ASSERT_OK(result.status());
ASSERT_STATUS(result->s, 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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
// In cooked mode, client writes should have \n transformed to \r\n, and
// control chars untouched.
uint8_t kTestData[] = u8"hello\x03 world\ntest message\n";
const uint8_t kExpectedReadback[] = u8"hello\x03 world\r\ntest message\r\n";
{
auto result =
client.Write(fidl::VectorView<uint8_t>{fidl::unowned_ptr(kTestData), std::size(kTestData)});
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual, std::size(kTestData));
}
zx::eventpair event = GetEvent(&server);
ASSERT_OK(event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, zx::time::infinite(),
nullptr));
{
auto result = server.Read(std::size(kExpectedReadback) + 10);
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->data.count(), std::size(kExpectedReadback));
ASSERT_BYTES_EQ(result->data.data(), kExpectedReadback, std::size(kExpectedReadback));
}
// Nothing left to read
ASSERT_STATUS(
event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, 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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
// In cooked mode, server writes should have newlines untouched and control
// chars should cause a short write
uint8_t kTestData[] = u8"hello world\ntest\x03 message\n";
// We expect to read this back, but without the trailing nul
const uint8_t kExpectedReadbackWithNul[] = u8"hello world\ntest";
{
auto result =
server.Write(fidl::VectorView<uint8_t>{fidl::unowned_ptr(kTestData), std::size(kTestData)});
ASSERT_OK(result.status());
ASSERT_OK(result->s);
// We expect to see the written count to include the ^C
ASSERT_EQ(result->actual, std::size(kExpectedReadbackWithNul) - 1 + 1);
}
zx::eventpair event = GetEvent(&client);
ASSERT_OK(event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, zx::time::infinite(),
nullptr));
{
auto result = client.Read(std::size(kExpectedReadbackWithNul) + 10);
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->data.count(), std::size(kExpectedReadbackWithNul) - 1);
ASSERT_BYTES_EQ(result->data.data(), kExpectedReadbackWithNul,
std::size(kExpectedReadbackWithNul) - 1);
}
// Nothing left to read
ASSERT_STATUS(
event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, 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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
{
auto result = client.ClrSetFeature(0, ::llcpp::fuchsia::hardware::pty::FEATURE_RAW);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
// In raw mode, client writes should be untouched.
uint8_t kTestData[] = u8"hello\x03 world\ntest message\n";
{
auto result =
client.Write(fidl::VectorView<uint8_t>{fidl::unowned_ptr(kTestData), std::size(kTestData)});
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual, std::size(kTestData));
}
zx::eventpair event = GetEvent(&server);
ASSERT_OK(event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, zx::time::infinite(),
nullptr));
{
auto result = server.Read(std::size(kTestData) + 10);
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->data.count(), std::size(kTestData));
ASSERT_BYTES_EQ(result->data.data(), kTestData, std::size(kTestData));
}
// Nothing left to read
ASSERT_STATUS(
event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, 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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
Connection control_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &control_client));
{
auto result = client.ClrSetFeature(0, ::llcpp::fuchsia::hardware::pty::FEATURE_RAW);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
// In raw mode, server writes should be untouched.
uint8_t kTestData[] = u8"hello world\ntest\x03 message\n";
{
auto result =
server.Write(fidl::VectorView<uint8_t>{fidl::unowned_ptr(kTestData), std::size(kTestData)});
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual, std::size(kTestData));
}
zx::eventpair event = GetEvent(&client);
ASSERT_OK(event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, zx::time::infinite(),
nullptr));
{
auto result = client.Read(std::size(kTestData) + 10);
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->data.count(), std::size(kTestData));
ASSERT_BYTES_EQ(result->data.data(), kTestData, std::size(kTestData));
}
// Nothing left to read
ASSERT_STATUS(
event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, 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()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
zx::eventpair server_event = GetEvent(&server);
zx::eventpair client_event = GetEvent(&client);
uint8_t kTestString[] = "abcdefghijklmnopqrstuvwxyz";
size_t total_written = 0;
while (server_event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_WRITABLE, zx::time{},
nullptr) == ZX_OK) {
auto result = server.Write(
fidl::VectorView<uint8_t>{fidl::unowned_ptr(kTestString), std::size(kTestString) - 1});
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_GT(result->actual, 0);
total_written += result->actual;
}
// Trying to write when full gets SHOULD_WAIT
{
auto result = server.Write(
fidl::VectorView<uint8_t>{fidl::unowned_ptr(kTestString), std::size(kTestString) - 1});
ASSERT_OK(result.status());
ASSERT_STATUS(result->s, 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(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, zx::time{},
nullptr));
auto result = client.Read(std::size(kTestString) - 1);
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->data.count(),
std::min(std::size(kTestString) - 1, total_written - total_read));
ASSERT_BYTES_EQ(result->data.data(), kTestString, result->data.count());
total_read += result->data.count();
}
ASSERT_STATUS(
client_event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
}
TEST_F(PtyTestCase, ClientFillsServerFifo) {
Connection server{take_server()};
Connection client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &client));
zx::eventpair server_event = GetEvent(&server);
zx::eventpair client_event = GetEvent(&client);
uint8_t kTestString[] = "abcdefghijklmnopqrstuvwxyz";
size_t total_written = 0;
while (client_event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_WRITABLE, zx::time{},
nullptr) == ZX_OK) {
auto result = client.Write(
fidl::VectorView<uint8_t>{fidl::unowned_ptr(kTestString), std::size(kTestString) - 1});
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_GT(result->actual, 0);
total_written += result->actual;
}
// Trying to write when full gets SHOULD_WAIT
{
auto result = client.Write(
fidl::VectorView<uint8_t>{fidl::unowned_ptr(kTestString), std::size(kTestString) - 1});
ASSERT_OK(result.status());
ASSERT_STATUS(result->s, 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(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, zx::time{},
nullptr));
auto result = server.Read(std::size(kTestString) - 1);
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->data.count(),
std::min(std::size(kTestString) - 1, total_written - total_read));
ASSERT_BYTES_EQ(result->data.data(), kTestString, result->data.count());
total_read += result->data.count();
}
ASSERT_STATUS(
server_event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
}
TEST_F(PtyTestCase, NonActiveClientsCantWrite) {
Connection server{take_server()};
Connection control_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &control_client));
Connection other_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &other_client));
// control_client is the current active
zx::eventpair event = GetEvent(&other_client);
zx_signals_t observed = 0;
ASSERT_STATUS(event.wait_one(0, zx::time{}, &observed), ZX_ERR_TIMED_OUT);
ASSERT_FALSE(observed & ::llcpp::fuchsia::device::DEVICE_SIGNAL_WRITABLE);
{
uint8_t byte = 0;
auto result = other_client.Write(fidl::VectorView<uint8_t>{fidl::unowned_ptr(&byte), 1});
ASSERT_OK(result.status());
ASSERT_STATUS(result->s, ZX_ERR_SHOULD_WAIT);
}
}
TEST_F(PtyTestCase, ClientsHaveIndependentFifos) {
Connection server{take_server()};
Connection control_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 0, &control_client));
Connection other_client{zx::channel{}};
ASSERT_OK(OpenClient(&server, 1, &other_client));
uint8_t kControlClientByte = 1;
uint8_t kOtherClientByte = 2;
// control_client is the current active, so it should go to its FIFO
{
auto result =
server.Write(fidl::VectorView<uint8_t>{fidl::unowned_ptr(&kControlClientByte), 1});
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual, 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
{
auto result = server.Write(fidl::VectorView<uint8_t>{fidl::unowned_ptr(&kOtherClientByte), 1});
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual, 1);
}
auto check_client = [&](Connection* client, uint8_t expected_value) {
zx::eventpair event = GetEvent(client);
ASSERT_OK(
event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, zx::time{}, nullptr));
auto result = client->Read(10);
ASSERT_OK(result.status());
ASSERT_OK(result->s);
ASSERT_EQ(result->data.count(), 1);
ASSERT_EQ(result->data.data()[0], expected_value);
ASSERT_STATUS(
event.wait_one(::llcpp::fuchsia::device::DEVICE_SIGNAL_READABLE, zx::time{}, nullptr),
ZX_ERR_TIMED_OUT);
};
ASSERT_NO_FATAL_FAILURES(check_client(&other_client, kOtherClientByte));
ASSERT_NO_FATAL_FAILURES(check_client(&control_client, kControlClientByte));
}
} // namespace