diff --git a/tests/code/net_test/CMakeLists.txt b/tests/code/net_test/CMakeLists.txt new file mode 100644 index 0000000..1cee7b3 --- /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;code/loggers/nbio_stream_logger.cpp;code/loggers/bio_stream_logger.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/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 new file mode 100644 index 0000000..532bdc3 --- /dev/null +++ b/tests/code/net_test/code/loggers/bio_stream_logger.h @@ -0,0 +1,19 @@ +#include "../test.h" +#include "logger.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/loggers/logger.h b/tests/code/net_test/code/loggers/logger.h new file mode 100644 index 0000000..02ce01d --- /dev/null +++ b/tests/code/net_test/code/loggers/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/loggers/nbio_stream_logger.cpp b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp new file mode 100644 index 0000000..21aad11 --- /dev/null +++ b/tests/code/net_test/code/loggers/nbio_stream_logger.cpp @@ -0,0 +1,415 @@ +#include "nbio_stream_logger.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 { + u64 log_res; + char fmt[1024]; + bool copied; +}; + +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 {}; + 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); + 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 + 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); + } 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) { + // 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); + 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("NBIOStreamLoggerServerEpoll", 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 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); + 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); + 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; + } + + // Connect to the server + 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("NBIOStreamLoggerClientEpoll", 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; + } + + // Prepare message to send + char send_buf[0x1000]; + memset(send_buf, 0, sizeof(send_buf)); + sprintf(send_buf, fmt, log_res); + + // 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); + } + 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, "NBIOStreamLoggerServerThread"); + + // Block until logger thread finishes initializing + while (!logger_ready) { + sceKernelUsleep(10000); + } +} + +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(); +} \ No newline at end of file diff --git a/tests/code/net_test/code/loggers/nbio_stream_logger.h b/tests/code/net_test/code/loggers/nbio_stream_logger.h new file mode 100644 index 0000000..b3612cc --- /dev/null +++ b/tests/code/net_test/code/loggers/nbio_stream_logger.h @@ -0,0 +1,19 @@ +#include "../test.h" +#include "logger.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/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..6e21c4c --- /dev/null +++ b/tests/code/net_test/code/test.cpp @@ -0,0 +1,254 @@ +#include "test.h" + +#include "CppUTest/TestHarness.h" +#include "loggers/bio_stream_logger.h" +#include "loggers/logger.h" +#include "loggers/nbio_stream_logger.h" + +TEST_GROUP (NetTest) { + void setup() {} + void teardown() {} +}; + +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 LogMessage(const char* msg, const u64 log_res) { + if (!g_active_logger) { + return; + } + g_active_logger->LogMessage(msg, log_res); +} + +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); + 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 {}; + 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); + 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; + + // 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); + 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 + 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 {}; + // 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); + UNSIGNED_INT_EQUALS(0, 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); + UNSIGNED_INT_EQUALS(0, 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); + 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; +} + +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); + CHECK(stream_sock > 0); + + // 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); + 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 + 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) { + 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); + 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; +} + +TEST(NetTest, Test) { + // Init libSceNet + s32 result = sceNetInit(); + 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); + UNSIGNED_INT_EQUALS(0, result); + + pthread_t server_tid {}; + pthread_t client_tid {}; + + // 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(); + UNSIGNED_INT_EQUALS(0, 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..2286dcb --- /dev/null +++ b/tests/code/net_test/code/test.h @@ -0,0 +1,246 @@ +#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); + +void pthread_set_name_np(pthread_t tid, const char* name); +} \ No newline at end of file