Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion core/libs/prx/libSceNet/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,16 @@ if(BUILD_TESTING)
target_link_libraries(guest_sce_net_tests PRIVATE libSceNet libSceNetCtl libkernel libc)
configure_windows_unwind(guest_sce_net_tests)
add_test(NAME guest_sce_net COMMAND guest_sce_net_tests)
add_test_executable(guest_net_thread_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetThread.cpp)
target_include_directories(guest_net_thread_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_net_thread_tests PRIVATE libSceNet libkernel libc)
configure_windows_unwind(guest_net_thread_tests)
add_test(NAME guest_net_thread COMMAND guest_net_thread_tests)
set_tests_properties(guest_net_thread PROPERTIES TIMEOUT 20)
set_tests_properties(guest_sce_net PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77)
if(WIN32)
target_link_libraries(guest_sce_net_tests PRIVATE ws2_32)
set_tests_properties(guest_sce_net PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(guest_sce_net guest_net_thread PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNet>")
endif()
add_test_executable(net_epoll_lifetime_tests ${LIBS_INCLUDE_DIR}/tests/NetEpollLifetime.cpp)
Expand Down
41 changes: 28 additions & 13 deletions core/libs/prx/libSceNet/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp"
#include "src/ResolverTestHooks.hpp"

// Guest socket descriptors retain PS5 semantics while their transport is backed by host sockets.
Expand Down Expand Up @@ -96,7 +97,12 @@ struct NetDataTraffic;
struct NetEmulationParameter;

struct NetResolverConnection;
struct NetThreadParameter;
struct NetThreadParameter {
void (APS5_VABI *entry)(void*);
void* arg;
std::uint64_t reserved;
std::uint32_t flags;
};
struct NetIpv4InterfaceAddress;
struct NetStatisticsInfoInternal;
struct NetInitializeParameter;
Expand Down Expand Up @@ -499,6 +505,15 @@ int fail(int err) {
return NET_ERROR_BASE | err;
}

extern "C" int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
extern "C" int APS5_VABI scePthreadJoin(Pthread thread, void** retval);

void* APS5_VABI RunNetThread(void* param) {
const auto* thread = static_cast<const NetThreadParameter*>(param);
thread->entry(thread->arg);
return nullptr;
}

int set_native_timeout(NativeSocket native, int optname, std::int64_t microseconds) {
const int native_option = optname == NET_SO_RCVTIMEO ? SO_RCVTIMEO : SO_SNDTIMEO;
#ifdef _WIN32
Expand Down Expand Up @@ -2538,18 +2553,20 @@ int APS5_VABI sceNetSysctl(const int* name, std::uint32_t nameLength, void* oldV
}

int APS5_VABI sceNetThreadCreate(Pthread* thread, const NetThreadParameter* parameter, const char* name) {
NotImplemented_nid_no_patch(__func__);
if (!parameter) return fail(NET_EINVAL);
Pthread created = nullptr;
const int result = scePthreadCreate(&created, nullptr, RunNetThread, const_cast<NetThreadParameter*>(parameter), name != nullptr ? name : "");
if (result != 0) return result;
if (thread) *thread = created;
return 0;
}

int APS5_VABI sceNetThreadExit(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
void APS5_VABI sceNetThreadExit() {
scePthreadExit(nullptr);
}

int APS5_VABI sceNetThreadJoin(void* thread) {
NotImplemented_nid_no_patch(__func__);
return 0;
return scePthreadJoin(static_cast<Pthread>(thread), nullptr);
}

int APS5_VABI sceNetUsleep(int microseconds) {
Expand Down Expand Up @@ -3105,14 +3122,12 @@ int APS5_VABI sceNetEmulationDebugSettingsGet(NetEmulationParameter* parameter,
return 0;
}

int APS5_VABI sceNetEmulationSet(const NetEmulationParameter* parameter, std::uint32_t flags) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNetEmulationSet(const NetEmulationParameter*, std::uint32_t) {
return fail(NET_EOPNOTSUPP);
}

int APS5_VABI sceNetEmulationGet(NetEmulationParameter* parameter, std::uint32_t flags) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNetEmulationGet(NetEmulationParameter*, std::uint32_t) {
return fail(NET_EOPNOTSUPP);
}

}
86 changes: 86 additions & 0 deletions core/libs/tests/GuestNetThread.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#include "SceTypes.hpp"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <thread>

extern "C" {
int APS5_VABI sceNetThreadCreate(Pthread*, void*, const char*);
void APS5_VABI sceNetThreadExit();
int APS5_VABI sceNetThreadJoin(Pthread);
int APS5_VABI sceNetEmulationGet();
int APS5_VABI sceNetEmulationSet();
int* APS5_VABI sceNetErrnoLoc();
}

namespace {

constexpr int Einval = 22;
constexpr int Eopnotsupp = 45;
constexpr int NetError = static_cast<int>(0x80410100u);

struct ThreadParam {
void (APS5_VABI *entry)(void*);
void* arg;
std::uint64_t reserved;
std::uint32_t flags;
};

struct Run {
std::atomic<int> started{0};
std::atomic<bool> pastExit{false};
};

Run run;

void Require(bool value, const char* what) {
if (value) return;
std::fprintf(stderr, "%s\n", what);
std::abort();
}

void APS5_VABI Count(void* arg) {
++static_cast<Run*>(arg)->started;
}

void APS5_VABI ExitEarly(void* arg) {
auto* run = static_cast<Run*>(arg);
++run->started;
sceNetThreadExit();
run->pastExit = true;
}

}

int main() {
ThreadParam counting{Count, &run, 0, 0};
Pthread thread = nullptr;
Require(sceNetThreadCreate(&thread, &counting, "anyps5-net-thread") == 0 && thread != nullptr, "sceNetThreadCreate failed");
Require(sceNetThreadJoin(thread) == 0, "sceNetThreadJoin failed");
Require(run.started == 1, "the thread did not run its entry with its argument");

Pthread untouched = reinterpret_cast<Pthread>(&run);
*sceNetErrnoLoc() = 0;
Require(sceNetThreadCreate(&untouched, nullptr, "anyps5-net-thread") == (NetError | Einval), "a null parameter was not refused with EINVAL");
Require(*sceNetErrnoLoc() == Einval && untouched == reinterpret_cast<Pthread>(&run), "a refused create set no errno or wrote the thread");

ThreadParam exiting{ExitEarly, &run, 0, 1};
Require(sceNetThreadCreate(&thread, &exiting, nullptr) == 0, "sceNetThreadCreate failed without a name");
Require(sceNetThreadJoin(thread) == 0, "joining an exited thread failed");
Require(run.started == 2 && !run.pastExit, "sceNetThreadExit did not end the thread");

static ThreadParam detached{Count, &run, 0, 0};
Require(sceNetThreadCreate(nullptr, &detached, "anyps5-net-thread") == 0, "a null thread output was refused");
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
while (run.started != 3 && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1));
Require(run.started == 3, "the thread created without an output did not run");

*sceNetErrnoLoc() = 0;
Require(sceNetEmulationGet() == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationGet did not return EOPNOTSUPP");
*sceNetErrnoLoc() = 0;
Require(sceNetEmulationSet() == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationSet did not return EOPNOTSUPP");
std::puts("net thread tests passed");
return 0;
}
4 changes: 2 additions & 2 deletions docs/dev/debt/libSceNet.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@

- [sceNetConfigAddIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - disassembly confirms the same interface/address/broadcast/netmask arguments as SetIfaddr, entering its shared handler with add mode selected. GetIfaddr copies up to two 16-byte interface-address records. GetRouteInfo uses a family, record buffer, signed capacity and byte flags; internal statistics output is 16 bytes. The address-record and internal-statistics fields remain incomplete.

- [Resolver-connect, thread creation, interface and diagnostic queries, WLAN configuration and DHCP queries](../../../core/libs/prx/libSceNet/Export.cpp) retain unsupported implementations. Their restored argument lists still reference incomplete connection, thread, interface, route, WLAN and statistics types. Kernel ioctl payloads need reconstruction from their handlers or consumers; buffer lengths alone do not establish field types.
- [Resolver-connect, interface and diagnostic queries, WLAN configuration and DHCP queries](../../../core/libs/prx/libSceNet/Export.cpp) retain unsupported implementations. Their restored argument lists still reference incomplete connection, interface, route, WLAN and statistics types. Kernel ioctl payloads need reconstruction from their handlers or consumers; buffer lengths alone do not establish field types.
- [sceNetThreadCreate, sceNetThreadExit, sceNetThreadJoin, sceNetEmulationGet and sceNetEmulationSet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the signatures, the `{ entry, arg, reserved, flags }` parameter block and the `EOPNOTSUPP` of the two emulation calls ("not available on retail units") follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/net/net.cpp) (PS4), not a PS5 measurement. The block is read on the new thread, so it must stay valid until the thread starts, `flags` is ignored (shadPS4 ends the thread with `pthread_exit` when bit 0 is set, which ends it the same way), and a failing `scePthreadCreate` or `scePthreadJoin` result is returned as it is, where shadPS4 also sets the net errno from it
- [sceHttp2GetMemoryPoolStats, sceSslGetMemoryPoolStats, sceNetGetMemoryPoolStats](../../../core/libs/prx/libSceNet/Export.cpp) (libSceHttp2, libSceSsl, libSceNet) - the signatures `(int id, stats*)`, the layouts (pool size, peak and current use, and for Http2 and Ssl a 32-bit reserved field) and the error codes were measured on a PS5 (firmware 13.60). The pool size is the one given at init or pool creation. Http2 and Net allocate nothing from these pools. Ssl counts its certificate backend allocations and returned data against its pool; C++ bookkeeping is outside that budget. All three initially report zero use, where the console already reports 0x15C0 bytes for Ssl, 0x1640 for Http2 and 4 for a Net pool. Ssl's allocation sizes and peak usage do not reproduce the console allocator
- [sceNetClearDnsCache](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the library has no DNS cache to clear because lookups use the host resolver. The host resolver cache is left unchanged.
- [sceNetEtherStrton](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - strings shorter than 17 bytes return EINVAL without changing the output, rather than reading past the terminating NUL as in the [firmware 13.60 probe](https://gist.github.com/orezraey/789290577123af3c45b340d6db6f395c/71e0a6215c7ee86f49fdb31de989dd2b50720b7a). Non-ASCII digit bytes throw because their decoding is unmeasured; other firmware versions remain unverified. Fixed character pairs, ignored separators, alphabetic values beyond f, non-alphanumeric ASCII values of 0x7f, and null-pointer EINVAL follow those measurements, not the strict [shadPS4 parser](https://github.com/shadps4-emu/shadPS4/blob/d615b3887ba29025a59242e1a26f5b68bb176158/src/core/libraries/net/net.cpp#L1274-L1297)
- [sceNetGetDnsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(info, int flags)` signature, the two IPv4 server slots in network byte order and the server count as the return value follow the PS4 [OpenOrbis musl resolver](https://github.com/OpenOrbis/musl/blob/2b0bb8ce2fa64ac255cb656785729d159a5f074c/src/network/resolvconf.c) and [OpenOrbis Net.h](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9dd4a92695538bdeb09fb056d06dd11725/include/orbis/Net.h); unverified on PS5 hardware. It reports the host DNS servers of the interface `sceNetCtlGetInfo` reports, never servers set through `sceNetSetDnsInfo`. Zeroing the unused slots and returning 0 without a host IPv4 interface are unverified. Nonzero flags and a null info throw because their console results are unknown
- [sceNetGetRandom](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - generates values with the host random device; the system library maintains its own seeded PRNG state.
- [sceNetInetPtonEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(int af, const char* src, void* dst, int flags)` signature follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp); IPv4 conversion and the ignored flags follow PS4 firmware 10.71, not yet confirmed on PS5. There IPv4 text is read as `inet_aton` reads it (`a`, `a.b`, `a.b.c`, hex and octal parts, values wrapping at 32 bits, the text ending at the first space, tab or VT), except that a part starting with `09` is decimal (`"0989"` is 989) and an octal part with `8` or `9` fails. Flags 0, each single bit up to 0x100, 0x10000, 0x80000000 and -1 gave the same results; other values were not measured. LF, FF and CR as terminators, and `NET_EINVAL` for a null `src` or `dst`, follow `sceNetInetPton` and were not measured for this function. IPv6 converts as `sceNetInetPton` does.
- [sceNetMemoryAllocate and sceNetMemoryFree](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - allocations use the guest heap instead of the system network pool. Pool limits, its allocation accounting and free-time fill are not reproduced: a reused block allocated without flag 2 holds 0xdd bytes on the console and its previous contents here. Flag 4 bypasses the system pool reserve threshold; with no pool there is no threshold, so it allocates as other flags do. What the console returns once the pool is exhausted is unmeasured; AnyPS5 returns null without setting an errno. Measured on a PS5 (firmware 13.60): a size of 0 or less returns null and leaves `sceNetErrnoLoc` unchanged, flag 2 zero-fills, flags 1, 4, 8, 0x10, 0x100 and 0x80000000 had no visible effect, and every block of 1 to 65536 bytes was 64-byte aligned
- [sceNetThreadExit](../../../core/libs/prx/libSceNet/Export.cpp) is a zero-argument, non-returning void export in the native module. The protected host stub still declares int and contains `return 0`; changing it to void requires changing that protected body, so the return-type mismatch is retained.
- [sceNetConfigCleanUpAllInterfaces](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the native routine consumes no arguments; the throwing implementation remains unchanged.
- [sceNetDhcpdStart](../../../core/libs/prx/libSceNet/Export.cpp): six arguments and the three-argument address callback are confirmed. The 24-byte parameter contains five network-order addresses and a client limit. Individual address roles and the two unused thread-option meanings remain unresolved.
- [sceNetPppoeStart](../../../core/libs/prx/libSceNet/Export.cpp): seven arguments are confirmed, including the parameter pointer on the stack. The 272-byte parameter contains two DNS addresses, two 128-byte credential arrays, MTU and flags. Callback arguments are user data, event and error; the two unused thread-option meanings remain unresolved.
Expand Down
Loading