blob: 8ed1cd6d511c02f3fa675560dbfe784890cc93b0 [file] [log] [blame]
// Copyright 2023 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 <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <array>
#include <cstddef>
#include <iostream>
#include <sstream>
#include "src/ui/tests/integration_input_tests/starnix-input/relay-api.h"
#include "third_party/android/platform/bionic/libc/kernel/uapi/linux/input.h"
// Note: This program uses `fprintf()` instead of `std::cerr`, because the latter flushes the writes
// after each token. That flushing causing a single error message to be split over multiple log
// lines.
void fail() {
std::string packet(relay_api::kFailedMessage);
write(STDOUT_FILENO, packet.data(), packet.size());
abort();
}
template <auto F, typename... Args>
auto ensure(size_t caller_lineno, const std::string& callee, Args... args) {
auto res = F(args...);
if (errno != 0) {
fprintf(stderr, "`%s` failed: %s (called from line %zu)", callee.c_str(), strerror(errno),
caller_lineno);
fail();
}
return res;
}
// Invoke `function`, then `abort()` if `errno` is non-zero.
// In case of failure, logs the caller line number and callee name.
#define ENSURE(function, ...) ensure<function>(__LINE__, #function, __VA_ARGS__)
// Asserts that `val1` equals `val2`.
//
// Written as a macro so that the compiler can verify that
// `format` matches the types of `val1` and `val2`.
//
// Evaluates macro parameters into local variables to ensure
// that any expressions are only evaluated once.
#define ASSERT_EQ(val1, val2, format) \
do { \
auto v1 = val1; \
auto v2 = val2; \
auto f = format; \
if (v1 != v2) { \
fprintf(stderr, f, v1, v2); \
fail(); \
} \
} while (0)
// Asserts that `val1` is greater than or equal to `val2`.
//
// Written as a macro so that the compiler can verify that
// `format` matches the types of `val1` and `val2`.
//
// Evaluates macro parameters into local variables to ensure
// that any expressions are only evaluated once.
#define ASSERT_GE(val1, val2, format) \
do { \
auto v1 = val1; \
auto v2 = val2; \
auto f = format; \
if (v1 < v2) { \
fprintf(stderr, f, v1, v2); \
fail(); \
} \
} while (0)
void relay_events(int epoll_fd, size_t num_of_events) {
// To account for variable call counts, we subtract each batch from a
// running count. If we ever read more than the expected amount, something
// has gone wrong.
size_t num_remaining = num_of_events;
constexpr size_t kExpectedEventLen = sizeof(input_event);
constexpr int kMaxEvents = 1;
constexpr int kInfiniteTimeout = -1;
while (num_remaining > 0) {
// Wait for data.
std::array<epoll_event, kMaxEvents> event_buf{};
int n_ready = ENSURE(epoll_wait, epoll_fd, event_buf.data(), kMaxEvents, kInfiniteTimeout);
ASSERT_EQ(kMaxEvents, n_ready, "expected n_ready=%d, but got %d");
ASSERT_EQ(EPOLLIN, event_buf[0].events, "expected events_buf[0].events=%u, but got %u");
// Read the raw data into an `input_event`.
std::array<unsigned char, static_cast<size_t>(64 * 1024)> data_buf{};
ssize_t n_read = ENSURE(read, event_buf[0].data.fd, data_buf.data(), data_buf.size());
size_t num_read = n_read / kExpectedEventLen;
ASSERT_GE(num_remaining, num_read, "expected %zu events at most but got %zu");
num_remaining = num_remaining - num_read;
std::array<char, relay_api::kMaxPacketLen * relay_api::kDownUpNumPackets> text_event_buf{};
size_t total_formatted_len = 0;
for (size_t i = 0; i < num_read; i++) {
struct input_event event{};
memcpy(&event, &data_buf[i * kExpectedEventLen], kExpectedEventLen);
// Format the event as a string.
std::array<char, relay_api::kMaxPacketLen> partial_text_event_buf{};
size_t formatted_len = snprintf(partial_text_event_buf.data(), partial_text_event_buf.size(),
relay_api::kEventFormat, event.time.tv_sec,
event.time.tv_usec, event.type, event.code, event.value);
ASSERT_GE(partial_text_event_buf.size(), formatted_len,
"expected formatted_len < %zu, but got %zu");
memcpy(&text_event_buf[total_formatted_len], partial_text_event_buf.data(), formatted_len);
total_formatted_len = total_formatted_len + formatted_len;
}
// Write the string to `stdout`.
ssize_t n_written = ENSURE(write, STDOUT_FILENO, text_event_buf.data(), total_formatted_len);
ASSERT_EQ(total_formatted_len, static_cast<size_t>(n_written),
"expected n_written=%zu, but got %zd");
}
}
int open_device(int epoll_fd, const std::string& device_path) {
int device_fd = ENSURE(open, device_path.c_str(), O_RDONLY);
epoll_event epoll_params = {.events = EPOLLIN, .data = {.fd = device_fd}};
ENSURE(epoll_ctl, epoll_fd, EPOLL_CTL_ADD, device_fd, &epoll_params);
return device_fd;
}
void close_device(int device_fd, int epoll_fd) {
ENSURE(epoll_ctl, epoll_fd, EPOLL_CTL_DEL, device_fd, nullptr);
ENSURE(close, device_fd);
}
void write_message_to_stdout(const std::string& message) {
auto n_written = ENSURE(write, STDOUT_FILENO, message.data(), message.size());
ASSERT_EQ(message.size(), static_cast<size_t>(n_written), "expected n_written=%zu, but got %zd");
}
constexpr std::string kTouchDevice = "/dev/input/event0";
constexpr std::string kMouseDevice = "/dev/input/event2";
int main() {
int epoll_fd = ENSURE(epoll_create, 1); // Per manual page, must be >0.
char input_buffer[128];
std::string device;
while (true) {
// Let starnix test fixture know that we are ready for an input message.
write_message_to_stdout(relay_api::kWaitForStdinMessage);
std::string cmd;
// Read input from STDIN_FILENO
ssize_t bytes_read = read(STDIN_FILENO, input_buffer, sizeof(input_buffer) - 1);
if (bytes_read <= 0) {
write_message_to_stdout(relay_api::kFailedMessage);
return 0;
}
// Null-terminate the string
input_buffer[bytes_read] = '\0';
std::stringstream ss(input_buffer);
ss >> cmd;
if (cmd == relay_api::kQuitCmd) {
return 0;
}
if (cmd == relay_api::kDeviceDelimiter) {
ss >> device;
if (device == relay_api::kTouchDev) {
device = kTouchDevice;
} else if (device == relay_api::kMouseDev) {
device = kMouseDevice;
}
continue;
}
if (cmd == relay_api::kEventCmd) {
// open the device, handle X events, then close.
size_t num_of_events;
ss >> num_of_events;
int device_fd = open_device(epoll_fd, device);
// Let starnix test fixture know that we're ready for it to inject input events.
write_message_to_stdout(relay_api::kReadyMessage);
// Now just copy events from `evdev` to stdout.
// We expect two taps received across two to four calls to `read`, depending
// on the state of what's been written at the time that we read.
relay_events(epoll_fd, num_of_events);
// Close file.
close_device(device_fd, epoll_fd);
} else {
// Unknown command.
write_message_to_stdout(relay_api::kFailedMessage);
return 0;
}
}
}