mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
net: emulate Windows PEEK and WAITALL receives
Co-authored-by: Firas Ben Mbarek <firasbenmbarek@outlook.com> Co-authored-by: rudy-07 <kanhaiffco2007@gmail.com> Co-authored-by: Matthew Hurley <171001439+theantipopau@users.noreply.github.com> Co-authored-by: usopp-sama <go4av05.pvtx@gmail.com> Co-authored-by: wruwami <wruwami@users.noreply.github.com>
This commit is contained in:
+211
-15
@@ -25,10 +25,12 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <future>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::LibKernelApr {
|
||||
@@ -811,6 +813,210 @@ void CheckAprPaths(const std::filesystem::path &root) {
|
||||
FileSystem::Umount("/app0");
|
||||
}
|
||||
|
||||
std::array<int, 2> CreateTcpPair() {
|
||||
namespace Net = Libs::Network::Net;
|
||||
// Guest sockaddr_in: length, family, network-order port/address, padding.
|
||||
std::array<uint8_t, 16> address {16, 2, 0, 0, 127, 0, 0, 1};
|
||||
const int listener = Net::Socket(2, 1, 0);
|
||||
Check(listener >= 0, "create loopback listener");
|
||||
Check(Net::Bind(listener, address.data(), address.size()) == 0, "bind loopback");
|
||||
Check(Net::Listen(listener, 1) == 0, "listen on loopback");
|
||||
uint32_t address_size = address.size();
|
||||
Check(Net::Getsockname(listener, address.data(), &address_size) == 0,
|
||||
"get assigned loopback port");
|
||||
const int writer = Net::Socket(2, 1, 0);
|
||||
Check(writer >= 0 && Net::Connect(writer, address.data(), address_size) == 0,
|
||||
"connect wake socket");
|
||||
const int reader = Net::Accept(listener, nullptr, nullptr);
|
||||
Check(reader >= 0, "accept wake socket");
|
||||
Check(Net::SocketClose(listener) == 0, "close listener");
|
||||
return {reader, writer};
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
void CheckSocketReceiveConcurrency() {
|
||||
namespace Net = Libs::Network::Net;
|
||||
const auto [reader, writer] = CreateTcpPair();
|
||||
const int timeout_ms = 3000;
|
||||
Check(Net::Setsockopt(reader, 0xffff, 0x1006, &timeout_ms, sizeof(timeout_ms)) == 0,
|
||||
"bound the blocked receive concurrency fixture");
|
||||
std::promise<void> entered;
|
||||
std::promise<int64_t> completed;
|
||||
auto entered_future = entered.get_future();
|
||||
auto completed_future = completed.get_future();
|
||||
std::jthread receiver([&] {
|
||||
char byte = 0;
|
||||
entered.set_value();
|
||||
completed.set_value(Net::Recv(reader, &byte, 1, 0x42));
|
||||
});
|
||||
Check(entered_future.wait_for(std::chrono::seconds(2)) == std::future_status::ready,
|
||||
"start blocked reader");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
char byte = 0;
|
||||
const auto before_nonblocking = std::chrono::steady_clock::now();
|
||||
Check(Net::Recv(reader, &byte, 1, 0x80) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EWOULDBLOCK &&
|
||||
std::chrono::steady_clock::now() - before_nonblocking < std::chrono::milliseconds(500) &&
|
||||
completed_future.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout,
|
||||
"DONTWAIT does not wait behind another blocked reader");
|
||||
const auto before_close = std::chrono::steady_clock::now();
|
||||
Check(Net::SocketClose(reader) == 0, "close succeeds while another thread receives");
|
||||
Check(std::chrono::steady_clock::now() - before_close < std::chrono::seconds(1),
|
||||
"close does not wait for the blocked receive timeout");
|
||||
Check(completed_future.wait_for(std::chrono::seconds(1)) == std::future_status::ready,
|
||||
"close wakes the blocked receive within one second");
|
||||
Check(completed_future.get() <= 0, "closed receive reports EOF or error");
|
||||
Check(std::chrono::steady_clock::now() - before_close < std::chrono::seconds(1),
|
||||
"closing a socket wakes its blocked receive before the receive timeout");
|
||||
receiver.join();
|
||||
Check(Net::SocketClose(writer) == 0, "close blocked receive writer");
|
||||
|
||||
const auto [shutdown_reader, shutdown_writer] = CreateTcpPair();
|
||||
Check(Net::Setsockopt(shutdown_reader, 0xffff, 0x1006, &timeout_ms, sizeof(timeout_ms)) == 0 &&
|
||||
Net::Send(shutdown_writer, "x", 1, 0) == 1 &&
|
||||
Net::Recv(shutdown_reader, &byte, 1, 0x42) == 1 && byte == 'x' &&
|
||||
Net::Shutdown(shutdown_reader, 0) == 0 &&
|
||||
Net::Recv(shutdown_reader, &byte, 1, 0) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_ESHUTDOWN,
|
||||
"receive shutdown rejects bytes retained by an earlier peek");
|
||||
Check(Net::SocketClose(shutdown_reader) == 0 && Net::SocketClose(shutdown_writer) == 0,
|
||||
"close receive shutdown fixture");
|
||||
}
|
||||
|
||||
void CheckSocketReceiveBuffer(int reader, int writer) {
|
||||
namespace Net = Libs::Network::Net;
|
||||
constexpr int PeekWaitAll = 0x42;
|
||||
constexpr char Payload[] = "abcdef";
|
||||
const int timeout_ms = 2000;
|
||||
Check(Net::Setsockopt(reader, 0xffff, 0x1006, &timeout_ms, sizeof(timeout_ms)) == 0,
|
||||
"bound blocking receive regressions with SO_RCVTIMEO");
|
||||
Check(Net::Send(writer, Payload, 2, 0) == 2, "send first TCP fragment");
|
||||
std::jthread sender([&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
Check(Net::Send(writer, Payload + 2, 4, 0) == 4, "send second TCP fragment");
|
||||
});
|
||||
std::array<char, 8> received {};
|
||||
Check(Net::Recv(reader, received.data(), 6, PeekWaitAll) == 6 &&
|
||||
std::memcmp(received.data(), Payload, 6) == 0,
|
||||
"combined peek waits for fragmented TCP bytes");
|
||||
sender.join();
|
||||
received.fill(0);
|
||||
Check(Net::Recv(reader, received.data(), 6, PeekWaitAll) == 6 &&
|
||||
std::memcmp(received.data(), Payload, 6) == 0,
|
||||
"repeated combined peek preserves all bytes");
|
||||
|
||||
const int epoll = Net::EpollCreate("buffered receive", 0);
|
||||
Net::NetEpollEvent registration {};
|
||||
registration.events = 1;
|
||||
registration.data.u64 = 0x12345678;
|
||||
Check(epoll >= 0 && Net::EpollControl(epoll, 1, reader, ®istration) == 0,
|
||||
"register buffered socket readability");
|
||||
const auto check_readable = [&](bool expected) {
|
||||
std::array<uint64_t, 16> readable {};
|
||||
const auto bit = uint64_t {1} << (reader % 64);
|
||||
readable[reader / 64] = bit;
|
||||
const std::array<int64_t, 2> timeout {expected ? 1 : 0, 0};
|
||||
Check(Net::Select(reader + 1, readable.data(), nullptr, nullptr, timeout.data()) ==
|
||||
(expected ? 1 : 0) && readable[reader / 64] == (expected ? bit : 0),
|
||||
"select reflects buffered bytes and their removal");
|
||||
if (expected) {
|
||||
auto writable = readable;
|
||||
Check(Net::Select(reader + 1, readable.data(), writable.data(), nullptr,
|
||||
timeout.data()) == 2 && readable[reader / 64] == bit &&
|
||||
writable[reader / 64] == bit,
|
||||
"select counts readable and writable bits once each");
|
||||
}
|
||||
Net::NetEpollEvent event {};
|
||||
Check(Net::EpollWait(epoll, &event, 1, expected ? 1000000 : 0) ==
|
||||
(expected ? 1 : 0) &&
|
||||
(!expected || (event.events == 1 && event.data.u64 == registration.data.u64)),
|
||||
"epoll reflects buffered bytes and preserves registration data");
|
||||
};
|
||||
check_readable(true);
|
||||
std::array<uint8_t, 16> peer {}, expected_peer {};
|
||||
uint32_t peer_size = peer.size(), expected_peer_size = expected_peer.size();
|
||||
Check(Net::Getsockname(writer, expected_peer.data(), &expected_peer_size) == 0 &&
|
||||
Net::Recv(reader, received.data(), 0, PeekWaitAll) == 0 &&
|
||||
Net::Recvfrom(reader, received.data(), 2, 0, peer.data(), &peer_size) == 2 &&
|
||||
peer_size == expected_peer_size && peer == expected_peer &&
|
||||
std::memcmp(received.data(), Payload, 2) == 0 &&
|
||||
FileSystem::KernelRead(reader, received.data(), 2) == 2 &&
|
||||
std::memcmp(received.data(), Payload + 2, 2) == 0,
|
||||
"zero-length peek preserves bytes; recvfrom returns peer and shares kernel read buffer");
|
||||
check_readable(true);
|
||||
Check(Net::Send(writer, "gh", 2, 0) == 2 &&
|
||||
Net::Recv(reader, received.data(), 4, 0x40) == 4 &&
|
||||
std::memcmp(received.data(), "efgh", 4) == 0,
|
||||
"waitall joins buffered bytes with newly received bytes");
|
||||
check_readable(false);
|
||||
Check(Net::Send(writer, "mn", 2, 0) == 2, "send plain peek fixture");
|
||||
const auto before_peek = std::chrono::steady_clock::now();
|
||||
Check(Net::Recv(reader, received.data(), 4, 2) == 2 &&
|
||||
std::memcmp(received.data(), "mn", 2) == 0 &&
|
||||
std::chrono::steady_clock::now() - before_peek < std::chrono::milliseconds(500) &&
|
||||
Net::Recv(reader, received.data(), 4, 0) == 2 &&
|
||||
std::memcmp(received.data(), "mn", 2) == 0,
|
||||
"plain peek returns available bytes promptly and preserves them for ordinary receive");
|
||||
|
||||
const int enabled = 1, disabled = 0;
|
||||
Check(Net::Setsockopt(reader, 0xffff, 0x1200, &enabled, sizeof(enabled)) == 0 &&
|
||||
Net::Recv(reader, received.data(), 4, PeekWaitAll) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EWOULDBLOCK,
|
||||
"empty nonblocking combined peek returns would-block");
|
||||
Check(Net::Send(writer, "ij", 2, 0) == 2, "send nonblocking receive fixture");
|
||||
check_readable(true);
|
||||
*Libs::Posix::GetErrorAddr() = Libs::Posix::POSIX_EINVAL;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Check(Net::Recv(reader, received.data(), 4, PeekWaitAll) == 2 &&
|
||||
std::memcmp(received.data(), "ij", 2) == 0 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EINVAL,
|
||||
"nonblocking combined peek preserves a short result and successful errno");
|
||||
}
|
||||
Check(Net::Recv(reader, received.data(), 4, 0) == 2 &&
|
||||
std::memcmp(received.data(), "ij", 2) == 0,
|
||||
"ordinary receive consumes a short buffered result");
|
||||
check_readable(false);
|
||||
Check(Net::Setsockopt(reader, 0xffff, 0x1200, &disabled, sizeof(disabled)) == 0 &&
|
||||
Net::EpollDestroy(epoll) == 0,
|
||||
"restore blocking receives and destroy epoll");
|
||||
Check(Net::Recv(reader, received.data(), 4, PeekWaitAll | 0x80) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EWOULDBLOCK,
|
||||
"per-call nonblocking combined peek returns would-block on a blocking socket");
|
||||
Check(Net::Send(writer, "kl", 2, 0) == 2 && Net::Shutdown(writer, 1) == 0,
|
||||
"send a final partial payload and EOF");
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Check(Net::Recv(reader, received.data(), 4, PeekWaitAll) == 2 &&
|
||||
std::memcmp(received.data(), "kl", 2) == 0,
|
||||
"EOF ends waitall while repeated peeks preserve the short payload");
|
||||
}
|
||||
Check(Net::Recv(reader, received.data(), 4, 0x40) == 2 &&
|
||||
std::memcmp(received.data(), "kl", 2) == 0 &&
|
||||
Net::Recv(reader, received.data(), 4, PeekWaitAll) == 0,
|
||||
"draining the final buffered payload exposes EOF");
|
||||
|
||||
const int datagram = Net::Socket(2, 2, 0);
|
||||
const int datagram_writer = Net::Socket(2, 2, 0);
|
||||
std::array<uint8_t, 16> address {16, 2, 0, 0, 127, 0, 0, 1};
|
||||
uint32_t address_size = address.size();
|
||||
Check(datagram >= 0 && datagram_writer >= 0 &&
|
||||
Net::Bind(datagram, address.data(), address.size()) == 0 &&
|
||||
Net::Getsockname(datagram, address.data(), &address_size) == 0 &&
|
||||
Net::Setsockopt(datagram, 0xffff, 0x1006, &timeout_ms, sizeof(timeout_ms)) == 0 &&
|
||||
Net::Sendto(datagram_writer, Payload, 6, 0, address.data(), address_size) == 6,
|
||||
"create bounded UDP truncation fixture");
|
||||
*Libs::Posix::GetErrorAddr() = Libs::Posix::POSIX_EINVAL;
|
||||
Check(Net::Recvfrom(datagram, received.data(), 2, 0, nullptr, nullptr) == 2 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EINVAL &&
|
||||
std::memcmp(received.data(), Payload, 2) == 0 &&
|
||||
Net::Setsockopt(datagram, 0xffff, 0x1200, &enabled, sizeof(enabled)) == 0 &&
|
||||
Net::Recv(datagram, received.data(), received.size(), 0) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EWOULDBLOCK,
|
||||
"UDP truncation returns its prefix and discards the rest of the datagram");
|
||||
Check(Net::SocketClose(datagram) == 0 && Net::SocketClose(datagram_writer) == 0,
|
||||
"close UDP truncation fixture");
|
||||
}
|
||||
#endif
|
||||
|
||||
void CheckSocketWakeup() {
|
||||
namespace Net = Libs::Network::Net;
|
||||
Loader::SymbolDatabase symbols;
|
||||
@@ -829,21 +1035,7 @@ void CheckSocketWakeup() {
|
||||
const auto net_send = reinterpret_cast<Send>(send_symbol->vaddr);
|
||||
const auto net_recv = reinterpret_cast<Recv>(recv_symbol->vaddr);
|
||||
auto *net_errno = reinterpret_cast<Errno>(errno_symbol->vaddr)();
|
||||
// Guest sockaddr_in: length, family, network-order port/address, padding.
|
||||
std::array<uint8_t, 16> address {16, 2, 0, 0, 127, 0, 0, 1};
|
||||
const int listener = Net::Socket(2, 1, 0);
|
||||
Check(listener >= 0, "create loopback listener");
|
||||
Check(Net::Bind(listener, address.data(), address.size()) == 0, "bind loopback");
|
||||
Check(Net::Listen(listener, 1) == 0, "listen on loopback");
|
||||
uint32_t address_size = address.size();
|
||||
Check(Net::Getsockname(listener, address.data(), &address_size) == 0,
|
||||
"get assigned loopback port");
|
||||
const int writer = Net::Socket(2, 1, 0);
|
||||
Check(writer >= 0 && Net::Connect(writer, address.data(), address_size) == 0,
|
||||
"connect wake socket");
|
||||
const int reader = Net::Accept(listener, nullptr, nullptr);
|
||||
Check(reader >= 0, "accept wake socket");
|
||||
Check(Net::SocketClose(listener) == 0, "close listener");
|
||||
const auto [reader, writer] = CreateTcpPair();
|
||||
const int enabled = 1;
|
||||
Check(Net::Setsockopt(writer, 6, 1, &enabled, sizeof(enabled)) == 0,
|
||||
"enable TCP_NODELAY");
|
||||
@@ -911,6 +1103,10 @@ void CheckSocketWakeup() {
|
||||
*net_errno == Libs::Posix::POSIX_EPIPE,
|
||||
"Net send reports a broken pipe without raising host SIGPIPE");
|
||||
Check(Net::SocketClose(disconnected) == 0, "close unconnected socket");
|
||||
#endif
|
||||
#if defined(_WIN32)
|
||||
CheckSocketReceiveBuffer(reader, writer);
|
||||
CheckSocketReceiveConcurrency();
|
||||
#endif
|
||||
Check(Net::SocketClose(writer) == 0, "close wake writer");
|
||||
*net_errno = Libs::Posix::POSIX_EINVAL;
|
||||
|
||||
Reference in New Issue
Block a user