libSceNet: implement sceNetSendmsg and sceNetRecvmsg

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adrià Franch 2026-10-06 10:26:36 +02:00
parent 5895ea721a
commit 92d79996c7
3 changed files with 194 additions and 0 deletions

View File

@ -27,6 +27,7 @@
#include <new>
#include <vector>
#include <set>
#include <stdexcept>
#include <string>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
@ -37,6 +38,7 @@ namespace {
// FreeBSD errno numbers as reported through sceNetErrnoLoc (SCE_NET_ERROR_* is 0x80410100 + errno).
constexpr int NET_ENOENT = 2;
constexpr int NET_EBADF = 9;
constexpr int NET_EFAULT = 14;
constexpr int NET_EINVAL = 22;
constexpr int NET_ENOSPC = 28;
constexpr int NET_EAGAIN = 35;
@ -62,6 +64,9 @@ constexpr int NET_SOL_SOCKET = 0xFFFF;
constexpr int NET_SO_SNDTIMEO = 0x1005;
constexpr int NET_SO_RCVTIMEO = 0x1006;
constexpr int NET_SO_NBIO = 0x1200;
constexpr int NET_MSG_PEEK = 0x2;
constexpr int NET_MSG_TRUNC = 0x10;
constexpr int NET_UIO_MAXIOV = 1024;
#ifdef _WIN32
using NativeSocket = SOCKET;
@ -247,6 +252,37 @@ std::uint64_t swap64(std::uint64_t v) {
((v >> 8) & 0xFF000000u) | ((v >> 24) & 0xFF0000u) | ((v >> 40) & 0xFF00u) | (v >> 56);
}
struct NetIovec {
void* base;
std::uint64_t length;
};
struct NetMsghdr {
void* name;
std::uint32_t name_length;
NetIovec* iov;
int iov_length;
void* control;
std::uint32_t control_length;
int flags;
};
static_assert(sizeof(NetMsghdr) == 48 && offsetof(NetMsghdr, iov) == 16 && offsetof(NetMsghdr, control) == 32 &&
offsetof(NetMsghdr, flags) == 44);
std::int64_t message_length(const NetMsghdr* message) {
if (!message) return fail(NET_EFAULT);
if (message->iov_length < 0 || message->iov_length > NET_UIO_MAXIOV) return fail(NET_EMSGSIZE);
if (message->iov_length && !message->iov) return fail(NET_EFAULT);
std::uint64_t total = 0;
for (int i = 0; i < message->iov_length; ++i) {
const auto& entry = message->iov[i];
if (!entry.base && entry.length) return fail(NET_EFAULT);
if (entry.length > INT_MAX - total) return fail(NET_EINVAL);
total += entry.length;
}
return static_cast<std::int64_t>(total);
}
} // namespace
extern "C" {
@ -525,6 +561,70 @@ int64_t APS5_VABI sceNetSendto(int s, const void* buf, size_t len, int flags, co
return result >= 0 ? result : fail(native_error());
}
int64_t APS5_VABI sceNetSendmsg(int s, const NetMsghdr* msg, int flags) {
const auto total = message_length(msg);
if (total < 0) return total;
if (msg->control && msg->control_length) throw std::runtime_error("sceNetSendmsg: control data is not supported");
std::vector<char> buffer;
try {
buffer.resize(static_cast<std::size_t>(total));
} catch (const std::bad_alloc&) {
return fail(55);
}
std::size_t offset = 0;
for (int i = 0; i < msg->iov_length; ++i) {
if (msg->iov[i].length) std::memcpy(buffer.data() + offset, msg->iov[i].base, msg->iov[i].length);
offset += msg->iov[i].length;
}
return sceNetSendto(s, buffer.data(), buffer.size(), flags, msg->name, msg->name ? msg->name_length : 0u);
}
int64_t APS5_VABI sceNetRecvmsg(int s, NetMsghdr* msg, int flags) {
const auto total = message_length(msg);
if (total < 0) return total;
if (flags != 0 && flags != NET_MSG_PEEK) return fail(NET_EOPNOTSUPP);
Sock socket;
{
std::lock_guard<std::mutex> lk(g_mutex);
auto it = g_socks.find(s);
if (it == g_socks.end()) return fail(NET_EBADF);
socket = it->second;
}
const bool datagram = socket.type != NET_SOCK_STREAM;
std::vector<char> buffer;
try {
buffer.resize(static_cast<std::size_t>(total) + (datagram ? 1u : 0u));
} catch (const std::bad_alloc&) {
return fail(55);
}
sockaddr_storage peer{};
NativeLength peer_length = sizeof(peer);
std::int64_t received = ::recvfrom(socket.native->value, buffer.data(), static_cast<int>(buffer.size()),
flags == NET_MSG_PEEK ? MSG_PEEK : 0, reinterpret_cast<sockaddr*>(&peer), &peer_length);
if (received < 0) {
#ifdef _WIN32
if (!datagram || WSAGetLastError() != WSAEMSGSIZE) return fail(native_error());
received = static_cast<std::int64_t>(buffer.size());
#else
return fail(native_error());
#endif
}
msg->flags = 0;
if (received > total) {
received = total;
msg->flags = NET_MSG_TRUNC;
}
std::size_t offset = 0;
for (int i = 0; i < msg->iov_length && offset < static_cast<std::size_t>(received); ++i) {
const auto count = std::min<std::size_t>(msg->iov[i].length, static_cast<std::size_t>(received) - offset);
std::memcpy(msg->iov[i].base, buffer.data() + offset, count);
offset += count;
}
if (msg->name && !native_to_guest_address(peer, msg->name, &msg->name_length)) msg->name_length = 0;
msg->control_length = 0;
return received;
}
int APS5_VABI sceNetShutdown(int s, int how) {
if (how < 0 || how > 2) return fail(NET_EINVAL);
Sock socket;

View File

@ -5,6 +5,7 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <thread>
extern "C" {
@ -36,10 +37,91 @@ const char* APS5_VABI sceNetInetNtop(int, const void*, char*, std::uint32_t);
int* APS5_VABI sceNetErrnoLoc(void);
}
struct NetIovec {
void* base;
std::uint64_t length;
};
struct NetMsghdr {
void* name;
std::uint32_t name_length;
NetIovec* iov;
int iov_length;
void* control;
std::uint32_t control_length;
int flags;
};
extern "C" {
std::int64_t APS5_VABI sceNetSendmsg(int, const NetMsghdr*, int);
std::int64_t APS5_VABI sceNetRecvmsg(int, NetMsghdr*, int);
}
static void Require(bool condition) {
if (!condition) std::abort();
}
static bool Failed(std::int64_t result, int error) {
return result == static_cast<int>(0x80410100u | static_cast<unsigned>(error)) && *sceNetErrnoLoc() == error;
}
static void CheckMessages(int receiver, int sender, const std::array<std::uint8_t, 16>& address) {
char head[] = "scatter ";
char tail[] = "gather";
NetIovec out[] = {{head, 8}, {nullptr, 0}, {tail, 6}};
std::array<std::uint8_t, 16> destination = address;
NetMsghdr send{destination.data(), 16, out, 3, nullptr, 0, 0};
Require(sceNetSendmsg(sender, &send, 0) == 14);
char first[5]{};
char second[20]{};
NetIovec in[] = {{first, sizeof(first)}, {second, sizeof(second)}};
std::array<std::uint8_t, 28> source{};
char control[16]{};
NetMsghdr receive{source.data(), static_cast<std::uint32_t>(source.size()), in, 2, control, sizeof(control), -1};
Require(sceNetRecvmsg(receiver, &receive, 2) == 14);
Require(std::memcmp(first, "scatt", 5) == 0 && std::memcmp(second, "er gather", 9) == 0);
Require(receive.flags == 0 && receive.control_length == 0 && receive.name_length == 16);
Require(source[0] == 16 && source[1] == 2 && source[4] == 127 && source[7] == 1);
std::memset(second, 0, sizeof(second));
receive.iov_length = 1;
receive.flags = -1;
Require(sceNetRecvmsg(receiver, &receive, 0) == 5);
Require(receive.flags == 0x10 && std::memcmp(first, "scatt", 5) == 0 && second[0] == 0);
NetMsghdr bad = send;
Require(Failed(sceNetSendmsg(sender, nullptr, 0), 14));
Require(Failed(sceNetRecvmsg(receiver, nullptr, 0), 14));
bad.iov_length = -1;
Require(Failed(sceNetSendmsg(sender, &bad, 0), 40));
bad.iov_length = 1025;
Require(Failed(sceNetRecvmsg(receiver, &bad, 0), 40));
bad.iov_length = 1;
bad.iov = nullptr;
Require(Failed(sceNetSendmsg(sender, &bad, 0), 14));
NetIovec hole[] = {{nullptr, 1}};
bad.iov = hole;
Require(Failed(sceNetRecvmsg(receiver, &bad, 0), 14));
NetIovec huge[] = {{head, 0x7fffffff}, {tail, 1}};
bad.iov = huge;
bad.iov_length = 2;
Require(Failed(sceNetSendmsg(sender, &bad, 0), 22));
Require(Failed(sceNetRecvmsg(receiver, &receive, 1), 45));
Require(Failed(sceNetSendmsg(sender, &send, 1), 45));
Require(Failed(sceNetRecvmsg(12345, &receive, 0), 9));
Require(Failed(sceNetSendmsg(12345, &send, 0), 9));
NetMsghdr with_control = send;
with_control.control = control;
with_control.control_length = sizeof(control);
bool threw = false;
try {
sceNetSendmsg(sender, &with_control, 0);
} catch (const std::runtime_error&) {
threw = true;
}
Require(threw);
}
static void CheckAddressText(int family, const char* text) {
std::array<std::uint8_t, 16> address{};
Require(sceNetInetPton(family, text, address.data()) == 1);
@ -103,6 +185,16 @@ int main() {
Require(ready_count == 1 && (ready.events & 1) && ready.ident == static_cast<std::uint64_t>(accepted));
Require(sceNetRecv(accepted, response, sizeof(response), 0) == sizeof(response));
Require(std::strcmp(request, response) == 0);
char reply[] = "stream reply";
NetIovec reply_out[] = {{reply, 7}, {reply + 7, sizeof(reply) - 7}};
NetMsghdr reply_send{nullptr, 0, reply_out, 2, nullptr, 0, 0};
Require(sceNetSendmsg(accepted, &reply_send, 0) == sizeof(reply));
char reply_result[sizeof(reply)]{};
NetIovec reply_in[] = {{reply_result, sizeof(reply_result)}};
std::array<std::uint8_t, 16> reply_name{};
NetMsghdr reply_receive{reply_name.data(), 16, reply_in, 1, nullptr, 0, -1};
Require(sceNetRecvmsg(client, &reply_receive, 0) == sizeof(reply));
Require(std::strcmp(reply, reply_result) == 0 && reply_receive.flags == 0 && reply_receive.name_length == 0);
Require(sceNetEpollDestroy(epoll) == 0);
Require(sceNetSocketClose(accepted) == 0);
bool send_failed = false;
@ -130,6 +222,7 @@ int main() {
Require(sceNetRecvfrom(udp_receiver, datagram_result, sizeof(datagram_result), 0,
source.data(), &address_size) == sizeof(datagram_result));
Require(std::strcmp(datagram, datagram_result) == 0 && source[1] == 2);
CheckMessages(udp_receiver, udp_sender, address);
Require(sceNetSocketClose(udp_sender) == 0);
Require(sceNetSocketClose(udp_receiver) == 0);

View File

@ -135,6 +135,7 @@ Throughout the project, every function at every stage either **does exactly what
### Functional
- [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported
- [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out
- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`.
- [MIMG `tfe` and `lwe`](../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, and `lwe` on sample, gather and `image_get_lod` throws.