From 13ea0d59f16073cb560003487766a0b115d9507d Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:32:40 -0600 Subject: [PATCH 01/11] "Basic" socket testing The actual test part of the test is fairly basic, but the logging serves as a somewhat more advanced test. --- tests/code/net_test/CMakeLists.txt | 5 + tests/code/net_test/assets/.gitkeep | 0 tests/code/net_test/code/main.cpp | 15 ++ tests/code/net_test/code/test.cpp | 333 ++++++++++++++++++++++++++++ tests/code/net_test/code/test.h | 244 ++++++++++++++++++++ 5 files changed, 597 insertions(+) create mode 100644 tests/code/net_test/CMakeLists.txt create mode 100644 tests/code/net_test/assets/.gitkeep create mode 100644 tests/code/net_test/code/main.cpp create mode 100644 tests/code/net_test/code/test.cpp create mode 100644 tests/code/net_test/code/test.h diff --git a/tests/code/net_test/CMakeLists.txt b/tests/code/net_test/CMakeLists.txt new file mode 100644 index 0000000..627a841 --- /dev/null +++ b/tests/code/net_test/CMakeLists.txt @@ -0,0 +1,5 @@ +project(net_test LANGUAGES CXX) + +link_libraries(SceSystemService SceNet) + +create_pkg("TNET00100" 1 00 "code/main.cpp;code/test.cpp") diff --git a/tests/code/net_test/assets/.gitkeep b/tests/code/net_test/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/code/net_test/code/main.cpp b/tests/code/net_test/code/main.cpp new file mode 100644 index 0000000..0e68896 --- /dev/null +++ b/tests/code/net_test/code/main.cpp @@ -0,0 +1,15 @@ +#include "CppUTest/CommandLineTestRunner.h" + +#include +#include +#include + +IMPORT_TEST_GROUP(NetTest); + +int main(int ac, char** av) { + // No buffering + setvbuf(stdout, NULL, _IONBF, 0); + int result = RUN_ALL_TESTS(ac, av); + sceSystemServiceLoadExec("EXIT", nullptr); + return result; +} diff --git a/tests/code/net_test/code/test.cpp b/tests/code/net_test/code/test.cpp new file mode 100644 index 0000000..0a7f0d5 --- /dev/null +++ b/tests/code/net_test/code/test.cpp @@ -0,0 +1,333 @@ +#include "test.h" + +#include "CppUTest/TestHarness.h" + +TEST_GROUP (NetTest) { + void setup() {} + void teardown() {} +}; + +static constexpr u16 g_test_port = 8080; +static constexpr u16 g_log_port = 8181; +static bool g_server_ready = false; +static bool g_cond = false; +static bool g_logging_init = false; + +void* LoggingThread(void* user_arg) { + // To continue in the lovely steps of this test suite, this logger server will be built on socket communication. + s32 stream_sock = sceNetSocket("LoggerServer", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + CHECK(stream_sock > 0); + + // Use setsockopt to ensure it uses specified addr and port + s32 opt = 1; + s32 result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, &opt, sizeof(opt)); + UNSIGNED_INT_EQUALS(0, result); + opt = 1; + result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, &opt, sizeof(opt)); + UNSIGNED_INT_EQUALS(0, result); + + // Bind this socket to a specific address and port + OrbisNetSockaddrIn in_addr {}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_addr.s_addr = ORBIS_NET_INADDR_ANY; + in_addr.sin_port = sceNetHtons(g_log_port); + result = sceNetBind(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); + UNSIGNED_INT_EQUALS(0, result); + + // Set to non-blocking + opt = 1; + result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, &opt, sizeof(opt)); + UNSIGNED_INT_EQUALS(0, result); + + result = sceNetListen(stream_sock, 3); + UNSIGNED_INT_EQUALS(0, result); + + // Socket is ready, mark server as initialized. + g_logging_init = true; + + // For the remainder of this test, this thread will run a loop. + while (g_logging_init) { + // Use accept to wait for, then connect with a client + u32 addr_len = sizeof(in_addr); + s32 log_sock = sceNetAccept(stream_sock, (OrbisNetSockaddr*)&in_addr, &addr_len); + if (!g_logging_init) { + // Logger terminated + if (log_sock > 0) { + sceNetSocketClose(log_sock); + } + break; + } else if (log_sock == ORBIS_NET_ERROR_EAGAIN) { + // Not ready for client, wait then loop. + sceKernelUsleep(10000); + continue; + } + + CHECK(log_sock > 0); + + // Create an epoll + s32 log_epoll = sceNetEpollCreate("LoggerMessageEpoll", 0); + CHECK(log_epoll > 0); + + // Add the new socket to the epoll + OrbisNetEpollEvent epoll_event {}; + epoll_event.events = ORBIS_NET_EPOLLIN; + OrbisNetEpollData epoll_data {}; + epoll_data.fd = log_sock; + epoll_event.data = epoll_data; + result = sceNetEpollControl(log_epoll, ORBIS_NET_EPOLL_CTL_ADD, log_sock, &epoll_event); + UNSIGNED_INT_EQUALS(0, result); + + // Perform an epoll_wait with indefinite timeout + OrbisNetEpollEvent epoll_ev_out {}; + result = sceNetEpollWait(log_epoll, &epoll_ev_out, 1, -1); + UNSIGNED_INT_EQUALS(1, result); + + // If epoll_wait returned a positive number of fds, then this socket is ready for reading. + if (result > 0) { + char log_buf[0x1000]; + memset(log_buf, 0, sizeof(log_buf)); + result = sceNetRecv(log_sock, log_buf, sizeof(log_buf), 0); + CHECK(result > 0); + printf("%s", log_buf); + } + + // Destroy the epoll and close the socket + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(log_sock); + } + sceNetSocketClose(stream_sock); + return nullptr; +} + +void LogMessage(const char* fmt, u64 log_res) { + CHECK(g_logging_init); + + // Because I want to make everything extraordinarily difficult, this creates a socket, connects to logging socket, sends message, closes socket. + s32 stream_sock = sceNetSocket("LoggerClientSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + CHECK(stream_sock > 0); + + OrbisNetSockaddrIn in_addr {}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_port = sceNetHtons(g_log_port); + s32 result = sceNetInetPton(ORBIS_NET_AF_INET, "127.0.0.1", &in_addr.sin_addr); + UNSIGNED_INT_EQUALS(1, result); + + // Set to non-blocking + s32 opt = 1; + result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, &opt, sizeof(opt)); + UNSIGNED_INT_EQUALS(0, result); + + result = sceNetConnect(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); + while (result != 0) { + if (result == ORBIS_NET_ERROR_EINPROGRESS || result == ORBIS_NET_ERROR_EAGAIN) { + // Connection is incomplete, wait, then retry. + sceKernelUsleep(10000); + result = sceNetConnect(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); + continue; + } else if (result == ORBIS_NET_ERROR_EISCONN) { + // Connection finished, break out of loop. + break; + } + // Should never reach here, if we did, then connect returned an unexpected error. + UNSIGNED_INT_EQUALS(0, result); + } + + // Create an epoll, use it to wait until socket is ready for writing. + s32 log_epoll = sceNetEpollCreate("LoggerMessageEpoll", 0); + CHECK(log_epoll > 0); + + // Add the new socket to the epoll + OrbisNetEpollEvent epoll_event {}; + epoll_event.events = ORBIS_NET_EPOLLOUT; + OrbisNetEpollData epoll_data {}; + epoll_data.fd = stream_sock; + epoll_event.data = epoll_data; + result = sceNetEpollControl(log_epoll, ORBIS_NET_EPOLL_CTL_ADD, stream_sock, &epoll_event); + UNSIGNED_INT_EQUALS(0, result); + + // Perform an epoll_wait with indefinite timeout + OrbisNetEpollEvent epoll_ev_out {}; + result = sceNetEpollWait(log_epoll, &epoll_ev_out, 1, -1); + UNSIGNED_INT_EQUALS(1, result); + + char send_buf[0x1000]; + memset(send_buf, 0, sizeof(send_buf)); + sprintf(send_buf, fmt, log_res); + result = sceNetSend(stream_sock, send_buf, strlen(send_buf), 0); + CHECK(result > 0); + + sceNetSocketClose(stream_sock); + sceNetEpollDestroy(log_epoll); +} + +void* ServerThread(void* user_arg) { + // This will be a thread for actually sending data + // Create a stream socket + s32 stream_sock = sceNetSocket("TestStreamServer", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + LogMessage("Server: socket(AF_INET, SOCK_STREAM, 0) returns 0x%08x\n", stream_sock); + + // Use setsockopt to ensure it uses specified addr and port + s32 opt = 1; + s32 result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, &opt, sizeof(opt)); + LogMessage("Server: setsockopt(SOL_SOCKET, SO_REUSEADDR) returns 0x%08x\n", result); + opt = 1; + result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, &opt, sizeof(opt)); + LogMessage("Server: setsockopt(SOL_SOCKET, SO_REUSEPORT) returns 0x%08x\n", result); + + // Bind this socket to a specific address and port + OrbisNetSockaddrIn in_addr {}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_addr.s_addr = ORBIS_NET_INADDR_ANY; + in_addr.sin_port = sceNetHtons(g_test_port); + result = sceNetBind(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); + LogMessage("Server: bind returns 0x%08x\n", result); + + // Mark the socket as passive + result = sceNetListen(stream_sock, 3); + LogMessage("Server: listen returns 0x%08x\n", result); + + // Mark server as ready, so client knows to try connecting. + g_server_ready = true; + + // Accept a connection with a socket, producing a new socket id + u32 addr_len = sizeof(in_addr); + s32 connected_sock = sceNetAccept(stream_sock, (OrbisNetSockaddr*)&in_addr, &addr_len); + LogMessage("Server: accept returns 0x%08x\n", connected_sock); + + // Send a message to the client thread + const char* message = "This is a test message coming from the server"; + result = sceNetSend(connected_sock, message, strlen(message), 0); + LogMessage("Server: send returns 0x%08x\n", result); + + // Receive a message from the client thread + char buffer[1024]; + memset(buffer, 0, sizeof(buffer)); + result = sceNetRecv(connected_sock, buffer, sizeof(buffer), 0); + LogMessage("Server: recv returns 0x%08x\n", result); + + // Create an epoll, these are primarily used to wait for a file descriptor + s32 epoll = sceNetEpollCreate("ServerEpoll", 0); + LogMessage("Server: epoll_create returns 0x%08x\n", epoll); + + // These can take any "s32", and an "event" describing what to wait on. + OrbisNetEpollEvent epoll_event {}; + // ORBIS_NET_EPOLLIN event means the socket is ready for reads + epoll_event.events = ORBIS_NET_EPOLLIN; + // OrbisNetEpollData is data that gets returns when wait completes + OrbisNetEpollData epoll_data {}; + epoll_data.fd = connected_sock; + epoll_event.data = epoll_data; + result = sceNetEpollControl(epoll, ORBIS_NET_EPOLL_CTL_ADD, connected_sock, &epoll_event); + LogMessage("Server: epoll_ctl returns 0x%08x\n", result); + + // Perform an epoll_wait with no timeout. + OrbisNetEpollEvent epoll_ev_out {}; + result = sceNetEpollWait(epoll, &epoll_ev_out, 1, 0); + // epoll_wait returns number of ready FDs that were polled. + // Until client thread sends a message, this should be 0. + LogMessage("Server: epoll_wait returns 0x%08x\n", result); + + // Tell client to send data over the socket + g_cond = true; + + // Perform a blocking epoll_wait to wait for data + // -1 timeout indicates no timeout. + result = sceNetEpollWait(epoll, &epoll_ev_out, 1, -1); + LogMessage("Server: epoll_wait returns 0x%08x\n", result); + + // Now it should be safe to read from the socket. + memset(buffer, 0, sizeof(buffer)); + result = sceNetRecv(connected_sock, buffer, sizeof(buffer), 0); + LogMessage("Server: recv returns 0x%08x\n", result); + + // Close the epoll + result = sceNetEpollDestroy(epoll); + LogMessage("Server: epoll_destroy returns 0x%08x\n", result); + + return nullptr; +} + +void* ClientThread(void* user_arg) { + while (!g_server_ready) { + // Wait for server to prepare it's socket. + sceKernelUsleep(10000); + } + + // This will be a thread for testing communication via sockets + // Create a stream socket + s32 stream_sock = sceNetSocket("TestStreamSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + LogMessage("Client: socket(AF_INET, SOCK_STREAM, 0) returns 0x%08x\n", stream_sock); + + // Connect the socket to the server thread's socket + OrbisNetSockaddrIn in_addr {}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_port = sceNetHtons(g_test_port); + s32 result = sceNetInetPton(ORBIS_NET_AF_INET, "127.0.0.1", &in_addr.sin_addr); + LogMessage("Client: inet_pton(ORBIS_NET_AF_INET) returns 0x%08x\n", result); + result = sceNetConnect(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); + LogMessage("Client: connect returns 0x%08x\n", result); + + // Read a message through the connected socket + char buffer[1024]; + memset(buffer, 0, sizeof(buffer)); + result = sceNetRecv(stream_sock, buffer, sizeof(buffer), 0); + LogMessage("Client: recv returns 0x%08x\n", result); + + // Send a message to the server through this socket + const char* message = "This is a test message coming from the client"; + result = sceNetSend(stream_sock, message, strlen(message), 0); + LogMessage("Client: send returns 0x%08x\n", result); + + // Wait for g_cond to be true, this will serve as a signal to test epoll behavior + while (!g_cond) { + sceKernelUsleep(10000); + } + g_cond = false; + + // Server wants to test epoll behavior, send data over the socket + result = sceNetSend(stream_sock, message, strlen(message), 0); + LogMessage("Client: send returns 0x%08x\n", result); + + return nullptr; +} + +TEST(NetTest, Test) { + // Init libSceNet + s32 result = sceNetInit(); + printf("sceNetInit returns 0x%08x\n", result); + + // Create a server and client thread. + // These will serve as a way of testing behavior all in one application, hopefully. + pthread_attr_t thread_attr {}; + result = pthread_attr_init(&thread_attr); + printf("pthread_attr_init returns 0x%08x\n", result); + + pthread_t logger_tid {}; + result = pthread_create(&logger_tid, &thread_attr, LoggingThread, nullptr); + printf("Logger thread created, pthread_create returns 0x%08x\n", result); + + // Wait for logging to initialize + while (!g_logging_init) { + sceKernelUsleep(10000); + } + + pthread_t server_tid {}; + result = pthread_create(&server_tid, &thread_attr, ServerThread, nullptr); + printf("Server thread created, pthread_create returns 0x%08x\n", result); + pthread_t client_tid {}; + result = pthread_create(&client_tid, &thread_attr, ClientThread, nullptr); + printf("Client thread created, pthread_create returns 0x%08x\n", result); + + // Wait for all threads to exit + result = pthread_join(server_tid, nullptr); + result = pthread_join(client_tid, nullptr); + + // Terminate logger thread + g_logging_init = false; + result = pthread_join(logger_tid, nullptr); + printf("All threads exited, tests complete\n"); + + // Terminate libSceNet + result = sceNetTerm(); + printf("sceNetTerm returns 0x%08x\n", result); +} diff --git a/tests/code/net_test/code/test.h b/tests/code/net_test/code/test.h new file mode 100644 index 0000000..3ae0517 --- /dev/null +++ b/tests/code/net_test/code/test.h @@ -0,0 +1,244 @@ +#pragma once + +#include "CppUTest/TestHarness.h" + +#define UNSIGNED_INT_EQUALS(expected, actual) UNSIGNED_LONGS_EQUAL_LOCATION((u32)expected, (u32)actual, NULLPTR, __FILE__, __LINE__) + +using s8 = int8_t; +using s16 = int16_t; +using s32 = int32_t; +using s64 = int64_t; +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; + +struct OrbisNetInAddr { + u32 s_addr; +}; + +struct OrbisNetSockaddrIn { + u8 sin_len; + u8 sin_family; + u16 sin_port; + OrbisNetInAddr sin_addr; + u16 sin_vport; + char sin_zero[6]; +}; + +struct OrbisNetIovec { + void* iov_base; + u64 iov_len; +}; + +struct OrbisNetMsghdr { + void* msg_name; + u32 msg_namelen; + OrbisNetIovec* msg_iov; + s32 msg_iovlen; + void* msg_control; + u32 msg_controllen; + s32 msg_flags; +}; + +struct OrbisNetSockaddr { + u8 sa_len; + u8 sa_family; + char sa_data[14]; +}; + +union OrbisNetEpollData { + void* ptr; + u32 u32; + s32 fd; + u64 u64; +}; + +struct OrbisNetEpollEvent { + u32 events; + u32 reserved; + u64 ident; + OrbisNetEpollData data; +}; + +// Socket types +#define ORBIS_NET_SOCK_STREAM 1 + +// Socket options +#define ORBIS_NET_SO_REUSEADDR 0x4 +#define ORBIS_NET_SO_REUSEPORT 0x200 +#define ORBIS_NET_SO_NBIO 0x1200 + +// Socket option levels +#define ORBIS_NET_SOL_SOCKET 0xffff + +// Socket address families +#define ORBIS_NET_AF_INET 2 + +// Default input addresses +#define ORBIS_NET_INADDR_ANY ((u32)0x00000000) + +// Epoll event flags +#define ORBIS_NET_EPOLLIN 0x00000001 +#define ORBIS_NET_EPOLLOUT 0x00000002 + +// Epoll control operations +#define ORBIS_NET_EPOLL_CTL_ADD 1 +#define ORBIS_NET_EPOLL_CTL_MOD 2 +#define ORBIS_NET_EPOLL_CTL_DEL 3 + +// Error codes +enum OrbisNetError : s32 { + ORBIS_NET_ERROR_EPERM = s32(0x80410101), + ORBIS_NET_ERROR_ENOENT = s32(0x80410102), + ORBIS_NET_ERROR_ESRCH = s32(0x80410103), + ORBIS_NET_ERROR_EINTR = s32(0x80410104), + ORBIS_NET_ERROR_EIO = s32(0x80410105), + ORBIS_NET_ERROR_ENXIO = s32(0x80410106), + ORBIS_NET_ERROR_E2BIG = s32(0x80410107), + ORBIS_NET_ERROR_ENOEXEC = s32(0x80410108), + ORBIS_NET_ERROR_EBADF = s32(0x80410109), + ORBIS_NET_ERROR_ECHILD = s32(0x8041010A), + ORBIS_NET_ERROR_EDEADLK = s32(0x8041010B), + ORBIS_NET_ERROR_ENOMEM = s32(0x8041010C), + ORBIS_NET_ERROR_EACCES = s32(0x8041010D), + ORBIS_NET_ERROR_EFAULT = s32(0x8041010E), + ORBIS_NET_ERROR_ENOTBLK = s32(0x8041010F), + ORBIS_NET_ERROR_EBUSY = s32(0x80410110), + ORBIS_NET_ERROR_EEXIST = s32(0x80410111), + ORBIS_NET_ERROR_EXDEV = s32(0x80410112), + ORBIS_NET_ERROR_ENODEV = s32(0x80410113), + ORBIS_NET_ERROR_ENOTDIR = s32(0x80410114), + ORBIS_NET_ERROR_EISDIR = s32(0x80410115), + ORBIS_NET_ERROR_EINVAL = s32(0x80410116), + ORBIS_NET_ERROR_ENFILE = s32(0x80410117), + ORBIS_NET_ERROR_EMFILE = s32(0x80410118), + ORBIS_NET_ERROR_ENOTTY = s32(0x80410119), + ORBIS_NET_ERROR_ETXTBSY = s32(0x8041011A), + ORBIS_NET_ERROR_EFBIG = s32(0x8041011B), + ORBIS_NET_ERROR_ENOSPC = s32(0x8041011C), + ORBIS_NET_ERROR_ESPIPE = s32(0x8041011D), + ORBIS_NET_ERROR_EROFS = s32(0x8041011E), + ORBIS_NET_ERROR_EMLINK = s32(0x8041011F), + ORBIS_NET_ERROR_EPIPE = s32(0x80410120), + ORBIS_NET_ERROR_EDOM = s32(0x80410121), + ORBIS_NET_ERROR_ERANGE = s32(0x80410122), + ORBIS_NET_ERROR_EAGAIN = s32(0x80410123), + ORBIS_NET_ERROR_EWOULDBLOCK = s32(0x80410123), + ORBIS_NET_ERROR_EINPROGRESS = s32(0x80410124), + ORBIS_NET_ERROR_EALREADY = s32(0x80410125), + ORBIS_NET_ERROR_ENOTSOCK = s32(0x80410126), + ORBIS_NET_ERROR_EDESTADDRREQ = s32(0x80410127), + ORBIS_NET_ERROR_EMSGSIZE = s32(0x80410128), + ORBIS_NET_ERROR_EPROTOTYPE = s32(0x80410129), + ORBIS_NET_ERROR_ENOPROTOOPT = s32(0x8041012A), + ORBIS_NET_ERROR_EPROTONOSUPPORT = s32(0x8041012B), + ORBIS_NET_ERROR_ESOCKTNOSUPPORT = s32(0x8041012C), + ORBIS_NET_ERROR_EOPNOTSUPP = s32(0x8041012D), + ORBIS_NET_ERROR_ENOTSUP = s32(0x8041012D), + ORBIS_NET_ERROR_EPFNOSUPPORT = s32(0x8041012E), + ORBIS_NET_ERROR_EAFNOSUPPORT = s32(0x8041012F), + ORBIS_NET_ERROR_EADDRINUSE = s32(0x80410130), + ORBIS_NET_ERROR_EADDRNOTAVAIL = s32(0x80410131), + ORBIS_NET_ERROR_ENETDOWN = s32(0x80410132), + ORBIS_NET_ERROR_ENETUNREACH = s32(0x80410133), + ORBIS_NET_ERROR_ENETRESET = s32(0x80410134), + ORBIS_NET_ERROR_ECONNABORTED = s32(0x80410135), + ORBIS_NET_ERROR_ECONNRESET = s32(0x80410136), + ORBIS_NET_ERROR_ENOBUFS = s32(0x80410137), + ORBIS_NET_ERROR_EISCONN = s32(0x80410138), + ORBIS_NET_ERROR_ENOTCONN = s32(0x80410139), + ORBIS_NET_ERROR_ESHUTDOWN = s32(0x8041013A), + ORBIS_NET_ERROR_ETOOMANYREFS = s32(0x8041013B), + ORBIS_NET_ERROR_ETIMEDOUT = s32(0x8041013C), + ORBIS_NET_ERROR_ECONNREFUSED = s32(0x8041013D), + ORBIS_NET_ERROR_ELOOP = s32(0x8041013E), + ORBIS_NET_ERROR_ENAMETOOLONG = s32(0x8041013F), + ORBIS_NET_ERROR_EHOSTDOWN = s32(0x80410140), + ORBIS_NET_ERROR_EHOSTUNREACH = s32(0x80410141), + ORBIS_NET_ERROR_ENOTEMPTY = s32(0x80410142), + ORBIS_NET_ERROR_EPROCLIM = s32(0x80410143), + ORBIS_NET_ERROR_EUSERS = s32(0x80410144), + ORBIS_NET_ERROR_EDQUOT = s32(0x80410145), + ORBIS_NET_ERROR_ESTALE = s32(0x80410146), + ORBIS_NET_ERROR_EREMOTE = s32(0x80410147), + ORBIS_NET_ERROR_EBADRPC = s32(0x80410148), + ORBIS_NET_ERROR_ERPCMISMATCH = s32(0x80410149), + ORBIS_NET_ERROR_EPROGUNAVAIL = s32(0x8041014A), + ORBIS_NET_ERROR_EPROGMISMATCH = s32(0x8041014B), + ORBIS_NET_ERROR_EPROCUNAVAIL = s32(0x8041014C), + ORBIS_NET_ERROR_ENOLCK = s32(0x8041014D), + ORBIS_NET_ERROR_ENOSYS = s32(0x8041014E), + ORBIS_NET_ERROR_EFTYPE = s32(0x8041014F), + ORBIS_NET_ERROR_EAUTH = s32(0x80410150), + ORBIS_NET_ERROR_ENEEDAUTH = s32(0x80410151), + ORBIS_NET_ERROR_EIDRM = s32(0x80410152), + ORBIS_NET_ERROR_ENOMS = s32(0x80410153), + ORBIS_NET_ERROR_EOVERFLOW = s32(0x80410154), + ORBIS_NET_ERROR_ECANCELED = s32(0x80410155), + ORBIS_NET_ERROR_EPROTO = s32(0x8041015C), + ORBIS_NET_ERROR_EADHOC = s32(0x804101A0), + ORBIS_NET_ERROR_EINACTIVEDISABLED = s32(0x804101A3), + ORBIS_NET_ERROR_ENODATA = s32(0x804101A4), + ORBIS_NET_ERROR_EDESC = s32(0x804101A5), + ORBIS_NET_ERROR_EDESCTIMEDOUT = s32(0x804101A6), + ORBIS_NET_ERROR_ENETINTR = s32(0x804101A7), + ORBIS_NET_ERROR_ENOTINIT = s32(0x804101C8), + ORBIS_NET_ERROR_ENOLIBMEM = s32(0x804101C9), + ORBIS_NET_ERROR_ECALLBACK = s32(0x804101CB), + ORBIS_NET_ERROR_EINTERNAL = s32(0x804101CC), + ORBIS_NET_ERROR_ERETURN = s32(0x804101CD), + ORBIS_NET_ERROR_ENOALLOCMEM = s32(0x804101CE), + ORBIS_NET_ERROR_RESOLVER_EINTERNAL = s32(0x804101DC), + ORBIS_NET_ERROR_RESOLVER_EBUSY = s32(0x804101DD), + ORBIS_NET_ERROR_RESOLVER_ENOSPACE = s32(0x804101DE), + ORBIS_NET_ERROR_RESOLVER_EPACKET = s32(0x804101DF), + ORBIS_NET_ERROR_RESOLVER_ENODNS = s32(0x804101E1), + ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT = s32(0x804101E2), + ORBIS_NET_ERROR_RESOLVER_ENOSUPPORT = s32(0x804101E3), + ORBIS_NET_ERROR_RESOLVER_EFORMAT = s32(0x804101E4), + ORBIS_NET_ERROR_RESOLVER_ESERVERFAILURE = s32(0x804101E5), + ORBIS_NET_ERROR_RESOLVER_ENOHOST = s32(0x804101E6), + ORBIS_NET_ERROR_RESOLVER_ENOTIMPLEMENTED = s32(0x804101E7), + ORBIS_NET_ERROR_RESOLVER_ESERVERREFUSED = s32(0x804101E8), + ORBIS_NET_ERROR_RESOLVER_ENORECORD = s32(0x804101E9), + ORBIS_NET_ERROR_RESOLVER_EALIGNMENT = s32(0x804101EA), + ORBIS_NET_ERROR_RESOLVER_ENOTFOUND = s32(0x804101EB), + ORBIS_NET_ERROR_RESOLVER_ENOTINIT = s32(0x804101EC), +}; + +extern "C" { +s32 sceNetInit(); +s32 sceNetTerm(); + +s32 sceNetSocket(const char* name, s32 domain, s32 type, s32 protocol); +s32 sceNetAccept(s32 s, OrbisNetSockaddr* addr, u32* addrlen); +s32 sceNetBind(s32 s, const OrbisNetSockaddr* addr, u32 addrlen); +s32 sceNetConnect(s32 s, const OrbisNetSockaddr* name, u32 namelen); +s32 sceNetGetpeername(s32 s, OrbisNetSockaddr* name, u32* namelen); +s32 sceNetGetsockname(s32 s, OrbisNetSockaddr* name, u32* namelen); +s32 sceNetGetsockopt(s32 s, s32 level, s32 optname, void* optval, u32* optlen); +s32 sceNetListen(s32 s, s32 backlog); +s32 sceNetRecv(s32 s, void* buf, u64 len, s32 flags); +s32 sceNetRecvfrom(s32 s, void* buf, u64 len, s32 flags, OrbisNetSockaddr* from, u32* fromlen); +s32 sceNetRecvmsg(s32 s, OrbisNetMsghdr* msg, s32 flags); +s32 sceNetSend(s32 s, const void* msg, u64 len, s32 flags); +s32 sceNetSendto(s32 s, const void* msg, u64 len, s32 flags, const OrbisNetSockaddr* to, u32 tolen); +s32 sceNetSendmsg(s32 s, const OrbisNetMsghdr* msg, s32 flags); +s32 sceNetSetsockopt(s32 s, s32 level, s32 optname, const void* optval, u32 optlen); +s32 sceNetShutdown(s32 s, s32 how); +s32 sceNetSocketClose(s32 s); +s32 sceNetSocketAbort(s32 s, s32 flags); + +s32 sceNetEpollCreate(const char* name, s32 flags); +s32 sceNetEpollControl(s32 eid, s32 op, s32 id, OrbisNetEpollEvent* event); +s32 sceNetEpollWait(s32 eid, OrbisNetEpollEvent* events, s32 maxevents, s32 timeout); +s32 sceNetEpollDestroy(s32 eid); +s32 sceNetEpollAbort(s32 eid, s32 flags); + +u16 sceNetHtons(u16 host16); + +s32 sceNetInetPton(s32 af, const char* src, void* dst); + +s32 sceKernelUsleep(u32 micros); +} \ No newline at end of file From e786b92ea8fbc6c6019f34e534fc462dbda5a626 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:43:55 -0600 Subject: [PATCH 02/11] Formatting --- tests/code/net_test/code/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/code/net_test/code/test.cpp b/tests/code/net_test/code/test.cpp index 0a7f0d5..5a6a8b2 100644 --- a/tests/code/net_test/code/test.cpp +++ b/tests/code/net_test/code/test.cpp @@ -155,7 +155,7 @@ void LogMessage(const char* fmt, u64 log_res) { sprintf(send_buf, fmt, log_res); result = sceNetSend(stream_sock, send_buf, strlen(send_buf), 0); CHECK(result > 0); - + sceNetSocketClose(stream_sock); sceNetEpollDestroy(log_epoll); } From a4053ec91af838bbf4e35d9485f7d7eec4910875 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:55:18 -0600 Subject: [PATCH 03/11] Separate overly-complicated logger into a separate class, and make it more complicated The goal is going to be to have separate "logger" classes to go with each test, serving as a "real-world" use case for the features being tested. Also made this non-blocking logger support asynchronous logging, to make it even harder to run. Probably going to work in separate tests for running as sync and async, but for now I've just got it set to run async. --- tests/code/net_test/CMakeLists.txt | 2 +- tests/code/net_test/code/logger.h | 10 + .../code/net_test/code/nbio_stream_logger.cpp | 389 ++++++++++++++++++ tests/code/net_test/code/nbio_stream_logger.h | 19 + tests/code/net_test/code/test.cpp | 172 +------- tests/code/net_test/code/test.h | 2 + 6 files changed, 431 insertions(+), 163 deletions(-) create mode 100644 tests/code/net_test/code/logger.h create mode 100644 tests/code/net_test/code/nbio_stream_logger.cpp create mode 100644 tests/code/net_test/code/nbio_stream_logger.h diff --git a/tests/code/net_test/CMakeLists.txt b/tests/code/net_test/CMakeLists.txt index 627a841..1301cd2 100644 --- a/tests/code/net_test/CMakeLists.txt +++ b/tests/code/net_test/CMakeLists.txt @@ -2,4 +2,4 @@ project(net_test LANGUAGES CXX) link_libraries(SceSystemService SceNet) -create_pkg("TNET00100" 1 00 "code/main.cpp;code/test.cpp") +create_pkg("TNET00100" 1 00 "code/main.cpp;code/test.cpp;code/nbio_stream_logger.cpp") diff --git a/tests/code/net_test/code/logger.h b/tests/code/net_test/code/logger.h new file mode 100644 index 0000000..f49db82 --- /dev/null +++ b/tests/code/net_test/code/logger.h @@ -0,0 +1,10 @@ +#include "test.h" + +#pragma once + +class Logger { + public: + Logger() = default; + virtual ~Logger() {}; + virtual void LogMessage(const char* fmt, const u64 log_res) {}; +}; \ No newline at end of file diff --git a/tests/code/net_test/code/nbio_stream_logger.cpp b/tests/code/net_test/code/nbio_stream_logger.cpp new file mode 100644 index 0000000..2cad68e --- /dev/null +++ b/tests/code/net_test/code/nbio_stream_logger.cpp @@ -0,0 +1,389 @@ +#include "nbio_stream_logger.h" + +#include "test.h" + +static constexpr u16 log_port = 8181; +static constexpr s32 max_clients = 10; +static bool logger_ready = false; +static std::mutex logger_mutex {}; +static pthread_t logger_server_tid {}; +static std::vector client_tids {}; +static bool async_logging = false; + +struct ClientArgs { + const char* fmt; + const u64 log_res; +}; + +void NBIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { + // Creates a thread that performs the logging + std::scoped_lock lk {logger_mutex}; + + pthread_t client_tid {}; + pthread_attr_t thread_attr {}; + s32 result = pthread_attr_init(&thread_attr); + if (result < 0) { + printf("NBIOStreamLogger unable to send message, pthread_attr_init failed with 0x%08x\n", result); + return; + } + + // For async logging, we need to limit possible client threads. + // To do this, just exit all threads any time the client_tids vector gets too large. + if (async_logging && client_tids.size() == max_clients) { + for (pthread_t client_tid: client_tids) { + result = pthread_join(client_tid, nullptr); + if (result < 0) { + printf("NBIOStreamLogger unable to terminate\n"); + } + } + + // Clear old client tids + client_tids.clear(); + } + + // For the user args, submit both arguments through a struct + ClientArgs user_args {fmt, log_res}; + result = pthread_create(&client_tid, &thread_attr, LoggingClientThread, &user_args); + if (result < 0) { + printf("NBIOStreamLogger unable to send message, pthread_create failed with 0x%08x\n", result); + return; + } + + char thread_name[128]; + memset(thread_name, 0, sizeof(thread_name)); + sprintf(thread_name, "LoggerThread0x%08lx", client_tid); + pthread_set_name_np(client_tid, thread_name); + + if (async_logging) { + // Add new client thread id to vector, to be exited on-demand or in destructor + client_tids.emplace_back(client_tid); + } else { + // Wait for logger thread to terminate + result = pthread_join(client_tid, nullptr); + if (result < 0) { + printf("LogMessage thread failed to terminate\n"); + } + } +} + +void* NBIOStreamLogger::LoggingServerThread(void* user_arg) { + // To continue in the lovely steps of this test suite, this logger server will be built on socket communication. + s32 stream_sock = sceNetSocket("LoggerServer", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + if (stream_sock <= 0) { + printf("NBIOStreamLogger unable to initialize, socket creation failed with 0x%08x\n", stream_sock); + return nullptr; + } + + // Use setsockopt to ensure it uses specified addr and port + s32 opt = 1; + s32 result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, &opt, sizeof(opt)); + if (result < 0) { + printf("NBIOStreamLogger unable to initialize, setsockopt failed with 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + opt = 1; + result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, &opt, sizeof(opt)); + if (result < 0) { + printf("NBIOStreamLogger unable to initialize, setsockopt failed with 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + + // Bind this socket to a specific address and port + OrbisNetSockaddrIn in_addr {}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_addr.s_addr = ORBIS_NET_INADDR_ANY; + in_addr.sin_port = sceNetHtons(log_port); + result = sceNetBind(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); + if (result < 0) { + printf("NBIOStreamLogger unable to initialize, bind failed with 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + + // Set to non-blocking + opt = 1; + result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, &opt, sizeof(opt)); + if (result < 0) { + printf("NBIOStreamLogger unable to initialize, setsockopt failed with 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + + // Mark socket as passive + result = sceNetListen(stream_sock, max_clients); + if (result < 0) { + printf("NBIOStreamLogger unable to initialize, listen failed with 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + + // Socket is ready, mark server as initialized. + logger_ready = true; + + // For the remainder of this test, this thread will run a loop. + while (logger_ready) { + // Use accept to wait for, then connect with a client + u32 addr_len = sizeof(in_addr); + s32 log_sock = sceNetAccept(stream_sock, (OrbisNetSockaddr*)&in_addr, &addr_len); + if (!logger_ready) { + // Logger terminated + if (log_sock > 0) { + // Accept succeeded after logging terminated, abort anyway. + sceNetSocketClose(log_sock); + } + break; + } else if (log_sock == ORBIS_NET_ERROR_EAGAIN) { + // Not ready for client, wait then loop. + sceKernelUsleep(10000); + continue; + } else if (log_sock <= 0) { + // Log errors + printf("NBIOStreamLogger: Unexpected error 0x%08x while waiting for client\n", log_sock); + continue; + } + + // Create an epoll + s32 log_epoll = sceNetEpollCreate("LoggerMessageEpoll", 0); + if (log_epoll <= 0) { + // Log errors + printf("NBIOStreamLogger: Unexpected error 0x%08x while creating epoll\n", log_epoll); + sceNetSocketClose(log_sock); + continue; + } + + // Add the new socket to the epoll + OrbisNetEpollEvent epoll_event {}; + epoll_event.events = ORBIS_NET_EPOLLIN; + OrbisNetEpollData epoll_data {}; + epoll_data.fd = log_sock; + epoll_event.data = epoll_data; + result = sceNetEpollControl(log_epoll, ORBIS_NET_EPOLL_CTL_ADD, log_sock, &epoll_event); + if (result < 0) { + // Log errors + printf("NBIOStreamLogger: Unexpected error 0x%08x while preparing epoll\n", result); + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(log_sock); + continue; + } + + // Perform an epoll_wait with indefinite timeout + OrbisNetEpollEvent epoll_ev_out {}; + result = sceNetEpollWait(log_epoll, &epoll_ev_out, 1, -1); + if (result < 0) { + // Log errors + printf("NBIOStreamLogger: Unexpected error 0x%08x while waiting for socket\n", result); + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(log_sock); + continue; + } else if (result == 0) { + // Log errors + printf("NBIOStreamLogger: Blocking epoll wait returned no ready fds?\n"); + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(log_sock); + continue; + } + + // Read in the message from the socket + char log_buf[0x1000]; + do { + memset(log_buf, 0, sizeof(log_buf)); + result = sceNetRecv(log_sock, log_buf, sizeof(log_buf), 0); + if (result == ORBIS_NET_ERROR_EWOULDBLOCK || result == 0) { + // No more data to read + break; + } else if (result < 0) { + // Log errors + printf("NBIOStreamLogger: Unexpected error 0x%08x while reading from socket\n", result); + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(log_sock); + continue; + } + printf("%s", log_buf); + } while (result > 0); + + // Destroy the epoll and close the connection socket + result = sceNetEpollDestroy(log_epoll); + if (result < 0) { + printf("NBIOStreamLogger: Failed to destroy epoll, error 0x%08x\n", result); + } + result = sceNetSocketClose(log_sock); + if (result < 0) { + printf("NBIOStreamLogger: Failed to close connection, error 0x%08x\n", result); + } + } + result = sceNetSocketClose(stream_sock); + if (result < 0) { + printf("NBIOStreamLogger: Failed to close server socket, error 0x%08x\n", result); + } + return nullptr; +} + +void* NBIOStreamLogger::LoggingClientThread(void* user_arg) { + // user_arg here is the ClientArgs struct, unpack args before continuing + ClientArgs* argp = (ClientArgs*)user_arg; + const char* fmt = argp->fmt; + const u64 log_res = argp->log_res; + + // Because I want to make everything extraordinarily difficult, this creates a socket, connects to logging socket, sends message, closes socket. + s32 stream_sock = sceNetSocket("LoggerClientSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + if (stream_sock <= 0) { + printf("NBIOStreamLogger: Failed to create client socket, error 0x%08x\n", stream_sock); + return nullptr; + } + + OrbisNetSockaddrIn in_addr {}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_port = sceNetHtons(log_port); + s32 result = sceNetInetPton(ORBIS_NET_AF_INET, "127.0.0.1", &in_addr.sin_addr); + if (result < 1) { + printf("NBIOStreamLogger: Failed to retrieve loopback address, error 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + + // Set to non-blocking + s32 opt = 1; + result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, &opt, sizeof(opt)); + if (result < 0) { + printf("NBIOStreamLogger: Failed to mark client socket as non-blocking, error 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + + result = sceNetConnect(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); + while (result != 0) { + if (!logger_ready) { + // Logger was aborted while trying to log message + sceNetSocketClose(stream_sock); + return nullptr; + } + if (result == ORBIS_NET_ERROR_EINPROGRESS) { + // The connection could not be completed. Wait, then retry the connection. + sceKernelUsleep(100000); + result = sceNetConnect(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); + continue; + } else if (result == ORBIS_NET_ERROR_EISCONN) { + // Connection finished, break out of loop. + break; + } else { + printf("NBIOStreamLogger: Unexpected error while trying to connect to server, error 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + } + + // Create an epoll, use it to wait until socket is ready for writing. + s32 log_epoll = sceNetEpollCreate("LoggerMessageEpoll", 0); + if (log_epoll <= 0) { + printf("NBIOStreamLogger: Unexpected error while creating epoll, error 0x%08x\n", log_epoll); + sceNetSocketClose(stream_sock); + return nullptr; + } + + // Add the new socket to the epoll + OrbisNetEpollEvent epoll_event {}; + epoll_event.events = ORBIS_NET_EPOLLOUT; + OrbisNetEpollData epoll_data {}; + epoll_data.fd = stream_sock; + epoll_event.data = epoll_data; + result = sceNetEpollControl(log_epoll, ORBIS_NET_EPOLL_CTL_ADD, stream_sock, &epoll_event); + if (result < 0) { + printf("NBIOStreamLogger: Unexpected error while preparing epoll, error 0x%08x\n", result); + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(stream_sock); + return nullptr; + } + + // Perform an epoll_wait with indefinite timeout + OrbisNetEpollEvent epoll_ev_out {}; + result = sceNetEpollWait(log_epoll, &epoll_ev_out, 1, -1); + if (result < 0) { + printf("NBIOStreamLogger: Unexpected error while waiting for socket, error 0x%08x\n", result); + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(stream_sock); + return nullptr; + } else if (result == 0) { + printf("NBIOStreamLogger: Blocking epoll wait returned no ready fds?\n"); + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(stream_sock); + return nullptr; + } + + char send_buf[0x1000]; + memset(send_buf, 0, sizeof(send_buf)); + sprintf(send_buf, fmt, log_res); + result = sceNetSend(stream_sock, send_buf, strlen(send_buf), 0); + if (result < 0) { + printf("NBIOStreamLogger: Unexpected error while sending data to logger server, error 0x%08x\n", result); + } else if (result == 0) { + printf("NBIOStreamLogger: Failed to send data to logging server?\n"); + } + + result = sceNetEpollDestroy(log_epoll); + if (result < 0) { + printf("NBIOStreamLogger: Failed to destroy logger epoll, error 0x%08x\n", result); + } + + result = sceNetSocketClose(stream_sock); + if (result < 0) { + printf("NBIOStreamLogger: Failed to close logger socket, error 0x%08x\n", result); + } + return nullptr; +} + +NBIOStreamLogger::NBIOStreamLogger(bool async) { + std::scoped_lock lk {logger_mutex}; + // Initialize global "logger ready" value to false + logger_ready = false; + async_logging = async; + + // Create non-blocking stream socket logger thread. + pthread_attr_t thread_attr {}; + s32 result = pthread_attr_init(&thread_attr); + if (result < 0) { + printf("NBIOStreamLogger unable to initialize\n"); + return; + } + result = pthread_create(&logger_server_tid, &thread_attr, LoggingServerThread, nullptr); + if (result < 0) { + printf("NBIOStreamLogger unable to initialize\n"); + return; + } + + pthread_set_name_np(logger_server_tid, "LoggerServerThread"); + + // Block until logger thread finishes initializing + while (!logger_ready) { + sceKernelUsleep(10000); + } + + printf("NBIOStreamLogger initialized successfully\n"); +} + +NBIOStreamLogger::~NBIOStreamLogger() { + std::scoped_lock lk {logger_mutex}; + // Now go through and join with all child threads + for (pthread_t client_tid: client_tids) { + s32 result = pthread_join(client_tid, nullptr); + if (result < 0) { + printf("NBIOStreamLogger unable to terminate\n"); + } + } + + // Terminate logging thread. + // Since this class uses non-blocking sockets, we can just mark logger_ready as false, then wait for the thread to exit. + logger_ready = false; + s32 result = pthread_join(logger_server_tid, nullptr); + if (result < 0) { + printf("NBIOStreamLogger unable to terminate\n"); + } + + // Clear client_tids vector + client_tids.clear(); + // Manually destruct vector, as memory leaks otherwise. + client_tids.~vector(); + + printf("NBIOStreamLogger terminated successfully\n"); +} \ No newline at end of file diff --git a/tests/code/net_test/code/nbio_stream_logger.h b/tests/code/net_test/code/nbio_stream_logger.h new file mode 100644 index 0000000..d2ac6f9 --- /dev/null +++ b/tests/code/net_test/code/nbio_stream_logger.h @@ -0,0 +1,19 @@ +#include "logger.h" +#include "test.h" + +#include +#include + +#pragma once + +class NBIOStreamLogger: public Logger { + public: + NBIOStreamLogger(bool async); + ~NBIOStreamLogger() override; + + void LogMessage(const char* fmt, const u64 log_res) override; + + private: + static void* LoggingServerThread(void* user_arg); + static void* LoggingClientThread(void* user_arg); +}; \ No newline at end of file diff --git a/tests/code/net_test/code/test.cpp b/tests/code/net_test/code/test.cpp index 5a6a8b2..05dfd22 100644 --- a/tests/code/net_test/code/test.cpp +++ b/tests/code/net_test/code/test.cpp @@ -1,163 +1,21 @@ #include "test.h" #include "CppUTest/TestHarness.h" +#include "logger.h" +#include "nbio_stream_logger.h" TEST_GROUP (NetTest) { void setup() {} void teardown() {} }; -static constexpr u16 g_test_port = 8080; -static constexpr u16 g_log_port = 8181; -static bool g_server_ready = false; -static bool g_cond = false; -static bool g_logging_init = false; +static constexpr u16 g_test_port = 8080; +static bool g_server_ready = false; +static bool g_cond = false; +static Logger* g_active_logger = nullptr; -void* LoggingThread(void* user_arg) { - // To continue in the lovely steps of this test suite, this logger server will be built on socket communication. - s32 stream_sock = sceNetSocket("LoggerServer", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); - CHECK(stream_sock > 0); - - // Use setsockopt to ensure it uses specified addr and port - s32 opt = 1; - s32 result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, &opt, sizeof(opt)); - UNSIGNED_INT_EQUALS(0, result); - opt = 1; - result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, &opt, sizeof(opt)); - UNSIGNED_INT_EQUALS(0, result); - - // Bind this socket to a specific address and port - OrbisNetSockaddrIn in_addr {}; - in_addr.sin_family = ORBIS_NET_AF_INET; - in_addr.sin_addr.s_addr = ORBIS_NET_INADDR_ANY; - in_addr.sin_port = sceNetHtons(g_log_port); - result = sceNetBind(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); - UNSIGNED_INT_EQUALS(0, result); - - // Set to non-blocking - opt = 1; - result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, &opt, sizeof(opt)); - UNSIGNED_INT_EQUALS(0, result); - - result = sceNetListen(stream_sock, 3); - UNSIGNED_INT_EQUALS(0, result); - - // Socket is ready, mark server as initialized. - g_logging_init = true; - - // For the remainder of this test, this thread will run a loop. - while (g_logging_init) { - // Use accept to wait for, then connect with a client - u32 addr_len = sizeof(in_addr); - s32 log_sock = sceNetAccept(stream_sock, (OrbisNetSockaddr*)&in_addr, &addr_len); - if (!g_logging_init) { - // Logger terminated - if (log_sock > 0) { - sceNetSocketClose(log_sock); - } - break; - } else if (log_sock == ORBIS_NET_ERROR_EAGAIN) { - // Not ready for client, wait then loop. - sceKernelUsleep(10000); - continue; - } - - CHECK(log_sock > 0); - - // Create an epoll - s32 log_epoll = sceNetEpollCreate("LoggerMessageEpoll", 0); - CHECK(log_epoll > 0); - - // Add the new socket to the epoll - OrbisNetEpollEvent epoll_event {}; - epoll_event.events = ORBIS_NET_EPOLLIN; - OrbisNetEpollData epoll_data {}; - epoll_data.fd = log_sock; - epoll_event.data = epoll_data; - result = sceNetEpollControl(log_epoll, ORBIS_NET_EPOLL_CTL_ADD, log_sock, &epoll_event); - UNSIGNED_INT_EQUALS(0, result); - - // Perform an epoll_wait with indefinite timeout - OrbisNetEpollEvent epoll_ev_out {}; - result = sceNetEpollWait(log_epoll, &epoll_ev_out, 1, -1); - UNSIGNED_INT_EQUALS(1, result); - - // If epoll_wait returned a positive number of fds, then this socket is ready for reading. - if (result > 0) { - char log_buf[0x1000]; - memset(log_buf, 0, sizeof(log_buf)); - result = sceNetRecv(log_sock, log_buf, sizeof(log_buf), 0); - CHECK(result > 0); - printf("%s", log_buf); - } - - // Destroy the epoll and close the socket - sceNetEpollDestroy(log_epoll); - sceNetSocketClose(log_sock); - } - sceNetSocketClose(stream_sock); - return nullptr; -} - -void LogMessage(const char* fmt, u64 log_res) { - CHECK(g_logging_init); - - // Because I want to make everything extraordinarily difficult, this creates a socket, connects to logging socket, sends message, closes socket. - s32 stream_sock = sceNetSocket("LoggerClientSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); - CHECK(stream_sock > 0); - - OrbisNetSockaddrIn in_addr {}; - in_addr.sin_family = ORBIS_NET_AF_INET; - in_addr.sin_port = sceNetHtons(g_log_port); - s32 result = sceNetInetPton(ORBIS_NET_AF_INET, "127.0.0.1", &in_addr.sin_addr); - UNSIGNED_INT_EQUALS(1, result); - - // Set to non-blocking - s32 opt = 1; - result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, &opt, sizeof(opt)); - UNSIGNED_INT_EQUALS(0, result); - - result = sceNetConnect(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); - while (result != 0) { - if (result == ORBIS_NET_ERROR_EINPROGRESS || result == ORBIS_NET_ERROR_EAGAIN) { - // Connection is incomplete, wait, then retry. - sceKernelUsleep(10000); - result = sceNetConnect(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); - continue; - } else if (result == ORBIS_NET_ERROR_EISCONN) { - // Connection finished, break out of loop. - break; - } - // Should never reach here, if we did, then connect returned an unexpected error. - UNSIGNED_INT_EQUALS(0, result); - } - - // Create an epoll, use it to wait until socket is ready for writing. - s32 log_epoll = sceNetEpollCreate("LoggerMessageEpoll", 0); - CHECK(log_epoll > 0); - - // Add the new socket to the epoll - OrbisNetEpollEvent epoll_event {}; - epoll_event.events = ORBIS_NET_EPOLLOUT; - OrbisNetEpollData epoll_data {}; - epoll_data.fd = stream_sock; - epoll_event.data = epoll_data; - result = sceNetEpollControl(log_epoll, ORBIS_NET_EPOLL_CTL_ADD, stream_sock, &epoll_event); - UNSIGNED_INT_EQUALS(0, result); - - // Perform an epoll_wait with indefinite timeout - OrbisNetEpollEvent epoll_ev_out {}; - result = sceNetEpollWait(log_epoll, &epoll_ev_out, 1, -1); - UNSIGNED_INT_EQUALS(1, result); - - char send_buf[0x1000]; - memset(send_buf, 0, sizeof(send_buf)); - sprintf(send_buf, fmt, log_res); - result = sceNetSend(stream_sock, send_buf, strlen(send_buf), 0); - CHECK(result > 0); - - sceNetSocketClose(stream_sock); - sceNetEpollDestroy(log_epoll); +void LogMessage(const char* msg, const u64 log_res) { + g_active_logger->LogMessage(msg, log_res); } void* ServerThread(void* user_arg) { @@ -302,14 +160,7 @@ TEST(NetTest, Test) { result = pthread_attr_init(&thread_attr); printf("pthread_attr_init returns 0x%08x\n", result); - pthread_t logger_tid {}; - result = pthread_create(&logger_tid, &thread_attr, LoggingThread, nullptr); - printf("Logger thread created, pthread_create returns 0x%08x\n", result); - - // Wait for logging to initialize - while (!g_logging_init) { - sceKernelUsleep(10000); - } + g_active_logger = new NBIOStreamLogger(true); pthread_t server_tid {}; result = pthread_create(&server_tid, &thread_attr, ServerThread, nullptr); @@ -322,10 +173,7 @@ TEST(NetTest, Test) { result = pthread_join(server_tid, nullptr); result = pthread_join(client_tid, nullptr); - // Terminate logger thread - g_logging_init = false; - result = pthread_join(logger_tid, nullptr); - printf("All threads exited, tests complete\n"); + delete (g_active_logger); // Terminate libSceNet result = sceNetTerm(); diff --git a/tests/code/net_test/code/test.h b/tests/code/net_test/code/test.h index 3ae0517..2286dcb 100644 --- a/tests/code/net_test/code/test.h +++ b/tests/code/net_test/code/test.h @@ -241,4 +241,6 @@ u16 sceNetHtons(u16 host16); s32 sceNetInetPton(s32 af, const char* src, void* dst); s32 sceKernelUsleep(u32 micros); + +void pthread_set_name_np(pthread_t tid, const char* name); } \ No newline at end of file From 8730d59a5ede4fcc1aa7eb40a69634fd2a8aedb8 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 31 Jan 2026 10:33:58 -0600 Subject: [PATCH 04/11] Reorganize, fix potential bug with non-blocking logger thread --- tests/code/net_test/CMakeLists.txt | 2 +- .../net_test/code/loggers/bio_stream_logger.h | 19 +++++++++++ .../code/net_test/code/{ => loggers}/logger.h | 2 +- .../code/{ => loggers}/nbio_stream_logger.cpp | 34 ++++++++++++------- .../code/{ => loggers}/nbio_stream_logger.h | 2 +- tests/code/net_test/code/test.cpp | 5 +-- 6 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 tests/code/net_test/code/loggers/bio_stream_logger.h rename tests/code/net_test/code/{ => loggers}/logger.h (88%) rename tests/code/net_test/code/{ => loggers}/nbio_stream_logger.cpp (92%) rename tests/code/net_test/code/{ => loggers}/nbio_stream_logger.h (94%) diff --git a/tests/code/net_test/CMakeLists.txt b/tests/code/net_test/CMakeLists.txt index 1301cd2..1cee7b3 100644 --- a/tests/code/net_test/CMakeLists.txt +++ b/tests/code/net_test/CMakeLists.txt @@ -2,4 +2,4 @@ project(net_test LANGUAGES CXX) link_libraries(SceSystemService SceNet) -create_pkg("TNET00100" 1 00 "code/main.cpp;code/test.cpp;code/nbio_stream_logger.cpp") +create_pkg("TNET00100" 1 00 "code/main.cpp;code/test.cpp;code/loggers/nbio_stream_logger.cpp;code/loggers/bio_stream_logger.cpp") diff --git a/tests/code/net_test/code/loggers/bio_stream_logger.h b/tests/code/net_test/code/loggers/bio_stream_logger.h new file mode 100644 index 0000000..32796ae --- /dev/null +++ b/tests/code/net_test/code/loggers/bio_stream_logger.h @@ -0,0 +1,19 @@ +#include "logger.h" +#include "../test.h" + +#include +#include + +#pragma once + +class BIOStreamLogger: public Logger { + public: + BIOStreamLogger(); + ~BIOStreamLogger() override; + + void LogMessage(const char* fmt, const u64 log_res) override; + + private: + static void* LoggingServerThread(void* user_arg); + static void* LoggingClientThread(void* user_arg); +}; \ No newline at end of file diff --git a/tests/code/net_test/code/logger.h b/tests/code/net_test/code/loggers/logger.h similarity index 88% rename from tests/code/net_test/code/logger.h rename to tests/code/net_test/code/loggers/logger.h index f49db82..02ce01d 100644 --- a/tests/code/net_test/code/logger.h +++ b/tests/code/net_test/code/loggers/logger.h @@ -1,4 +1,4 @@ -#include "test.h" +#include "../test.h" #pragma once diff --git a/tests/code/net_test/code/nbio_stream_logger.cpp b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp similarity index 92% rename from tests/code/net_test/code/nbio_stream_logger.cpp rename to tests/code/net_test/code/loggers/nbio_stream_logger.cpp index 2cad68e..64050f3 100644 --- a/tests/code/net_test/code/nbio_stream_logger.cpp +++ b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp @@ -1,7 +1,5 @@ #include "nbio_stream_logger.h" -#include "test.h" - static constexpr u16 log_port = 8181; static constexpr s32 max_clients = 10; static bool logger_ready = false; @@ -51,7 +49,7 @@ void NBIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { char thread_name[128]; memset(thread_name, 0, sizeof(thread_name)); - sprintf(thread_name, "LoggerThread0x%08lx", client_tid); + sprintf(thread_name, "NBIOStreamLoggerThread0x%08lx", client_tid); pthread_set_name_np(client_tid, thread_name); if (async_logging) { @@ -68,7 +66,7 @@ void NBIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { void* NBIOStreamLogger::LoggingServerThread(void* user_arg) { // To continue in the lovely steps of this test suite, this logger server will be built on socket communication. - s32 stream_sock = sceNetSocket("LoggerServer", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + s32 stream_sock = sceNetSocket("NBIOStreamLoggerServerSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); if (stream_sock <= 0) { printf("NBIOStreamLogger unable to initialize, socket creation failed with 0x%08x\n", stream_sock); return nullptr; @@ -145,7 +143,7 @@ void* NBIOStreamLogger::LoggingServerThread(void* user_arg) { } // Create an epoll - s32 log_epoll = sceNetEpollCreate("LoggerMessageEpoll", 0); + s32 log_epoll = sceNetEpollCreate("NBIOStreamLoggerServerEpoll", 0); if (log_epoll <= 0) { // Log errors printf("NBIOStreamLogger: Unexpected error 0x%08x while creating epoll\n", log_epoll); @@ -227,7 +225,7 @@ void* NBIOStreamLogger::LoggingClientThread(void* user_arg) { const u64 log_res = argp->log_res; // Because I want to make everything extraordinarily difficult, this creates a socket, connects to logging socket, sends message, closes socket. - s32 stream_sock = sceNetSocket("LoggerClientSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + s32 stream_sock = sceNetSocket("NBIOStreamLoggerClientSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); if (stream_sock <= 0) { printf("NBIOStreamLogger: Failed to create client socket, error 0x%08x\n", stream_sock); return nullptr; @@ -275,7 +273,7 @@ void* NBIOStreamLogger::LoggingClientThread(void* user_arg) { } // Create an epoll, use it to wait until socket is ready for writing. - s32 log_epoll = sceNetEpollCreate("LoggerMessageEpoll", 0); + s32 log_epoll = sceNetEpollCreate("NBIOStreamLoggerClientEpoll", 0); if (log_epoll <= 0) { printf("NBIOStreamLogger: Unexpected error while creating epoll, error 0x%08x\n", log_epoll); sceNetSocketClose(stream_sock); @@ -311,21 +309,31 @@ void* NBIOStreamLogger::LoggingClientThread(void* user_arg) { return nullptr; } + // Prepare message to send char send_buf[0x1000]; memset(send_buf, 0, sizeof(send_buf)); sprintf(send_buf, fmt, log_res); - result = sceNetSend(stream_sock, send_buf, strlen(send_buf), 0); - if (result < 0) { - printf("NBIOStreamLogger: Unexpected error while sending data to logger server, error 0x%08x\n", result); - } else if (result == 0) { - printf("NBIOStreamLogger: Failed to send data to logging server?\n"); + + // To ensure the full message sends, we use a loop here. + u64 send_len = strlen(send_buf); + char* cur_buf_ptr = send_buf; + while (send_len > 0) { + // Send the requested log message to the server + result = sceNetSend(stream_sock, cur_buf_ptr, send_len, 0); + if (result < 0) { + printf("NBIOStreamLogger: Unexpected error while sending data to logger server, error 0x%08x\n", result); + break; + } + // Adjust remaining length and pointer in string based on the bytes sceNetSend returned. + send_len -= result; + cur_buf_ptr += result; } + // Clean up epoll and socket now that connection is complete. result = sceNetEpollDestroy(log_epoll); if (result < 0) { printf("NBIOStreamLogger: Failed to destroy logger epoll, error 0x%08x\n", result); } - result = sceNetSocketClose(stream_sock); if (result < 0) { printf("NBIOStreamLogger: Failed to close logger socket, error 0x%08x\n", result); diff --git a/tests/code/net_test/code/nbio_stream_logger.h b/tests/code/net_test/code/loggers/nbio_stream_logger.h similarity index 94% rename from tests/code/net_test/code/nbio_stream_logger.h rename to tests/code/net_test/code/loggers/nbio_stream_logger.h index d2ac6f9..b3612cc 100644 --- a/tests/code/net_test/code/nbio_stream_logger.h +++ b/tests/code/net_test/code/loggers/nbio_stream_logger.h @@ -1,5 +1,5 @@ +#include "../test.h" #include "logger.h" -#include "test.h" #include #include diff --git a/tests/code/net_test/code/test.cpp b/tests/code/net_test/code/test.cpp index 05dfd22..5caa8bf 100644 --- a/tests/code/net_test/code/test.cpp +++ b/tests/code/net_test/code/test.cpp @@ -1,8 +1,9 @@ #include "test.h" #include "CppUTest/TestHarness.h" -#include "logger.h" -#include "nbio_stream_logger.h" +#include "loggers/bio_stream_logger.h" +#include "loggers/logger.h" +#include "loggers/nbio_stream_logger.h" TEST_GROUP (NetTest) { void setup() {} From 9de61792134370f18e3c3df95a748b1b2dc39c9f Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:53:33 -0600 Subject: [PATCH 05/11] Update nbio_stream_logger.cpp --- .../net_test/code/loggers/nbio_stream_logger.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/code/net_test/code/loggers/nbio_stream_logger.cpp b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp index 64050f3..e3cfbcd 100644 --- a/tests/code/net_test/code/loggers/nbio_stream_logger.cpp +++ b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp @@ -15,7 +15,7 @@ struct ClientArgs { void NBIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { // Creates a thread that performs the logging - std::scoped_lock lk {logger_mutex}; + std::scoped_lock lk {logger_mutex}; pthread_t client_tid {}; pthread_attr_t thread_attr {}; @@ -49,7 +49,7 @@ void NBIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { char thread_name[128]; memset(thread_name, 0, sizeof(thread_name)); - sprintf(thread_name, "NBIOStreamLoggerThread0x%08lx", client_tid); + sprintf(thread_name, "NBIOStreamLoggerClientThread0x%08lx", client_tid); pthread_set_name_np(client_tid, thread_name); if (async_logging) { @@ -65,7 +65,7 @@ void NBIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { } void* NBIOStreamLogger::LoggingServerThread(void* user_arg) { - // To continue in the lovely steps of this test suite, this logger server will be built on socket communication. + // Create a socket to serve as the logger server s32 stream_sock = sceNetSocket("NBIOStreamLoggerServerSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); if (stream_sock <= 0) { printf("NBIOStreamLogger unable to initialize, socket creation failed with 0x%08x\n", stream_sock); @@ -224,13 +224,14 @@ void* NBIOStreamLogger::LoggingClientThread(void* user_arg) { const char* fmt = argp->fmt; const u64 log_res = argp->log_res; - // Because I want to make everything extraordinarily difficult, this creates a socket, connects to logging socket, sends message, closes socket. + // Create a socket to connect to the logger server s32 stream_sock = sceNetSocket("NBIOStreamLoggerClientSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); if (stream_sock <= 0) { printf("NBIOStreamLogger: Failed to create client socket, error 0x%08x\n", stream_sock); return nullptr; } + // Retrieve loopback address to connect to OrbisNetSockaddrIn in_addr {}; in_addr.sin_family = ORBIS_NET_AF_INET; in_addr.sin_port = sceNetHtons(log_port); @@ -250,6 +251,7 @@ void* NBIOStreamLogger::LoggingClientThread(void* user_arg) { return nullptr; } + // Connect to the server result = sceNetConnect(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); while (result != 0) { if (!logger_ready) { @@ -315,7 +317,7 @@ void* NBIOStreamLogger::LoggingClientThread(void* user_arg) { sprintf(send_buf, fmt, log_res); // To ensure the full message sends, we use a loop here. - u64 send_len = strlen(send_buf); + u64 send_len = strlen(send_buf); char* cur_buf_ptr = send_buf; while (send_len > 0) { // Send the requested log message to the server @@ -360,7 +362,7 @@ NBIOStreamLogger::NBIOStreamLogger(bool async) { return; } - pthread_set_name_np(logger_server_tid, "LoggerServerThread"); + pthread_set_name_np(logger_server_tid, "NBIOStreamLoggerServerThread"); // Block until logger thread finishes initializing while (!logger_ready) { From 49aa054b71e0b5d591a2ae21c3087515c10ecfd4 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 31 Jan 2026 12:09:59 -0600 Subject: [PATCH 06/11] Update test thread code to be runnable multiple times, and use test macros to validate returns. --- tests/code/net_test/code/test.cpp | 54 ++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/tests/code/net_test/code/test.cpp b/tests/code/net_test/code/test.cpp index 5caa8bf..68f36f6 100644 --- a/tests/code/net_test/code/test.cpp +++ b/tests/code/net_test/code/test.cpp @@ -16,6 +16,9 @@ static bool g_cond = false; static Logger* g_active_logger = nullptr; void LogMessage(const char* msg, const u64 log_res) { + if (!g_active_logger) { + return; + } g_active_logger->LogMessage(msg, log_res); } @@ -24,14 +27,17 @@ void* ServerThread(void* user_arg) { // Create a stream socket s32 stream_sock = sceNetSocket("TestStreamServer", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); LogMessage("Server: socket(AF_INET, SOCK_STREAM, 0) returns 0x%08x\n", stream_sock); + CHECK(stream_sock > 0); // Use setsockopt to ensure it uses specified addr and port s32 opt = 1; s32 result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, &opt, sizeof(opt)); LogMessage("Server: setsockopt(SOL_SOCKET, SO_REUSEADDR) returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); opt = 1; result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, &opt, sizeof(opt)); LogMessage("Server: setsockopt(SOL_SOCKET, SO_REUSEPORT) returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); // Bind this socket to a specific address and port OrbisNetSockaddrIn in_addr {}; @@ -40,10 +46,12 @@ void* ServerThread(void* user_arg) { in_addr.sin_port = sceNetHtons(g_test_port); result = sceNetBind(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); LogMessage("Server: bind returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); // Mark the socket as passive result = sceNetListen(stream_sock, 3); LogMessage("Server: listen returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); // Mark server as ready, so client knows to try connecting. g_server_ready = true; @@ -52,21 +60,27 @@ void* ServerThread(void* user_arg) { u32 addr_len = sizeof(in_addr); s32 connected_sock = sceNetAccept(stream_sock, (OrbisNetSockaddr*)&in_addr, &addr_len); LogMessage("Server: accept returns 0x%08x\n", connected_sock); + CHECK(connected_sock > 0); // Send a message to the client thread const char* message = "This is a test message coming from the server"; result = sceNetSend(connected_sock, message, strlen(message), 0); LogMessage("Server: send returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(strlen(message), result); // Receive a message from the client thread - char buffer[1024]; + const char* expected_message = "This is a test message coming from the client"; + char buffer[1024]; memset(buffer, 0, sizeof(buffer)); result = sceNetRecv(connected_sock, buffer, sizeof(buffer), 0); LogMessage("Server: recv returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(strlen(expected_message), result); + STRCMP_EQUAL(expected_message, buffer); // Create an epoll, these are primarily used to wait for a file descriptor s32 epoll = sceNetEpollCreate("ServerEpoll", 0); LogMessage("Server: epoll_create returns 0x%08x\n", epoll); + CHECK(epoll > 0); // These can take any "s32", and an "event" describing what to wait on. OrbisNetEpollEvent epoll_event {}; @@ -78,6 +92,7 @@ void* ServerThread(void* user_arg) { epoll_event.data = epoll_data; result = sceNetEpollControl(epoll, ORBIS_NET_EPOLL_CTL_ADD, connected_sock, &epoll_event); LogMessage("Server: epoll_ctl returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); // Perform an epoll_wait with no timeout. OrbisNetEpollEvent epoll_ev_out {}; @@ -85,6 +100,7 @@ void* ServerThread(void* user_arg) { // epoll_wait returns number of ready FDs that were polled. // Until client thread sends a message, this should be 0. LogMessage("Server: epoll_wait returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); // Tell client to send data over the socket g_cond = true; @@ -93,15 +109,31 @@ void* ServerThread(void* user_arg) { // -1 timeout indicates no timeout. result = sceNetEpollWait(epoll, &epoll_ev_out, 1, -1); LogMessage("Server: epoll_wait returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(1, result); // Now it should be safe to read from the socket. memset(buffer, 0, sizeof(buffer)); result = sceNetRecv(connected_sock, buffer, sizeof(buffer), 0); LogMessage("Server: recv returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(strlen(expected_message), result); + STRCMP_EQUAL(expected_message, buffer); // Close the epoll result = sceNetEpollDestroy(epoll); LogMessage("Server: epoll_destroy returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); + + // Close sockets + result = sceNetSocketClose(connected_sock); + LogMessage("Server: socket_close returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); + result = sceNetSocketClose(stream_sock); + LogMessage("Server: socket_close returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); + + // Update globals used for testing. + g_server_ready = false; + g_cond = false; return nullptr; } @@ -116,6 +148,7 @@ void* ClientThread(void* user_arg) { // Create a stream socket s32 stream_sock = sceNetSocket("TestStreamSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); LogMessage("Client: socket(AF_INET, SOCK_STREAM, 0) returns 0x%08x\n", stream_sock); + CHECK(stream_sock > 0); // Connect the socket to the server thread's socket OrbisNetSockaddrIn in_addr {}; @@ -123,19 +156,25 @@ void* ClientThread(void* user_arg) { in_addr.sin_port = sceNetHtons(g_test_port); s32 result = sceNetInetPton(ORBIS_NET_AF_INET, "127.0.0.1", &in_addr.sin_addr); LogMessage("Client: inet_pton(ORBIS_NET_AF_INET) returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(1, result); result = sceNetConnect(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); LogMessage("Client: connect returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); // Read a message through the connected socket - char buffer[1024]; + const char* expected_message = "This is a test message coming from the server"; + char buffer[1024]; memset(buffer, 0, sizeof(buffer)); result = sceNetRecv(stream_sock, buffer, sizeof(buffer), 0); LogMessage("Client: recv returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(strlen(expected_message), result); + STRCMP_EQUAL(expected_message, buffer); // Send a message to the server through this socket const char* message = "This is a test message coming from the client"; result = sceNetSend(stream_sock, message, strlen(message), 0); LogMessage("Client: send returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(strlen(message), result); // Wait for g_cond to be true, this will serve as a signal to test epoll behavior while (!g_cond) { @@ -146,6 +185,12 @@ void* ClientThread(void* user_arg) { // Server wants to test epoll behavior, send data over the socket result = sceNetSend(stream_sock, message, strlen(message), 0); LogMessage("Client: send returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(strlen(message), result); + + // Close socket to conclude tests + result = sceNetSocketClose(stream_sock); + LogMessage("Client: socket_close returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); return nullptr; } @@ -153,15 +198,14 @@ void* ClientThread(void* user_arg) { TEST(NetTest, Test) { // Init libSceNet s32 result = sceNetInit(); - printf("sceNetInit returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); // Create a server and client thread. // These will serve as a way of testing behavior all in one application, hopefully. pthread_attr_t thread_attr {}; result = pthread_attr_init(&thread_attr); - printf("pthread_attr_init returns 0x%08x\n", result); - g_active_logger = new NBIOStreamLogger(true); + UNSIGNED_INT_EQUALS(0, result); pthread_t server_tid {}; result = pthread_create(&server_tid, &thread_attr, ServerThread, nullptr); From aa912ad55e5b9a04f7ef5960f3bc9dc6ccc27736 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 31 Jan 2026 12:10:35 -0600 Subject: [PATCH 07/11] Another non-blocking logger fix Need to copy log result, otherwise it can change on the fly. --- tests/code/net_test/code/loggers/nbio_stream_logger.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/code/net_test/code/loggers/nbio_stream_logger.cpp b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp index e3cfbcd..e4ad3b6 100644 --- a/tests/code/net_test/code/loggers/nbio_stream_logger.cpp +++ b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp @@ -17,6 +17,9 @@ void NBIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { // Creates a thread that performs the logging std::scoped_lock lk {logger_mutex}; + // Make a copy of log_res to make sure that data isn't destroyed + const u64 result_to_log = log_res; + pthread_t client_tid {}; pthread_attr_t thread_attr {}; s32 result = pthread_attr_init(&thread_attr); @@ -40,7 +43,7 @@ void NBIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { } // For the user args, submit both arguments through a struct - ClientArgs user_args {fmt, log_res}; + ClientArgs user_args {fmt, result_to_log}; result = pthread_create(&client_tid, &thread_attr, LoggingClientThread, &user_args); if (result < 0) { printf("NBIOStreamLogger unable to send message, pthread_create failed with 0x%08x\n", result); @@ -368,8 +371,6 @@ NBIOStreamLogger::NBIOStreamLogger(bool async) { while (!logger_ready) { sceKernelUsleep(10000); } - - printf("NBIOStreamLogger initialized successfully\n"); } NBIOStreamLogger::~NBIOStreamLogger() { @@ -394,6 +395,4 @@ NBIOStreamLogger::~NBIOStreamLogger() { client_tids.clear(); // Manually destruct vector, as memory leaks otherwise. client_tids.~vector(); - - printf("NBIOStreamLogger terminated successfully\n"); } \ No newline at end of file From fa88742de29b920bbc31b026f07e78a5c5c82f86 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 31 Jan 2026 12:12:15 -0600 Subject: [PATCH 08/11] Blocking IO stream socket logger A far more simplistic logging backend, where blocking IO saves us from needing to mess with epolls, and we don't need to deal with the various errors coming from incomplete connections or messages. --- .../code/loggers/bio_stream_logger.cpp | 231 ++++++++++++++++++ .../net_test/code/loggers/bio_stream_logger.h | 2 +- 2 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 tests/code/net_test/code/loggers/bio_stream_logger.cpp diff --git a/tests/code/net_test/code/loggers/bio_stream_logger.cpp b/tests/code/net_test/code/loggers/bio_stream_logger.cpp new file mode 100644 index 0000000..5635e82 --- /dev/null +++ b/tests/code/net_test/code/loggers/bio_stream_logger.cpp @@ -0,0 +1,231 @@ +#include "bio_stream_logger.h" + +static constexpr u16 log_port = 8181; +static bool logger_ready = false; +static std::mutex logger_mutex {}; +static pthread_t logger_server_tid {}; + +struct ClientArgs { + const char* fmt; + const u64 log_res; +}; + +void BIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { + // Creates a thread that performs the logging + std::scoped_lock lk {logger_mutex}; + + pthread_t client_tid {}; + pthread_attr_t thread_attr {}; + s32 result = pthread_attr_init(&thread_attr); + if (result < 0) { + printf("BIOStreamLogger unable to send message, pthread_attr_init failed with 0x%08x\n", result); + return; + } + + // For the user args, submit both arguments through a struct + ClientArgs user_args {fmt, log_res}; + result = pthread_create(&client_tid, &thread_attr, LoggingClientThread, &user_args); + if (result < 0) { + printf("BIOStreamLogger unable to send message, pthread_create failed with 0x%08x\n", result); + return; + } + + char thread_name[128]; + memset(thread_name, 0, sizeof(thread_name)); + sprintf(thread_name, "BIOStreamLoggerClientThread0x%08lx", client_tid); + pthread_set_name_np(client_tid, thread_name); + + // Wait for logger thread to terminate + result = pthread_join(client_tid, nullptr); + if (result < 0) { + printf("LogMessage thread failed to terminate\n"); + } +} + +void* BIOStreamLogger::LoggingServerThread(void* user_arg) { + // To continue in the lovely steps of this test suite, this logger server will be built on socket communication. + s32 stream_sock = sceNetSocket("BIOStreamLoggerServerSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + if (stream_sock <= 0) { + printf("BIOStreamLogger unable to initialize, socket creation failed with 0x%08x\n", stream_sock); + return nullptr; + } + + // Use setsockopt to ensure it uses specified addr and port + s32 opt = 1; + s32 result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, &opt, sizeof(opt)); + if (result < 0) { + printf("BIOStreamLogger unable to initialize, setsockopt failed with 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + opt = 1; + result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, &opt, sizeof(opt)); + if (result < 0) { + printf("BIOStreamLogger unable to initialize, setsockopt failed with 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + + // Bind this socket to a specific address and port + OrbisNetSockaddrIn in_addr {}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_addr.s_addr = ORBIS_NET_INADDR_ANY; + in_addr.sin_port = sceNetHtons(log_port); + result = sceNetBind(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); + if (result < 0) { + printf("BIOStreamLogger unable to initialize, bind failed with 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + + // Mark socket as passive. There will only ever be one client at a time. + result = sceNetListen(stream_sock, 1); + if (result < 0) { + printf("BIOStreamLogger unable to initialize, listen failed with 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + + // Socket is ready, mark server as initialized. + logger_ready = true; + + // For the remainder of this test, this thread will run a loop. + while (logger_ready) { + // Use accept to wait for, then connect with a client + u32 addr_len = sizeof(in_addr); + s32 log_sock = sceNetAccept(stream_sock, (OrbisNetSockaddr*)&in_addr, &addr_len); + if (!logger_ready) { + // Logger terminated + if (log_sock > 0) { + // Accept succeeded after logging terminated, abort anyway. + sceNetSocketClose(log_sock); + } + break; + } else if (log_sock <= 0) { + // Log errors + printf("BIOStreamLogger: Unexpected error 0x%08x while waiting for client\n", log_sock); + continue; + } + + // Read in the message from the socket + // Since this logger uses blocking socets, all sends should be complete data, so this will retrieve full packets. + char log_buf[0x1000]; + memset(log_buf, 0, sizeof(log_buf)); + result = sceNetRecv(log_sock, log_buf, sizeof(log_buf), 0); + if (result <= 0) { + // Log errors + printf("BIOStreamLogger: Unexpected error 0x%08x while reading from socket\n", result); + sceNetSocketClose(log_sock); + continue; + } + printf("%s", log_buf); + + // Close the connection socket + result = sceNetSocketClose(log_sock); + if (result < 0) { + printf("BIOStreamLogger: Failed to close connection, error 0x%08x\n", result); + } + } + + // Close the server socket + result = sceNetSocketClose(stream_sock); + if (result < 0) { + printf("BIOStreamLogger: Failed to close server socket, error 0x%08x\n", result); + } + return nullptr; +} + +void* BIOStreamLogger::LoggingClientThread(void* user_arg) { + // user_arg here is the ClientArgs struct, unpack args before continuing + ClientArgs* argp = (ClientArgs*)user_arg; + const char* fmt = argp->fmt; + const u64 log_res = argp->log_res; + + // Create a socket for the connection + s32 stream_sock = sceNetSocket("BIOStreamLoggerClientSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + if (stream_sock <= 0) { + printf("BIOStreamLogger: Failed to create client socket, error 0x%08x\n", stream_sock); + return nullptr; + } + + // Get the loopback address for the connection + OrbisNetSockaddrIn in_addr {}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_port = sceNetHtons(log_port); + s32 result = sceNetInetPton(ORBIS_NET_AF_INET, "127.0.0.1", &in_addr.sin_addr); + if (result < 1) { + printf("BIOStreamLogger: Failed to retrieve loopback address, error 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + + // Connect to the server + result = sceNetConnect(stream_sock, (OrbisNetSockaddr*)&in_addr, sizeof(in_addr)); + if (!logger_ready) { + // Logging was disabled, abort here. + sceNetSocketClose(stream_sock); + return nullptr; + } else if (result < 0) { + // Non-blocking socket connect should block until success, unless a server error occurs + printf("BIOStreamLogger: Unexpected error while trying to connect to server, error 0x%08x\n", result); + sceNetSocketClose(stream_sock); + return nullptr; + } + + char send_buf[0x1000]; + memset(send_buf, 0, sizeof(send_buf)); + sprintf(send_buf, fmt, log_res); + result = sceNetSend(stream_sock, send_buf, strlen(send_buf), 0); + if (result <= 0) { + printf("BIOStreamLogger: Unexpected error while sending data to logger server, error 0x%08x\n", result); + } + + result = sceNetSocketClose(stream_sock); + if (result < 0) { + printf("BIOStreamLogger: Failed to close logger socket, error 0x%08x\n", result); + } + return nullptr; +} + +BIOStreamLogger::BIOStreamLogger() { + std::scoped_lock lk {logger_mutex}; + // Initialize global "logger ready" value to false + logger_ready = false; + + // Create non-blocking stream socket logger thread. + pthread_attr_t thread_attr {}; + s32 result = pthread_attr_init(&thread_attr); + if (result < 0) { + printf("BIOStreamLogger unable to initialize\n"); + return; + } + result = pthread_create(&logger_server_tid, &thread_attr, LoggingServerThread, nullptr); + if (result < 0) { + printf("BIOStreamLogger unable to initialize\n"); + return; + } + + pthread_set_name_np(logger_server_tid, "BIOStreamLoggerServerThread"); + + // Block until logger thread finishes initializing + while (!logger_ready) { + sceKernelUsleep(10000); + } +} + +BIOStreamLogger::~BIOStreamLogger() { + // Terminate logging thread. + // Since this server uses blocking socket IO, we need to mark the server as disabled, then connect with the server to free it. + { + std::scoped_lock lk {logger_mutex}; + logger_ready = false; + } + + // This message shouldn't make it through to stdout, as checks on server and client side will terminate both before any logging. + LogMessage("Closing BIOLoggerServer%lx\n", 0); + + s32 result = pthread_join(logger_server_tid, nullptr); + if (result < 0) { + printf("BIOStreamLogger unable to terminate\n"); + } +} \ No newline at end of file diff --git a/tests/code/net_test/code/loggers/bio_stream_logger.h b/tests/code/net_test/code/loggers/bio_stream_logger.h index 32796ae..532bdc3 100644 --- a/tests/code/net_test/code/loggers/bio_stream_logger.h +++ b/tests/code/net_test/code/loggers/bio_stream_logger.h @@ -1,5 +1,5 @@ -#include "logger.h" #include "../test.h" +#include "logger.h" #include #include From 2a048e006dad958e69f0a7b579ac1da93ba05d24 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 31 Jan 2026 12:13:59 -0600 Subject: [PATCH 09/11] Update main test suite to integrate different logging backends. Now the loggers are actually relevant to the tests. The "first" round of tests runs with no logging backend, perfect for ensuring basic behavior works. The second round runs with the blocking sockets logging backend, third round with the non-blocking sync logging, and finally the last round uses non-blocking async logging. --- tests/code/net_test/code/test.cpp | 42 +++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/tests/code/net_test/code/test.cpp b/tests/code/net_test/code/test.cpp index 68f36f6..6e21c4c 100644 --- a/tests/code/net_test/code/test.cpp +++ b/tests/code/net_test/code/test.cpp @@ -204,23 +204,51 @@ TEST(NetTest, Test) { // These will serve as a way of testing behavior all in one application, hopefully. pthread_attr_t thread_attr {}; result = pthread_attr_init(&thread_attr); - g_active_logger = new NBIOStreamLogger(true); UNSIGNED_INT_EQUALS(0, result); pthread_t server_tid {}; - result = pthread_create(&server_tid, &thread_attr, ServerThread, nullptr); - printf("Server thread created, pthread_create returns 0x%08x\n", result); pthread_t client_tid {}; - result = pthread_create(&client_tid, &thread_attr, ClientThread, nullptr); - printf("Client thread created, pthread_create returns 0x%08x\n", result); - // Wait for all threads to exit + // Run these tests each with a different logger, the tests mostly serve as a way for testers to confirm the loggers work. + printf("Main: Running first round of tests\n"); + // Run with no logging, this will test for basic blocking stream socket behavior without interference. + result = pthread_create(&server_tid, &thread_attr, ServerThread, nullptr); + result = pthread_create(&client_tid, &thread_attr, ClientThread, nullptr); result = pthread_join(server_tid, nullptr); result = pthread_join(client_tid, nullptr); + printf("Main: Running second round of tests\n"); + // Run tests with a logger built around blocking stream sockets. + g_active_logger = new BIOStreamLogger(); + result = pthread_create(&server_tid, &thread_attr, ServerThread, nullptr); + result = pthread_create(&client_tid, &thread_attr, ClientThread, nullptr); + result = pthread_join(server_tid, nullptr); + result = pthread_join(client_tid, nullptr); + delete (g_active_logger); + + printf("Main: Running third round of tests\n"); + // Run tests with a logger built around non-blocking stream sockets running synchronously + g_active_logger = new NBIOStreamLogger(false); + result = pthread_create(&server_tid, &thread_attr, ServerThread, nullptr); + result = pthread_create(&client_tid, &thread_attr, ClientThread, nullptr); + result = pthread_join(server_tid, nullptr); + result = pthread_join(client_tid, nullptr); + delete (g_active_logger); + + printf("Main: Running fourth round of tests\n"); + // Run tests with a logger built around non-blocking stream sockets running asynchronously + g_active_logger = new NBIOStreamLogger(true); + result = pthread_create(&server_tid, &thread_attr, ServerThread, nullptr); + result = pthread_create(&client_tid, &thread_attr, ClientThread, nullptr); + result = pthread_join(server_tid, nullptr); + result = pthread_join(client_tid, nullptr); delete (g_active_logger); + // Logger behavior is demonstrated through logged messages, errors will log if the logger behaves differently from real hardware. + // The server and client threads here log errors through the current logging backend, but also run Cpputest macros to validate behavior. + printf("Main: Test suite completed\n"); + // Terminate libSceNet result = sceNetTerm(); - printf("sceNetTerm returns 0x%08x\n", result); + UNSIGNED_INT_EQUALS(0, result); } From 63c75b4d8ec8959b1c55195fa2185a5998125f3e Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:34:47 -0600 Subject: [PATCH 10/11] NBIOStreamLogger copy args to client thread before leaving LogMessage. Didn't cause issues too often on PS4, but was an issue that could occur. --- .../code/loggers/nbio_stream_logger.cpp | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/tests/code/net_test/code/loggers/nbio_stream_logger.cpp b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp index e4ad3b6..e200c81 100644 --- a/tests/code/net_test/code/loggers/nbio_stream_logger.cpp +++ b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp @@ -9,17 +9,22 @@ static std::vector client_tids {}; static bool async_logging = false; struct ClientArgs { - const char* fmt; - const u64 log_res; + u64 log_res; + char fmt[1024]; + bool copied; }; void NBIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { - // Creates a thread that performs the logging std::scoped_lock lk {logger_mutex}; - // Make a copy of log_res to make sure that data isn't destroyed - const u64 result_to_log = log_res; + // Make copies of both parameters to ensure data is not destroyed + ClientArgs data_to_pass{}; + memset(&data_to_pass, 0, sizeof(data_to_pass)); + data_to_pass.log_res = log_res; + strncpy(data_to_pass.fmt, fmt, strlen(fmt) + 1); + data_to_pass.copied = false; + // Creates a thread that performs the logging pthread_t client_tid {}; pthread_attr_t thread_attr {}; s32 result = pthread_attr_init(&thread_attr); @@ -43,18 +48,23 @@ void NBIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { } // For the user args, submit both arguments through a struct - ClientArgs user_args {fmt, result_to_log}; - result = pthread_create(&client_tid, &thread_attr, LoggingClientThread, &user_args); + result = pthread_create(&client_tid, &thread_attr, LoggingClientThread, &data_to_pass); if (result < 0) { printf("NBIOStreamLogger unable to send message, pthread_create failed with 0x%08x\n", result); return; } + // Give the created thread a name char thread_name[128]; memset(thread_name, 0, sizeof(thread_name)); sprintf(thread_name, "NBIOStreamLoggerClientThread0x%08lx", client_tid); pthread_set_name_np(client_tid, thread_name); + // Wait for logging client thread to indicate that data was properly copied before continuing. + while (!data_to_pass.copied) { + sceKernelUsleep(10000); + } + if (async_logging) { // Add new client thread id to vector, to be exited on-demand or in destructor client_tids.emplace_back(client_tid); @@ -222,10 +232,17 @@ void* NBIOStreamLogger::LoggingServerThread(void* user_arg) { } void* NBIOStreamLogger::LoggingClientThread(void* user_arg) { - // user_arg here is the ClientArgs struct, unpack args before continuing - ClientArgs* argp = (ClientArgs*)user_arg; - const char* fmt = argp->fmt; - const u64 log_res = argp->log_res; + // user_arg here is a ClientArgs struct + ClientArgs* user_argp = (ClientArgs*)user_arg; + + // To ensure async logging doesn't break, we need to copy data from user args to a local variable. + ClientArgs argp{}; + memcpy(&argp, user_arg, sizeof(ClientArgs)); + const char* fmt = argp.fmt; + const u64 log_res = argp.log_res; + + // Update args to state data was copied. + user_argp->copied = true; // Create a socket to connect to the logger server s32 stream_sock = sceNetSocket("NBIOStreamLoggerClientSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); From 8203bc2c8579916bd5909b81bbc452bd7408eb1a Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:35:04 -0600 Subject: [PATCH 11/11] Format --- tests/code/net_test/code/loggers/nbio_stream_logger.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/code/net_test/code/loggers/nbio_stream_logger.cpp b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp index e200c81..21aad11 100644 --- a/tests/code/net_test/code/loggers/nbio_stream_logger.cpp +++ b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp @@ -18,7 +18,7 @@ void NBIOStreamLogger::LogMessage(const char* fmt, const u64 log_res) { std::scoped_lock lk {logger_mutex}; // Make copies of both parameters to ensure data is not destroyed - ClientArgs data_to_pass{}; + ClientArgs data_to_pass {}; memset(&data_to_pass, 0, sizeof(data_to_pass)); data_to_pass.log_res = log_res; strncpy(data_to_pass.fmt, fmt, strlen(fmt) + 1); @@ -234,9 +234,9 @@ void* NBIOStreamLogger::LoggingServerThread(void* user_arg) { void* NBIOStreamLogger::LoggingClientThread(void* user_arg) { // user_arg here is a ClientArgs struct ClientArgs* user_argp = (ClientArgs*)user_arg; - + // To ensure async logging doesn't break, we need to copy data from user args to a local variable. - ClientArgs argp{}; + ClientArgs argp {}; memcpy(&argp, user_arg, sizeof(ClientArgs)); const char* fmt = argp.fmt; const u64 log_res = argp.log_res;