#include "SceTypes.hpp" #include #include #include #include #include #include #include #include #include #include #include #include struct NetIovec { void* base; std::uint64_t length; }; struct NetMsghdr { void* name; std::uint32_t nameLength; NetIovec* iov; int iovLength; void* control; std::uint32_t controlLength; int flags; }; extern "C" { int APS5_VABI sceNetSocket(const char*, int, int, int); int APS5_VABI sceNetSocketClose(int); int APS5_VABI sceNetSocketAbort(int, int); int APS5_VABI sceNetBind_nid_postfix(int, const void*, std::uint32_t); int APS5_VABI sceNetGetsockname(int, void*, std::uint32_t*); int APS5_VABI sceNetListen(int, int); int APS5_VABI sceNetConnect(int, const void*, std::uint32_t); int APS5_VABI sceNetAccept(int, void*, std::uint32_t*); int APS5_VABI sceNetSetsockopt(int, int, int, const void*, std::uint32_t); int APS5_VABI sceNetGetsockopt(int, int, int, void*, std::uint32_t*); std::int64_t APS5_VABI sceNetSend(int, const void*, std::size_t, int); std::int64_t APS5_VABI sceNetSendto(int, const void*, std::size_t, int, const void*, std::uint32_t); std::int64_t APS5_VABI sceNetSendmsg(int, const NetMsghdr*, int); std::int64_t APS5_VABI sceNetRecv(int, void*, std::size_t, int); std::int64_t APS5_VABI sceNetRecvfrom(int, void*, std::size_t, int, void*, std::uint32_t*); std::int64_t APS5_VABI sceNetRecvmsg(int, NetMsghdr*, int); int* APS5_VABI sceNetErrnoLoc(); int APS5_VABI fcntl_nid_postfix(int, int, ...); } namespace { constexpr int DontWait = 0x80; void Require(bool value, const char* message) { if (value) return; std::fprintf(stderr, "net dontwait: %s\n", message); std::exit(1); } void SetOption(int descriptor, int option, int value) { Require(sceNetSetsockopt(descriptor, 0xffff, option, &value, sizeof(value)) == 0, "set socket option"); } struct Connection { int listener = sceNetSocket("dontwait-listener", 2, 1, 0); int sender = sceNetSocket("dontwait-sender", 2, 1, 0); int receiver = -1; Connection() { Require(listener >= 0 && sender >= 0, "create TCP sockets"); SetOption(listener, 0x1002, 4096); SetOption(sender, 0x1001, 4096); std::array address{16, 2, 0, 0, 127, 0, 0, 1}; Require(sceNetBind_nid_postfix(listener, address.data(), address.size()) == 0, "bind listener"); std::uint32_t size = address.size(); Require(sceNetGetsockname(listener, address.data(), &size) == 0, "get listener address"); Require(sceNetListen(listener, 1) == 0, "listen"); Require(sceNetConnect(sender, address.data(), size) == 0, "blocking connect"); receiver = sceNetAccept(listener, nullptr, nullptr); Require(receiver >= 0, "blocking accept"); SetOption(receiver, 0x1002, 4096); SetOption(receiver, 0x1106, 2000000); } ~Connection() { for (const int descriptor : {receiver, sender, listener}) Require(sceNetSocketClose(descriptor) == 0, "close socket"); } void Abort() { sceNetSocketAbort(sender, 0); sceNetSocketAbort(receiver, 0); } }; std::int64_t Send(std::string_view api, int descriptor, const std::vector& bytes) { if (api == "send" || api == "duplex") return sceNetSend(descriptor, bytes.data(), bytes.size(), DontWait); if (api == "sendto") return sceNetSendto(descriptor, bytes.data(), bytes.size(), DontWait, nullptr, 0); std::array vectors{{{const_cast(bytes.data()), bytes.size() / 2}, {const_cast(bytes.data()) + bytes.size() / 2, bytes.size() - bytes.size() / 2}}}; const NetMsghdr message{nullptr, 0, vectors.data(), static_cast(vectors.size()), nullptr, 0, 0}; return sceNetSendmsg(descriptor, &message, DontWait); } void CheckSend(std::string_view api) { Connection connection; std::vector bytes(16 * 1024 * 1024, 'x'); std::future reading; char reply = 0; if (api == "duplex") { reading = std::async(std::launch::async, [&] { return sceNetRecv(connection.sender, &reply, 1, 0); }); Require(reading.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout, "ordinary receive must wait while a DONTWAIT send runs on the same socket"); } auto sending = std::async(std::launch::async, [&] { return Send(api, connection.sender, bytes); }); const bool completed = sending.wait_for(std::chrono::milliseconds(500)) == std::future_status::ready; if (!completed) connection.Abort(); const auto sent = sending.get(); Require(completed, "MSG_DONTWAIT send blocked behind a slow peer or an ordinary receive"); Require(sent > 0 && sent <= static_cast(bytes.size()), "large send returned an invalid byte count"); int nonblocking = -1; std::uint32_t size = sizeof(nonblocking); Require(sceNetGetsockopt(connection.sender, 0xffff, 0x1200, &nonblocking, &size) == 0 && nonblocking == 0, "DONTWAIT changed the socket option"); Require(fcntl_nid_postfix(connection.sender, 3) == 2, "DONTWAIT changed descriptor flags"); if (reading.valid()) { Require(reading.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout, "DONTWAIT interrupted an ordinary receive"); const char value = 'r'; Require(sceNetSend(connection.receiver, &value, 1, 0) == 1, "send reply"); if (reading.wait_for(std::chrono::seconds(2)) != std::future_status::ready) connection.Abort(); Require(reading.get() == 1 && reply == value, "ordinary receive did not complete with its reply"); } std::int64_t received = 0; SetOption(connection.receiver, 0x1002, 1024 * 1024); std::array buffer{}; while (received < sent) { const auto count = sceNetRecv(connection.receiver, buffer.data(), std::min(buffer.size(), static_cast(sent - received)), 0); Require(count > 0, "reported send bytes were not delivered"); for (std::int64_t index = 0; index < count; ++index) Require(buffer[index] == 'x', "payload changed"); received += count; } Require(sceNetRecv(connection.receiver, buffer.data(), buffer.size(), DontWait) < 0 && *sceNetErrnoLoc() == 35, "send left unreported bytes or receive did not return EAGAIN"); std::printf("%.*s: sent=%lld, delivered=%lld, blocking flags preserved\n", static_cast(api.size()), api.data(), static_cast(sent), static_cast(received)); } std::pair Receive(std::string_view api, int descriptor) { char byte = 0; std::int64_t result; if (api == "recv") result = sceNetRecv(descriptor, &byte, 1, DontWait); else if (api == "recvfrom") result = sceNetRecvfrom(descriptor, &byte, 1, DontWait, nullptr, nullptr); else { NetIovec vector{&byte, 1}; NetMsghdr message{nullptr, 0, &vector, 1, nullptr, 0, 0}; result = sceNetRecvmsg(descriptor, &message, DontWait); } if (result == 1) Require(byte == 'x', "received payload changed"); return {result, *sceNetErrnoLoc()}; } void CheckReceive(std::string_view api) { Connection connection; SetOption(connection.receiver, 0x1106, 0); constexpr int readers = 8; for (int iteration = 0; iteration < 32; ++iteration) { const char byte = 'x'; Require(sceNetSend(connection.sender, &byte, 1, 0) == 1, "send race payload"); std::barrier start(readers + 1); std::vector>> receiving; for (int index = 0; index < readers; ++index) receiving.push_back(std::async(std::launch::async, [&] { start.arrive_and_wait(); return Receive(api, connection.receiver); })); start.arrive_and_wait(); int successes = 0; for (auto& future : receiving) { const bool completed = future.wait_for(std::chrono::milliseconds(500)) == std::future_status::ready; if (!completed) connection.Abort(); const auto [result, error] = future.get(); Require(completed, "MSG_DONTWAIT receive blocked after another reader consumed the byte"); if (result == 1) ++successes; else Require(result < 0 && error == 35, "empty receive must return EAGAIN"); } Require(successes == 1, "one byte must be delivered to exactly one reader"); } std::printf("%.*s: 32 races, one successful reader and seven EAGAIN results each\n", static_cast(api.size()), api.data()); } void CheckBlocking() { const int listener = sceNetSocket("blocking-listener", 2, 1, 0); const int sender = sceNetSocket("blocking-sender", 2, 1, 0); Require(listener >= 0 && sender >= 0, "create blocking sockets"); std::array address{16, 2, 0, 0, 127, 0, 0, 1}; Require(sceNetBind_nid_postfix(listener, address.data(), address.size()) == 0, "bind blocking listener"); std::uint32_t size = address.size(); Require(sceNetGetsockname(listener, address.data(), &size) == 0, "get blocking listener address"); Require(sceNetListen(listener, 1) == 0, "listen before accept"); auto accepting = std::async(std::launch::async, [&] { return sceNetAccept(listener, nullptr, nullptr); }); Require(accepting.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout, "accept did not wait for a connection"); Require(sceNetConnect(sender, address.data(), size) == 0, "connect to waiting accept"); if (accepting.wait_for(std::chrono::seconds(2)) != std::future_status::ready) sceNetSocketAbort(listener, 0); const int receiver = accepting.get(); Require(receiver >= 0, "waiting accept failed"); SetOption(receiver, 0x1200, 1); char byte = 0; Require(sceNetRecv(receiver, &byte, 1, 0) < 0 && *sceNetErrnoLoc() == 35, "socket nonblocking option did not affect receive"); SetOption(receiver, 0x1200, 0); auto receiving = std::async(std::launch::async, [&] { return sceNetRecv(receiver, &byte, 1, 0); }); Require(receiving.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout, "blocking receive did not wait"); Require(sceNetSocketClose(receiver) == 0, "close a socket with a waiting receive"); Require(receiving.wait_for(std::chrono::seconds(2)) == std::future_status::ready, "close did not wake the receive"); const auto closedResult = receiving.get(); std::fprintf(stderr, "closed receive result=%lld\n", static_cast(closedResult)); Require(closedResult <= 0, "closed receive returned data that was never sent"); Require(sceNetSocketClose(sender) == 0 && sceNetSocketClose(listener) == 0, "close blocking fixture"); std::puts("blocking: accept waited, socket mode toggled, close woke the pending receive"); } } int main(int argc, char** argv) { Require(argc == 2, "one API argument required"); const std::string_view api(argv[1]); if (api == "blocking") { CheckBlocking(); return 0; } if (api == "send" || api == "sendto" || api == "sendmsg" || api == "duplex") CheckSend(api); else { Require(api == "recv" || api == "recvfrom" || api == "recvmsg", "unknown API"); CheckReceive(api); } }