Merge pull request #10954 from AZLisme/feat/batch2-rudp-empty-poll

test(libSceRudp): cover empty poll wait behavior
This commit is contained in:
Dean authored and GitHub committed 2026-10-27 08:35:26 +13:00
commit e0b13f2990
3 files changed
+116 -2

No files matched your search

+13
View File
@@ -4,3 +4,16 @@ add_library(libSceRudp SHARED EXCLUDE_FROM_ALL
)
target_include_directories(libSceRudp PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceRudp)
if(BUILD_TESTING)
add_test_executable(guest_rudp_empty_poll_tests ${LIBS_INCLUDE_DIR}/tests/GuestRudpEmptyPoll.cpp)
target_include_directories(guest_rudp_empty_poll_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_rudp_empty_poll_tests PRIVATE libSceRudp libkernel libc)
configure_windows_unwind(guest_rudp_empty_poll_tests)
add_test(NAME guest_rudp_empty_poll COMMAND guest_rudp_empty_poll_tests)
set_tests_properties(guest_rudp_empty_poll PROPERTIES TIMEOUT 10)
if(WIN32)
set_tests_properties(guest_rudp_empty_poll PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceRudp>")
endif()
endif()
+100
View File
@@ -0,0 +1,100 @@
#include "prx/libSceRudp/libSceRudp.hpp"
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <future>
#include <limits>
#include <source_location>
#include <thread>
static void Require(bool condition, std::source_location location = std::source_location::current()) {
if (condition) return;
std::fprintf(stderr, "empty poll check failed at line %u\n", location.line());
std::abort();
}
static void APS5_VABI Handler(int, int, int, void*) {}
static void RequireUntouched(const RudpPollEvent& event) {
Require(event.contextId == 0x12345678 && event.events == 0x1234 && event.returnedEvents == 0x5678);
}
static void CheckTimeout(int poll, std::uint64_t timeout) {
RudpPollEvent event{0x12345678, 0x1234, 0x5678};
struct Result {
int code;
std::chrono::steady_clock::duration elapsed;
};
std::promise<Result> result;
auto completed = result.get_future();
std::thread waiter([&] {
const auto start = std::chrono::steady_clock::now();
const int code = sceRudpPollWait(poll, &event, 1, timeout);
result.set_value({code, std::chrono::steady_clock::now() - start});
});
const bool ready = completed.wait_for(std::chrono::seconds(2)) == std::future_status::ready;
if (!ready) sceRudpPollDestroy(poll);
waiter.join();
Require(ready);
const auto value = completed.get();
Require(value.code == 0 && value.elapsed >= std::chrono::microseconds(timeout));
RequireUntouched(event);
}
static void CheckInfiniteWait() {
const int poll = sceRudpPollCreate(1);
Require(poll > 0);
RudpPollEvent event{0x12345678, 0x1234, 0x5678};
std::promise<void> started;
auto entered = started.get_future();
std::promise<void> result;
auto completed = result.get_future();
std::thread waiter([&] {
started.set_value();
sceRudpPollWait(poll, &event, 1, std::numeric_limits<std::uint64_t>::max());
result.set_value();
});
entered.wait();
const bool blocked = completed.wait_for(std::chrono::milliseconds(20)) == std::future_status::timeout;
sceRudpPollDestroy(poll);
const bool woken = completed.wait_for(std::chrono::seconds(2)) == std::future_status::ready;
if (!woken) sceRudpEnd();
waiter.join();
Require(blocked && woken);
RequireUntouched(event);
}
int main() {
constexpr int notInitialized = static_cast<int>(0x80770001u);
constexpr int invalidArgument = static_cast<int>(0x80770004u);
constexpr int invalidPoll = static_cast<int>(0x8077001fu);
alignas(16) static std::array<unsigned char, 0x10000> pool{};
RudpPollEvent event{0x12345678, 0x1234, 0x5678};
Require(sceRudpPollWait(-1, &event, 1, 0) == notInitialized);
Require(sceRudpPollWait(-1, nullptr, 0, 0) == notInitialized);
RequireUntouched(event);
Require(sceRudpInit_nid_postfix(pool.data(), pool.size()) == 0);
const int poll = sceRudpPollCreate(1);
Require(poll > 0);
Require(sceRudpPollWait(-1, &event, 1, 0) == invalidArgument);
Require(sceRudpPollWait(12345, nullptr, 1, 0) == invalidArgument);
Require(sceRudpPollWait(12345, &event, 0, 0) == invalidArgument);
Require(sceRudpPollWait(12345, &event, 1, 0) == invalidPoll);
RequireUntouched(event);
CheckTimeout(poll, 0);
CheckTimeout(poll, 20000);
CheckTimeout(poll, 50000);
int context = -1;
Require(sceRudpCreateContext(Handler, nullptr, &context) == 0);
Require(sceRudpPollControl(poll, 1, context, 1) == 0);
Require(sceRudpPollControl(poll, 3, context, 0) == 0);
CheckTimeout(poll, 20000);
Require(sceRudpPollControl(poll, 1, context, 1) == 0);
Require(sceRudpTerminate(context) == 0);
CheckTimeout(poll, 20000);
Require(sceRudpPollDestroy(poll) == 0);
CheckInfiniteWait();
Require(sceRudpEnd() == 0);
}
+3 -2
View File
@@ -11,5 +11,6 @@
- [sceRudpGetStatus](../../../core/libs/prx/libSceRudp/Export.cpp) - PPSA01521 passes an output pointer and byte count `0xF8`. Measured on a PS5 (firmware 13.60): the status is 0xF8 bytes; before `sceRudpInit` every call returns `RUDP_ERROR_NOT_INITIALIZED`, and after it a null pointer or a byte count of 0 or above 0xF8 returns `RUDP_ERROR_INVALID_ARGUMENT`. The layout matches `SceRudpStatus` in [Vita3K](https://github.com/AndnixSH/Vita3K-PS-Vita-OS-Fork/blob/master/vita3k/modules/SceRudp/SceLibRudp.cpp) with `currentContexts` at 0x64 and `allocs`, `frees`, `memCurrent` and `memPeak` at 0xC8 to 0xD4. Only `currentContexts` is reported: the traffic counters stay 0 because nothing is sent, and the allocator counters stay 0 because the library does not allocate from the title's pool (the console reports 6 allocations and 0x1260 bytes right after `sceRudpInit`, and 4 more allocations and 0x3F0 bytes per context). The console also writes `currentContexts` and the allocator counters when the byte count is smaller than their offset; only the requested bytes are written here.
- [sceRudpGetMaxSegmentSize](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - measured on a PS5 (firmware 13.60) in #3073: before `sceRudpInit` it returns `RUDP_ERROR_NOT_INITIALIZED` and leaves the value untouched, also for a null pointer; after it a null pointer returns `RUDP_ERROR_INVALID_ARGUMENT` and a valid one gets 1410 with result 0. The library keeps no configurable segment size (there is no `sceRudpSetMaxSegmentSize`), so 1410 is always reported.
- [sceRudpGetOption, sceRudpSetOption](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - full PS5 option contracts are unverified; defaults and validation follow [Vita3K](https://github.com/AndnixSH/Vita3K-PS-Vita-OS-Fork/blob/c86ac732bc811057e690bed280c5076bdf95385e/vita3k/modules/SceRudp/SceLibRudp.cpp). Options 12 and 13 have unknown meanings; last-error option 14 is not maintained. Their setters store all 32 bits per context and return 0 for valid four-byte buffers, preserving acceptance on `main`; their getters throw. Static inspection of PPSA06888 v01.001.008's bundled Unity transport shows its context-set constructor configuring IDs 2, 3, 5 and 6; runtime usage and other paths remain unverified. The plugin wrappers pass four-byte values. No context can connect, so stored settings cannot affect traffic. Closes when: PS5 option contracts are verified and the remaining behavior is implemented.
- [sceRudpPollCreate, sceRudpPollControl, sceRudpPollWait, sceRudpPollDestroy](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - unverified on a PS5; imported by PPSA31327 and not called before the main menu. Signatures and the 8-byte event layout are recovered from the supplied module. The operations (1 add, 2 modify, 3 remove) and the argument checks follow the PS Vita library in [Vita3K](https://github.com/AndnixSH/Vita3K-PS-Vita-OS-Fork/blob/master/vita3k/modules/SceRudp/SceLibRudp.cpp), and the error codes match [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellRudp.h). Vita3K leaves `sceRudpPollDestroy` unimplemented, so `int sceRudpPollDestroy(int pollId)` and its checks are inferred. No context can connect, so no event is ever reported: `sceRudpPollWait` returns 0 when its timeout elapses, also for an empty poll, where Vita3K returns at once
- [sceRudpAccept, sceRudpFlush, sceRudpGetLocalInfo, sceRudpListen, sceRudpPollCancel and sceRudpSetMaxSegmentSize](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceRudp.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim.
- [sceRudpPollCreate, sceRudpPollControl, sceRudpPollWait, sceRudpPollDestroy](../../../core/libs/prx/libSceRudp/Export.cpp) - console hardware behavior and removal of the last entry during an existing wait remain unverified. A [firmware 13.42 report](https://github.com/boykopovar/AnyPS5/pull/10954#issuecomment-6398457761) executes the actual module under emulation with kernel/libc imports stubbed and condition waits scripted, not on console hardware: an empty poll waits for its remaining timeout, including after spurious wakes, and leaves event output untouched. This agrees with the retained wait behavior here and conflicts with the immediate empty return in [Vita3K](https://github.com/AndnixSH/Vita3K-PS-Vita-OS-Fork/blob/c86ac732bc811057e690bed280c5076bdf95385e/vita3k/modules/SceRudp/SceLibRudp.cpp). The argument checks, control operations and 8-byte event layout retain their existing sources; the firmware report has not been independently reproduced here.
- [sceRudpPollCancel, sceRudpPollDestroy and sceRudpEnd](../../../core/libs/prx/libSceRudp/Export.cpp) - cancellation remains incomplete. PollCancel still throws through its parameterless placeholder; [Orbital b321fcd](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json) declares `int sceRudpPollCancel(int pollId)`. The [detailed firmware 13.42 report](https://github.com/boykopovar/AnyPS5/pull/10954#issuecomment-6399054346), under the emulated/stubbed imports described above, observes a signed low-32-bit argument and initialization, negative-ID and unknown-ID errors `0x80770001`, `0x80770004` and `0x8077001F`. Two idle cancels coalesce into one per-poll cancellation consumed even by a zero-timeout wait. In the scripted two-waiter Cancel/Destroy cases both waiters return `0x80770014`; those calls drain the waiters before returning, and Destroy then destroys the condition variable. A later wait on that destroyed ID returns `0x8077001F`. End similarly drains its active waiter with `0x80770014`; subsequent Wait/Cancel calls return `0x80770001`. Here Destroy/End return without draining and wake existing waiters with `0x8077001F`/`0x80770001`. Real-kernel wake/timeout races, simultaneous control calls, End/re-init overlap and extreme timeout boundaries remain untested; the report is not a console hardware measurement or an independent reproduction here.
- [sceRudpAccept, sceRudpFlush, sceRudpGetLocalInfo, sceRudpListen and sceRudpSetMaxSegmentSize](../../../core/libs/prx/libSceRudp/Export.cpp) - remain throwing placeholders. Their names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceRudp.c); their parameterless declarations solely resolve known imports and do not establish the ABI.