Files
AnyPS5/core/libs/tests/GuestPoll.cpp
T

331 lines
17 KiB
C++

#include "prx/libkernel/include/Api.hpp"
#include "prx/libc/include/Api.hpp"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#else
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
#include "prx/libc/include/general/VabiMacros.hpp"
#include "SceTypes.hpp"
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <string>
#include <thread>
#ifdef _WIN32
using NativeSocket = SOCKET;
constexpr NativeSocket NativeInvalid = INVALID_SOCKET;
static void CloseNative(NativeSocket socket) { closesocket(socket); }
#else
using NativeSocket = int;
constexpr NativeSocket NativeInvalid = -1;
static void CloseNative(NativeSocket socket) { close(socket); }
#endif
static void Require(bool value) { if (!value) std::abort(); }
constexpr short In = 0x1;
constexpr short Priority = 0x2;
constexpr short ReadBand = 0x80;
constexpr short Out = 0x4;
constexpr short Hup = 0x10;
constexpr short Invalid = 0x20;
constexpr short ReadNormal = 0x40;
constexpr short WriteBand = 0x100;
constexpr std::int16_t EvfiltUser = -11;
constexpr std::uint16_t EvAdd = 0x0001;
constexpr std::uint16_t EvClear = 0x0020;
constexpr std::uint32_t NoteTrigger = 0x01000000;
static KernelEvent UserEvent(std::uint16_t flags, std::uint32_t fflags) {
KernelEvent change{};
change.ident = 7;
change.filter = EvfiltUser;
change.flags = flags;
change.fflags = fflags;
return change;
}
static void PollsKqueues(int socket) {
const int queue = kqueue_nid_postfix();
Require(queue >= 0);
GuestPollDescriptor kq{queue, static_cast<short>(In | ReadNormal | Out | Priority | ReadBand | WriteBand), -1};
Require(poll_nid_postfix(&kq, 1, 0) == 0 && kq.revents == 0);
const auto added = UserEvent(EvAdd | EvClear, 0);
Require(kevent_nid_postfix(queue, &added, 1, nullptr, 0, nullptr) == 0);
Require(poll_nid_postfix(&kq, 1, 0) == 0 && kq.revents == 0);
const auto start = std::chrono::steady_clock::now();
Require(poll_nid_postfix(&kq, 1, 30) == 0 && kq.revents == 0);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(25));
std::thread trigger([queue] {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
const auto triggered = UserEvent(0, NoteTrigger);
Require(kevent_nid_postfix(queue, &triggered, 1, nullptr, 0, nullptr) == 0);
});
GuestPollDescriptor mixed[2]{{socket, In, -1}, {queue, static_cast<short>(In | Out), -1}};
Require(poll_nid_postfix(mixed, 2, -1) == 1);
trigger.join();
Require(mixed[0].revents == 0 && mixed[1].revents == In);
Require(poll_nid_postfix(&kq, 1, 0) == 1 && kq.revents == (In | ReadNormal));
KernelEvent events[1]{};
const KernelTimespec zero{0, 0};
Require(kevent_nid_postfix(queue, nullptr, 0, events, 1, &zero) == 1);
Require(poll_nid_postfix(&kq, 1, 0) == 0 && kq.revents == 0);
Require(close_nid_postfix(queue) == 0);
Require(poll_nid_postfix(&kq, 1, 0) == 1 && kq.revents == Invalid);
}
static void PollsFilesAndDirectories(int socket) {
constexpr int ReadWrite = 0x0002, Create = 0x0200, DirectoryFlag = 0x00020000;
const auto stamp = std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
const std::string filePath = "anyps5-poll-file-" + stamp;
const std::string directoryPath = "anyps5-poll-directory-" + stamp;
const int file = open_nid_postfix(filePath.c_str(), ReadWrite | Create, 0644);
Require(file >= 0);
Require(mkdir_nid_postfix(directoryPath.c_str(), 0755) == 0);
const int directory = open_nid_postfix(directoryPath.c_str(), DirectoryFlag, 0);
Require(directory >= 0);
GuestPollDescriptor vnode{file, static_cast<short>(In | Out | ReadNormal | Priority | ReadBand | WriteBand), -1};
Require(poll_nid_postfix(&vnode, 1, 0) == 1 && vnode.revents == (In | Out | ReadNormal));
vnode.events = static_cast<short>(Priority | ReadBand | WriteBand);
Require(poll_nid_postfix(&vnode, 1, 0) == 0 && vnode.revents == 0);
const auto start = std::chrono::steady_clock::now();
Require(poll_nid_postfix(&vnode, 1, 30) == 0);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(25));
GuestPollDescriptor folder{directory, static_cast<short>(In | Out), -1};
Require(poll_nid_postfix(&folder, 1, -1) == 1 && folder.revents == (In | Out));
GuestPollDescriptor mixed[3]{{socket, In, -1}, {file, Out, -1}, {directory, ReadNormal, -1}};
Require(poll_nid_postfix(mixed, 3, -1) == 2);
Require(mixed[0].revents == 0 && mixed[1].revents == Out && mixed[2].revents == ReadNormal);
Require(close_nid_postfix(directory) == 0 && close_nid_postfix(file) == 0);
Require(rmdir_nid_postfix(directoryPath.c_str()) == 0 && unlink_nid_postfix(filePath.c_str()) == 0);
}
static void TestPipes(int socket) {
int ends[2];
Require(pipe_nid_postfix(ends) == 0);
GuestPollDescriptor empty[2]{{ends[0], In, -1}, {ends[1], Out, -1}};
Require(poll_nid_postfix(empty, 2, 0) == 1 && empty[0].revents == 0 && empty[1].revents == Out);
GuestPollDescriptor reader{ends[0], In | ReadNormal, -1};
const auto start = std::chrono::steady_clock::now();
Require(poll_nid_postfix(&reader, 1, 50) == 0 && reader.revents == 0);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40));
const auto shortWaitStart = std::chrono::steady_clock::now();
for (int i = 0; i < 20; ++i) Require(poll_nid_postfix(&reader, 1, 2) == 0 && reader.revents == 0);
const auto shortWait = std::chrono::steady_clock::now() - shortWaitStart;
Require(shortWait >= std::chrono::milliseconds(40) && shortWait < std::chrono::milliseconds(200));
Require(write_nid_postfix(ends[1], "wake", 4) == 4);
Require(poll_nid_postfix(&reader, 1, 1000) == 1 && reader.revents == (In | ReadNormal));
GuestPollDescriptor writer{ends[1], In, -1};
Require(poll_nid_postfix(&writer, 1, 0) == 0 && writer.revents == 0);
GuestPollDescriptor readerOut{ends[0], Out, -1};
bool threw = false;
try { poll_nid_postfix(&readerOut, 1, 0); } catch (const std::runtime_error&) { threw = true; }
Require(threw);
GuestPollDescriptor mixed[2]{{socket, In, -1}, {ends[0], In, -1}};
Require(poll_nid_postfix(mixed, 2, 1000) == 1 && mixed[0].revents == 0 && mixed[1].revents == In);
char buffer[8]{};
Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 4);
Require(poll_nid_postfix(mixed, 2, 0) == 0 && mixed[1].revents == 0);
std::thread late([&] {
std::this_thread::sleep_for(std::chrono::milliseconds(30));
Require(write_nid_postfix(ends[1], "x", 1) == 1);
});
Require(poll_nid_postfix(mixed, 2, 2000) == 1 && mixed[1].revents == In);
late.join();
std::thread lateInfinite([&] {
std::this_thread::sleep_for(std::chrono::milliseconds(30));
Require(write_nid_postfix(ends[1], "y", 1) == 1);
});
Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 1);
const auto waitStart = std::chrono::steady_clock::now();
Require(poll_nid_postfix(&reader, 1, -1) == 1 && reader.revents == (In | ReadNormal));
Require(std::chrono::steady_clock::now() - waitStart >= std::chrono::milliseconds(20));
lateInfinite.join();
Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 1);
Require(write_nid_postfix(ends[1], "z", 1) == 1);
Require(close_nid_postfix(ends[1]) == 0);
Require(poll_nid_postfix(&reader, 1, 0) == 1 && reader.revents == (In | ReadNormal | Hup));
Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 1);
Require(poll_nid_postfix(&reader, 1, 0) == 1 && reader.revents == (In | ReadNormal | Hup));
Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 0);
GuestPollDescriptor closedPipe{ends[1], In, -1};
Require(poll_nid_postfix(&closedPipe, 1, 0) == 1 && closedPipe.revents == Invalid);
Require(close_nid_postfix(ends[0]) == 0);
int full[2];
Require(pipe_nid_postfix(full) == 0);
static char block[4096];
std::int64_t queued = 0;
GuestPollDescriptor fill{full[1], Out, -1};
while (poll_nid_postfix(&fill, 1, 0) == 1) {
Require(fill.revents == Out && queued < (1 << 20));
Require(write_nid_postfix(full[1], block, 256) == 256);
queued += 256;
}
Require(queued > 0 && fill.revents == 0);
while (queued > 0) {
const std::int64_t taken = read_nid_postfix(full[0], block, static_cast<std::uint64_t>(std::min<std::int64_t>(queued, sizeof(block))));
Require(taken > 0);
queued -= taken;
}
Require(poll_nid_postfix(&fill, 1, 0) == 1 && fill.revents == Out);
Require(close_nid_postfix(full[0]) == 0);
Require(close_nid_postfix(full[1]) == 0);
int other[2];
Require(pipe_nid_postfix(other) == 0);
Require(close_nid_postfix(other[0]) == 0);
GuestPollDescriptor orphan{other[1], Out, -1};
Require(poll_nid_postfix(&orphan, 1, 0) == 1 && orphan.revents == Hup);
orphan.events = In | Out;
Require(poll_nid_postfix(&orphan, 1, 0) == 1 && orphan.revents == (In | Hup));
Require(close_nid_postfix(other[1]) == 0);
}
static void PollsUrgentData() {
const int listener = socket_nid_postfix(2, 1, 0);
Require(listener >= 0);
std::array<unsigned char, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
Require(bind_nid_postfix(listener, address.data(), address.size()) == 0);
std::uint32_t size = address.size();
Require(getsockname_nid_postfix(listener, address.data(), &size) == 0);
Require(listen_nid_postfix(listener, 4) == 0);
const int client = socket_nid_postfix(2, 1, 0);
Require(client >= 0 && fcntl_nid_postfix(client, 4, 4) == 0);
if (connect_nid_postfix(client, address.data(), address.size()) != 0)
Require(*__error_nid_postfix() == 36);
GuestPollDescriptor connecting{client, Priority | Out, -1};
Require(poll_nid_postfix(&connecting, 1, 1000) == 1 && connecting.revents == Out);
const int server = accept_nid_postfix(listener, nullptr, nullptr);
Require(server >= 0);
GuestPollDescriptor pair[2]{{server, Priority | Out, -1}, {client, In | Priority | Out | ReadBand, -1}};
Require(poll_nid_postfix(pair, 2, 0) == 2 && pair[0].revents == Out && pair[1].revents == Out);
char byte = 'u';
Require(send_nid_postfix(client, &byte, 1, 1) == -1 && *__error_nid_postfix() == 45);
Require(recv_nid_postfix(server, &byte, 1, 1) == -1 && *__error_nid_postfix() == 45);
const NativeSocket peer = ::socket(AF_INET, SOCK_STREAM, 0);
Require(peer != NativeInvalid);
sockaddr_in native{};
native.sin_family = AF_INET;
std::memcpy(&native.sin_port, address.data() + 2, 2);
std::memcpy(&native.sin_addr, address.data() + 4, 4);
Require(::connect(peer, reinterpret_cast<const sockaddr*>(&native), sizeof(native)) == 0);
const int urgent = accept_nid_postfix(listener, nullptr, nullptr);
Require(urgent >= 0);
GuestPollDescriptor waiting{urgent, Priority, -1};
Require(poll_nid_postfix(&waiting, 1, 0) == 0 && waiting.revents == 0);
Require(::send(peer, "u", 1, MSG_OOB) == 1);
Require(poll_nid_postfix(&waiting, 1, 1000) == 1 && waiting.revents == Priority);
GuestPollDescriptor bands{urgent, In | Priority | Out, -1};
Require(poll_nid_postfix(&bands, 1, 0) == 1 && bands.revents == (Priority | Out));
#ifdef _WIN32
GuestPollDescriptor band{urgent, ReadBand, -1};
Require(poll_nid_postfix(&band, 1, 0) == 1 && band.revents == ReadBand);
#endif
CloseNative(peer);
Require(close_nid_postfix(urgent) == 0);
Require(close_nid_postfix(server) == 0);
Require(close_nid_postfix(client) == 0);
Require(close_nid_postfix(listener) == 0);
}
static void PollsAStreamWhosePeerClosed() {
const int listener = socket_nid_postfix(2, 1, 0);
Require(listener >= 0);
std::array<unsigned char, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
Require(bind_nid_postfix(listener, address.data(), address.size()) == 0);
std::uint32_t size = address.size();
Require(getsockname_nid_postfix(listener, address.data(), &size) == 0 && listen_nid_postfix(listener, 4) == 0);
const int client = socket_nid_postfix(2, 1, 0);
Require(client >= 0 && connect_nid_postfix(client, address.data(), address.size()) == 0);
const int server = accept_nid_postfix(listener, nullptr, nullptr);
Require(server >= 0);
Require(shutdown_nid_postfix(client, 1) == 0);
GuestPollDescriptor wait{server, In, 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == In);
wait = {server, static_cast<short>(In | Out), 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == (In | Out));
wait = {server, ReadNormal, 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == ReadNormal);
wait = {server, Out, 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == Out);
char byte;
Require(recv_nid_postfix(server, &byte, 1, 0) == 0);
Require(shutdown_nid_postfix(server, 1) == 0);
wait = {server, In, 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == (In | Hup));
wait = {client, In, 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == (In | Hup));
for (const int descriptor : {client, server, listener}) Require(close_nid_postfix(descriptor) == 0);
}
int main() {
PollsUrgentData();
PollsAStreamWhosePeerClosed();
const int listener = socket_nid_postfix(2, 1, 0);
Require(listener >= 0 && listener < 1024);
std::array<unsigned char, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
Require(bind_nid_postfix(listener, address.data(), address.size()) == 0);
std::uint32_t size = address.size();
Require(getsockname_nid_postfix(listener, address.data(), &size) == 0);
Require(listen_nid_postfix(listener, 4) == 0);
GuestPollDescriptor single{listener, In, -1};
Require(poll_nid_postfix(&single, 1, 0) == 0 && single.revents == 0);
const auto start = std::chrono::steady_clock::now();
Require(poll_nid_postfix(&single, 1, 50) == 0);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40));
const int client = socket_nid_postfix(2, 1, 0);
Require(client >= 0 && connect_nid_postfix(client, address.data(), address.size()) == 0);
Require(poll_nid_postfix(&single, 1, 1000) == 1 && single.revents == In);
const int server = accept_nid_postfix(listener, nullptr, nullptr);
Require(server >= 0);
GuestPollDescriptor pair[3]{{server, In, 0}, {-1, In, 7}, {client, Out, 0}};
Require(poll_nid_postfix(pair, 3, 1000) == 1);
Require(pair[0].revents == 0 && pair[1].revents == 0 && pair[2].revents == Out);
const char message[] = "poll";
Require(send_nid_postfix(client, message, sizeof(message), 0) == sizeof(message));
pair[2].events = In;
Require(poll_nid_postfix(pair, 3, 1000) == 1 && pair[0].revents == In && pair[2].revents == 0);
char buffer[8]{};
Require(recv_nid_postfix(server, buffer, sizeof(buffer), 0) == sizeof(message));
Require(close_nid_postfix(client) == 0);
Require(poll_nid_postfix(pair, 1, 1000) == 1 && (pair[0].revents & (In | Hup)) != 0);
Require(recv_nid_postfix(server, buffer, sizeof(buffer), 0) == 0);
GuestPollDescriptor closed[2]{{client, In, 0}, {listener, In, 0}};
Require(poll_nid_postfix(closed, 2, -1) == 1 && closed[0].revents == Invalid && closed[1].revents == 0);
Require(poll_nid_postfix(nullptr, 0, 10) == 0);
const auto shortWaitStart = std::chrono::steady_clock::now();
for (int i = 0; i < 20; ++i) Require(poll_nid_postfix(nullptr, 0, 1) == 0);
const auto shortWait = std::chrono::steady_clock::now() - shortWaitStart;
Require(shortWait >= std::chrono::milliseconds(20) && shortWait < std::chrono::milliseconds(200));
Require(poll_nid_postfix(nullptr, 1, 0) == -1 && *__error_nid_postfix() == 14);
Require(poll_nid_postfix(&single, 1, -2) == -1 && *__error_nid_postfix() == 22);
TestPipes(listener);
PollsFilesAndDirectories(listener);
Require(close_nid_postfix(server) == 0);
for (const char* path : {"/dev/urandom", "/dev/random"}) {
const int random = open_nid_postfix(path, 0, 0);
Require(random >= 0);
GuestPollDescriptor device{random, static_cast<short>(In | Out | Priority), -1};
Require(poll_nid_postfix(&device, 1, 0) == 1 && device.revents == In);
device.events = static_cast<short>(ReadNormal | Out);
Require(poll_nid_postfix(&device, 1, 0) == 1 && device.revents == ReadNormal);
device.events = static_cast<short>(Out | Priority | ReadBand | WriteBand);
Require(poll_nid_postfix(&device, 1, -1) == 1 && device.revents == (Out | Priority | ReadBand | WriteBand));
GuestPollDescriptor mixed[2]{{listener, In, 0}, {random, Out, 0}};
Require(poll_nid_postfix(mixed, 2, -1) == 1 && mixed[0].revents == 0 && mixed[1].revents == Out);
Require(close_nid_postfix(random) == 0);
}
GuestPollDescriptor console[3]{{0, static_cast<short>(In | ReadNormal | Out), -1}, {1, static_cast<short>(In | Out | WriteBand), -1}, {2, Out, -1}};
Require(poll_nid_postfix(console, 3, 0) == 2);
Require(console[0].revents == 0 && console[1].revents == Out && console[2].revents == Out);
GuestPollDescriptor input{0, In, -1};
const auto inputStart = std::chrono::steady_clock::now();
Require(poll_nid_postfix(&input, 1, 30) == 0 && input.revents == 0);
Require(std::chrono::steady_clock::now() - inputStart >= std::chrono::milliseconds(25));
PollsKqueues(listener);
Require(close_nid_postfix(listener) == 0);
}