From 2a2f1003b00675ef83be6435bbba32e3cec51c7f Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 6 Oct 2026 19:14:56 +0300 Subject: [PATCH] Major net rewrite (#5236) * p2p crypto schema * adding p2p keyring * p2p framing and testing * fixups * test fix * missing definations * implementation of host functions * tcp/transport of p2p packets * improvements in binding * guest_net ,improvements and test cases * all mighty clang * fixing macOS tests failures * misc * net_rewrite : net_errors , types and translation schemas * updated fixed chunks transfer on windows * bye bye old net * small updates for the new net * settings and tests * net lib first draft * allow more than 1 P2P instances on from same pc * improved shadnet replies * possible fix for jojo * cleanup * move net files to libaries/net * fixed macOS tests * mighty clang * fixed cmakelists * don't cosume inviation event on client (test code) * fix macOS tests * Large multi-file rework for Signaling and Matching2 network handling / optimization. Consolidates Matching2-signaling and signaling to one process (signaling_handler) General cleanup and consolidation of matching2 context management > nphandler Removal of a lot SIgnaling overall structure cleanup. Matching2: added get and set roommemberdatainternal and the proper type / shadnet call / request / reply. Consolidated signaling/matching2 typedefs to np_types2 Utilized Codex as a reviewer and for general cleanup. * all mighty clang * fixed member attributes * fixed MAPPED address for matching signaling * Fixed sceNpMatching2SignalingGetLocalNetInfo * fixups on net core/library based on fw * improved errno * spilt p2p to net_p2p * some cleanups * fix for localinfo * clang * fix up * cleanups * cleanups * added KernelFstat * fixed fstat * one more fix for fstat... * cleanup old deprecated code * cleanup * General logging cleanup across NP changes. Thanks Stephen! * cleanup based on stephen's suggestions * fixup * fixing tests * blah * fixup * improve memory pools * cleaunp * force https disable option * clang * @stephen's fixes * added network field in settings and ported old network ones --------- Co-authored-by: Metr1k --- CMakeLists.txt | 61 +- src/core/emulator_settings.cpp | 56 +- src/core/emulator_settings.h | 83 +- src/core/file_sys/fs.cpp | 24 - src/core/file_sys/fs.h | 13 - .../libraries/companion/companion_httpd.h | 2 +- .../invitation_dialog_ui.cpp | 2 +- src/core/libraries/kernel/file_system.cpp | 363 +- src/core/libraries/kernel/kernel.cpp | 8 +- src/core/libraries/libs.cpp | 2 +- src/core/libraries/net/net.cpp | 3054 +++++++++++++++++ src/core/libraries/net/net.h | 255 ++ .../libraries/{network => net}/net_error.h | 90 +- src/core/libraries/net/net_kernel.cpp | 339 ++ .../{network/sys_net.h => net/net_kernel.h} | 22 +- src/core/libraries/net/net_log.cpp | 526 +++ src/core/libraries/net/net_log.h | 26 + src/core/libraries/net/net_p2p.cpp | 314 ++ src/core/libraries/net/net_p2p.h | 34 + src/core/libraries/net/net_p2p_ioctl.cpp | 145 + src/core/libraries/net/net_p2p_ioctl.h | 17 + src/core/libraries/net/net_resolver.cpp | 350 ++ src/core/libraries/net/net_resolver.h | 27 + src/core/libraries/net/net_translate.cpp | 392 +++ src/core/libraries/net/net_translate.h | 73 + src/core/libraries/net/net_types.h | 337 ++ .../libraries/{network => net}/net_upnp.cpp | 30 + .../libraries/{network => net}/net_upnp.h | 9 + .../libraries/{network => net}/net_util.cpp | 4 +- .../libraries/{network => net}/net_util.h | 0 src/core/libraries/network/http.cpp | 4 + src/core/libraries/network/net.cpp | 2193 ------------ src/core/libraries/network/net.h | 560 --- src/core/libraries/network/net_epoll.cpp | 79 - src/core/libraries/network/net_epoll.h | 86 - src/core/libraries/network/net_obj.cpp | 0 src/core/libraries/network/net_obj.h | 0 src/core/libraries/network/net_resolver.cpp | 48 - src/core/libraries/network/net_resolver.h | 39 - src/core/libraries/network/netctl.cpp | 2 +- src/core/libraries/network/p2p_sockets.cpp | 133 - src/core/libraries/network/posix_sockets.cpp | 716 ---- src/core/libraries/network/sockets.h | 197 -- src/core/libraries/network/sys_net.cpp | 452 --- src/core/libraries/network/unix_sockets.cpp | 397 --- src/core/libraries/np/np_handler.cpp | 279 +- src/core/libraries/np/np_handler.h | 64 +- src/core/libraries/np/np_manager.h | 2 + .../np/np_matching2/np_matching2.cpp | 226 +- .../libraries/np/np_matching2/np_matching2.h | 134 +- .../np/np_matching2/np_matching2_cache.h | 83 + .../np/np_matching2/np_matching2_internal.cpp | 666 +++- .../np/np_matching2/np_matching2_internal.h | 203 +- .../np/np_matching2/np_matching2_mm.cpp | 772 +++-- .../np/np_matching2/np_matching2_mm.h | 3 + .../np_matching2/np_matching2_signaling.cpp | 504 +-- .../np/np_matching2/np_matching2_signaling.h | 12 +- .../np/np_signaling/np_signaling.cpp | 404 +-- .../np/np_signaling/np_signaling_helpers.cpp | 460 +-- .../np/np_signaling/np_signaling_helpers.h | 6 +- .../np/np_signaling/np_signaling_state.cpp | 1032 +----- .../np/np_signaling/np_signaling_state.h | 147 +- ...g_stubs.cpp => np_signaling_transport.cpp} | 6 +- ...aling_stubs.h => np_signaling_transport.h} | 5 +- src/core/libraries/np/np_types2.h | 82 +- src/core/libraries/np/signaling_handler.cpp | 1645 +++++++++ src/core/libraries/np/signaling_handler.h | 33 + src/core/net/guest_net.cpp | 1740 ++++++++++ src/core/net/guest_net.h | 154 + src/core/net/host_net.cpp | 920 +++++ src/core/net/host_net.h | 238 ++ src/core/net/p2p_codec.cpp | 266 ++ src/core/net/p2p_codec.h | 108 + src/core/net/p2p_crypto.cpp | 108 + src/core/net/p2p_crypto.h | 29 + src/core/net/p2p_tcp.cpp | 878 +++++ src/core/net/p2p_tcp.h | 233 ++ src/core/net/p2p_transport.cpp | 866 +++++ src/core/net/p2p_transport.h | 256 ++ src/emulator.cpp | 1 + src/shadnet/client.cpp | 12 + src/shadnet/client.h | 3 + src/shadnet/shadnet.proto | 11 + tests/CMakeLists.txt | 215 ++ tests/network/test_net_host.cpp | 593 ++++ tests/network/test_net_kernel.cpp | 162 + tests/network/test_net_lib.cpp | 1931 +++++++++++ tests/network/test_net_lib_p2p_ioctl.cpp | 165 + tests/network/test_net_lib_translate.cpp | 360 ++ tests/network/test_net_p2p_codec.cpp | 263 ++ tests/network/test_net_p2p_guest.cpp | 515 +++ tests/network/test_net_p2p_tcp.cpp | 395 +++ tests/network/test_net_p2p_tcp_interop.cpp | 475 +++ tests/network/test_net_sample_sockets.cpp | 714 ++++ tests/stubs/net_kernel_stub.cpp | 61 + tests/stubs/net_lib_kernel_stub.cpp | 50 + 96 files changed, 22110 insertions(+), 7944 deletions(-) create mode 100644 src/core/libraries/net/net.cpp create mode 100644 src/core/libraries/net/net.h rename src/core/libraries/{network => net}/net_error.h (66%) create mode 100644 src/core/libraries/net/net_kernel.cpp rename src/core/libraries/{network/sys_net.h => net/net_kernel.h} (72%) create mode 100644 src/core/libraries/net/net_log.cpp create mode 100644 src/core/libraries/net/net_log.h create mode 100644 src/core/libraries/net/net_p2p.cpp create mode 100644 src/core/libraries/net/net_p2p.h create mode 100644 src/core/libraries/net/net_p2p_ioctl.cpp create mode 100644 src/core/libraries/net/net_p2p_ioctl.h create mode 100644 src/core/libraries/net/net_resolver.cpp create mode 100644 src/core/libraries/net/net_resolver.h create mode 100644 src/core/libraries/net/net_translate.cpp create mode 100644 src/core/libraries/net/net_translate.h create mode 100644 src/core/libraries/net/net_types.h rename src/core/libraries/{network => net}/net_upnp.cpp (80%) rename src/core/libraries/{network => net}/net_upnp.h (78%) rename src/core/libraries/{network => net}/net_util.cpp (99%) rename src/core/libraries/{network => net}/net_util.h (100%) delete mode 100644 src/core/libraries/network/net.cpp delete mode 100644 src/core/libraries/network/net.h delete mode 100644 src/core/libraries/network/net_epoll.cpp delete mode 100644 src/core/libraries/network/net_epoll.h delete mode 100644 src/core/libraries/network/net_obj.cpp delete mode 100644 src/core/libraries/network/net_obj.h delete mode 100644 src/core/libraries/network/net_resolver.cpp delete mode 100644 src/core/libraries/network/net_resolver.h delete mode 100644 src/core/libraries/network/p2p_sockets.cpp delete mode 100644 src/core/libraries/network/posix_sockets.cpp delete mode 100644 src/core/libraries/network/sockets.h delete mode 100644 src/core/libraries/network/sys_net.cpp delete mode 100644 src/core/libraries/network/unix_sockets.cpp create mode 100644 src/core/libraries/np/np_matching2/np_matching2_cache.h rename src/core/libraries/np/np_signaling/{np_signaling_stubs.cpp => np_signaling_transport.cpp} (93%) rename src/core/libraries/np/np_signaling/{np_signaling_stubs.h => np_signaling_transport.h} (93%) create mode 100644 src/core/libraries/np/signaling_handler.cpp create mode 100644 src/core/libraries/np/signaling_handler.h create mode 100644 src/core/net/guest_net.cpp create mode 100644 src/core/net/guest_net.h create mode 100644 src/core/net/host_net.cpp create mode 100644 src/core/net/host_net.h create mode 100644 src/core/net/p2p_codec.cpp create mode 100644 src/core/net/p2p_codec.h create mode 100644 src/core/net/p2p_crypto.cpp create mode 100644 src/core/net/p2p_crypto.h create mode 100644 src/core/net/p2p_tcp.cpp create mode 100644 src/core/net/p2p_tcp.h create mode 100644 src/core/net/p2p_transport.cpp create mode 100644 src/core/net/p2p_transport.h create mode 100644 tests/network/test_net_host.cpp create mode 100644 tests/network/test_net_kernel.cpp create mode 100644 tests/network/test_net_lib.cpp create mode 100644 tests/network/test_net_lib_p2p_ioctl.cpp create mode 100644 tests/network/test_net_lib_translate.cpp create mode 100644 tests/network/test_net_p2p_codec.cpp create mode 100644 tests/network/test_net_p2p_guest.cpp create mode 100644 tests/network/test_net_p2p_tcp.cpp create mode 100644 tests/network/test_net_p2p_tcp_interop.cpp create mode 100644 tests/network/test_net_sample_sockets.cpp create mode 100644 tests/stubs/net_kernel_stub.cpp create mode 100644 tests/stubs/net_lib_kernel_stub.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 180d2831e..67a5c634b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -376,32 +376,15 @@ set(NETWORK_LIBS src/core/libraries/network/http.cpp src/core/libraries/network/http_error.h src/core/libraries/network/http2.cpp src/core/libraries/network/http2.h - src/core/libraries/network/net.cpp src/core/libraries/network/netctl.cpp src/core/libraries/network/netctl.h src/core/libraries/network/net_ctl_obj.cpp src/core/libraries/network/net_ctl_obj.h src/core/libraries/network/net_ctl_codes.h - src/core/libraries/network/net_util.cpp - src/core/libraries/network/net_util.h - src/core/libraries/network/net_epoll.cpp - src/core/libraries/network/net_epoll.h - src/core/libraries/network/net_resolver.cpp - src/core/libraries/network/net_resolver.h - src/core/libraries/network/net_upnp.cpp - src/core/libraries/network/net_upnp.h - src/core/libraries/network/net_error.h - src/core/libraries/network/net.h src/core/libraries/network/ssl.cpp src/core/libraries/network/ssl.h src/core/libraries/network/ssl2.cpp src/core/libraries/network/ssl2.h - src/core/libraries/network/sys_net.cpp - src/core/libraries/network/sys_net.h - src/core/libraries/network/posix_sockets.cpp - src/core/libraries/network/p2p_sockets.cpp - src/core/libraries/network/unix_sockets.cpp - src/core/libraries/network/sockets.h ) set(AVPLAYER_LIB src/core/libraries/avplayer/avplayer_common.cpp @@ -666,6 +649,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/object_manager.h src/core/libraries/np/np_handler.cpp src/core/libraries/np/np_handler.h + src/core/libraries/np/signaling_handler.cpp + src/core/libraries/np/signaling_handler.h src/core/libraries/np/np_commerce/np_commerce.cpp src/core/libraries/np/np_commerce/np_commerce.h src/core/libraries/np/np_commerce/np_commerce_ui.cpp @@ -683,8 +668,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/np_signaling/np_signaling_state.h src/core/libraries/np/np_signaling/np_signaling_helpers.cpp src/core/libraries/np/np_signaling/np_signaling_helpers.h - src/core/libraries/np/np_signaling/np_signaling_stubs.cpp - src/core/libraries/np/np_signaling/np_signaling_stubs.h + src/core/libraries/np/np_signaling/np_signaling_transport.cpp + src/core/libraries/np/np_signaling/np_signaling_transport.h src/core/libraries/np/np_web_api/np_web_api.cpp src/core/libraries/np/np_web_api/np_web_api.h src/core/libraries/np/np_web_api/np_web_api_internal.cpp @@ -862,6 +847,42 @@ if (ENABLE_DISCORD_RPC) list(APPEND COMMON src/common/discord_rpc_handler.cpp src/common/discord_rpc_handler.h) endif() +set(NET_CORE src/core/net/guest_net.cpp + src/core/net/guest_net.h + src/core/net/host_net.cpp + src/core/net/host_net.h + src/core/net/p2p_codec.cpp + src/core/net/p2p_codec.h + src/core/net/p2p_crypto.cpp + src/core/net/p2p_crypto.h + src/core/net/p2p_tcp.cpp + src/core/net/p2p_tcp.h + src/core/net/p2p_transport.cpp + src/core/net/p2p_transport.h +) + +set(NET_LIB src/core/libraries/net/net.cpp + src/core/libraries/net/net.h + src/core/libraries/net/net_error.h + src/core/libraries/net/net_kernel.cpp + src/core/libraries/net/net_kernel.h + src/core/libraries/net/net_log.cpp + src/core/libraries/net/net_log.h + src/core/libraries/net/net_p2p.cpp + src/core/libraries/net/net_p2p.h + src/core/libraries/net/net_p2p_ioctl.cpp + src/core/libraries/net/net_p2p_ioctl.h + src/core/libraries/net/net_resolver.cpp + src/core/libraries/net/net_resolver.h + src/core/libraries/net/net_translate.cpp + src/core/libraries/net/net_translate.h + src/core/libraries/net/net_types.h + src/core/libraries/net/net_util.cpp + src/core/libraries/net/net_util.h + src/core/libraries/net/net_upnp.cpp + src/core/libraries/net/net_upnp.h +) + set(CORE src/core/aerolib/stubs.cpp src/core/aerolib/stubs.h src/core/aerolib/aerolib.cpp @@ -973,6 +994,8 @@ set(CORE src/core/aerolib/stubs.cpp src/core/user_manager.h src/core/user_settings.cpp src/core/user_settings.h + ${NET_CORE} + ${NET_LIB} ) if (ARCHITECTURE STREQUAL "x86_64") diff --git a/src/core/emulator_settings.cpp b/src/core/emulator_settings.cpp index 3716432b0..c5b68bd58 100644 --- a/src/core/emulator_settings.cpp +++ b/src/core/emulator_settings.cpp @@ -223,6 +223,7 @@ void EmulatorSettingsImpl::SetAddonInstallDir(const std::filesystem::path& dir) // ── Game-specific override management ──────────────────────────────── void EmulatorSettingsImpl::ClearGameSpecificOverrides() { ClearGroupOverrides(m_general); + ClearGroupOverrides(m_network); ClearGroupOverrides(m_log); ClearGroupOverrides(m_debug); ClearGroupOverrides(m_input); @@ -242,6 +243,22 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) { json generalObj = json::object(); SaveGroupGameSpecific(m_general, generalObj); + + json networkObj = json::object(); + SaveGroupGameSpecific(m_network, networkObj); + j["Network"] = networkObj; + + json existing = json::object(); + if (std::ifstream existingIn{path}; existingIn.good()) { + try { + existingIn >> existing; + } catch (...) { + existing = json::object(); + } + } + if (existing.contains("General") && existing["General"].is_object()) { + SyncLegacyNetworkKeys(existing["General"], generalObj, networkObj); + } j["General"] = generalObj; json logObj = json::object(); @@ -285,6 +302,7 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) { json j; j["General"] = m_general; + j["Network"] = m_network; j["Log"] = m_log; j["Debug"] = m_debug; j["Input"] = m_input; @@ -309,6 +327,9 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) { else existing[section] = val; } + if (existing["General"].is_object()) { + SyncLegacyNetworkKeys(existing["General"], existing["General"], j["Network"]); + } std::ofstream out(path); if (!out) { @@ -324,6 +345,29 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) { } } +void EmulatorSettingsImpl::ApplyLegacyNetworkKeys(const json& general) { + json current = m_network; + bool found = false; + for (const auto& item : m_network.GetOverrideableFields()) { + if (general.contains(item.key)) { + current[item.key] = general.at(item.key); + found = true; + } + } + if (found) { + m_network = current.get(); + } +} + +void EmulatorSettingsImpl::SyncLegacyNetworkKeys(const json& old_general, json& general, + const json& network) { + for (const auto& [key, value] : network.items()) { + if (old_general.contains(key)) { + general[key] = value; + } + } +} + // ── Load ────────────────────────────────────────────────────────────── bool EmulatorSettingsImpl::Load(const std::string& serial) { @@ -349,6 +393,10 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) { }; mergeGroup(m_general, "General"); + mergeGroup(m_network, "Network"); + if (gj.contains("General") && gj["General"].is_object()) { + ApplyLegacyNetworkKeys(gj["General"]); + } mergeGroup(m_log, "Log"); mergeGroup(m_debug, "Debug"); mergeGroup(m_input, "Input"); @@ -418,6 +466,10 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) { if (gj.contains("General")) ApplyGroupOverrides(m_general, gj.at("General"), changed); + if (gj.contains("Network")) + ApplyGroupOverrides(m_network, gj.at("Network"), changed); + if (gj.contains("General")) + ApplyGroupOverrides(m_network, gj.at("General"), changed); if (gj.contains("Log")) ApplyGroupOverrides(m_log, gj.at("Log"), changed); if (gj.contains("Debug")) @@ -452,6 +504,7 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) { void EmulatorSettingsImpl::SetDefaultValues() { m_general = GeneralSettings{}; + m_network = NetworkSettings{}; m_log = LogSettings{}; m_debug = DebugSettings{}; m_input = InputSettings{}; @@ -488,7 +541,7 @@ bool EmulatorSettingsImpl::TransferSettings() { setFromToml(s.discord_rpc_enabled, general, "enableDiscordRPC"); setFromToml(s.show_splash, general, "showSplash"); setFromToml(s.trophy_notification_side, general, "sideTrophy"); - setFromToml(s.connected_to_network, general, "isConnectedToNetwork"); + setFromToml(m_network.connected_to_network, general, "isConnectedToNetwork"); setFromToml(s.sys_modules_dir, general, "sysModulesPath"); setFromToml(s.font_dir, general, "fontsPath"); // setFromToml(, general, "userName"); @@ -710,6 +763,7 @@ std::vector EmulatorSettingsImpl::GetAllOverrideableKeys() const { keys.push_back(item.key); }; addGroup(m_general.GetOverrideableFields()); + addGroup(m_network.GetOverrideableFields()); addGroup(m_log.GetOverrideableFields()); addGroup(m_debug.GetOverrideableFields()); addGroup(m_input.GetOverrideableFields()); diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index dff7b77c0..e09ce8d2b 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -189,23 +189,16 @@ struct GeneralSettings { Setting dev_kit_mode{false}; Setting extra_dmem_in_mbytes{0}; Setting extra_fmem_in_mbytes{0}; - Setting shad_net_enabled{false}; Setting trophy_popup_disabled{false}; Setting trophy_notification_duration{6.0}; Setting trophy_notification_side{"right"}; Setting show_splash{false}; - Setting connected_to_network{false}; Setting discord_rpc_enabled{false}; Setting show_fps_counter{false}; Setting console_language{1}; Setting big_picture_scale{1000}; Setting big_picture_folder_depth{2}; - Setting shadnet_server{"srv.shadps4.net:31313"}; - Setting shadnet_webapi_server{"http://srv.shadps4.net:31315"}; - Setting signaling_info{}; - Setting enable_upnp{true}; Setting redzone_patches{false}; - // return a vector of override descriptors (runtime, but tiny) std::vector GetOverrideableFields() const { return std::vector{ @@ -216,7 +209,6 @@ struct GeneralSettings { &GeneralSettings::extra_dmem_in_mbytes), make_override("extra_fmem_in_mbytes", &GeneralSettings::extra_fmem_in_mbytes), - make_override("shad_net_enabled", &GeneralSettings::shad_net_enabled), make_override("trophy_popup_disabled", &GeneralSettings::trophy_popup_disabled), make_override("trophy_notification_duration", @@ -224,28 +216,52 @@ struct GeneralSettings { make_override("show_splash", &GeneralSettings::show_splash), make_override("trophy_notification_side", &GeneralSettings::trophy_notification_side), - make_override("connected_to_network", - &GeneralSettings::connected_to_network), make_override("console_language", &GeneralSettings::console_language), - make_override("shadnet_server", &GeneralSettings::shadnet_server), - make_override("shadnet_webapi_server", - &GeneralSettings::shadnet_webapi_server), - make_override("signaling_info", &GeneralSettings::signaling_info), - make_override("enable_upnp", &GeneralSettings::enable_upnp), make_override("redzone_patches", &GeneralSettings::redzone_patches)}; } }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, install_dirs, addon_install_dir, home_dir, sys_modules_dir, font_dir, volume_slider, neo_mode, dev_kit_mode, - extra_dmem_in_mbytes, extra_fmem_in_mbytes, shad_net_enabled, + extra_dmem_in_mbytes, extra_fmem_in_mbytes, trophy_popup_disabled, trophy_notification_duration, show_splash, - trophy_notification_side, connected_to_network, - discord_rpc_enabled, show_fps_counter, console_language, - big_picture_scale, big_picture_folder_depth, shadnet_server, - shadnet_webapi_server, signaling_info, enable_upnp, + trophy_notification_side, discord_rpc_enabled, show_fps_counter, + console_language, big_picture_scale, big_picture_folder_depth, redzone_patches) +// ------------------------------- +// Network settings +// ------------------------------- +struct NetworkSettings { + Setting connected_to_network{false}; + Setting shad_net_enabled{false}; + Setting shadnet_server{"srv.shadps4.net:31313"}; + Setting shadnet_webapi_server{"http://srv.shadps4.net:31315"}; + Setting signaling_info{}; + Setting enable_upnp{true}; + Setting p2p_port{3658}; // 0 is any free port, 3658 is the default PS4 port. With UPnP + // enabled it is forwarded on the router. + Setting disable_https{false}; + + std::vector GetOverrideableFields() const { + return std::vector{ + make_override("connected_to_network", + &NetworkSettings::connected_to_network), + make_override("shad_net_enabled", &NetworkSettings::shad_net_enabled), + make_override("shadnet_server", &NetworkSettings::shadnet_server), + make_override("shadnet_webapi_server", + &NetworkSettings::shadnet_webapi_server), + make_override("signaling_info", &NetworkSettings::signaling_info), + make_override("enable_upnp", &NetworkSettings::enable_upnp), + make_override("p2p_port", &NetworkSettings::p2p_port), + make_override("disable_https", &NetworkSettings::disable_https)}; + } +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(NetworkSettings, connected_to_network, shad_net_enabled, + shadnet_server, shadnet_webapi_server, signaling_info, + enable_upnp, p2p_port, disable_https) + // ------------------------------- // Log settings // ------------------------------- @@ -547,6 +563,7 @@ public: private: GeneralSettings m_general{}; + NetworkSettings m_network{}; LogSettings m_log{}; DebugSettings m_debug{}; InputSettings m_input{}; @@ -588,12 +605,18 @@ private: } static void PrintChangedSummary(const std::vector& changed); + void ApplyLegacyNetworkKeys(const nlohmann::json& general); + static void SyncLegacyNetworkKeys(const nlohmann::json& old_general, nlohmann::json& general, + const nlohmann::json& network); public: // Add these getters to access overrideable fields std::vector GetGeneralOverrideableFields() const { return m_general.GetOverrideableFields(); } + std::vector GetNetworkOverrideableFields() const { + return m_network.GetOverrideableFields(); + } std::vector GetDebugOverrideableFields() const { return m_debug.GetOverrideableFields(); } @@ -637,14 +660,14 @@ public: SETTING_FORWARD(m_general, ExtraDmemInMBytes, extra_dmem_in_mbytes) SETTING_FORWARD(m_general, ExtraFmemInMBytes, extra_fmem_in_mbytes) bool IsShadNetEnabled() const { - return m_general.shad_net_enabled.get(m_configMode) && + return m_network.shad_net_enabled.get(m_configMode) && !m_shadnet_session_disabled.load(std::memory_order_relaxed); } void SetShadNetEnabled(bool v, bool specific = false) { - m_general.shad_net_enabled.set(v, specific); + m_network.shad_net_enabled.set(v, specific); } bool IsShadNetEnabledSetting() const { - return m_general.shad_net_enabled.get(m_configMode); + return m_network.shad_net_enabled.get(m_configMode); } void SetShadNetSessionDisabled(bool v) { m_shadnet_session_disabled.store(v, std::memory_order_relaxed); @@ -656,18 +679,22 @@ public: SETTING_FORWARD(m_general, TrophyNotificationDuration, trophy_notification_duration) SETTING_FORWARD(m_general, TrophyNotificationSide, trophy_notification_side) SETTING_FORWARD_BOOL(m_general, ShowSplash, show_splash) - SETTING_FORWARD_BOOL(m_general, ConnectedToNetwork, connected_to_network) SETTING_FORWARD_BOOL(m_general, DiscordRPCEnabled, discord_rpc_enabled) SETTING_FORWARD_BOOL(m_general, ShowFpsCounter, show_fps_counter) SETTING_FORWARD(m_general, ConsoleLanguage, console_language) SETTING_FORWARD(m_general, BigPictureScale, big_picture_scale) SETTING_FORWARD(m_general, BigPictureFolderDepth, big_picture_folder_depth) - SETTING_FORWARD(m_general, ShadNetServer, shadnet_server) - SETTING_FORWARD(m_general, ShadNetWebApiServer, shadnet_webapi_server) - SETTING_FORWARD(m_general, SignalingInfo, signaling_info) - SETTING_FORWARD_BOOL(m_general, UPnPEnabled, enable_upnp) SETTING_FORWARD_BOOL(m_general, RedZonePatchingEnabled, redzone_patches) + // Network settings + SETTING_FORWARD_BOOL(m_network, ConnectedToNetwork, connected_to_network) + SETTING_FORWARD(m_network, ShadNetServer, shadnet_server) + SETTING_FORWARD(m_network, ShadNetWebApiServer, shadnet_webapi_server) + SETTING_FORWARD(m_network, SignalingInfo, signaling_info) + SETTING_FORWARD_BOOL(m_network, UPnPEnabled, enable_upnp) + SETTING_FORWARD(m_network, P2PPort, p2p_port) + SETTING_FORWARD_BOOL(m_network, ForcedHttpsDisabled, disable_https) + // Log settings SETTING_FORWARD_BOOL(m_log, LogAppend, append) SETTING_FORWARD_BOOL(m_log, LogEnable, enable) diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index c1863203a..baa9ee951 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -602,30 +602,6 @@ std::vector HandleTable::GetSocketHandles() { return handles; } -File* HandleTable::GetEpoll(int d) { - std::scoped_lock lock{m_mutex}; - if (d < 0 || d >= m_files.size()) { - return nullptr; - } - auto file = m_files.at(d); - if (file->type != Core::FileSys::FileType::Epoll) { - return nullptr; - } - return file; -} - -File* HandleTable::GetResolver(int d) { - std::scoped_lock lock{m_mutex}; - if (d < 0 || d >= m_files.size()) { - return nullptr; - } - auto file = m_files.at(d); - if (file->type != Core::FileSys::FileType::Resolver) { - return nullptr; - } - return file; -} - File* HandleTable::GetFile(const std::filesystem::path& host_name) { std::scoped_lock lock{m_mutex}; for (auto* file : m_files) { diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index f19950925..a1442cc56 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -16,12 +16,6 @@ #include "core/file_sys/directories/base_directory.h" #include "core/file_sys/ifile.h" -namespace Libraries::Net { -struct Socket; -struct Epoll; -struct Resolver; -} // namespace Libraries::Net - namespace Core::FileSys { /// Builds the path of an overlay that sits next to a game @@ -131,8 +125,6 @@ enum class FileType { Directory, Device, Socket, - Epoll, - Resolver, Equeue }; @@ -145,9 +137,6 @@ struct File { std::mutex m_mutex; std::shared_ptr directory; // only valid for type == Directory std::shared_ptr device; // only valid for type == Device - std::shared_ptr socket; // only valid for type == Socket - std::shared_ptr epoll; // only valid for type == Epoll - std::shared_ptr resolver; // only valid for type == Resolver bool IsBackendOpen() const { return handle && handle->IsOpen(); @@ -196,8 +185,6 @@ public: File* GetFile(int d); File* GetSocket(int d); std::vector GetSocketHandles(); - File* GetEpoll(int d); - File* GetResolver(int d); File* GetFile(const std::filesystem::path& host_name); int GetFileDescriptor(File* file); diff --git a/src/core/libraries/companion/companion_httpd.h b/src/core/libraries/companion/companion_httpd.h index bc6807fbc..965d8d10d 100644 --- a/src/core/libraries/companion/companion_httpd.h +++ b/src/core/libraries/companion/companion_httpd.h @@ -4,7 +4,7 @@ #pragma once #include "common/types.h" -#include "core/libraries/network/net.h" +#include "core/libraries/net/net.h" #include "core/libraries/system/userservice.h" namespace Core::Loader { diff --git a/src/core/libraries/invitation_dialog/invitation_dialog_ui.cpp b/src/core/libraries/invitation_dialog/invitation_dialog_ui.cpp index 51b37e36e..32b33834e 100644 --- a/src/core/libraries/invitation_dialog/invitation_dialog_ui.cpp +++ b/src/core/libraries/invitation_dialog/invitation_dialog_ui.cpp @@ -11,8 +11,8 @@ #include "common/logging/log.h" #include "core/libraries/invitation_dialog/invitation_dialog.h" #include "core/libraries/invitation_dialog/invitation_dialog_ui.h" +#include "core/libraries/net/net.h" #include "core/libraries/network/http.h" -#include "core/libraries/network/net.h" #include "core/libraries/network/ssl.h" #include "core/libraries/np/np_handler.h" #include "core/libraries/np/np_web_api/np_web_api.h" diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index f3a5c9304..5f06f7bec 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -1,8 +1,13 @@ // SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include #include #include +#include +#include #include #include "common/assert.h" @@ -26,7 +31,7 @@ #include "core/libraries/kernel/orbis_error.h" #include "core/libraries/kernel/posix_error.h" #include "core/libraries/libs.h" -#include "core/libraries/network/sockets.h" +#include "core/libraries/net/net_kernel.h" #include "core/memory.h" #include "kernel.h" @@ -279,10 +284,11 @@ s32 PS4_SYSV_ABI close(s32 fd) { *__Error() = POSIX_EPERM; return -1; } + if (file->type == Core::FileSys::FileType::Socket) { + return Libraries::Net::KernelClose(fd); + } if (file->type == Core::FileSys::FileType::Regular) { file->handle.reset(); - } else if (file->type == Core::FileSys::FileType::Socket) { - file->socket->Close(); } file->is_opened = false; LOG_INFO(Kernel_Fs, "Closing {}", file->m_guest_name); @@ -312,6 +318,9 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, u64 nbytes) { return -1; } + if (file->type == Core::FileSys::FileType::Socket) { + return Libraries::Net::KernelWrite(fd, buf, nbytes); + } std::scoped_lock lk{file->m_mutex}; if (file->type == Core::FileSys::FileType::Device) { s64 result = file->device->write(buf, nbytes); @@ -320,9 +329,6 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, u64 nbytes) { return -1; } return result; - } else if (file->type == Core::FileSys::FileType::Socket) { - // Socket functions handle errnos internally. - return file->socket->SendPacket(buf, nbytes, 0, nullptr, 0); } else if (file->type == Core::FileSys::FileType::Directory) { *__Error() = POSIX_EBADF; return -1; @@ -532,6 +538,9 @@ s64 PS4_SYSV_ABI read(s32 fd, void* buf, u64 nbytes) { return -1; } + if (file->type == Core::FileSys::FileType::Socket) { + return Libraries::Net::KernelRead(fd, buf, nbytes); + } std::scoped_lock lk{file->m_mutex}; if (file->type == Core::FileSys::FileType::Device) { s64 result = file->device->read(buf, nbytes); @@ -547,9 +556,6 @@ s64 PS4_SYSV_ABI read(s32 fd, void* buf, u64 nbytes) { return -1; } return result; - } else if (file->type == Core::FileSys::FileType::Socket) { - // Socket functions handle errnos internally. - return file->socket->ReceivePacket(buf, nbytes, 0, nullptr, 0); } if (file->IsWriteOnly()) { @@ -840,11 +846,8 @@ s32 PS4_SYSV_ABI fstat(s32 fd, OrbisKernelStat* sb) { return result; } case Core::FileSys::FileType::Socket: { - // Socket functions handle errnos internally - return file->socket->fstat(sb); + return Libraries::Net::KernelFstat(fd, sb); } - case Core::FileSys::FileType::Epoll: - case Core::FileSys::FileType::Resolver: case Core::FileSys::FileType::Equeue: { LOG_ERROR(Kernel_Fs, "(STUBBED) file type {}", magic_enum::enum_name(file->type.load())); break; @@ -1271,56 +1274,52 @@ s32 PS4_SYSV_ABI sceKernelUnlink(const char* path) { return result; } -#ifdef _WIN32 +namespace { -typedef struct { - u64 fds_bits[16]; -} fd_set_posix; +struct GuestFdSet { + u64 bits[1024 / 64]; +}; +constexpr s32 kGuestFdSetSize = 1024; -static void FD_SET_POSIX(s32 fd, fd_set_posix* set) { - set->fds_bits[fd / (8 * sizeof(u64))] |= (1ULL << (fd % (8 * sizeof(u64)))); +bool GuestFdIsSet(const GuestFdSet* set, s32 fd) { + return set != nullptr && (set->bits[fd / 64] >> (fd % 64) & 1) != 0; } -static void FD_CLR_POSIX(s32 fd, fd_set_posix* set) { - set->fds_bits[fd / (8 * sizeof(u64))] &= ~(1ULL << (fd % (8 * sizeof(u64)))); +void GuestFdSetBit(GuestFdSet* set, s32 fd) { + set->bits[fd / 64] |= 1ull << (fd % 64); } -static bool FD_ISSET_POSIX(s32 fd, fd_set_posix* set) { - return (set->fds_bits[fd / (8 * sizeof(u64))] & (1ULL << (fd % (8 * sizeof(u64))))) != 0; -} +} // namespace -static void FD_ZERO_POSIX(fd_set_posix* set) { - std::memset(set, 0, sizeof(fd_set_posix)); -} - -s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set_posix* readfds, fd_set_posix* writefds, - fd_set_posix* exceptfds, OrbisKernelTimeval* timeout) { +s32 PS4_SYSV_ABI posix_select(s32 nfds, GuestFdSet* readfds, GuestFdSet* writefds, + GuestFdSet* exceptfds, OrbisKernelTimeval* timeout) { LOG_DEBUG(Kernel_Fs, "nfds = {}, readfds = {}, writefds = {}, exceptfds = {}, timeout = {}", nfds, fmt::ptr(readfds), fmt::ptr(writefds), fmt::ptr(exceptfds), fmt::ptr(timeout)); + if (nfds < 0 || nfds > kGuestFdSetSize) { + *__Error() = POSIX_EINVAL; + return -1; + } + s64 timeout_us = -1; // no limit + if (timeout != nullptr) { + if (timeout->tv_sec < 0 || timeout->tv_usec < 0 || timeout->tv_usec >= 1'000'000) { + *__Error() = POSIX_EINVAL; + return -1; + } + timeout_us = timeout->tv_sec > (INT64_MAX / 1'000'000 - 1) + ? INT64_MAX + : timeout->tv_sec * 1'000'000 + timeout->tv_usec; + } auto* h = Common::Singleton::Instance(); - - fd_set read_host = {}, write_host = {}, except_host = {}; - FD_ZERO(&read_host); - FD_ZERO(&write_host); - FD_ZERO(&except_host); - - fd_set_posix read_ready, write_ready, except_ready; - FD_ZERO_POSIX(&read_ready); - FD_ZERO_POSIX(&write_ready); - FD_ZERO_POSIX(&except_ready); - - std::map host_to_guest; - s32 socket_max_fd = -1; - + GuestFdSet read_ready{}, write_ready{}; + s32 ready = 0; + std::vector sockets; for (s32 i = 0; i < nfds; ++i) { - bool want_read = readfds && FD_ISSET_POSIX(i, readfds); - bool want_write = writefds && FD_ISSET_POSIX(i, writefds); - bool want_except = exceptfds && FD_ISSET_POSIX(i, exceptfds); - if (!(want_read || want_write || want_except)) { + const bool want_read = GuestFdIsSet(readfds, i); + const bool want_write = GuestFdIsSet(writefds, i); + if (!want_read && !want_write && !GuestFdIsSet(exceptfds, i)) { continue; } - auto* file = h->GetFile(i); if (!file || ((file->type == Core::FileSys::FileType::Regular && !file->IsBackendOpen()) || (file->type == Core::FileSys::FileType::Socket && !file->is_opened))) { @@ -1328,246 +1327,60 @@ s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set_posix* readfds, fd_set_posix* wri *__Error() = POSIX_EBADF; return -1; } - - s32 native_fd = -1; - switch (file->type) { - case Core::FileSys::FileType::Regular: - if (auto* host = file->GetHostFile()) { - native_fd = static_cast(host->GetFileMapping()); + if (file->type == Core::FileSys::FileType::Socket) { + const u32 interest = (want_read ? Core::Net::Host::EvIn : 0u) | + (want_write ? Core::Net::Host::EvOut : 0u); + if (interest != 0) { + sockets.push_back({i, interest, 0}); } - break; - case Core::FileSys::FileType::Socket: { - auto sock = file->socket->Native(); - native_fd = sock ? static_cast(*sock) : -1; - break; - } - case Core::FileSys::FileType::Device: - native_fd = -1; - break; - default: - UNREACHABLE(); - break; - } - - if (file->type == Core::FileSys::FileType::Regular || - file->type == Core::FileSys::FileType::Device) { - // Disk files always ready - // For devices, stdin (fd 0) is never read-ready. - if (want_read && i != 0) { - FD_SET_POSIX(i, &read_ready); - } - if (want_write) { - FD_SET_POSIX(i, &write_ready); - } - // exceptfds not supported on regular files - } else if (file->type == Core::FileSys::FileType::Socket) { - if (want_read) { - FD_SET(native_fd, &read_host); - } - if (want_write) { - FD_SET(native_fd, &write_host); - } - if (want_except) { - FD_SET(native_fd, &except_host); - } - socket_max_fd = std::max(socket_max_fd, native_fd); - } - - if (native_fd == -1) { - LOG_WARNING(Kernel_Fs, "Unsupported fd {}", i); continue; } - - host_to_guest[native_fd] = i; + if (want_read && i != 0) { + GuestFdSetBit(&read_ready, i); + ++ready; + } + if (want_write) { + GuestFdSetBit(&write_ready, i); + ++ready; + } } - LOG_DEBUG(Kernel_Fs, - "Before select(): read_host.fd_count = {}, write_host.fd_count = {}, " - "except_host.fd_count = {}", - read_host.fd_count, write_host.fd_count, except_host.fd_count); - - if (read_host.fd_count == 0 && write_host.fd_count == 0 && except_host.fd_count == 0) { - LOG_WARNING(Kernel_Fs, "No sockets in fd_sets, select() will return immediately"); + if (ready > 0) { + timeout_us = 0; + } + if (!sockets.empty()) { + const s64 n = Libraries::Net::KernelSelect(sockets, timeout_us); + if (n < 0) { + return -1; // errno set + } + ready += static_cast(n); + } else if (ready == 0 && timeout_us != 0) { + // Nothing that can become ready: select() is a sleep (forever without a timeout). + do { + std::this_thread::sleep_for( + std::chrono::microseconds{timeout_us < 0 ? 1'000'000 : timeout_us}); + } while (timeout_us < 0); } + for (const auto& e : sockets) { + if (e.ready & Core::Net::Host::EvIn) { + GuestFdSetBit(&read_ready, e.id); + } + if (e.ready & Core::Net::Host::EvOut) { + GuestFdSetBit(&write_ready, e.id); + } + } if (readfds) { - FD_ZERO_POSIX(readfds); + *readfds = read_ready; } if (writefds) { - FD_ZERO_POSIX(writefds); + *writefds = write_ready; } if (exceptfds) { - FD_ZERO_POSIX(exceptfds); + *exceptfds = {}; } - - s32 result = 0; - if (socket_max_fd != -1) { - timeval tv = {}; - timeval* tv_ptr = nullptr; - if (timeout) { - tv.tv_sec = timeout->tv_sec; - tv.tv_usec = timeout->tv_usec; - tv_ptr = &tv; - } - result = select(0, read_host.fd_count > 0 ? &read_host : nullptr, - write_host.fd_count > 0 ? &write_host : nullptr, - except_host.fd_count > 0 ? &except_host : nullptr, tv_ptr); - if (result == SOCKET_ERROR) { - s32 err = WSAGetLastError(); - LOG_ERROR(Kernel_Fs, "select() failed with error {}", err); - switch (err) { - case WSAEFAULT: - *__Error() = POSIX_EFAULT; - break; - case WSAEINVAL: - *__Error() = POSIX_EINVAL; - break; - case WSAENOBUFS: - *__Error() = POSIX_ENOBUFS; - break; - default: - LOG_ERROR(Kernel_Fs, "Unhandled error case {}", err); - break; - } - return -1; - } - - for (s32 i = 0; i < read_host.fd_count; ++i) { - s32 fd = static_cast(read_host.fd_array[i]); - FD_SET_POSIX(host_to_guest[fd], readfds); - } - for (s32 i = 0; i < write_host.fd_count; ++i) { - s32 fd = static_cast(write_host.fd_array[i]); - FD_SET_POSIX(host_to_guest[fd], writefds); - } - for (s32 i = 0; i < except_host.fd_count; ++i) { - s32 fd = static_cast(except_host.fd_array[i]); - FD_SET_POSIX(host_to_guest[fd], exceptfds); - } - } - - // Add regular/device files ready count - s32 disk_ready = 0; - for (s32 i = 0; i < nfds; ++i) { - if (FD_ISSET_POSIX(i, &read_ready)) { - FD_SET_POSIX(i, readfds); - disk_ready++; - } - if (FD_ISSET_POSIX(i, &write_ready)) { - FD_SET_POSIX(i, writefds); - disk_ready++; - } - } - - return result + disk_ready; + return ready; } -#else -s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, - OrbisKernelTimeval* timeout) { - LOG_DEBUG(Kernel_Fs, "nfds = {}, readfds = {}, writefds = {}, exceptfds = {}, timeout = {}", - nfds, fmt::ptr(readfds), fmt::ptr(writefds), fmt::ptr(exceptfds), fmt::ptr(timeout)); - - auto* h = Common::Singleton::Instance(); - fd_set read_host, write_host, except_host; - FD_ZERO(&read_host); - FD_ZERO(&write_host); - FD_ZERO(&except_host); - - std::map host_to_guest; - s32 max_fd = -1; - - for (s32 i = 0; i < nfds; ++i) { - auto read = readfds && FD_ISSET(i, readfds); - auto write = writefds && FD_ISSET(i, writefds); - auto except = exceptfds && FD_ISSET(i, exceptfds); - if (read || write || except) { - auto* file = h->GetFile(i); - if (file == nullptr || - ((file->type == Core::FileSys::FileType::Regular && !file->IsBackendOpen()) || - (file->type == Core::FileSys::FileType::Socket && !file->is_opened))) { - LOG_ERROR(Kernel_Fs, "fd {} is null or not opened", i); - *__Error() = POSIX_EBADF; - return -1; - } - - s32 native_fd = [&] { - switch (file->type) { - case Core::FileSys::FileType::Regular: - if (auto* host = file->GetHostFile()) { - return static_cast(host->GetFileMapping()); - } - return -1; - case Core::FileSys::FileType::Device: - return -1; - case Core::FileSys::FileType::Socket: { - auto sock = file->socket->Native(); - // until P2P sockets contain a proper socket - return sock ? static_cast(*sock) : -1; - } - default: - UNREACHABLE(); - } - }(); - if (native_fd == -1) { - LOG_WARNING(Kernel_Fs, "Unsupported fd {}", i); - continue; - } - host_to_guest.emplace(native_fd, i); - - max_fd = std::max(max_fd, native_fd); - - if (read) { - FD_SET(native_fd, &read_host); - } - if (write) { - FD_SET(native_fd, &write_host); - } - if (except) { - FD_SET(native_fd, &except_host); - } - } - } - - if (max_fd == -1) { - LOG_WARNING(Kernel_Fs, "all requested file descriptors are unsupported"); - return 0; - } - - s32 ret = select(max_fd + 1, &read_host, &write_host, &except_host, (timeval*)timeout); - - if (ret > 0) { - if (readfds) { - FD_ZERO(readfds); - } - if (writefds) { - FD_ZERO(writefds); - } - if (exceptfds) { - FD_ZERO(exceptfds); - } - - for (s32 i = 0; i < max_fd + 1; ++i) { - if (readfds && FD_ISSET(i, &read_host)) { - FD_SET(host_to_guest[i], readfds); - } - if (writefds && FD_ISSET(i, &write_host)) { - FD_SET(host_to_guest[i], writefds); - } - if (exceptfds && FD_ISSET(i, &except_host)) { - FD_SET(host_to_guest[i], exceptfds); - } - } - } - if (ret < 0) { - s32 error = errno; - LOG_ERROR(Kernel_Fs, "native select call failed with {} ({})", error, - Common::NativeErrorToString(error)); - SetPosixErrno(error); - } - - return ret; -} -#endif void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("6c3rCVE-fTU", "libkernel", 1, "libkernel", open); diff --git a/src/core/libraries/kernel/kernel.cpp b/src/core/libraries/kernel/kernel.cpp index e996ce3f2..6163b800f 100644 --- a/src/core/libraries/kernel/kernel.cpp +++ b/src/core/libraries/kernel/kernel.cpp @@ -27,7 +27,8 @@ #include "core/libraries/kernel/threads/pthread.h" #include "core/libraries/kernel/time.h" #include "core/libraries/libs.h" -#include "core/libraries/network/sys_net.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_kernel.h" #ifdef _WIN64 #include @@ -35,8 +36,6 @@ #include #endif #include -#include -#include #include #include "aio.h" @@ -448,6 +447,9 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { static char const** kernel_environ = g_environment; + // Before any socket exists: net ids are file descriptors from the kernel's table. + Libraries::Net::InstallKernelIntegration(); + Libraries::Kernel::RegisterFileSystem(sym); Libraries::Kernel::RegisterTime(sym); Libraries::Kernel::RegisterThreads(sym); diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 4ab303c7f..3b71022de 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -31,9 +31,9 @@ #include "core/libraries/libs.h" #include "core/libraries/mouse/mouse.h" #include "core/libraries/move/move.h" +#include "core/libraries/net/net.h" #include "core/libraries/network/http.h" #include "core/libraries/network/http2.h" -#include "core/libraries/network/net.h" #include "core/libraries/network/netctl.h" #include "core/libraries/network/ssl.h" #include "core/libraries/network/ssl2.h" diff --git a/src/core/libraries/net/net.cpp b/src/core/libraries/net/net.cpp new file mode 100644 index 000000000..a80230692 --- /dev/null +++ b/src/core/libraries/net/net.cpp @@ -0,0 +1,3054 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/kernel/threads.h" +#include "core/libraries/kernel/threads/pthread.h" +#include "core/libraries/kernel/time.h" +#include "core/libraries/libs.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_error.h" +#include "core/libraries/net/net_log.h" +#include "core/libraries/net/net_p2p.h" +#include "core/libraries/net/net_p2p_ioctl.h" +#include "core/libraries/net/net_resolver.h" +#include "core/libraries/net/net_translate.h" +#include "core/net/guest_net.h" +#include "core/net/p2p_codec.h" +#include "core/net/p2p_tcp.h" + +namespace Libraries::Net { + +namespace { + +using Host::Error; + +thread_local s32 g_net_errno = 0; + +constexpr s32 FirstPoolId = 3000; +constexpr size_t MaxPools = 31; +constexpr s32 MinPoolSize = 4 * 1024; +struct Pool { + bool in_use = false; + s32 size = 0; + s32 users = 0; // ENOTEMPTY on destroy +}; +std::mutex g_pools_mutex; +std::array g_pools{}; +std::unordered_map g_resolver_pools; // resolver id -> memory id + +// Caller holds g_pools_mutex. +Pool* FindPool(s32 memid) { + const auto index = static_cast(memid - FirstPoolId); + if (memid < FirstPoolId || index >= g_pools.size() || !g_pools[index].in_use) { + return nullptr; + } + return &g_pools[index]; +} + +s32 CheckPoolId(s32 memid, const std::source_location where = std::source_location::current()) { + if (memid == 0) { + return ORBIS_NET_EINVAL; + } + std::scoped_lock lock{g_pools_mutex}; + if (FindPool(memid) == nullptr) { + LOG_ERROR(Lib_Net, "{}: unknown memory pool id {}", where.function_name(), memid); + return ORBIS_NET_EBADF; + } + return 0; +} + +bool IsEpoll(OrbisNetId id) { + return Core::Net::GetObjectKind(id) == Core::Net::NetObjectKind::Epoll; +} + +std::atomic g_kernel_errno_hook{nullptr}; + +s32 SetErrno(int orbis_errno, const std::source_location where = std::source_location::current()) { + g_net_errno = orbis_errno; + if (const auto hook = g_kernel_errno_hook.load(std::memory_order_relaxed)) { + hook(orbis_errno); + } + LogFailure(orbis_errno, where); + return ORBIS_NET_ERROR_BASE | orbis_errno; +} + +s32 Return(const Core::Net::NetResult& result, + const std::source_location where = std::source_location::current()) { + if (result.error == Error::Ok) { + return static_cast(result.value); + } + return SetErrno(ToOrbisErrno(result.error), where); +} + +OrbisNetSockaddrIn AsP2PSockaddr(const OrbisNetSockaddr& addr) { + OrbisNetSockaddrIn in; + std::memcpy(&in, &addr, sizeof(in)); // caller checked length + return in; +} + +int CheckP2PBindAddress(const OrbisNetSockaddr& addr, P2PKind kind) { + const OrbisNetSockaddrIn in = AsP2PSockaddr(addr); + if ((ntohl(in.sin_addr) & 0xf0000000u) == 0xe0000000u) { + return ORBIS_NET_EADDRNOTAVAIL; + } + if (kind == P2PKind::Datagram) { + return in.sin_port == 0 ? ORBIS_NET_EADDRNOTAVAIL : 0; + } + // TODO: the PS4 only accepts 3658. The configured port is accepted too, for tests and setups + // that change it. + const u16 udp_port = ntohs(in.sin_vport); + const bool ok = udp_port == 0 || udp_port == DefaultP2PUdpPort || + udp_port == GetP2PBoundPort() || udp_port == GetP2PConfiguredPort(); + return ok ? 0 : ORBIS_NET_EINVAL; +} + +bool IsCompleteP2PAddress(const OrbisNetSockaddr& addr, P2PKind kind) { + const OrbisNetSockaddrIn in = AsP2PSockaddr(addr); + return in.sin_addr != 0 && in.sin_port != 0 && (kind == P2PKind::Stream || in.sin_vport != 0); +} + +bool IsP2PBound(OrbisNetId s) { + u16 udp_port = 0; + u16 vport = 0; + return Core::Net::P2PSocketGetName(s, &udp_port, &vport).error == Error::Ok && vport != 0; +} + +// Socket options + +enum KindBits : u8 { + KindTcp = 1u << 0, + KindUdp = 1u << 1, + KindRaw = 1u << 2, + KindUdpP2P = 1u << 3, + KindTcpP2P = 1u << 4, +}; +constexpr u8 AllKinds = KindTcp | KindUdp | KindRaw | KindUdpP2P | KindTcpP2P; +constexpr u8 Streams = KindTcp | KindTcpP2P; +constexpr u8 Datagrams = KindUdp | KindUdpP2P; + +u8 KindBit(const Core::Net::SocketInfo& info) { + if (info.p2p) { + return info.type == SOCK_STREAM ? KindTcpP2P : KindUdpP2P; + } + switch (info.type) { + case SOCK_STREAM: + return KindTcp; + case SOCK_DGRAM: + return KindUdp; + default: + return KindRaw; + } +} + +constexpr s32 BsdSndTimeo = 0x1005; +constexpr s32 BsdRcvTimeo = 0x1006; +constexpr s32 IpDontFrag = ORBIS_NET_IP_DONTFRAG; +constexpr s32 MaxSocketBuffer = 512 * 1024; + +struct OptionRule { + s32 level; + s32 name; + bool get; + bool set; + u8 valid_for; + bool others_ignored; // ignored on other kinds + bool stored; // no host equivalent + bool byte_or_int; // u8 or int +}; + +// clang-format off +constexpr OptionRule OptionRules[] = { + // level name get set valid_for ignored stored byte + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ACCEPTTIMEO, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_BROADCAST, true, true, Datagrams, true, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_CONNECTTIMEO, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ERROR, true, false, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ERROR_EX, true, false, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_KEEPALIVE, true, true, Streams, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, true, true, Streams, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NAME, false, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ONESBCAST, true, true, AllKinds, false, true, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_POLICY, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_PRIORITY, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, BsdRcvTimeo, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, true, true, AllKinds, false, true, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDTIMEO, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, BsdSndTimeo, true, true, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_TYPE, true, false, AllKinds, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO, true, true, KindUdpP2P | KindTcpP2P, false, false, false}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USESIGNATURE, true, true, KindUdpP2P | KindTcpP2P, false, false, false}, + {ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_ADD_MEMBERSHIP, false, true, KindUdp | KindRaw, false, false, false}, + {ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_DROP_MEMBERSHIP, false, true, KindUdp | KindRaw, false, false, false}, + {ORBIS_NET_IPPROTO_IP, IpDontFrag, true, true, Datagrams | KindRaw, false, true, false}, + {ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_HDRINCL, true, true, KindRaw, false, false, false}, + {ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MAXTTL, true, false, AllKinds & ~KindRaw, false, true, false}, + {ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MULTICAST_IF, true, true, KindUdp, false, false, false}, + {ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MULTICAST_LOOP, true, true, KindUdp | KindRaw, false, false, true}, + {ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MULTICAST_TTL, true, true, KindUdp | KindRaw, false, false, true}, + {ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_TOS, true, true, AllKinds, false, false, false}, + {ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_TTL, true, true, AllKinds, false, false, false}, + {ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_TTLCHK, true, true, AllKinds & ~KindRaw, false, true, false}, + {ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_MAXSEG, true, true, Streams, false, false, false}, + {ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_MSS_TO_ADVERTISE, true, true, Streams, false, true, false}, + {ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_NODELAY, true, true, Streams, false, false, false}, + {ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_KEEPLISTEN, true, true, KindTcp, false, true, false}, + {ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, true, true, KindUdp, false, true, false}, +}; +// clang-format on + +const OptionRule* FindOptionRule(s32 level, s32 name) { + for (const auto& rule : OptionRules) { + if (rule.level == level && rule.name == name) { + return &rule; + } + } + return nullptr; +} + +s32 StoredOption(OrbisNetId s, s32 level, s32 name, const Core::Net::SocketInfo& info) { + Core::Net::SocketAttributes attributes; + Core::Net::SocketGetAttributes(s, &attributes); + if (const auto it = attributes.stored_options.find({level, name}); + it != attributes.stored_options.end()) { + return it->second; + } + if (level == ORBIS_NET_IPPROTO_TCP && name == ORBIS_NET_TCP_NODELAY) { + return info.p2p ? 1 : 0; // on for STREAM_P2P + } + if (level == ORBIS_NET_IPPROTO_TCP && name == ORBIS_NET_TCP_MAXSEG && info.p2p) { + return Core::Net::P2P::TcpConfig{}.mss; + } + if (level == ORBIS_NET_IPPROTO_IP && name == ORBIS_NET_IP_TTL) { + return 64; // TODO: FreeBSD default + } + if (level == ORBIS_NET_IPPROTO_IP && + (name == ORBIS_NET_IP_MULTICAST_TTL || name == ORBIS_NET_IP_MULTICAST_LOOP)) { + return 1; + } + return 0; +} + +// FreeBSD timeval on the PS4: two 8-byte fields. +struct OrbisTimeval { + s64 tv_sec; + s64 tv_usec; +}; + +std::optional ReadTimeval(const void* optval, u32 optlen) { + if (optval == nullptr || optlen < sizeof(OrbisTimeval)) { + return std::nullopt; + } + OrbisTimeval tv; + std::memcpy(&tv, optval, sizeof(tv)); + if (tv.tv_sec < 0 || tv.tv_usec < 0 || tv.tv_usec > 999'999) { + return std::nullopt; + } + return std::chrono::seconds{tv.tv_sec} + std::chrono::microseconds{tv.tv_usec}; +} + +s32 WriteTimeval(s64 microseconds, void* optval, u32* optlen) { + if (*optlen < sizeof(OrbisTimeval)) { + return SetErrno(ORBIS_NET_EINVAL); + } + const OrbisTimeval tv{microseconds / 1'000'000, microseconds % 1'000'000}; + std::memcpy(optval, &tv, sizeof(tv)); + *optlen = sizeof(tv); + return ORBIS_OK; +} + +// Each host OS spells IP_DONTFRAG differently. Returns 0 or the guest errno. +int SetHostDontFragment(OrbisNetId s, bool enable) { +#if defined(_WIN32) + const DWORD value = enable ? 1 : 0; + const auto r = + Core::Net::SocketSetHostOption(s, IPPROTO_IP, IP_DONTFRAGMENT, &value, sizeof(value)); +#elif defined(IP_MTU_DISCOVER) + const int value = enable ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT; + const auto r = + Core::Net::SocketSetHostOption(s, IPPROTO_IP, IP_MTU_DISCOVER, &value, sizeof(value)); +#elif defined(IP_DONTFRAG) + const int value = enable ? 1 : 0; + const auto r = + Core::Net::SocketSetHostOption(s, IPPROTO_IP, IP_DONTFRAG, &value, sizeof(value)); +#else + (void)s; + (void)enable; + const Core::Net::NetResult r = Core::Net::NetResult::Ok(); +#endif + return r.error == Error::Ok ? 0 : ToOrbisErrno(r.error); +} + +// P2P SO_LINGER: on/off is stored under the option, seconds under this key. +constexpr s32 LingerSecondsKey = -ORBIS_NET_SO_LINGER; + +bool IsBound(OrbisNetId s) { + sockaddr_storage local{}; + socklen_t len = sizeof(local); + if (Core::Net::SocketGetName(s, reinterpret_cast(&local), &len).error != Error::Ok) { + return false; // Windows: unbound sockets have no name + } + if (local.ss_family == AF_INET) { + return reinterpret_cast(&local)->sin_port != 0; + } + if (local.ss_family == AF_INET6) { + return reinterpret_cast(&local)->sin6_port != 0; + } + return true; +} + +s32 SetMembership(OrbisNetId s, bool add, const HostOption& option, const void* optval, + u32 optlen) { + std::array request{}; + if (optval == nullptr || optlen < request.size()) { + return SetErrno(ORBIS_NET_EINVAL); + } + std::memcpy(request.data(), optval, request.size()); + bool was_pending = false; + if (!add) { + Core::Net::SocketUpdateAttributes(s, [&](Core::Net::SocketAttributes& a) { + was_pending = std::erase(a.pending_memberships, request) > 0; + }); + if (was_pending) { + return ORBIS_OK; + } + } + const auto r = + Core::Net::SocketSetHostOption(s, option.level, option.name, optval, request.size()); + if (add && r.error == Error::Inval && !IsBound(s)) { + return Return(Core::Net::SocketUpdateAttributes( + s, [&](Core::Net::SocketAttributes& a) { a.pending_memberships.push_back(request); })); + } + return Return(r); +} + +void ApplyPendingMemberships(OrbisNetId s) { + std::vector> pending; + Core::Net::SocketUpdateAttributes( + s, [&](Core::Net::SocketAttributes& a) { pending.swap(a.pending_memberships); }); + for (const auto& request : pending) { + const auto r = Core::Net::SocketSetHostOption(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, + request.data(), request.size()); + if (r.error != Error::Ok) { + LOG_WARNING(Lib_Net, "multicast join after bind failed: {}", static_cast(r.error)); + } + } +} + +constexpr s32 MaxRawSend = 8192; +constexpr u64 MaxPeekLen = 512 * 1024; + +s32 SetSendOnSuspend(OrbisNetId s, const Core::Net::SocketInfo& info, const void* optval, + u32 optlen) { + if (optval == nullptr || optlen < sizeof(OrbisNetUdpSndOnSuspend)) { + return SetErrno(ORBIS_NET_EINVAL); + } + OrbisNetUdpSndOnSuspend value; + std::memcpy(&value, optval, sizeof(value)); + if (value.onoff != 0) { + if (value.addr == nullptr || value.data == nullptr || value.datalen == 0 || + value.datalen > ORBIS_NET_UDP_SND_ON_SUSPEND_DATALEN_MAX) { + return SetErrno(ORBIS_NET_EINVAL); + } + if (info.connected) { + return SetErrno(ORBIS_NET_EPROCUNAVAIL); // not allowed after connect + } + } + return Return(Core::Net::SocketUpdateAttributes(s, [&](Core::Net::SocketAttributes& a) { + a.stored_options[{ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND}] = + value.onoff != 0 ? 1 : 0; + })); +} + +constexpr int ReservedPortErrno = ORBIS_NET_EACCES; + +P2PKind KindOf(const Core::Net::SocketInfo& info) { + return info.type == SOCK_STREAM ? P2PKind::Stream : P2PKind::Datagram; +} + +std::mutex g_hooks_mutex; +SystemHooks g_hooks; + +s32 ResolverReturn(s32 code) { + if (code < 0) { + g_net_errno = static_cast(static_cast(code) & 0xff); + } + return code; +} + +int GatherSize(const OrbisNetMsghdr& msg, std::vector* buffer) { + if (msg.msg_iovlen > 1024) { + return ORBIS_NET_EMSGSIZE; // more than 1024 iovecs + } + if (msg.msg_iovlen < 0 || (msg.msg_iovlen > 0 && msg.msg_iov == nullptr)) { + return ORBIS_NET_EINVAL; + } + size_t total = 0; + for (s32 i = 0; i < msg.msg_iovlen; ++i) { + if (msg.msg_iov[i].iov_base == nullptr && msg.msg_iov[i].iov_len != 0) { + return ORBIS_NET_EFAULT; + } + total += msg.msg_iov[i].iov_len; + if (total > static_cast(INT32_MAX)) { + return ORBIS_NET_EINVAL; + } + } + buffer->resize(total); + return 0; +} + +bool FillSockInfo(OrbisNetId id, OrbisNetSockInfo* out) { + Core::Net::SocketInfo info{}; + if (Core::Net::SocketGetInfo(id, &info).error != Error::Ok) { + return false; + } + Core::Net::SocketAttributes attributes; + Core::Net::SocketGetAttributes(id, &attributes); + OrbisNetSockInfo record{}; + std::memcpy(record.name, attributes.name.data(), + std::min(sizeof(record.name) - 1, attributes.name.size())); + record.s = id; + record.socket_type = static_cast(ToOrbisSocketType(info.type, info.p2p)); + record.policy = static_cast(attributes.policy); + record.priority = static_cast(attributes.priority); + record.flags = + ORBIS_NET_SOCKINFO_F_SELF | (info.nonblocking ? ORBIS_NET_SOCKINFO_F_NONBLOCK : 0); + record.send_buffer_size = + attributes.snd_buf != 0 ? attributes.snd_buf : DefaultSendBuffer(info.type); + record.recv_buffer_size = + attributes.rcv_buf != 0 ? attributes.rcv_buf : DefaultReceiveBuffer(info.type); + // TODO: TCP state, queues and bandwidth aren't tracked. Reports OPENED, or ESTABLISHED + // once there is a peer. + record.state = ORBIS_NET_SOCKINFO_STATE_OPENED; + if (info.p2p) { + u16 udp_port = 0; + u16 vport = 0; + if (Core::Net::P2PSocketGetName(id, &udp_port, &vport).error == Error::Ok) { + record.local_port = htons(udp_port); + record.local_vport = htons(vport); + } + P2P::Endpoint peer; + u16 peer_vport = 0; + if (Core::Net::P2PSocketGetPeerName(id, &peer, &peer_vport).error == Error::Ok) { + OrbisNetSockaddrIn in{}; + u32 len = sizeof(in); + ToOrbisP2PAddress(peer, peer_vport, KindOf(info), + reinterpret_cast(&in), &len); + record.remote_adr.inaddr_addr = in.sin_addr; + record.remote_port = in.sin_port; + record.remote_vport = in.sin_vport; + record.state = ORBIS_NET_SOCKINFO_STATE_ESTABLISHED; + } + } else { + sockaddr_storage host{}; + socklen_t len = sizeof(host); + if (Core::Net::SocketGetName(id, reinterpret_cast(&host), &len).error == + Error::Ok && + host.ss_family == AF_INET) { + const auto* in = reinterpret_cast(&host); + record.local_adr.inaddr_addr = in->sin_addr.s_addr; + record.local_port = in->sin_port; + } + len = sizeof(host); + if (Core::Net::SocketGetPeerName(id, reinterpret_cast(&host), &len).error == + Error::Ok && + host.ss_family == AF_INET) { + const auto* in = reinterpret_cast(&host); + record.remote_adr.inaddr_addr = in->sin_addr.s_addr; + record.remote_port = in->sin_port; + record.state = ORBIS_NET_SOCKINFO_STATE_ESTABLISHED; + } + } + std::memcpy(out, &record, sizeof(record)); + return true; +} + +template +constexpr T ToBigEndian(T value) { + if constexpr (std::endian::native == std::endian::little) { + return std::byteswap(value); + } else { + return value; + } +} + +} // namespace + +namespace { +using In6Addr = std::array; +constexpr In6Addr In6Any{}; +constexpr In6Addr In6Loopback{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; +constexpr In6Addr In6NodeLocalAllNodes{0xff, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; +constexpr In6Addr In6LinkLocalAllNodes{0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; +constexpr In6Addr In6LinkLocalAllRouters{0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}; +// Guests may hold non-const pointers, so give each export its own writable copy. +In6Addr g_in6addr_any = In6Any; +In6Addr g_in6addr_loopback = In6Loopback; +In6Addr g_sce_net_in6addr_any = In6Any; +In6Addr g_sce_net_in6addr_loopback = In6Loopback; +In6Addr g_sce_net_in6addr_nodelocal_allnodes = In6NodeLocalAllNodes; +In6Addr g_sce_net_in6addr_linklocal_allnodes = In6LinkLocalAllNodes; +In6Addr g_sce_net_in6addr_linklocal_allrouters = In6LinkLocalAllRouters; +u32 g_sce_net_dummy = 0; +} // namespace + +static OrbisNetId sceNetAcceptImpl(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { + if (addr != nullptr && paddrlen == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + Core::Net::SocketInfo info{}; + if (const auto r = Core::Net::SocketGetInfo(s, &info); r.error != Error::Ok) { + return Return(r); + } + if (info.p2p) { + P2P::Endpoint peer; + u16 peer_vport = 0; + const auto r = Core::Net::P2PSocketAccept(s, &peer, &peer_vport); + if (r.error == Error::Ok && addr != nullptr) { + ToOrbisP2PAddress(peer, peer_vport, P2PKind::Stream, addr, paddrlen); + } + return Return(r); + } + sockaddr_storage host{}; + socklen_t host_len = sizeof(host); + const auto r = Core::Net::SocketAccept(s, reinterpret_cast(&host), &host_len); + if (r.error == Error::Ok && addr != nullptr) { + ToOrbisSockaddr(reinterpret_cast(&host), host_len, addr, paddrlen); + } + return Return(r); +} + +OrbisNetId PS4_SYSV_ABI sceNetAccept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { + const OrbisNetId r = sceNetAcceptImpl(s, addr, paddrlen); + if (r >= 0) { + LOG_DEBUG(Lib_Net, "s = {} -> new socket {}{}", s, r, + addr != nullptr && paddrlen != nullptr + ? " from " + FormatSockaddr(addr, *paddrlen) + : std::string{}); + } + return r; +} + +s32 PS4_SYSV_ABI sceNetAddrConfig6GetInfo() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetAddrConfig6Start() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetAddrConfig6Stop() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetAllocateAllRouteInfo() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetBandwidthControlGetDataTraffic() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetBandwidthControlGetDefaultParam() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetBandwidthControlGetIfParam() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetBandwidthControlGetPolicy() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetBandwidthControlSetDefaultParam() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetBandwidthControlSetIfParam() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetBandwidthControlSetPolicy() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetBind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) { + LOG_INFO(Lib_Net, "s = {}, addr = {}", s, FormatSockaddr(addr, addrlen)); + Core::Net::SocketInfo info{}; + if (const auto r = Core::Net::SocketGetInfo(s, &info); r.error != Error::Ok) { + return Return(r); + } + if (info.p2p) { + const P2PKind kind = KindOf(info); + P2P::Endpoint udp; + u16 port = 0; + if (const int e = ToP2PAddress(addr, addrlen, kind, &udp, &port); e != 0) { + return SetErrno(e); + } + // sa_len is not checked, games leave it 0 (seen binding DGRAM_P2P during boot). + if (const int e = CheckP2PBindAddress(*addr, kind); e != 0) { + return SetErrno(e); + } + // P2P sockets share the transport, so no UDP port is bound here. + const bool reserved = + kind == P2PKind::Stream ? IsReservedPort(port) : IsReservedP2PVport(port); + if (port != 0 && reserved) { + return SetErrno(ReservedPortErrno); + } + const auto r = Core::Net::P2PSocketBind(s, port); + if (r.error == Error::Ok && kind == P2PKind::Stream && port != 0) { + // Listener port. Connecting from it is EPROTO. + Core::Net::SocketUpdateAttributes( + s, [](Core::Net::SocketAttributes& a) { a.p2p_tcp_port_bound = true; }); + } + return Return(r); + } + sockaddr_storage host{}; + socklen_t host_len = 0; + if (const int e = ToHostSockaddr(addr, addrlen, &host, &host_len); e != 0) { + return SetErrno(e); + } + u16 port = 0; + if (host.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&host)->sin_port); + } else if (host.ss_family == AF_INET6) { + port = ntohs(reinterpret_cast(&host)->sin6_port); + } + if (port != 0 && IsReservedPort(port)) { + return SetErrno(ReservedPortErrno); + } + const auto r = Core::Net::SocketBind(s, reinterpret_cast(&host), host_len); + if (r.error == Error::Ok) { + ApplyPendingMemberships(s); + } + return Return(r); +} + +s32 PS4_SYSV_ABI sceNetClearDnsCache() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigAddArp() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigAddArpWithInterface() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigAddIfaddr() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigAddMRoute() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigAddRoute() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigAddRoute6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigAddRouteWithInterface() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigCleanUpAllInterfaces() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigDelArp() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigDelArpWithInterface() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigDelDefaultRoute() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigDelDefaultRoute6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigDelIfaddr() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigDelIfaddr6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigDelMRoute() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigDelRoute() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigDelRoute6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigDownInterface() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigEtherGetLinkMode() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigEtherPostPlugInOutEvent() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigEtherSetLinkMode() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigFlushRoute() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigGetDefaultRoute() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigGetDefaultRoute6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigGetIfaddr() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigGetIfaddr6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigRoutingShowRoutingConfig() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigRoutingShowtCtlVar() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigRoutingStart() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigRoutingStop() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigSetDefaultRoute() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigSetDefaultRoute6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigSetDefaultScope() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigSetIfaddr() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigSetIfaddr6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigSetIfaddr6WithFlags() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigSetIfFlags() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigSetIfLinkLocalAddr6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigSetIfmtu() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigUnsetIfFlags() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigUpInterface() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigUpInterfaceWithFlags() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocClearWakeOnWlan() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocCreate() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocGetWakeOnWlanInfo() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocJoin() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocLeave() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocScanJoin() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocSetExtInfoElement() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocSetWakeOnWlan() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanApStart() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanApStop() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanBackgroundScanQuery() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStart() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStop() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanDiagGetDeviceInfo() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanDiagSetAntenna() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanDiagSetTxFixedRate() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanGetDeviceConfig() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanInfraGetRssiInfo() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanInfraLeave() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanInfraScanJoin() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanScan() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConfigWlanSetDeviceConfig() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetConnect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) { + LOG_DEBUG(Lib_Net, "s = {}, addr = {}", s, FormatSockaddr(addr, addrlen)); + Core::Net::SocketInfo info{}; + if (const auto r = Core::Net::SocketGetInfo(s, &info); r.error != Error::Ok) { + return Return(r); + } + if (info.p2p) { + const P2PKind kind = KindOf(info); + const bool bound = IsP2PBound(s); + if (!bound && kind == P2PKind::Datagram) { + return SetErrno(ORBIS_NET_EINVAL); + } + P2P::Endpoint peer; + u16 port = 0; + if (const int e = ToP2PAddress(addr, addrlen, kind, &peer, &port); e != 0) { + return SetErrno(e); + } + if (!IsCompleteP2PAddress(*addr, kind)) { + return SetErrno(ORBIS_NET_EADDRNOTAVAIL); + } + if (info.listening) { + return SetErrno(ORBIS_NET_EOPNOTSUPP); + } + if (kind == P2PKind::Stream) { + Core::Net::SocketAttributes attributes; + Core::Net::SocketGetAttributes(s, &attributes); + if (attributes.p2p_tcp_port_bound) { + return SetErrno(ORBIS_NET_EPROTO); + } + if (!bound) { + if (const auto r = Core::Net::P2PSocketBind(s, 0); r.error != Error::Ok) { + return Return(r); + } + LOG_DEBUG(Lib_Net, "s = {}: connect on an unbound P2P stream, bound implicitly", s); + } + } + if (kind == P2PKind::Datagram) { + P2P::Endpoint current; + u16 current_vport = 0; + if (Core::Net::P2PSocketGetPeerName(s, ¤t, ¤t_vport).error == Error::Ok) { + return SetErrno(ORBIS_NET_EISCONN); + } + } + return Return(Core::Net::P2PSocketConnect(s, peer, port)); + } + sockaddr_storage host{}; + socklen_t host_len = 0; + if (const int e = ToHostSockaddr(addr, addrlen, &host, &host_len); e != 0) { + return SetErrno(e); + } + if (info.type == SOCK_DGRAM && + StoredOption(s, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, info) != 0) { + return SetErrno(ORBIS_NET_EPROCUNAVAIL); + } + if (info.type == SOCK_STREAM && host.ss_family == AF_INET && + reinterpret_cast(&host)->sin_addr.s_addr == + ORBIS_NET_INADDR_BROADCAST) { + return SetErrno(ORBIS_NET_EACCES); + } + return Return(Core::Net::SocketConnect(s, reinterpret_cast(&host), host_len)); +} + +s32 PS4_SYSV_ABI sceNetControl() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDhcpdStart() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDhcpdStop() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDhcpGetAutoipInfo() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDhcpGetInfo() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDhcpGetInfoEx() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDhcpStart() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDhcpStop() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDumpAbort() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDumpCreate() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDumpDestroy() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDumpRead() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDuplicateIpStart() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetDuplicateIpStop() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetEpollAbort(OrbisNetId eid, s32 flags) { + LOG_DEBUG(Lib_Net, "eid = {}, flags = {:#x}", eid, flags); + if (!IsEpoll(eid)) { + return SetErrno(ORBIS_NET_EBADF); + } + if ((static_cast(flags) & ~Core::Net::kEpollAbortPreserve) != 0) { + return SetErrno(ORBIS_NET_EINVAL); + } + return Return(Core::Net::EpollAbort(eid, static_cast(flags))); +} + +s32 PS4_SYSV_ABI sceNetEpollControl(OrbisNetId eid, s32 op, OrbisNetId id, + OrbisNetEpollEvent* event) { + LOG_DEBUG(Lib_Net, "eid = {}, op = {}, id = {}, events = {}", eid, + op == ORBIS_NET_EPOLL_CTL_ADD ? "ADD" + : op == ORBIS_NET_EPOLL_CTL_MOD ? "MOD" + : op == ORBIS_NET_EPOLL_CTL_DEL ? "DEL" + : "invalid", + id, event != nullptr ? EpollEventsName(event->events) : "none"); + Core::Net::EpollOp guest_op; + switch (op) { + case ORBIS_NET_EPOLL_CTL_ADD: + guest_op = Core::Net::EpollOp::Add; + break; + case ORBIS_NET_EPOLL_CTL_MOD: + guest_op = Core::Net::EpollOp::Modify; + break; + case ORBIS_NET_EPOLL_CTL_DEL: + guest_op = Core::Net::EpollOp::Delete; + break; + default: + return SetErrno(ORBIS_NET_EINVAL); + } + if ((guest_op == Core::Net::EpollOp::Delete) != (event == nullptr)) { + return SetErrno(ORBIS_NET_EINVAL); + } + // Epolls can't be added (EPERM). Unknown ids are EBADF. + const auto kind = Core::Net::GetObjectKind(id); + if (!IsEpoll(eid) || !kind) { + return SetErrno(ORBIS_NET_EBADF); + } + if (*kind == Core::Net::NetObjectKind::Epoll) { + return SetErrno(ORBIS_NET_EPERM); + } + if (event != nullptr && event->events == 0) { + return SetErrno(ORBIS_NET_EINVAL); + } + const u32 events = + event != nullptr + ? ToHostEpollEvents(event->events & (ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLOUT)) + : 0; + const u64 data = event != nullptr ? event->data.data_u64 : 0; + auto r = Core::Net::EpollControl(eid, guest_op, id, events, 0, data); + if (r.error == Error::NoEnt && guest_op == Core::Net::EpollOp::Modify) { + r = Core::Net::EpollControl(eid, Core::Net::EpollOp::Add, id, events, 0, data); + } + if (r.error == Error::NoEnt) { + // DEL of an unregistered id: EBADF, not ENOENT. + return SetErrno(ORBIS_NET_EBADF); + } + return Return(r); +} + +OrbisNetId PS4_SYSV_ABI sceNetEpollCreate(const char* name, s32 flags) { + LOG_DEBUG(Lib_Net, "name = {}, flags = {:#x}", name != nullptr ? name : "", flags); + if (flags != 0) { + return SetErrno(ORBIS_NET_EINVAL); + } + if (name != nullptr && + strnlen(name, ORBIS_NET_DEBUG_NAME_LEN_MAX + 1) > ORBIS_NET_DEBUG_NAME_LEN_MAX) { + return SetErrno(ORBIS_NET_ENAMETOOLONG); + } + return Return(Core::Net::EpollCreate()); +} + +s32 PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId eid) { + LOG_DEBUG(Lib_Net, "eid = {}", eid); + const auto kind = Core::Net::GetObjectKind(eid); + if (kind && *kind != Core::Net::NetObjectKind::Epoll) { + return SetErrno(ORBIS_NET_EPERM); + } + return Return(Core::Net::EpollDestroy(eid)); +} + +static s32 sceNetEpollWaitImpl(OrbisNetId eid, OrbisNetEpollEvent* events, s32 maxevents, + s32 timeout_us) { + if (!IsEpoll(eid)) { + return SetErrno(ORBIS_NET_EBADF); + } + if (events == nullptr || maxevents <= 0) { + return SetErrno(ORBIS_NET_EINVAL); + } + std::vector ready(std::min(maxevents, 256)); + const auto r = Core::Net::EpollWait(eid, ready, timeout_us < 0 ? -1 : timeout_us); + if (r.error != Error::Ok) { + return Return(r); + } + for (s64 i = 0; i < r.value; ++i) { + events[i] = {}; + events[i].events = ToOrbisEpollEvents(ready[i].events); + events[i].ident = static_cast(ready[i].ident); + events[i].data.data_u64 = ready[i].data; + } + return static_cast(r.value); +} + +s32 PS4_SYSV_ABI sceNetEpollWait(OrbisNetId eid, OrbisNetEpollEvent* events, s32 maxevents, + s32 timeout_us) { + const s32 r = sceNetEpollWaitImpl(eid, events, maxevents, timeout_us); + if (r > 0) { + LOG_TRACE(Lib_Net, "eid = {}, timeout = {} us -> {} (first: id {} {})", eid, timeout_us, r, + events[0].ident, EpollEventsName(events[0].events)); + } else { + LOG_TRACE(Lib_Net, "eid = {}, timeout = {} us -> {}", eid, timeout_us, + r == 0 ? "timed out" : ErrorCodeName(r)); + } + return r; +} + +s32* PS4_SYSV_ABI sceNetErrnoLoc() { + return &g_net_errno; +} + +s32 PS4_SYSV_ABI sceNetEtherNtostr(const OrbisNetEtherAddr* n, char* str, u64 len) { + if (n == nullptr || str == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + if (len < static_cast(ORBIS_NET_ETHER_ADDRSTRLEN)) { + return SetErrno(ORBIS_NET_ENOSPC); + } + std::snprintf(str, static_cast(len), "%02x:%02x:%02x:%02x:%02x:%02x", n->data[0], + n->data[1], n->data[2], n->data[3], n->data[4], n->data[5]); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetEtherStrton(const char* str, OrbisNetEtherAddr* n) { + if (str == nullptr || n == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + OrbisNetEtherAddr out{}; + const char* p = str; + for (int i = 0; i < 6; ++i) { + int value = 0; + int digits = 0; + for (; digits < 2 && std::isxdigit(static_cast(*p)); ++digits, ++p) { + const char c = static_cast(std::tolower(static_cast(*p))); + value = value * 16 + (c <= '9' ? c - '0' : c - 'a' + 10); + } + if (digits == 0 || (i < 5 && *p != ':' && *p != '-') || (i == 5 && *p != '\0')) { + return SetErrno(ORBIS_NET_EINVAL); + } + out.data[i] = static_cast(value); + if (i < 5) { + ++p; + } + } + *n = out; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetEventCallbackCreate() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetEventCallbackDestroy() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetEventCallbackGetError() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetEventCallbackWaitCB() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetFreeAllRouteInfo() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetGetArpInfo() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +namespace { +std::mutex g_dns_mutex; +std::array g_dns_override{}; +std::array g_dns6_override{}; + +bool IsZero(std::span bytes) { + return std::all_of(bytes.begin(), bytes.end(), [](u8 b) { return b == 0; }); +} +} // namespace + +// Returns the number of servers, 0 to 2. +s32 PS4_SYSV_ABI sceNetGetDnsInfo(u32* info, s32 flags) { + if (info == nullptr || flags != 0) { + return SetErrno(ORBIS_NET_EINVAL); + } + std::array servers{}; + { + std::scoped_lock lock{g_dns_mutex}; + servers = g_dns_override; + } + if (servers[0] == 0) { + // What sceNetCtlGetInfo reports, while online. + const auto hooks = GetSystemHooks(); + if (!hooks.is_online || hooks.is_online()) { + const u32 cloudflare = ToBigEndian(0x01010101u); + servers = {cloudflare, cloudflare}; + } + } + info[0] = servers[0]; + info[1] = servers[1]; + return servers[0] == 0 ? 0 : servers[1] == 0 ? 1 : 2; +} + +s32 PS4_SYSV_ABI sceNetGetDns6Info(u8* info, s32 flags) { + if (info == nullptr || flags != 0) { + return SetErrno(ORBIS_NET_EINVAL); + } + std::array servers{}; + { + std::scoped_lock lock{g_dns_mutex}; + servers = g_dns6_override; // no IPv6 servers otherwise + } + std::memcpy(info, servers.data(), servers.size()); + const std::span view{servers}; + return IsZero(view.first(16)) ? 0 : IsZero(view.last(16)) ? 1 : 2; +} + +// A null info clears the override. +s32 PS4_SYSV_ABI sceNetSetDnsInfo(const u32* info, s32 flags) { + if (flags != 0 || (info != nullptr && info[0] == 0)) { + return SetErrno(ORBIS_NET_EINVAL); + } + std::scoped_lock lock{g_dns_mutex}; + g_dns_override = info != nullptr ? std::array{info[0], info[1]} : std::array{}; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetSetDns6Info(const u8* info, s32 flags) { + if (flags != 0 || (info != nullptr && IsZero({info, 16}))) { + return SetErrno(ORBIS_NET_EINVAL); + } + std::scoped_lock lock{g_dns_mutex}; + g_dns6_override = {}; + if (info != nullptr) { + std::memcpy(g_dns6_override.data(), info, g_dns6_override.size()); + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetGetIfList() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetGetIfListOnce() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +const char* PS4_SYSV_ABI sceNetGetIfName(u32 index) { + static constexpr std::array Names = { + "lo0", "eth0", "eth1", "dbg0", "wlan0", "wlan1", "gbe0", "bt0", "phone0", "pppoe0"}; + if (index >= Names.size()) { + g_net_errno = ORBIS_NET_EINVAL; + return ""; + } + return Names[index]; +} + +s32 PS4_SYSV_ABI sceNetGetIfnameNumList() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetGetMacAddress(OrbisNetEtherAddr* addr, s32 flags) { + LOG_DEBUG(Lib_Net, "flags = {:#x}", flags); + if (addr == nullptr) { + return SetErrno(ORBIS_NET_EINVAL); + } + std::array mac{}; + const auto hooks = GetSystemHooks(); + if (hooks.mac_address && !hooks.mac_address(&mac)) { + LOG_WARNING(Lib_Net, "host MAC address not available"); + } + std::memcpy(addr->data, mac.data(), mac.size()); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetGetMemoryPoolStats(s32 memid, OrbisNetMemoryPoolStats* stat) { + std::scoped_lock lock{g_pools_mutex}; + const Pool* pool = FindPool(memid); + if (pool == nullptr) { + return SetErrno(ORBIS_NET_EBADF); + } + if (stat == nullptr) { + return SetErrno(ORBIS_NET_EINVAL); + } + *stat = {}; + stat->pool_size = static_cast(pool->size); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetGetNameToIndex() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetGetpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { + if (addr == nullptr || paddrlen == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + Core::Net::SocketInfo info{}; + if (const auto r = Core::Net::SocketGetInfo(s, &info); r.error != Error::Ok) { + return Return(r); + } + if (info.p2p) { + P2P::Endpoint peer; + u16 vport = 0; + const auto r = Core::Net::P2PSocketGetPeerName(s, &peer, &vport); + if (r.error == Error::Ok) { + ToOrbisP2PAddress(peer, vport, KindOf(info), addr, paddrlen); + } + return Return(r); + } + sockaddr_storage host{}; + socklen_t host_len = sizeof(host); + const auto r = Core::Net::SocketGetPeerName(s, reinterpret_cast(&host), &host_len); + if (r.error == Error::Ok) { + ToOrbisSockaddr(reinterpret_cast(&host), host_len, addr, paddrlen); + } + return Return(r); +} + +s32 PS4_SYSV_ABI sceNetGetRandom(u32* out) { + if (out == nullptr) { + return SetErrno(ORBIS_NET_EINVAL); + } + static std::mutex mutex; + static std::mt19937 rng{std::random_device{}()}; + std::scoped_lock lock{mutex}; + *out = static_cast(rng()); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetGetRouteInfo() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetGetSockInfo(OrbisNetId s, OrbisNetSockInfo* info, s32 n, s32 flags) { + LOG_DEBUG(Lib_Net, "s = {}, n = {}, flags = {:#x}", s, n, flags); + if (info == nullptr) { + // No buffer and s < 0: return the socket count. + return s < 0 ? static_cast(Core::Net::ListSockets().size()) + : SetErrno(ORBIS_NET_EINVAL); + } + if (n <= 0) { + return SetErrno(ORBIS_NET_EINVAL); + } + if (s >= 0) { + if (!FillSockInfo(s, info)) { + return SetErrno(ORBIS_NET_EBADF); + } + return 1; + } + s32 written = 0; + for (const s32 id : Core::Net::ListSockets()) { + if (written == n) { + break; + } + written += FillSockInfo(id, &info[written]) ? 1 : 0; // closed meanwhile + } + return written; +} + +s32 PS4_SYSV_ABI sceNetGetSockInfo6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetGetsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { + if (addr == nullptr || paddrlen == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + Core::Net::SocketInfo info{}; + if (const auto r = Core::Net::SocketGetInfo(s, &info); r.error != Error::Ok) { + return Return(r); + } + if (info.p2p) { + u16 udp_port = 0; + u16 vport = 0; + const auto r = Core::Net::P2PSocketGetName(s, &udp_port, &vport); + if (r.error == Error::Ok) { + // TODO: reports INADDR_ANY. maybe need to report the advertised (NAT) address. + ToOrbisP2PAddress(P2P::Endpoint::IPv4("0.0.0.0", udp_port), vport, KindOf(info), addr, + paddrlen); + } + return Return(r); + } + sockaddr_storage host{}; + socklen_t host_len = sizeof(host); + const auto r = Core::Net::SocketGetName(s, reinterpret_cast(&host), &host_len); + if (r.error == Error::Ok) { + ToOrbisSockaddr(reinterpret_cast(&host), host_len, addr, paddrlen); + } + return Return(r); +} + +static s32 sceNetGetsockoptImpl(OrbisNetId s, s32 level, s32 optname, void* optval, u32* optlen) { + if (optval == nullptr || optlen == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + Core::Net::SocketInfo info{}; + if (const auto r = Core::Net::SocketGetInfo(s, &info); r.error != Error::Ok) { + return Return(r); + } + const auto write_int = [&](s64 value) -> s32 { + if (*optlen < sizeof(s32)) { + return SetErrno(ORBIS_NET_EINVAL); + } + const s32 v = static_cast(std::clamp(value, INT32_MIN, INT32_MAX)); + std::memcpy(optval, &v, sizeof(v)); + *optlen = sizeof(v); + return ORBIS_OK; + }; + + const OptionRule* rule = FindOptionRule(level, optname); + if (rule == nullptr || !rule->get) { + return SetErrno(ORBIS_NET_ENOPROTOOPT); + } + if (!(rule->valid_for & KindBit(info))) { + if (!rule->others_ignored) { + return SetErrno(ORBIS_NET_EPROCUNAVAIL); + } + return write_int(StoredOption(s, level, optname, info)); + } + Core::Net::SocketAttributes attributes; + Core::Net::SocketGetAttributes(s, &attributes); + + if (level == ORBIS_NET_SOL_SOCKET) { + switch (optname) { + case ORBIS_NET_SO_NBIO: + return write_int(info.nonblocking ? 1 : 0); + case ORBIS_NET_SO_RCVTIMEO: + return write_int(info.rcv_timeout_us); + case ORBIS_NET_SO_SNDTIMEO: + return write_int(info.snd_timeout_us); + case ORBIS_NET_SO_CONNECTTIMEO: + return write_int(info.connect_timeout_us); + case ORBIS_NET_SO_ACCEPTTIMEO: + return write_int(info.accept_timeout_us); + case BsdRcvTimeo: + case BsdSndTimeo: + return WriteTimeval(optname == BsdRcvTimeo ? info.rcv_timeout_us : info.snd_timeout_us, + optval, optlen); + case ORBIS_NET_SO_REUSEADDR: + return write_int(info.reuse_addr ? 1 : 0); + case ORBIS_NET_SO_TYPE: + return write_int(ToOrbisSocketType(info.type, info.p2p)); + case ORBIS_NET_SO_ERROR: + return write_int(info.pending_error == Error::Ok ? 0 + : ToOrbisErrno(info.pending_error)); + case ORBIS_NET_SO_ERROR_EX: + // Full SCE error code. Reading either option clears the error. + return write_int(info.pending_error == Error::Ok + ? 0 + : ORBIS_NET_ERROR_BASE | ToOrbisErrno(info.pending_error)); + case ORBIS_NET_SO_SNDBUF: + // PS4 value. Linux reports double what was set. + return write_int(attributes.snd_buf != 0 ? attributes.snd_buf + : DefaultSendBuffer(info.type)); + case ORBIS_NET_SO_RCVBUF: + return write_int(attributes.rcv_buf != 0 ? attributes.rcv_buf + : DefaultReceiveBuffer(info.type)); + case ORBIS_NET_SO_POLICY: + return write_int(attributes.policy); + case ORBIS_NET_SO_PRIORITY: + return write_int(attributes.priority); + case ORBIS_NET_SO_USECRYPTO: + case ORBIS_NET_SO_USESIGNATURE: { + bool crypto = false; + bool signature = false; + Core::Net::P2PSocketGetProtection(s, &crypto, &signature); + return write_int((optname == ORBIS_NET_SO_USECRYPTO ? crypto : signature) ? 1 : 0); + } + default: + break; + } + } + + if (level == ORBIS_NET_IPPROTO_UDP && optname == ORBIS_NET_UDP_SND_ON_SUSPEND) { + // Only onoff is stored. + if (*optlen < sizeof(OrbisNetUdpSndOnSuspend)) { + return SetErrno(ORBIS_NET_EINVAL); + } + OrbisNetUdpSndOnSuspend value{}; + value.onoff = StoredOption(s, level, optname, info); + std::memcpy(optval, &value, sizeof(value)); + *optlen = sizeof(value); + return ORBIS_OK; + } + const auto option = ToHostOption(level, optname); + if (level == ORBIS_NET_SOL_SOCKET && optname == ORBIS_NET_SO_LINGER && info.p2p) { + if (*optlen < sizeof(OrbisNetLinger)) { + return SetErrno(ORBIS_NET_EINVAL); + } + const OrbisNetLinger guest{StoredOption(s, level, optname, info), + StoredOption(s, level, LingerSecondsKey, info)}; + std::memcpy(optval, &guest, sizeof(guest)); + *optlen = sizeof(guest); + return ORBIS_OK; + } +#ifdef _WIN32 + if (level == ORBIS_NET_IPPROTO_TCP && optname == ORBIS_NET_TCP_MAXSEG && !info.p2p && + attributes.stored_options.contains({level, optname})) { + return write_int(attributes.stored_options.at({level, optname})); + } +#endif + if (info.p2p || !option || rule->stored) { + const s32 value = StoredOption(s, level, optname, info); + if (rule->byte_or_int && *optlen == 1) { + *static_cast(optval) = static_cast(value); + return ORBIS_OK; + } + return write_int(value); + } + switch (option->value) { + case OptionValue::Int: { + int value = 0; + socklen_t len = sizeof(value); + const auto r = Core::Net::SocketGetHostOption(s, option->level, option->name, &value, &len); + if (r.error != Error::Ok) { + return Return(r); + } + if (rule->byte_or_int && *optlen == 1) { + *static_cast(optval) = static_cast(value); + return ORBIS_OK; + } + return write_int(value); + } + case OptionValue::Linger: { + if (*optlen < sizeof(OrbisNetLinger)) { + return SetErrno(ORBIS_NET_EINVAL); + } + linger host{}; + socklen_t len = sizeof(host); + const auto r = Core::Net::SocketGetHostOption(s, option->level, option->name, &host, &len); + if (r.error != Error::Ok) { + return Return(r); + } + const OrbisNetLinger guest{host.l_onoff, host.l_linger}; + std::memcpy(optval, &guest, sizeof(guest)); + *optlen = sizeof(guest); + return ORBIS_OK; + } + case OptionValue::Raw: { + socklen_t len = static_cast(*optlen); + const auto r = Core::Net::SocketGetHostOption(s, option->level, option->name, optval, &len); + if (r.error == Error::Ok) { + *optlen = static_cast(len); + } + return Return(r); + } + } + return SetErrno(ORBIS_NET_ENOPROTOOPT); +} + +s32 PS4_SYSV_ABI sceNetGetsockopt(OrbisNetId s, s32 level, s32 optname, void* optval, u32* optlen) { + const s32 r = sceNetGetsockoptImpl(s, level, optname, optval, optlen); + if (r == ORBIS_OK && optval != nullptr && optlen != nullptr && *optlen >= sizeof(s32)) { + s32 value; + std::memcpy(&value, optval, sizeof(value)); + LOG_DEBUG(Lib_Net, "s = {}, {} -> {}", s, OptionName(level, optname), value); + } else { + LOG_DEBUG(Lib_Net, "s = {}, {} -> {}", s, OptionName(level, optname), + r == ORBIS_OK ? "ok" : ErrorCodeName(r)); + } + return r; +} + +s32 PS4_SYSV_ABI sceNetGetStatisticsInfo(OrbisNetStatisticsInfo* info, s32 flags) { + LOG_DEBUG(Lib_Net, "flags = {:#x}", flags); + if (info == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + // TODO: no host equivalent. Report plenty free and nothing queued so games don't throttle. + constexpr s32 PlentyFree = 16 * 1024 * 1024; + *info = {PlentyFree, PlentyFree, 0, 0, PlentyFree, PlentyFree}; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetGetStatisticsInfoInternal() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +// Process time in microseconds. No return value, a null pointer is ignored. +void PS4_SYSV_ABI sceNetGetSystemTime(u64* out) { + if (out != nullptr) { + *out = Kernel::sceKernelGetProcessTime(); + } +} + +u32 PS4_SYSV_ABI sceNetHtonl(u32 host32) { + return ToBigEndian(host32); +} + +u64 PS4_SYSV_ABI sceNetHtonll(u64 host64) { + return ToBigEndian(host64); +} + +u16 PS4_SYSV_ABI sceNetHtons(u16 host16) { + return ToBigEndian(host16); +} + +const char* PS4_SYSV_ABI sceNetInetNtop(s32 af, const void* src, char* dst, u32 size) { + if (src == nullptr || dst == nullptr) { + g_net_errno = ORBIS_NET_EFAULT; + return nullptr; + } + const auto family = ToHostFamily(af); + if (!family || (*family != AF_INET && *family != AF_INET6)) { + g_net_errno = ORBIS_NET_EAFNOSUPPORT; + return nullptr; + } + // TODO: uses host inet_ntop. glibc matches FreeBSD, Windows may format some IPv6 + // addresses differently. + if (inet_ntop(*family, const_cast(src), dst, size) == nullptr) { + g_net_errno = ORBIS_NET_ENOSPC; + return nullptr; + } + return dst; +} + +s32 PS4_SYSV_ABI sceNetInetNtopWithScopeId() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetInetPton(s32 af, const char* src, void* dst) { + if (src == nullptr || dst == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + const auto family = ToHostFamily(af); + if (!family || (*family != AF_INET && *family != AF_INET6)) { + return SetErrno(ORBIS_NET_EAFNOSUPPORT); + } + return inet_pton(*family, src, dst); // 1 on success, 0 for malformed text +} + +s32 PS4_SYSV_ABI sceNetInetPtonEx(s32 af, const char* src, void* dst, s32 flags) { + // TODO: flags unknown (IPv6 scope?). IPv4 needs none. + if (flags != 0) { + LOG_WARNING(Lib_Net, "ignoring flags {:#x}", flags); + } + return sceNetInetPton(af, src, dst); +} + +s32 PS4_SYSV_ABI sceNetInetPtonWithScopeId() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetInfoDumpStart() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetInfoDumpStop() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetInit() { + LOG_INFO(Lib_Net, "called"); + static const bool host_ready = Host::Initialize(); + if (!host_ready) { + LOG_ERROR(Lib_Net, "host networking could not be initialized"); + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetInitParam() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetIoctl(OrbisNetId s, u64 cmd, void* data) { + LOG_DEBUG(Lib_Net, "s = {}, cmd = {:#x}", s, cmd); + Core::Net::SocketInfo info{}; + if (const auto r = Core::Net::SocketGetInfo(s, &info); r.error != Error::Ok) { + return Return(r); + } + if (!info.p2p) { + constexpr u32 Fionbio = 0x8004667e; + constexpr u32 Fionread = 0x4004667f; + switch (static_cast(cmd)) { + case Fionbio: + if (data == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + return Return(Core::Net::SocketSetNonBlocking(s, *static_cast(data) != 0)); + case Fionread: { + if (data == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + const auto r = Core::Net::SocketBytesReadable(s); + if (r.error != Error::Ok) { + return Return(r); + } + *static_cast(data) = static_cast(r.value); + return ORBIS_OK; + } + default: + LOG_WARNING(Lib_Net, "unsupported ioctl {:#x} on a non-P2P socket", cmd); + return SetErrno(ORBIS_NET_EINVAL); + } + } + const u16 np_port = sceNetHtons(GetP2PBoundPort()); + if (const int e = P2PKeyIoctl(*GetP2PKeyring(), np_port, cmd, data); e != 0) { + LOG_WARNING(Lib_Net, "P2P ioctl {:#x} failed: {}", cmd, e); + return SetErrno(e); + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetListen(OrbisNetId s, s32 backlog) { + LOG_INFO(Lib_Net, "s = {}, backlog = {}", s, backlog); + Core::Net::SocketInfo info{}; + if (const auto r = Core::Net::SocketGetInfo(s, &info); r.error != Error::Ok) { + return Return(r); + } + if (info.p2p && !IsP2PBound(s)) { + return SetErrno(ORBIS_NET_EINVAL); // P2P listeners must be bound + } + return Return(Core::Net::SocketListen(s, backlog)); +} + +void* PS4_SYSV_ABI sceNetMemoryAllocate(s64 size, s32 flags) { + if (size <= 0) { + return nullptr; + } + constexpr s32 ZeroFill = 2; + return (flags & ZeroFill) != 0 ? std::calloc(1, static_cast(size)) + : std::malloc(static_cast(size)); +} + +void PS4_SYSV_ABI sceNetMemoryFree(void* ptr) { + std::free(ptr); +} + +u32 PS4_SYSV_ABI sceNetNtohl(u32 net32) { + return ToBigEndian(net32); +} + +u64 PS4_SYSV_ABI sceNetNtohll(u64 net64) { + return ToBigEndian(net64); +} + +u16 PS4_SYSV_ABI sceNetNtohs(u16 net16) { + return ToBigEndian(net16); +} + +s32 PS4_SYSV_ABI sceNetPoolCreate(const char* name, s32 size, s32 flags) { + LOG_INFO(Lib_Net, "name = {}, size = {}, flags = {:#x}", name != nullptr ? name : "", size, + flags); + if (name != nullptr && + strnlen(name, ORBIS_NET_DEBUG_NAME_LEN_MAX + 1) > ORBIS_NET_DEBUG_NAME_LEN_MAX) { + return ORBIS_NET_ERROR_ENAMETOOLONG; + } + if (flags != 0 || size < MinPoolSize) { + return ORBIS_NET_ERROR_EINVAL; + } + std::scoped_lock lock{g_pools_mutex}; + for (size_t i = 0; i < g_pools.size(); ++i) { + if (!g_pools[i].in_use) { + g_pools[i] = {true, size, 0}; + return FirstPoolId + static_cast(i); + } + } + return ORBIS_NET_ERROR_ENFILE; +} + +s32 PS4_SYSV_ABI sceNetPoolDestroy(s32 pool_id) { + LOG_INFO(Lib_Net, "pool_id = {}", pool_id); + std::scoped_lock lock{g_pools_mutex}; + Pool* pool = FindPool(pool_id); + if (pool == nullptr) { + return ORBIS_NET_ERROR_EBADF; + } + if (pool->users > 0) { + return ORBIS_NET_ERROR_ENOTEMPTY; + } + *pool = {}; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetPppoeStart() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetPppoeStop() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetRecv(OrbisNetId s, void* buf, u64 len, s32 flags) { + return sceNetRecvfrom(s, buf, len, flags, nullptr, nullptr); +} + +static s32 sceNetRecvfromImpl(OrbisNetId s, void* buf, u64 len, s32 flags, OrbisNetSockaddr* addr, + u32* paddrlen) { + const auto msg = ToHostMsgFlags(flags); + if (msg.peeklen && buf == nullptr) { + // MSG_PEEKLEN with no buffer: peek into our own and return the size waiting. + std::vector scratch(static_cast(std::min(len, MaxPeekLen))); + return sceNetRecvfrom(s, scratch.data(), scratch.size(), + (flags & ~ORBIS_NET_MSG_PEEKLEN) | ORBIS_NET_MSG_PEEK, addr, + paddrlen); + } + if (buf == nullptr && len != 0) { + return SetErrno(ORBIS_NET_EFAULT); + } + if (paddrlen == nullptr) { + addr = nullptr; // FreeBSD: no length, no address, not an error + } + if (msg.unsupported != 0) { + LOG_WARNING(Lib_Net, "ignoring flags {:#x}", msg.unsupported); + } + Core::Net::SocketInfo info{}; + if (const auto r = Core::Net::SocketGetInfo(s, &info); r.error != Error::Ok) { + return Return(r); + } + if (info.p2p) { + P2P::Endpoint from; + u16 from_vport = 0; + const auto r = Core::Net::P2PSocketRecvFrom(s, buf, static_cast(len), msg.peek, + msg.dontwait, addr ? &from : nullptr, + addr ? &from_vport : nullptr, msg.waitall); + if (r.error == Error::Ok && addr != nullptr) { + ToOrbisP2PAddress(from, from_vport, KindOf(info), addr, paddrlen); + } + return Return(r); + } + sockaddr_storage host{}; + socklen_t host_len = sizeof(host); + const auto r = Core::Net::SocketRecvFrom( + s, buf, static_cast(len), msg.host, msg.dontwait, + addr ? reinterpret_cast(&host) : nullptr, addr ? &host_len : nullptr); + if (r.error == Error::Ok && addr != nullptr) { + ToOrbisSockaddr(reinterpret_cast(&host), host_len, addr, paddrlen); + } + return Return(r); +} + +s32 PS4_SYSV_ABI sceNetRecvfrom(OrbisNetId s, void* buf, u64 len, s32 flags, OrbisNetSockaddr* addr, + u32* paddrlen) { + const s32 r = sceNetRecvfromImpl(s, buf, len, flags, addr, paddrlen); + if (r > 0) { + CountTraffic(s, false, r); + if (addr != nullptr && paddrlen != nullptr) { + NoteP2PPeer(s, false, addr, *paddrlen); + } + } + LOG_TRACE(Lib_Net, "s = {}, len = {}, flags = {:#x} -> {}{}", s, len, flags, + r >= 0 ? std::to_string(r) : ErrorCodeName(r), + r >= 0 && addr != nullptr && paddrlen != nullptr + ? " from " + FormatSockaddr(addr, *paddrlen) + : std::string{}); + return r; +} + +static s32 sceNetRecvmsgImpl(OrbisNetId s, OrbisNetMsghdr* msg, s32 flags) { + if (msg == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + if ((flags & ORBIS_NET_MSG_PEEKLEN) == ORBIS_NET_MSG_PEEKLEN) { + return SetErrno(ORBIS_NET_EINVAL); + } + std::vector buffer; + if (const int e = GatherSize(*msg, &buffer); e != 0) { + return SetErrno(e); + } + u32 namelen = msg->msg_name != nullptr ? msg->msg_namelen : 0; + const s32 r = sceNetRecvfrom(s, buffer.data(), buffer.size(), flags, + static_cast(msg->msg_name), + msg->msg_name != nullptr ? &namelen : nullptr); + if (r < 0) { + return r; + } + size_t offset = 0; + for (s32 i = 0; i < msg->msg_iovlen && offset < static_cast(r); ++i) { + const size_t chunk = + std::min(msg->msg_iov[i].iov_len, static_cast(r) - offset); + std::memcpy(msg->msg_iov[i].iov_base, buffer.data() + offset, chunk); + offset += chunk; + } + msg->msg_namelen = namelen; + return r; +} + +s32 PS4_SYSV_ABI sceNetRecvmsg(OrbisNetId s, OrbisNetMsghdr* msg, s32 flags) { + const s32 r = sceNetRecvmsgImpl(s, msg, flags); + if (r > 0) { + CountTraffic(s, false, r); + } + LOG_TRACE(Lib_Net, "s = {}, flags = {:#x} -> {}", s, flags, + r >= 0 ? std::to_string(r) : ErrorCodeName(r)); + return r; +} + +s32 PS4_SYSV_ABI sceNetResolverAbort(OrbisNetId rid, s32 flags) { + LOG_DEBUG(Lib_Net, "rid = {}, flags = {:#x}", rid, flags); + const int e = AbortResolver(rid, static_cast(flags)); + return e == 0 ? ORBIS_OK : SetErrno(e); +} + +// Not implemented. Fail rather than report success with nothing written. +s32 PS4_SYSV_ABI sceNetResolverConnect() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ResolverReturn(ORBIS_NET_ERROR_RESOLVER_ENOSUPPORT); +} + +// Not implemented. Fail rather than report success with nothing written. +s32 PS4_SYSV_ABI sceNetResolverConnectAbort() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ResolverReturn(ORBIS_NET_ERROR_RESOLVER_ENOSUPPORT); +} + +// Not implemented. Fail rather than report success with nothing written. +s32 PS4_SYSV_ABI sceNetResolverConnectCreate() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ResolverReturn(ORBIS_NET_ERROR_RESOLVER_ENOSUPPORT); +} + +// Not implemented. Fail rather than report success with nothing written. +s32 PS4_SYSV_ABI sceNetResolverConnectDestroy() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ResolverReturn(ORBIS_NET_ERROR_RESOLVER_ENOSUPPORT); +} + +void ReleaseResolverPool(OrbisNetId rid) { + std::scoped_lock lock{g_pools_mutex}; + if (const auto it = g_resolver_pools.find(rid); it != g_resolver_pools.end()) { + if (Pool* pool = FindPool(it->second)) { + --pool->users; + } + g_resolver_pools.erase(it); + } +} + +OrbisNetId PS4_SYSV_ABI sceNetResolverCreate(const char* name, s32 poolid, s32 flags) { + LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {:#x}", name != nullptr ? name : "", poolid, + flags); + if (flags != 0) { + return SetErrno(ORBIS_NET_EINVAL); + } + if (name != nullptr && + strnlen(name, ORBIS_NET_DEBUG_NAME_LEN_MAX + 1) > ORBIS_NET_DEBUG_NAME_LEN_MAX) { + return SetErrno(ORBIS_NET_ENAMETOOLONG); + } + std::scoped_lock lock{g_pools_mutex}; + Pool* pool = FindPool(poolid); + if (pool == nullptr) { + LOG_ERROR(Lib_Net, "unknown memory pool id {}", poolid); + return SetErrno(ORBIS_NET_EBADF); + } + const s32 id = CreateResolver(name != nullptr ? name : ""); + if (id < 0) { + return SetErrno(-id); + } + // The resolver holds its pool until destroyed. + ++pool->users; + g_resolver_pools[id] = poolid; + return id; +} + +s32 PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId rid) { + LOG_DEBUG(Lib_Net, "rid = {}", rid); + const int e = DestroyResolver(rid); + if (e != 0) { + return SetErrno(e); + } + ReleaseResolverPool(rid); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId rid, s32* status) { + if (status == nullptr) { + return SetErrno(ORBIS_NET_EINVAL); + } + const int e = ResolverGetError(rid, status); + LOG_DEBUG(Lib_Net, "rid = {}, status = {:#x}", rid, static_cast(e == 0 ? *status : 0)); + return e == 0 ? ORBIS_OK : SetErrno(e); +} + +s32 PS4_SYSV_ABI sceNetResolverStartAton(OrbisNetId rid, const OrbisNetInAddr* addr, char* hostname, + s32 len, s32 timeout, s32 retry, s32 flags) { + LOG_INFO(Lib_Net, "rid = {}, timeout = {}, retry = {}, flags = {:#x}", rid, timeout, retry, + flags); + return ResolverReturn( + ResolverStartAton(rid, addr, hostname, len, (flags & ORBIS_NET_RESOLVER_ASYNC) != 0)); +} + +// Not implemented. Fail rather than report success with nothing written. +s32 PS4_SYSV_ABI sceNetResolverStartAton6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ResolverReturn(ORBIS_NET_ERROR_RESOLVER_ENOSUPPORT); +} + +s32 PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId rid, const char* hostname, OrbisNetInAddr* addr, + s32 timeout, s32 retry, s32 flags) { + LOG_INFO(Lib_Net, "rid = {}, hostname = {}, timeout = {}, retry = {}, flags = {:#x}", rid, + hostname != nullptr ? hostname : "", timeout, retry, flags); + return ResolverReturn( + ResolverStartNtoa(rid, hostname, {addr, nullptr}, (flags & ORBIS_NET_RESOLVER_ASYNC) != 0, + (flags & ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS) != 0)); +} + +// Not implemented. Fail rather than report success with nothing written. +s32 PS4_SYSV_ABI sceNetResolverStartNtoa6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ResolverReturn(ORBIS_NET_ERROR_RESOLVER_ENOSUPPORT); +} + +s32 PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecords(OrbisNetId rid, const char* hostname, + OrbisNetResolverInfo* info, s32 timeout, + s32 retry, s32 flags) { + LOG_INFO(Lib_Net, "rid = {}, hostname = {}, timeout = {}, retry = {}, flags = {:#x}", rid, + hostname != nullptr ? hostname : "", timeout, retry, flags); + return ResolverReturn( + ResolverStartNtoa(rid, hostname, {nullptr, info}, (flags & ORBIS_NET_RESOLVER_ASYNC) != 0, + (flags & ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS) != 0)); +} + +// Flags 0x1000000 ask for AAAA records and 0x2000000 for A and AAAA. Only IPv4 is looked up, +// so AAAA-only has no records and A+AAAA returns the A records. +s32 PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecordsEx(OrbisNetId rid, const char* hostname, + OrbisNetResolverInfo* info, s32 timeout, + s32 retry, s32 flags) { + LOG_INFO(Lib_Net, "rid = {}, hostname = {}, timeout = {}, retry = {}, flags = {:#x}", rid, + hostname != nullptr ? hostname : "", timeout, retry, flags); + constexpr u32 Aaaa = 0x1000000; + constexpr u32 AAndAaaa = 0x2000000; + if ((static_cast(flags) & 0xfccefffeu) != 0) { + return ResolverReturn(ORBIS_NET_ERROR_EINVAL); + } + if ((flags & Aaaa) != 0 && (flags & AAndAaaa) == 0) { + LOG_WARNING(Lib_Net, "AAAA lookup of {} not supported", + hostname != nullptr ? hostname : ""); + return ResolverReturn(ORBIS_NET_ERROR_RESOLVER_ENORECORD); + } + return ResolverReturn( + ResolverStartNtoa(rid, hostname, {nullptr, info}, (flags & ORBIS_NET_RESOLVER_ASYNC) != 0, + (flags & ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS) != 0)); +} + +s32 PS4_SYSV_ABI sceNetSend(OrbisNetId s, const void* buf, u64 len, s32 flags) { + return sceNetSendto(s, buf, len, flags, nullptr, 0); +} + +static s32 sceNetSendmsgImpl(OrbisNetId s, const OrbisNetMsghdr* msg, s32 flags) { + if (msg == nullptr) { + return SetErrno(ORBIS_NET_EFAULT); + } + if (msg->msg_control != nullptr && msg->msg_controllen != 0) { + LOG_WARNING(Lib_Net, "ancillary data ignored ({} bytes)", msg->msg_controllen); + } + // Gather into one buffer so a datagram goes out whole. + std::vector buffer; + if (const int e = GatherSize(*msg, &buffer); e != 0) { + return SetErrno(e); + } + size_t offset = 0; + for (s32 i = 0; i < msg->msg_iovlen; ++i) { + std::memcpy(buffer.data() + offset, msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len); + offset += msg->msg_iov[i].iov_len; + } + return sceNetSendto(s, buffer.data(), buffer.size(), flags, + static_cast(msg->msg_name), msg->msg_namelen); +} + +s32 PS4_SYSV_ABI sceNetSendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, s32 flags) { + const s32 r = sceNetSendmsgImpl(s, msg, flags); + if (r > 0) { + CountTraffic(s, true, r); + } + LOG_TRACE(Lib_Net, "s = {}, flags = {:#x} -> {}", s, flags, + r >= 0 ? std::to_string(r) : ErrorCodeName(r)); + return r; +} + +static s32 sceNetSendtoImpl(OrbisNetId s, const void* buf, u64 len, s32 flags, + const OrbisNetSockaddr* addr, u32 addrlen) { + if (buf == nullptr && len != 0) { + return SetErrno(ORBIS_NET_EFAULT); + } + const auto msg = ToHostMsgFlags(flags); + if (msg.unsupported != 0) { + LOG_WARNING(Lib_Net, "ignoring flags {:#x}", msg.unsupported); + } + Core::Net::SocketInfo info{}; + if (const auto r = Core::Net::SocketGetInfo(s, &info); r.error != Error::Ok) { + return Return(r); + } + if (info.type != SOCK_STREAM) { + Core::Net::SocketAttributes attributes; + Core::Net::SocketGetAttributes(s, &attributes); + const s32 limit = info.type == SOCK_RAW ? MaxRawSend + : attributes.snd_buf != 0 ? attributes.snd_buf + : DefaultSendBuffer(info.type); + if (len > static_cast(limit)) { + return SetErrno(ORBIS_NET_EMSGSIZE); + } + } + if (addr != nullptr && info.connected) { + if (info.type != SOCK_STREAM) { + return SetErrno(ORBIS_NET_EISCONN); + } + addr = nullptr; + addrlen = 0; + } + if (info.p2p) { + P2P::Endpoint to; + u16 to_vport = 0; + if (addr != nullptr) { + if (const int e = ToP2PAddress(addr, addrlen, KindOf(info), &to, &to_vport); e != 0) { + return SetErrno(e); + } + if (info.type == SOCK_DGRAM && !IsCompleteP2PAddress(*addr, P2PKind::Datagram)) { + return SetErrno(ORBIS_NET_EADDRNOTAVAIL); + } + } + if ((msg.crypto || msg.signature) && info.type == SOCK_STREAM) { + LOG_WARNING(Lib_Net, + "per-message protection ignored on a stream (fixed per connection)"); + } + return Return(Core::Net::P2PSocketSendTo(s, buf, static_cast(len), msg.dontwait, + addr ? &to : nullptr, to_vport, + {msg.crypto, msg.signature})); + } + sockaddr_storage host{}; + socklen_t host_len = 0; + if (addr != nullptr) { + if (const int e = ToHostSockaddr(addr, addrlen, &host, &host_len); e != 0) { + return SetErrno(e); + } + } + return Return(Core::Net::SocketSendTo(s, buf, static_cast(len), msg.host, msg.dontwait, + addr ? reinterpret_cast(&host) : nullptr, + host_len)); +} + +s32 PS4_SYSV_ABI sceNetSendto(OrbisNetId s, const void* buf, u64 len, s32 flags, + const OrbisNetSockaddr* addr, u32 addrlen) { + const s32 r = sceNetSendtoImpl(s, buf, len, flags, addr, addrlen); + if (r > 0) { + CountTraffic(s, true, r); + NoteP2PPeer(s, true, addr, addrlen); + } + LOG_TRACE(Lib_Net, "s = {}, len = {}, flags = {:#x}, to = {} -> {}", s, len, flags, + addr != nullptr ? FormatSockaddr(addr, addrlen) : "connected peer", + r >= 0 ? std::to_string(r) : ErrorCodeName(r)); + return r; +} + +s32 PS4_SYSV_ABI sceNetSetDns6InfoToKernel() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetSetDnsInfoToKernel() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetSetsockopt(OrbisNetId s, s32 level, s32 optname, const void* optval, + u32 optlen) { + if (optval != nullptr && optlen >= sizeof(s32)) { + s32 logged; + std::memcpy(&logged, optval, sizeof(logged)); + LOG_DEBUG(Lib_Net, "s = {}, {} = {} (optlen {})", s, OptionName(level, optname), logged, + optlen); + } else { + LOG_DEBUG(Lib_Net, "s = {}, {} (optlen {})", s, OptionName(level, optname), optlen); + } + if (optval == nullptr && optlen != 0) { + return SetErrno(ORBIS_NET_EFAULT); + } + Core::Net::SocketInfo info{}; + if (const auto r = Core::Net::SocketGetInfo(s, &info); r.error != Error::Ok) { + return Return(r); + } + + const OptionRule* rule = FindOptionRule(level, optname); + if (rule == nullptr || !rule->set) { + return SetErrno(ORBIS_NET_ENOPROTOOPT); + } + + // Multicast TTL/loop also accept a single byte. + s32 value = 0; + bool has_int = false; + if (optval != nullptr && optlen >= sizeof(s32)) { + std::memcpy(&value, optval, sizeof(value)); + has_int = true; + } else if (optval != nullptr && optlen == 1 && rule->byte_or_int) { + value = *static_cast(optval); + has_int = true; + } + const auto store = [&](s32 v) { + return Return(Core::Net::SocketUpdateAttributes( + s, [&](Core::Net::SocketAttributes& a) { a.stored_options[{level, optname}] = v; })); + }; + + if (!(rule->valid_for & KindBit(info))) { + if (!rule->others_ignored) { + return SetErrno(ORBIS_NET_EPROCUNAVAIL); + } + return has_int ? store(value) : ORBIS_OK; + } + + if (level == ORBIS_NET_SOL_SOCKET) { + switch (optname) { + case ORBIS_NET_SO_NBIO: + return has_int ? Return(Core::Net::SocketSetNonBlocking(s, value != 0)) + : SetErrno(ORBIS_NET_EINVAL); + case ORBIS_NET_SO_RCVTIMEO: + case ORBIS_NET_SO_SNDTIMEO: + case ORBIS_NET_SO_CONNECTTIMEO: + case ORBIS_NET_SO_ACCEPTTIMEO: { + if (!has_int) { + return SetErrno(ORBIS_NET_EINVAL); + } + // Microseconds, <= 0 means no timeout. + const std::chrono::microseconds timeout{std::max(value, 0)}; + switch (optname) { + case ORBIS_NET_SO_RCVTIMEO: + return Return(Core::Net::SocketSetRecvTimeout(s, timeout)); + case ORBIS_NET_SO_SNDTIMEO: + return Return(Core::Net::SocketSetSendTimeout(s, timeout)); + case ORBIS_NET_SO_CONNECTTIMEO: + return Return(Core::Net::SocketSetConnectTimeout(s, timeout)); + default: + return Return(Core::Net::SocketSetAcceptTimeout(s, timeout)); + } + } + case BsdRcvTimeo: + case BsdSndTimeo: { + const auto timeout = ReadTimeval(optval, optlen); + if (!timeout) { + return SetErrno(ORBIS_NET_EINVAL); + } + return Return(optname == BsdRcvTimeo ? Core::Net::SocketSetRecvTimeout(s, *timeout) + : Core::Net::SocketSetSendTimeout(s, *timeout)); + } + case ORBIS_NET_SO_REUSEADDR: + return has_int ? Return(Core::Net::SocketSetReuseAddr(s, value != 0)) + : SetErrno(ORBIS_NET_EINVAL); + case ORBIS_NET_SO_REUSEPORT: + if (!has_int) { + return SetErrno(ORBIS_NET_EINVAL); + } + if (!info.p2p) { + Core::Net::SocketSetReusePort(s, value != 0); + } + return store(value); + case ORBIS_NET_SO_USECRYPTO: + case ORBIS_NET_SO_USESIGNATURE: { + if (!has_int) { + return SetErrno(ORBIS_NET_EINVAL); + } + bool crypto = false; + bool signature = false; + Core::Net::P2PSocketGetProtection(s, &crypto, &signature); + (optname == ORBIS_NET_SO_USECRYPTO ? crypto : signature) = value != 0; + return Return(Core::Net::P2PSocketSetProtection(s, crypto, signature)); + } + case ORBIS_NET_SO_SNDBUF: + case ORBIS_NET_SO_RCVBUF: { + if (!has_int || value <= 0) { + return SetErrno(ORBIS_NET_EINVAL); + } + value = std::min(value, MaxSocketBuffer); + Core::Net::SocketUpdateAttributes(s, [&](Core::Net::SocketAttributes& a) { + (optname == ORBIS_NET_SO_SNDBUF ? a.snd_buf : a.rcv_buf) = value; + }); + if (info.p2p || KindBit(info) == KindRaw) { + return ORBIS_OK; // meaningless for RAW + } + break; + } + case ORBIS_NET_SO_POLICY: + if (!has_int || + (value != ORBIS_NET_SOCK_POLICY_NA && (value < ORBIS_NET_SOCK_POLICY_NUM_MIN || + value > ORBIS_NET_SOCK_POLICY_NUM_MAX))) { + return SetErrno(ORBIS_NET_EINVAL); + } + return Return(Core::Net::SocketUpdateAttributes( + s, [&](Core::Net::SocketAttributes& a) { a.policy = value; })); + case ORBIS_NET_SO_PRIORITY: + if (!has_int || value < ORBIS_NET_SOCK_PRIORITY_NUM_USER_MIN || + value > ORBIS_NET_SOCK_PRIORITY_NUM_USER_MAX) { + return SetErrno(ORBIS_NET_EINVAL); + } + return Return(Core::Net::SocketUpdateAttributes( + s, [&](Core::Net::SocketAttributes& a) { a.priority = value; })); + case ORBIS_NET_SO_NAME: { + const auto* text = static_cast(optval); + const size_t length = text ? strnlen(text, optlen) : 0; + if (length > static_cast(ORBIS_NET_DEBUG_NAME_LEN_MAX)) { + return SetErrno(ORBIS_NET_ENAMETOOLONG); + } + return Return(Core::Net::SocketUpdateAttributes(s, [&](Core::Net::SocketAttributes& a) { + a.name = {}; + std::memcpy(a.name.data(), text, length); + })); + } + case ORBIS_NET_SO_ONESBCAST: + // TODO: 255.255.255.255 isn't converted to the interface broadcast address. Hosts + // deliver it on the LAN anyway. + return has_int ? store(value) : SetErrno(ORBIS_NET_EINVAL); + default: + break; + } + } + if (level == ORBIS_NET_IPPROTO_UDP && optname == ORBIS_NET_UDP_SND_ON_SUSPEND) { + return SetSendOnSuspend(s, info, optval, optlen); + } + if (level == ORBIS_NET_IPPROTO_TCP && optname == ORBIS_NET_TCP_MSS_TO_ADVERTISE) { + // 0 means the interface MSS. + if (!has_int || (value != 0 && value < 216)) { + return SetErrno(ORBIS_NET_EINVAL); + } + } + + const auto option = ToHostOption(level, optname); + if (level == ORBIS_NET_SOL_SOCKET && optname == ORBIS_NET_SO_LINGER && info.p2p) { + // Stored whole so getsockopt can return it. + // TODO: not applied on P2P TCP close. + if (optlen < sizeof(OrbisNetLinger)) { + return SetErrno(ORBIS_NET_EINVAL); + } + OrbisNetLinger guest; + std::memcpy(&guest, optval, sizeof(guest)); + return Return(Core::Net::SocketUpdateAttributes(s, [&](Core::Net::SocketAttributes& a) { + a.stored_options[{level, optname}] = guest.l_onoff; + a.stored_options[{level, LingerSecondsKey}] = guest.l_linger; + })); + } +#ifdef _WIN32 + if (level == ORBIS_NET_IPPROTO_TCP && optname == ORBIS_NET_TCP_MAXSEG && !info.p2p) { + // Windows can read TCP_MAXSEG but not set it, so just store it. + if (!has_int) { + return SetErrno(ORBIS_NET_EINVAL); + } + return store(value); + } +#endif + if (info.p2p || !option || rule->stored) { + if (!has_int) { + return SetErrno(ORBIS_NET_EINVAL); + } + if (!info.p2p && level == ORBIS_NET_IPPROTO_IP && optname == IpDontFrag) { + if (const int e = SetHostDontFragment(s, value != 0); e != 0) { + return SetErrno(e); + } + } + return store(value); + } + switch (option->value) { + case OptionValue::Int: { + if (!has_int) { + return SetErrno(ORBIS_NET_EINVAL); + } + const int host_value = value; + return Return(Core::Net::SocketSetHostOption(s, option->level, option->name, &host_value, + sizeof(host_value))); + } + case OptionValue::Linger: { + if (optlen < sizeof(OrbisNetLinger)) { + return SetErrno(ORBIS_NET_EINVAL); + } + OrbisNetLinger guest; + std::memcpy(&guest, optval, sizeof(guest)); + linger host{}; + host.l_onoff = static_cast(guest.l_onoff); + host.l_linger = static_cast(guest.l_linger); + return Return( + Core::Net::SocketSetHostOption(s, option->level, option->name, &host, sizeof(host))); + } + case OptionValue::Raw: + if (level == ORBIS_NET_IPPROTO_IP && + (optname == ORBIS_NET_IP_ADD_MEMBERSHIP || optname == ORBIS_NET_IP_DROP_MEMBERSHIP)) { + return SetMembership(s, optname == ORBIS_NET_IP_ADD_MEMBERSHIP, *option, optval, + optlen); + } + return Return(Core::Net::SocketSetHostOption(s, option->level, option->name, optval, + static_cast(optlen))); + } + return SetErrno(ORBIS_NET_ENOPROTOOPT); +} + +s32 PS4_SYSV_ABI sceNetShowIfconfig() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowIfconfigForBuffer() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowIfconfigWithMemory(s32 memid) { + if (const s32 e = CheckPoolId(memid); e != 0) { + return SetErrno(e); + } + LOG_ERROR(Lib_Net, "(STUBBED) called, memid = {}", memid); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowNetstat() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowNetstatEx() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowNetstatExForBuffer() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowNetstatForBuffer() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowNetstatWithMemory(s32 memid) { + if (const s32 e = CheckPoolId(memid); e != 0) { + return SetErrno(e); + } + LOG_ERROR(Lib_Net, "(STUBBED) called, memid = {}", memid); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowPolicy() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowPolicyWithMemory(s32 memid) { + if (const s32 e = CheckPoolId(memid); e != 0) { + return SetErrno(e); + } + LOG_ERROR(Lib_Net, "(STUBBED) called, memid = {}", memid); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowRoute() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowRoute6() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowRoute6ForBuffer() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowRoute6WithMemory(s32 memid) { + if (const s32 e = CheckPoolId(memid); e != 0) { + return SetErrno(e); + } + LOG_ERROR(Lib_Net, "(STUBBED) called, memid = {}", memid); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowRouteForBuffer() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShowRouteWithMemory(s32 memid) { + if (const s32 e = CheckPoolId(memid); e != 0) { + return SetErrno(e); + } + LOG_ERROR(Lib_Net, "(STUBBED) called, memid = {}", memid); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetShutdown(OrbisNetId s, s32 how) { + LOG_DEBUG(Lib_Net, "s = {}, how = {}", s, how); + if (how < ORBIS_NET_SHUT_RD || how > ORBIS_NET_SHUT_RDWR) { + return SetErrno(ORBIS_NET_EINVAL); + } + return Return(Core::Net::SocketShutdown(s, how)); +} + +static OrbisNetId sceNetSocketImpl(const char* name, s32 family, s32 type, s32 protocol) { + int error = 0; + const auto kind = ToHostSocketKind(family, type, protocol, &error); + if (!kind) { + return SetErrno(error); + } + // Names over 31 chars are ENAMETOOLONG, not truncated. + if (name != nullptr && + strnlen(name, ORBIS_NET_DEBUG_NAME_LEN_MAX + 1) > ORBIS_NET_DEBUG_NAME_LEN_MAX) { + return SetErrno(ORBIS_NET_ENAMETOOLONG); + } + if (kind->p2p && !EnsureP2PTransport()) { + return SetErrno(ORBIS_NET_EADDRINUSE); // P2P port taken + } + const auto r = kind->p2p ? Core::Net::P2PSocketCreate(kind->family, kind->type == SOCK_STREAM) + : Core::Net::SocketCreate(kind->family, kind->type, kind->protocol); + if (r.error == Error::Ok && name != nullptr) { + Core::Net::SocketUpdateAttributes(static_cast(r.value), + [&](Core::Net::SocketAttributes& a) { + std::strncpy(a.name.data(), name, a.name.size() - 1); + }); + } + return Return(r); +} + +OrbisNetId PS4_SYSV_ABI sceNetSocket(const char* name, s32 family, s32 type, s32 protocol) { + const OrbisNetId r = sceNetSocketImpl(name, family, type, protocol); + LOG_INFO(Lib_Net, "'{}' family {} {} protocol {} -> {}", name != nullptr ? name : "", family, + SocketTypeName(type), protocol, r >= 0 ? std::to_string(r) : ErrorCodeName(r)); + return r; +} + +s32 PS4_SYSV_ABI sceNetSocketAbort(OrbisNetId s, s32 flags) { + LOG_DEBUG(Lib_Net, "s = {}, flags = {:#x}", s, flags); + return Return(Core::Net::SocketAbort(s, static_cast(flags))); +} + +s32 PS4_SYSV_ABI sceNetSocketClose(OrbisNetId s) { + const auto r = Core::Net::SocketClose(s); + if (r.error == Error::Ok) { + LogSocketClosed(s); + } + return Return(r); +} + +s32 PS4_SYSV_ABI sceNetSyncCreate() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetSyncDestroy() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetSyncGet() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetSyncSignal() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetSyncWait() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetSysctl() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetTerm() { + LOG_INFO(Lib_Net, "called"); + return ORBIS_OK; +} + +namespace { +struct OrbisNetThreadParam { + void(PS4_SYSV_ABI* entry)(void* arg); + void* arg; + u64 reserved; + u32 flags; +}; + +void* PS4_SYSV_ABI NetThreadEntry(void* p) { + const auto* param = static_cast(p); + param->entry(param->arg); + if ((param->flags & 1) != 0) { + Kernel::posix_pthread_exit(nullptr); + } + return nullptr; +} + +s32 ThreadReturn(s32 code) { + if (code < 0) { + g_net_errno = (static_cast(code) & 0xff00) == 0x100 + ? static_cast(static_cast(code) & 0xff) + : 0xcd; + } + return code; +} +} // namespace + +s32 PS4_SYSV_ABI sceNetThreadCreate(Kernel::PthreadT* thread, void* param, const char* name) { + if (param == nullptr) { + return SetErrno(ORBIS_NET_EINVAL); + } + Kernel::PthreadT created = nullptr; + const int e = + Kernel::posix_pthread_create_name_np(&created, nullptr, NetThreadEntry, param, name); + if (e != 0) { + LOG_ERROR(Lib_Net, "thread '{}' not created: {}", name != nullptr ? name : "", e); + return ThreadReturn(ORBIS_KERNEL_ERROR_UNKNOWN + e); + } + if (thread != nullptr) { + *thread = created; + } + return ORBIS_OK; +} + +void PS4_SYSV_ABI sceNetThreadExit() { + Kernel::posix_pthread_exit(nullptr); +} + +s32 PS4_SYSV_ABI sceNetThreadJoin(Kernel::PthreadT thread) { + const int e = Kernel::posix_pthread_join(thread, nullptr); + return e == 0 ? ORBIS_OK : ThreadReturn(ORBIS_KERNEL_ERROR_UNKNOWN + e); +} + +s32 PS4_SYSV_ABI sceNetUsleep(s32 microseconds) { + if (microseconds < 0) { + return SetErrno(ORBIS_NET_EINVAL); + } + return Kernel::sceKernelUsleep(static_cast(microseconds)); +} + +s32 PS4_SYSV_ABI Func_0E707A589F751C68() { + LOG_ERROR(Lib_Net, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNetEmulationGet() { + LOG_WARNING(Lib_Net, "network emulation is not available on retail units"); + return SetErrno(ORBIS_NET_EOPNOTSUPP); +} + +s32 PS4_SYSV_ABI sceNetEmulationSet() { + LOG_WARNING(Lib_Net, "network emulation is not available on retail units"); + return SetErrno(ORBIS_NET_EOPNOTSUPP); +} + +void SetKernelErrnoHook(void (*hook)(int orbis_errno)) { + g_kernel_errno_hook.store(hook); +} + +SystemHooks GetSystemHooks() { + std::scoped_lock lock{g_hooks_mutex}; + return g_hooks; +} + +void SetSystemHooks(SystemHooks hooks) { + SetResolverOnlineCheck(hooks.is_online); + std::scoped_lock lock{g_hooks_mutex}; + g_hooks = std::move(hooks); +} + +void RegisterLib(Core::Loader::SymbolsResolver* sym) { + LIB_OBJ("ZRAJo-A-ukc", "libSceNet", 1, "libSceNet", &g_in6addr_any); + LIB_OBJ("XCuA-GqjA-k", "libSceNet", 1, "libSceNet", &g_in6addr_loopback); + LIB_OBJ("VZgoeBxPXUQ", "libSceNet", 1, "libSceNet", &g_sce_net_dummy); + LIB_OBJ("GAtITrgxKDE", "libSceNet", 1, "libSceNet", &g_sce_net_in6addr_any); + LIB_OBJ("84MgU4MMTLQ", "libSceNet", 1, "libSceNet", &g_sce_net_in6addr_linklocal_allnodes); + LIB_OBJ("2uSWyOKYc1M", "libSceNet", 1, "libSceNet", &g_sce_net_in6addr_linklocal_allrouters); + LIB_OBJ("P3AeWBvPrkg", "libSceNet", 1, "libSceNet", &g_sce_net_in6addr_loopback); + LIB_OBJ("PgNI+j4zxzM", "libSceNet", 1, "libSceNet", &g_sce_net_in6addr_nodelocal_allnodes); + LIB_FUNCTION("PIWqhn9oSxc", "libSceNet", 1, "libSceNet", sceNetAccept); + LIB_FUNCTION("BTUvkWzrP68", "libSceNet", 1, "libSceNet", sceNetAddrConfig6GetInfo); + LIB_FUNCTION("3qG7UJy2Fq8", "libSceNet", 1, "libSceNet", sceNetAddrConfig6Start); + LIB_FUNCTION("P+0ePpDfUAQ", "libSceNet", 1, "libSceNet", sceNetAddrConfig6Stop); + LIB_FUNCTION("PcdLABhYga4", "libSceNet", 1, "libSceNet", sceNetAllocateAllRouteInfo); + LIB_FUNCTION("xHq87H78dho", "libSceNet", 1, "libSceNet", sceNetBandwidthControlGetDataTraffic); + LIB_FUNCTION("c8IRpl4L74I", "libSceNet", 1, "libSceNet", sceNetBandwidthControlGetDefaultParam); + LIB_FUNCTION("b9Ft65tqvLk", "libSceNet", 1, "libSceNet", sceNetBandwidthControlGetIfParam); + LIB_FUNCTION("PDkapOwggRw", "libSceNet", 1, "libSceNet", sceNetBandwidthControlGetPolicy); + LIB_FUNCTION("P4zZXE7bpsA", "libSceNet", 1, "libSceNet", sceNetBandwidthControlSetDefaultParam); + LIB_FUNCTION("g4DKkzV2qC4", "libSceNet", 1, "libSceNet", sceNetBandwidthControlSetIfParam); + LIB_FUNCTION("7Z1hhsEmkQU", "libSceNet", 1, "libSceNet", sceNetBandwidthControlSetPolicy); + LIB_FUNCTION("bErx49PgxyY", "libSceNet", 1, "libSceNet", sceNetBind); + LIB_FUNCTION("eyLyLJrdEOU", "libSceNet", 1, "libSceNet", sceNetClearDnsCache); + LIB_FUNCTION("Ea2NaVMQNO8", "libSceNet", 1, "libSceNet", sceNetConfigAddArp); + LIB_FUNCTION("0g0qIuPN3ZQ", "libSceNet", 1, "libSceNet", sceNetConfigAddArpWithInterface); + LIB_FUNCTION("ge7g15Sqhks", "libSceNet", 1, "libSceNet", sceNetConfigAddIfaddr); + LIB_FUNCTION("FDHr4Iz7dQU", "libSceNet", 1, "libSceNet", sceNetConfigAddMRoute); + LIB_FUNCTION("Cyjl1yzi1qY", "libSceNet", 1, "libSceNet", sceNetConfigAddRoute); + LIB_FUNCTION("Bu+L5r1lKRg", "libSceNet", 1, "libSceNet", sceNetConfigAddRoute6); + LIB_FUNCTION("wIGold7Lro0", "libSceNet", 1, "libSceNet", sceNetConfigAddRouteWithInterface); + LIB_FUNCTION("MzA1YrRE6rA", "libSceNet", 1, "libSceNet", sceNetConfigCleanUpAllInterfaces); + LIB_FUNCTION("HJt+4x-CnY0", "libSceNet", 1, "libSceNet", sceNetConfigDelArp); + LIB_FUNCTION("xTcttXJ3Utg", "libSceNet", 1, "libSceNet", sceNetConfigDelArpWithInterface); + LIB_FUNCTION("RuVwHEW6dM4", "libSceNet", 1, "libSceNet", sceNetConfigDelDefaultRoute); + LIB_FUNCTION("UMlVCy7RX1s", "libSceNet", 1, "libSceNet", sceNetConfigDelDefaultRoute6); + LIB_FUNCTION("0239JNsI6PE", "libSceNet", 1, "libSceNet", sceNetConfigDelIfaddr); + LIB_FUNCTION("hvCXMwd45oc", "libSceNet", 1, "libSceNet", sceNetConfigDelIfaddr6); + LIB_FUNCTION("5Yl1uuh5i-A", "libSceNet", 1, "libSceNet", sceNetConfigDelMRoute); + LIB_FUNCTION("QO7+2E3cD-U", "libSceNet", 1, "libSceNet", sceNetConfigDelRoute); + LIB_FUNCTION("4wDGvfhmkmk", "libSceNet", 1, "libSceNet", sceNetConfigDelRoute6); + LIB_FUNCTION("3WzWV86AJ3w", "libSceNet", 1, "libSceNet", sceNetConfigDownInterface); + LIB_FUNCTION("mOUkgTaSkJU", "libSceNet", 1, "libSceNet", sceNetConfigEtherGetLinkMode); + LIB_FUNCTION("pF3Vy1iZ5bs", "libSceNet", 1, "libSceNet", sceNetConfigEtherPostPlugInOutEvent); + LIB_FUNCTION("QltDK6wWqF0", "libSceNet", 1, "libSceNet", sceNetConfigEtherSetLinkMode); + LIB_FUNCTION("18KNgSvYx+Y", "libSceNet", 1, "libSceNet", sceNetConfigFlushRoute); + LIB_FUNCTION("lFJb+BlPK1c", "libSceNet", 1, "libSceNet", sceNetConfigGetDefaultRoute); + LIB_FUNCTION("mCLdiNIKtW0", "libSceNet", 1, "libSceNet", sceNetConfigGetDefaultRoute6); + LIB_FUNCTION("ejwa0hWWhDs", "libSceNet", 1, "libSceNet", sceNetConfigGetIfaddr); + LIB_FUNCTION("FU6NK4RHQVE", "libSceNet", 1, "libSceNet", sceNetConfigGetIfaddr6); + LIB_FUNCTION("vbZLomImmEE", "libSceNet", 1, "libSceNet", sceNetConfigRoutingShowRoutingConfig); + LIB_FUNCTION("a6sS6iSE0IA", "libSceNet", 1, "libSceNet", sceNetConfigRoutingShowtCtlVar); + LIB_FUNCTION("eszLdtIMfQE", "libSceNet", 1, "libSceNet", sceNetConfigRoutingStart); + LIB_FUNCTION("toi8xxcSfJ0", "libSceNet", 1, "libSceNet", sceNetConfigRoutingStop); + LIB_FUNCTION("EAl7xvi7nXg", "libSceNet", 1, "libSceNet", sceNetConfigSetDefaultRoute); + LIB_FUNCTION("4zLOHbt3UFk", "libSceNet", 1, "libSceNet", sceNetConfigSetDefaultRoute6); + LIB_FUNCTION("yaVAdLDxUj0", "libSceNet", 1, "libSceNet", sceNetConfigSetDefaultScope); + LIB_FUNCTION("8Kh+1eidI3c", "libSceNet", 1, "libSceNet", sceNetConfigSetIfaddr); + LIB_FUNCTION("QJbV3vfBQ8Q", "libSceNet", 1, "libSceNet", sceNetConfigSetIfaddr6); + LIB_FUNCTION("POrSEl8zySw", "libSceNet", 1, "libSceNet", sceNetConfigSetIfaddr6WithFlags); + LIB_FUNCTION("0sesmAYH3Lk", "libSceNet", 1, "libSceNet", sceNetConfigSetIfFlags); + LIB_FUNCTION("uNTluLfYgS8", "libSceNet", 1, "libSceNet", sceNetConfigSetIfLinkLocalAddr6); + LIB_FUNCTION("s31rYkpIMMQ", "libSceNet", 1, "libSceNet", sceNetConfigSetIfmtu); + LIB_FUNCTION("tvdzQkm+UaY", "libSceNet", 1, "libSceNet", sceNetConfigUnsetIfFlags); + LIB_FUNCTION("oGEBX0eXGFs", "libSceNet", 1, "libSceNet", sceNetConfigUpInterface); + LIB_FUNCTION("6HNbayHPL7c", "libSceNet", 1, "libSceNet", sceNetConfigUpInterfaceWithFlags); + LIB_FUNCTION("6A6EweB3Dto", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocClearWakeOnWlan); + LIB_FUNCTION("ZLdJyQJUMkM", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocCreate); + LIB_FUNCTION("Yr3UeApLWTY", "libSceNet", 1, "libSceNet", + sceNetConfigWlanAdhocGetWakeOnWlanInfo); + LIB_FUNCTION("Xma8yHmV+TQ", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocJoin); + LIB_FUNCTION("K4o48GTNbSc", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocLeave); + LIB_FUNCTION("ZvKgNrrLCCQ", "libSceNet", 1, "libSceNet", + sceNetConfigWlanAdhocPspEmuClearWakeOnWlan); + LIB_FUNCTION("1j4DZ5dXbeQ", "libSceNet", 1, "libSceNet", + sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo); + LIB_FUNCTION("C-+JPjaEhdA", "libSceNet", 1, "libSceNet", + sceNetConfigWlanAdhocPspEmuSetWakeOnWlan); + LIB_FUNCTION("7xYdUWg1WdY", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocScanJoin); + LIB_FUNCTION("Q7ee2Uav5f8", "libSceNet", 1, "libSceNet", + sceNetConfigWlanAdhocSetExtInfoElement); + LIB_FUNCTION("xaOTiuxIQNY", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocSetWakeOnWlan); + LIB_FUNCTION("QlRJWya+dtE", "libSceNet", 1, "libSceNet", sceNetConfigWlanApStart); + LIB_FUNCTION("6uYcvVjH7Ms", "libSceNet", 1, "libSceNet", sceNetConfigWlanApStop); + LIB_FUNCTION("MDbg-oAj8Aw", "libSceNet", 1, "libSceNet", sceNetConfigWlanBackgroundScanQuery); + LIB_FUNCTION("cMA8f6jI6s0", "libSceNet", 1, "libSceNet", sceNetConfigWlanBackgroundScanStart); + LIB_FUNCTION("3T5aIe-7L84", "libSceNet", 1, "libSceNet", sceNetConfigWlanBackgroundScanStop); + LIB_FUNCTION("+3KMyS93TOs", "libSceNet", 1, "libSceNet", sceNetConfigWlanDiagGetDeviceInfo); + LIB_FUNCTION("9oiOWQ5FMws", "libSceNet", 1, "libSceNet", sceNetConfigWlanDiagSetAntenna); + LIB_FUNCTION("fHr45B97n0U", "libSceNet", 1, "libSceNet", sceNetConfigWlanDiagSetTxFixedRate); + LIB_FUNCTION("PNDDxnqqtk4", "libSceNet", 1, "libSceNet", sceNetConfigWlanGetDeviceConfig); + LIB_FUNCTION("Pkx0lwWVzmQ", "libSceNet", 1, "libSceNet", sceNetConfigWlanInfraGetRssiInfo); + LIB_FUNCTION("IkBCxG+o4Nk", "libSceNet", 1, "libSceNet", sceNetConfigWlanInfraLeave); + LIB_FUNCTION("273-I-zD8+8", "libSceNet", 1, "libSceNet", sceNetConfigWlanInfraScanJoin); + LIB_FUNCTION("-Mi5hNiWC4c", "libSceNet", 1, "libSceNet", sceNetConfigWlanScan); + LIB_FUNCTION("U1q6DrPbY6k", "libSceNet", 1, "libSceNet", sceNetConfigWlanSetDeviceConfig); + LIB_FUNCTION("OXXX4mUk3uk", "libSceNet", 1, "libSceNet", sceNetConnect); + LIB_FUNCTION("lDTIbqNs0ps", "libSceNet", 1, "libSceNet", sceNetControl); + LIB_FUNCTION("Q6T-zIblNqk", "libSceNet", 1, "libSceNet", sceNetDhcpdStart); + LIB_FUNCTION("xwWm8jzrpeM", "libSceNet", 1, "libSceNet", sceNetDhcpdStop); + LIB_FUNCTION("KhQxhlEslo0", "libSceNet", 1, "libSceNet", sceNetDhcpGetAutoipInfo); + LIB_FUNCTION("ix4LWXd12F0", "libSceNet", 1, "libSceNet", sceNetDhcpGetInfo); + LIB_FUNCTION("DrZuCQDnm3w", "libSceNet", 1, "libSceNet", sceNetDhcpGetInfoEx); + LIB_FUNCTION("Wzv6dngR-DQ", "libSceNet", 1, "libSceNet", sceNetDhcpStart); + LIB_FUNCTION("6AN7OlSMWk0", "libSceNet", 1, "libSceNet", sceNetDhcpStop); + LIB_FUNCTION("+ezgWao0wo8", "libSceNet", 1, "libSceNet", sceNetDumpAbort); + LIB_FUNCTION("bghgkeLKq1Q", "libSceNet", 1, "libSceNet", sceNetDumpCreate); + LIB_FUNCTION("xZ54Il-u1vs", "libSceNet", 1, "libSceNet", sceNetDumpDestroy); + LIB_FUNCTION("YWTpt45PxbI", "libSceNet", 1, "libSceNet", sceNetDumpRead); + LIB_FUNCTION("TwjkDIPdZ1Q", "libSceNet", 1, "libSceNet", sceNetDuplicateIpStart); + LIB_FUNCTION("QCbvCx9HL30", "libSceNet", 1, "libSceNet", sceNetDuplicateIpStop); + LIB_FUNCTION("w21YgGGNtBk", "libSceNet", 1, "libSceNet", sceNetEpollAbort); + LIB_FUNCTION("ZVw46bsasAk", "libSceNet", 1, "libSceNet", sceNetEpollControl); + LIB_FUNCTION("SF47kB2MNTo", "libSceNet", 1, "libSceNet", sceNetEpollCreate); + LIB_FUNCTION("Inp1lfL+Jdw", "libSceNet", 1, "libSceNet", sceNetEpollDestroy); + LIB_FUNCTION("drjIbDbA7UQ", "libSceNet", 1, "libSceNet", sceNetEpollWait); + LIB_FUNCTION("HQOwnfMGipQ", "libSceNet", 1, "libSceNet", sceNetErrnoLoc); + LIB_FUNCTION("v6M4txecCuo", "libSceNet", 1, "libSceNet", sceNetEtherNtostr); + LIB_FUNCTION("b-bFZvNV59I", "libSceNet", 1, "libSceNet", sceNetEtherStrton); + LIB_FUNCTION("cWGGXoeZUzA", "libSceNet", 1, "libSceNet", sceNetEventCallbackCreate); + LIB_FUNCTION("jzP0MoZpYnI", "libSceNet", 1, "libSceNet", sceNetEventCallbackDestroy); + LIB_FUNCTION("tB3BB8AsrjU", "libSceNet", 1, "libSceNet", sceNetEventCallbackGetError); + LIB_FUNCTION("5isaotjMWlA", "libSceNet", 1, "libSceNet", sceNetEventCallbackWaitCB); + LIB_FUNCTION("2ee14ktE1lw", "libSceNet", 1, "libSceNet", sceNetFreeAllRouteInfo); + LIB_FUNCTION("q8j9OSdnN1Y", "libSceNet", 1, "libSceNet", sceNetGetArpInfo); + LIB_FUNCTION("wmoIm94hqik", "libSceNet", 1, "libSceNet", sceNetGetDns6Info); + LIB_FUNCTION("nCL0NyZsd5A", "libSceNet", 1, "libSceNet", sceNetGetDnsInfo); + LIB_FUNCTION("HoV-GJyx7YY", "libSceNet", 1, "libSceNet", sceNetGetIfList); + LIB_FUNCTION("ahiOMqoYYMc", "libSceNet", 1, "libSceNet", sceNetGetIfListOnce); + LIB_FUNCTION("0MT2l3uIX7c", "libSceNet", 1, "libSceNet", sceNetGetIfName); + LIB_FUNCTION("5lrSEHdqyos", "libSceNet", 1, "libSceNet", sceNetGetIfnameNumList); + LIB_FUNCTION("6Oc0bLsIYe0", "libSceNet", 1, "libSceNet", sceNetGetMacAddress); + LIB_FUNCTION("rMyh97BU5pY", "libSceNet", 1, "libSceNet", sceNetGetMemoryPoolStats); + LIB_FUNCTION("+S-2-jlpaBo", "libSceNet", 1, "libSceNet", sceNetGetNameToIndex); + LIB_FUNCTION("TCkRD0DWNLg", "libSceNet", 1, "libSceNet", sceNetGetpeername); + LIB_FUNCTION("G3O2j9f5z00", "libSceNet", 1, "libSceNet", sceNetGetRandom); + LIB_FUNCTION("6Nx1hIQL9h8", "libSceNet", 1, "libSceNet", sceNetGetRouteInfo); + LIB_FUNCTION("hLuXdjHnhiI", "libSceNet", 1, "libSceNet", sceNetGetSockInfo); + LIB_FUNCTION("Cidi9Y65mP8", "libSceNet", 1, "libSceNet", sceNetGetSockInfo6); + LIB_FUNCTION("hoOAofhhRvE", "libSceNet", 1, "libSceNet", sceNetGetsockname); + LIB_FUNCTION("xphrZusl78E", "libSceNet", 1, "libSceNet", sceNetGetsockopt); + LIB_FUNCTION("GA5ZDaLtUBE", "libSceNet", 1, "libSceNet", sceNetGetStatisticsInfo); + LIB_FUNCTION("9mIcUExH34w", "libSceNet", 1, "libSceNet", sceNetGetStatisticsInfoInternal); + LIB_FUNCTION("p2vxsE2U3RQ", "libSceNet", 1, "libSceNet", sceNetGetSystemTime); + LIB_FUNCTION("9T2pDF2Ryqg", "libSceNet", 1, "libSceNet", sceNetHtonl); + LIB_FUNCTION("3CHi1K1wsCQ", "libSceNet", 1, "libSceNet", sceNetHtonll); + LIB_FUNCTION("iWQWrwiSt8A", "libSceNet", 1, "libSceNet", sceNetHtons); + LIB_FUNCTION("9vA2aW+CHuA", "libSceNet", 1, "libSceNet", sceNetInetNtop); + LIB_FUNCTION("Eh+Vqkrrc00", "libSceNet", 1, "libSceNet", sceNetInetNtopWithScopeId); + LIB_FUNCTION("8Kcp5d-q1Uo", "libSceNet", 1, "libSceNet", sceNetInetPton); + LIB_FUNCTION("Xn2TA2QhxHc", "libSceNet", 1, "libSceNet", sceNetInetPtonEx); + LIB_FUNCTION("b+LixqREH6A", "libSceNet", 1, "libSceNet", sceNetInetPtonWithScopeId); + LIB_FUNCTION("cYW1ISGlOmo", "libSceNet", 1, "libSceNet", sceNetInfoDumpStart); + LIB_FUNCTION("XfV-XBCuhDo", "libSceNet", 1, "libSceNet", sceNetInfoDumpStop); + LIB_FUNCTION("Nlev7Lg8k3A", "libSceNet", 1, "libSceNet", sceNetInit); + LIB_FUNCTION("6MojQ8uFHEI", "libSceNet", 1, "libSceNet", sceNetInitParam); + LIB_FUNCTION("ghqRRVQxqKo", "libSceNet", 1, "libSceNet", sceNetIoctl); + LIB_FUNCTION("kOj1HiAGE54", "libSceNet", 1, "libSceNet", sceNetListen); + LIB_FUNCTION("HKIa-WH0AZ4", "libSceNet", 1, "libSceNet", sceNetMemoryAllocate); + LIB_FUNCTION("221fvqVs+sQ", "libSceNet", 1, "libSceNet", sceNetMemoryFree); + LIB_FUNCTION("pQGpHYopAIY", "libSceNet", 1, "libSceNet", sceNetNtohl); + LIB_FUNCTION("tOrRi-v3AOM", "libSceNet", 1, "libSceNet", sceNetNtohll); + LIB_FUNCTION("Rbvt+5Y2iEw", "libSceNet", 1, "libSceNet", sceNetNtohs); + LIB_FUNCTION("dgJBaeJnGpo", "libSceNet", 1, "libSceNet", sceNetPoolCreate); + LIB_FUNCTION("K7RlrTkI-mw", "libSceNet", 1, "libSceNet", sceNetPoolDestroy); + LIB_FUNCTION("QGOqGPnk5a4", "libSceNet", 1, "libSceNet", sceNetPppoeStart); + LIB_FUNCTION("FIV95WE1EuE", "libSceNet", 1, "libSceNet", sceNetPppoeStop); + LIB_FUNCTION("9wO9XrMsNhc", "libSceNet", 1, "libSceNet", sceNetRecv); + LIB_FUNCTION("304ooNZxWDY", "libSceNet", 1, "libSceNet", sceNetRecvfrom); + LIB_FUNCTION("wvuUDv0jrMI", "libSceNet", 1, "libSceNet", sceNetRecvmsg); + LIB_FUNCTION("AzqoBha7js4", "libSceNet", 1, "libSceNet", sceNetResolverAbort); + LIB_FUNCTION("JQk8ck8vnPY", "libSceNet", 1, "libSceNet", sceNetResolverConnect); + LIB_FUNCTION("bonnMiDoOZg", "libSceNet", 1, "libSceNet", sceNetResolverConnectAbort); + LIB_FUNCTION("V5q6gvEJpw4", "libSceNet", 1, "libSceNet", sceNetResolverConnectCreate); + LIB_FUNCTION("QFPjG6rqeZg", "libSceNet", 1, "libSceNet", sceNetResolverConnectDestroy); + LIB_FUNCTION("C4UgDHHPvdw", "libSceNet", 1, "libSceNet", sceNetResolverCreate); + LIB_FUNCTION("kJlYH5uMAWI", "libSceNet", 1, "libSceNet", sceNetResolverDestroy); + LIB_FUNCTION("J5i3hiLJMPk", "libSceNet", 1, "libSceNet", sceNetResolverGetError); + LIB_FUNCTION("Apb4YDxKsRI", "libSceNet", 1, "libSceNet", sceNetResolverStartAton); + LIB_FUNCTION("zvzWA5IZMsg", "libSceNet", 1, "libSceNet", sceNetResolverStartAton6); + LIB_FUNCTION("Nd91WaWmG2w", "libSceNet", 1, "libSceNet", sceNetResolverStartNtoa); + LIB_FUNCTION("zl35YNs9jnI", "libSceNet", 1, "libSceNet", sceNetResolverStartNtoa6); + LIB_FUNCTION("RCCY01Xd+58", "libSceNet", 1, "libSceNet", + sceNetResolverStartNtoaMultipleRecords); + LIB_FUNCTION("sT4nBQKUPqM", "libSceNet", 1, "libSceNet", + sceNetResolverStartNtoaMultipleRecordsEx); + LIB_FUNCTION("beRjXBn-z+o", "libSceNet", 1, "libSceNet", sceNetSend); + LIB_FUNCTION("2eKbgcboJso", "libSceNet", 1, "libSceNet", sceNetSendmsg); + LIB_FUNCTION("gvD1greCu0A", "libSceNet", 1, "libSceNet", sceNetSendto); + LIB_FUNCTION("15Ywg-ZsSl0", "libSceNet", 1, "libSceNet", sceNetSetDns6Info); + LIB_FUNCTION("E3oH1qsdqCA", "libSceNet", 1, "libSceNet", sceNetSetDns6InfoToKernel); + LIB_FUNCTION("B-M6KjO8-+w", "libSceNet", 1, "libSceNet", sceNetSetDnsInfo); + LIB_FUNCTION("8s+T0bJeyLQ", "libSceNet", 1, "libSceNet", sceNetSetDnsInfoToKernel); + LIB_FUNCTION("2mKX2Spso7I", "libSceNet", 1, "libSceNet", sceNetSetsockopt); + LIB_FUNCTION("k1V1djYpk7k", "libSceNet", 1, "libSceNet", sceNetShowIfconfig); + LIB_FUNCTION("j6pkkO2zJtg", "libSceNet", 1, "libSceNet", sceNetShowIfconfigForBuffer); + LIB_FUNCTION("E8dTcvQw3hg", "libSceNet", 1, "libSceNet", sceNetShowIfconfigWithMemory); + LIB_FUNCTION("WxislcDAW5I", "libSceNet", 1, "libSceNet", sceNetShowNetstat); + LIB_FUNCTION("rX30iWQqqzg", "libSceNet", 1, "libSceNet", sceNetShowNetstatEx); + LIB_FUNCTION("vjwKTGa21f0", "libSceNet", 1, "libSceNet", sceNetShowNetstatExForBuffer); + LIB_FUNCTION("mqoB+LN0pW8", "libSceNet", 1, "libSceNet", sceNetShowNetstatForBuffer); + LIB_FUNCTION("H5WHYRfDkR0", "libSceNet", 1, "libSceNet", sceNetShowNetstatWithMemory); + LIB_FUNCTION("tk0p0JmiBkM", "libSceNet", 1, "libSceNet", sceNetShowPolicy); + LIB_FUNCTION("dbrSNEuZfXI", "libSceNet", 1, "libSceNet", sceNetShowPolicyWithMemory); + LIB_FUNCTION("cEMX1VcPpQ8", "libSceNet", 1, "libSceNet", sceNetShowRoute); + LIB_FUNCTION("fCa7-ihdRdc", "libSceNet", 1, "libSceNet", sceNetShowRoute6); + LIB_FUNCTION("nTJqXsbSS1I", "libSceNet", 1, "libSceNet", sceNetShowRoute6ForBuffer); + LIB_FUNCTION("TCZyE2YI1uM", "libSceNet", 1, "libSceNet", sceNetShowRoute6WithMemory); + LIB_FUNCTION("n-IAZb7QB1Y", "libSceNet", 1, "libSceNet", sceNetShowRouteForBuffer); + LIB_FUNCTION("0-XSSp1kEFM", "libSceNet", 1, "libSceNet", sceNetShowRouteWithMemory); + LIB_FUNCTION("TSM6whtekok", "libSceNet", 1, "libSceNet", sceNetShutdown); + LIB_FUNCTION("Q4qBuN-c0ZM", "libSceNet", 1, "libSceNet", sceNetSocket); + LIB_FUNCTION("zJGf8xjFnQE", "libSceNet", 1, "libSceNet", sceNetSocketAbort); + LIB_FUNCTION("45ggEzakPJQ", "libSceNet", 1, "libSceNet", sceNetSocketClose); + LIB_FUNCTION("6AJE2jKg-c0", "libSceNet", 1, "libSceNet", sceNetSyncCreate); + LIB_FUNCTION("atGfzCaXMak", "libSceNet", 1, "libSceNet", sceNetSyncDestroy); + LIB_FUNCTION("sAleh-BoxLA", "libSceNet", 1, "libSceNet", sceNetSyncGet); + LIB_FUNCTION("Z-8Jda650Vk", "libSceNet", 1, "libSceNet", sceNetSyncSignal); + LIB_FUNCTION("NP5gxDeYhIM", "libSceNet", 1, "libSceNet", sceNetSyncWait); + LIB_FUNCTION("3zRdT3O2Kxo", "libSceNet", 1, "libSceNet", sceNetSysctl); + LIB_FUNCTION("cTGkc6-TBlI", "libSceNet", 1, "libSceNet", sceNetTerm); + LIB_FUNCTION("j-Op3ibRJaQ", "libSceNet", 1, "libSceNet", sceNetThreadCreate); + LIB_FUNCTION("KirVfZbqniw", "libSceNet", 1, "libSceNet", sceNetThreadExit); + LIB_FUNCTION("pRbEzaV30qI", "libSceNet", 1, "libSceNet", sceNetThreadJoin); + LIB_FUNCTION("bjrzRLFali0", "libSceNet", 1, "libSceNet", sceNetUsleep); + LIB_FUNCTION("DnB6WJ91HGg", "libSceNet", 1, "libSceNet", Func_0E707A589F751C68); + LIB_FUNCTION("JK1oZe4UysY", "libSceNetDebug", 1, "libSceNet", sceNetEmulationGet); + LIB_FUNCTION("pfn3Fha1ydc", "libSceNetDebug", 1, "libSceNet", sceNetEmulationSet); +}; + +} // namespace Libraries::Net diff --git a/src/core/libraries/net/net.h b/src/core/libraries/net/net.h new file mode 100644 index 000000000..87ffa9789 --- /dev/null +++ b/src/core/libraries/net/net.h @@ -0,0 +1,255 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/types.h" +#include "core/libraries/net/net_types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Net { + +OrbisNetId PS4_SYSV_ABI sceNetAccept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen); +s32 PS4_SYSV_ABI sceNetAddrConfig6GetInfo(); +s32 PS4_SYSV_ABI sceNetAddrConfig6Start(); +s32 PS4_SYSV_ABI sceNetAddrConfig6Stop(); +s32 PS4_SYSV_ABI sceNetAllocateAllRouteInfo(); +s32 PS4_SYSV_ABI sceNetBandwidthControlGetDataTraffic(); +s32 PS4_SYSV_ABI sceNetBandwidthControlGetDefaultParam(); +s32 PS4_SYSV_ABI sceNetBandwidthControlGetIfParam(); +s32 PS4_SYSV_ABI sceNetBandwidthControlGetPolicy(); +s32 PS4_SYSV_ABI sceNetBandwidthControlSetDefaultParam(); +s32 PS4_SYSV_ABI sceNetBandwidthControlSetIfParam(); +s32 PS4_SYSV_ABI sceNetBandwidthControlSetPolicy(); +s32 PS4_SYSV_ABI sceNetBind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen); +s32 PS4_SYSV_ABI sceNetClearDnsCache(); +s32 PS4_SYSV_ABI sceNetConfigAddArp(); +s32 PS4_SYSV_ABI sceNetConfigAddArpWithInterface(); +s32 PS4_SYSV_ABI sceNetConfigAddIfaddr(); +s32 PS4_SYSV_ABI sceNetConfigAddMRoute(); +s32 PS4_SYSV_ABI sceNetConfigAddRoute(); +s32 PS4_SYSV_ABI sceNetConfigAddRoute6(); +s32 PS4_SYSV_ABI sceNetConfigAddRouteWithInterface(); +s32 PS4_SYSV_ABI sceNetConfigCleanUpAllInterfaces(); +s32 PS4_SYSV_ABI sceNetConfigDelArp(); +s32 PS4_SYSV_ABI sceNetConfigDelArpWithInterface(); +s32 PS4_SYSV_ABI sceNetConfigDelDefaultRoute(); +s32 PS4_SYSV_ABI sceNetConfigDelDefaultRoute6(); +s32 PS4_SYSV_ABI sceNetConfigDelIfaddr(); +s32 PS4_SYSV_ABI sceNetConfigDelIfaddr6(); +s32 PS4_SYSV_ABI sceNetConfigDelMRoute(); +s32 PS4_SYSV_ABI sceNetConfigDelRoute(); +s32 PS4_SYSV_ABI sceNetConfigDelRoute6(); +s32 PS4_SYSV_ABI sceNetConfigDownInterface(); +s32 PS4_SYSV_ABI sceNetConfigEtherGetLinkMode(); +s32 PS4_SYSV_ABI sceNetConfigEtherPostPlugInOutEvent(); +s32 PS4_SYSV_ABI sceNetConfigEtherSetLinkMode(); +s32 PS4_SYSV_ABI sceNetConfigFlushRoute(); +s32 PS4_SYSV_ABI sceNetConfigGetDefaultRoute(); +s32 PS4_SYSV_ABI sceNetConfigGetDefaultRoute6(); +s32 PS4_SYSV_ABI sceNetConfigGetIfaddr(); +s32 PS4_SYSV_ABI sceNetConfigGetIfaddr6(); +s32 PS4_SYSV_ABI sceNetConfigRoutingShowRoutingConfig(); +s32 PS4_SYSV_ABI sceNetConfigRoutingShowtCtlVar(); +s32 PS4_SYSV_ABI sceNetConfigRoutingStart(); +s32 PS4_SYSV_ABI sceNetConfigRoutingStop(); +s32 PS4_SYSV_ABI sceNetConfigSetDefaultRoute(); +s32 PS4_SYSV_ABI sceNetConfigSetDefaultRoute6(); +s32 PS4_SYSV_ABI sceNetConfigSetDefaultScope(); +s32 PS4_SYSV_ABI sceNetConfigSetIfaddr(); +s32 PS4_SYSV_ABI sceNetConfigSetIfaddr6(); +s32 PS4_SYSV_ABI sceNetConfigSetIfaddr6WithFlags(); +s32 PS4_SYSV_ABI sceNetConfigSetIfFlags(); +s32 PS4_SYSV_ABI sceNetConfigSetIfLinkLocalAddr6(); +s32 PS4_SYSV_ABI sceNetConfigSetIfmtu(); +s32 PS4_SYSV_ABI sceNetConfigUnsetIfFlags(); +s32 PS4_SYSV_ABI sceNetConfigUpInterface(); +s32 PS4_SYSV_ABI sceNetConfigUpInterfaceWithFlags(); +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocClearWakeOnWlan(); +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocCreate(); +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocGetWakeOnWlanInfo(); +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocJoin(); +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocLeave(); +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan(); +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo(); +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan(); +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocScanJoin(); +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocSetExtInfoElement(); +s32 PS4_SYSV_ABI sceNetConfigWlanAdhocSetWakeOnWlan(); +s32 PS4_SYSV_ABI sceNetConfigWlanApStart(); +s32 PS4_SYSV_ABI sceNetConfigWlanApStop(); +s32 PS4_SYSV_ABI sceNetConfigWlanBackgroundScanQuery(); +s32 PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStart(); +s32 PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStop(); +s32 PS4_SYSV_ABI sceNetConfigWlanDiagGetDeviceInfo(); +s32 PS4_SYSV_ABI sceNetConfigWlanDiagSetAntenna(); +s32 PS4_SYSV_ABI sceNetConfigWlanDiagSetTxFixedRate(); +s32 PS4_SYSV_ABI sceNetConfigWlanGetDeviceConfig(); +s32 PS4_SYSV_ABI sceNetConfigWlanInfraGetRssiInfo(); +s32 PS4_SYSV_ABI sceNetConfigWlanInfraLeave(); +s32 PS4_SYSV_ABI sceNetConfigWlanInfraScanJoin(); +s32 PS4_SYSV_ABI sceNetConfigWlanScan(); +s32 PS4_SYSV_ABI sceNetConfigWlanSetDeviceConfig(); +s32 PS4_SYSV_ABI sceNetConnect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen); +s32 PS4_SYSV_ABI sceNetControl(); +s32 PS4_SYSV_ABI sceNetDhcpdStart(); +s32 PS4_SYSV_ABI sceNetDhcpdStop(); +s32 PS4_SYSV_ABI sceNetDhcpGetAutoipInfo(); +s32 PS4_SYSV_ABI sceNetDhcpGetInfo(); +s32 PS4_SYSV_ABI sceNetDhcpGetInfoEx(); +s32 PS4_SYSV_ABI sceNetDhcpStart(); +s32 PS4_SYSV_ABI sceNetDhcpStop(); +s32 PS4_SYSV_ABI sceNetDumpAbort(); +s32 PS4_SYSV_ABI sceNetDumpCreate(); +s32 PS4_SYSV_ABI sceNetDumpDestroy(); +s32 PS4_SYSV_ABI sceNetDumpRead(); +s32 PS4_SYSV_ABI sceNetDuplicateIpStart(); +s32 PS4_SYSV_ABI sceNetDuplicateIpStop(); +s32 PS4_SYSV_ABI sceNetEpollAbort(OrbisNetId eid, s32 flags); +s32 PS4_SYSV_ABI sceNetEpollControl(OrbisNetId eid, s32 op, OrbisNetId id, + OrbisNetEpollEvent* event); +OrbisNetId PS4_SYSV_ABI sceNetEpollCreate(const char* name, s32 flags); +s32 PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId eid); +s32 PS4_SYSV_ABI sceNetEpollWait(OrbisNetId eid, OrbisNetEpollEvent* events, s32 maxevents, + s32 timeout_us); +s32* PS4_SYSV_ABI sceNetErrnoLoc(); +s32 PS4_SYSV_ABI sceNetEtherNtostr(const OrbisNetEtherAddr* n, char* str, u64 len); +s32 PS4_SYSV_ABI sceNetEtherStrton(const char* str, OrbisNetEtherAddr* n); +s32 PS4_SYSV_ABI sceNetEventCallbackCreate(); +s32 PS4_SYSV_ABI sceNetEventCallbackDestroy(); +s32 PS4_SYSV_ABI sceNetEventCallbackGetError(); +s32 PS4_SYSV_ABI sceNetEventCallbackWaitCB(); +s32 PS4_SYSV_ABI sceNetFreeAllRouteInfo(); +s32 PS4_SYSV_ABI sceNetGetArpInfo(); +s32 PS4_SYSV_ABI sceNetGetDns6Info(u8* info, s32 flags); +s32 PS4_SYSV_ABI sceNetGetDnsInfo(u32* info, s32 flags); +s32 PS4_SYSV_ABI sceNetGetIfList(); +s32 PS4_SYSV_ABI sceNetGetIfListOnce(); +const char* PS4_SYSV_ABI sceNetGetIfName(u32 index); +s32 PS4_SYSV_ABI sceNetGetIfnameNumList(); +s32 PS4_SYSV_ABI sceNetGetMacAddress(OrbisNetEtherAddr* addr, s32 flags); +s32 PS4_SYSV_ABI sceNetGetMemoryPoolStats(s32 memid, OrbisNetMemoryPoolStats* stat); +s32 PS4_SYSV_ABI sceNetGetNameToIndex(); +s32 PS4_SYSV_ABI sceNetGetpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen); +s32 PS4_SYSV_ABI sceNetGetRandom(u32* out); +s32 PS4_SYSV_ABI sceNetGetRouteInfo(); +s32 PS4_SYSV_ABI sceNetGetSockInfo(OrbisNetId s, OrbisNetSockInfo* info, s32 n, s32 flags); +s32 PS4_SYSV_ABI sceNetGetSockInfo6(); +s32 PS4_SYSV_ABI sceNetGetsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen); +s32 PS4_SYSV_ABI sceNetGetsockopt(OrbisNetId s, s32 level, s32 optname, void* optval, u32* optlen); +s32 PS4_SYSV_ABI sceNetGetStatisticsInfo(OrbisNetStatisticsInfo* info, s32 flags); +s32 PS4_SYSV_ABI sceNetGetStatisticsInfoInternal(); +void PS4_SYSV_ABI sceNetGetSystemTime(u64* out); +u32 PS4_SYSV_ABI sceNetHtonl(u32 host32); +u64 PS4_SYSV_ABI sceNetHtonll(u64 host64); +u16 PS4_SYSV_ABI sceNetHtons(u16 host16); +const char* PS4_SYSV_ABI sceNetInetNtop(s32 af, const void* src, char* dst, u32 size); +s32 PS4_SYSV_ABI sceNetInetNtopWithScopeId(); +s32 PS4_SYSV_ABI sceNetInetPton(s32 af, const char* src, void* dst); +s32 PS4_SYSV_ABI sceNetInetPtonEx(s32 af, const char* src, void* dst, s32 flags); +s32 PS4_SYSV_ABI sceNetInetPtonWithScopeId(); +s32 PS4_SYSV_ABI sceNetInfoDumpStart(); +s32 PS4_SYSV_ABI sceNetInfoDumpStop(); +s32 PS4_SYSV_ABI sceNetInit(); +s32 PS4_SYSV_ABI sceNetInitParam(); +s32 PS4_SYSV_ABI sceNetIoctl(OrbisNetId s, u64 cmd, void* data); +s32 PS4_SYSV_ABI sceNetListen(OrbisNetId s, s32 backlog); +void* PS4_SYSV_ABI sceNetMemoryAllocate(s64 size, s32 flags); +void PS4_SYSV_ABI sceNetMemoryFree(void* ptr); +u32 PS4_SYSV_ABI sceNetNtohl(u32 net32); +u64 PS4_SYSV_ABI sceNetNtohll(u64 net64); +u16 PS4_SYSV_ABI sceNetNtohs(u16 net16); +s32 PS4_SYSV_ABI sceNetPoolCreate(const char* name, s32 size, s32 flags); +s32 PS4_SYSV_ABI sceNetPoolDestroy(s32 pool_id); +s32 PS4_SYSV_ABI sceNetPppoeStart(); +s32 PS4_SYSV_ABI sceNetPppoeStop(); +s32 PS4_SYSV_ABI sceNetRecv(OrbisNetId s, void* buf, u64 len, s32 flags); +s32 PS4_SYSV_ABI sceNetRecvfrom(OrbisNetId s, void* buf, u64 len, s32 flags, OrbisNetSockaddr* addr, + u32* paddrlen); +s32 PS4_SYSV_ABI sceNetRecvmsg(OrbisNetId s, OrbisNetMsghdr* msg, s32 flags); +s32 PS4_SYSV_ABI sceNetResolverAbort(OrbisNetId rid, s32 flags); +s32 PS4_SYSV_ABI sceNetResolverConnect(); +s32 PS4_SYSV_ABI sceNetResolverConnectAbort(); +s32 PS4_SYSV_ABI sceNetResolverConnectCreate(); +s32 PS4_SYSV_ABI sceNetResolverConnectDestroy(); +OrbisNetId PS4_SYSV_ABI sceNetResolverCreate(const char* name, s32 poolid, s32 flags); +s32 PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId rid); +s32 PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId rid, s32* status); +s32 PS4_SYSV_ABI sceNetResolverStartAton(OrbisNetId rid, const OrbisNetInAddr* addr, char* hostname, + s32 len, s32 timeout, s32 retry, s32 flags); +s32 PS4_SYSV_ABI sceNetResolverStartAton6(); +s32 PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId rid, const char* hostname, OrbisNetInAddr* addr, + s32 timeout, s32 retry, s32 flags); +s32 PS4_SYSV_ABI sceNetResolverStartNtoa6(); +s32 PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecords(OrbisNetId rid, const char* hostname, + OrbisNetResolverInfo* info, s32 timeout, + s32 retry, s32 flags); +s32 PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecordsEx(OrbisNetId rid, const char* hostname, + OrbisNetResolverInfo* info, s32 timeout, + s32 retry, s32 flags); +s32 PS4_SYSV_ABI sceNetSend(OrbisNetId s, const void* buf, u64 len, s32 flags); +s32 PS4_SYSV_ABI sceNetSendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, s32 flags); +s32 PS4_SYSV_ABI sceNetSendto(OrbisNetId s, const void* buf, u64 len, s32 flags, + const OrbisNetSockaddr* addr, u32 addrlen); +s32 PS4_SYSV_ABI sceNetSetDns6Info(const u8* info, s32 flags); +s32 PS4_SYSV_ABI sceNetSetDns6InfoToKernel(); +s32 PS4_SYSV_ABI sceNetSetDnsInfo(const u32* info, s32 flags); +s32 PS4_SYSV_ABI sceNetSetDnsInfoToKernel(); +s32 PS4_SYSV_ABI sceNetSetsockopt(OrbisNetId s, s32 level, s32 optname, const void* optval, + u32 optlen); +s32 PS4_SYSV_ABI sceNetShowIfconfig(); +s32 PS4_SYSV_ABI sceNetShowIfconfigForBuffer(); +s32 PS4_SYSV_ABI sceNetShowIfconfigWithMemory(s32 memid); +s32 PS4_SYSV_ABI sceNetShowNetstat(); +s32 PS4_SYSV_ABI sceNetShowNetstatEx(); +s32 PS4_SYSV_ABI sceNetShowNetstatExForBuffer(); +s32 PS4_SYSV_ABI sceNetShowNetstatForBuffer(); +s32 PS4_SYSV_ABI sceNetShowNetstatWithMemory(s32 memid); +s32 PS4_SYSV_ABI sceNetShowPolicy(); +s32 PS4_SYSV_ABI sceNetShowPolicyWithMemory(s32 memid); +s32 PS4_SYSV_ABI sceNetShowRoute(); +s32 PS4_SYSV_ABI sceNetShowRoute6(); +s32 PS4_SYSV_ABI sceNetShowRoute6ForBuffer(); +s32 PS4_SYSV_ABI sceNetShowRoute6WithMemory(s32 memid); +s32 PS4_SYSV_ABI sceNetShowRouteForBuffer(); +s32 PS4_SYSV_ABI sceNetShowRouteWithMemory(s32 memid); +s32 PS4_SYSV_ABI sceNetShutdown(OrbisNetId s, s32 how); +OrbisNetId PS4_SYSV_ABI sceNetSocket(const char* name, s32 family, s32 type, s32 protocol); +s32 PS4_SYSV_ABI sceNetSocketAbort(OrbisNetId s, s32 flags); +s32 PS4_SYSV_ABI sceNetSocketClose(OrbisNetId s); +s32 PS4_SYSV_ABI sceNetSyncCreate(); +s32 PS4_SYSV_ABI sceNetSyncDestroy(); +s32 PS4_SYSV_ABI sceNetSyncGet(); +s32 PS4_SYSV_ABI sceNetSyncSignal(); +s32 PS4_SYSV_ABI sceNetSyncWait(); +s32 PS4_SYSV_ABI sceNetSysctl(); +s32 PS4_SYSV_ABI sceNetTerm(); +void PS4_SYSV_ABI sceNetThreadExit(); +s32 PS4_SYSV_ABI sceNetUsleep(s32 microseconds); +s32 PS4_SYSV_ABI Func_0E707A589F751C68(); +s32 PS4_SYSV_ABI sceNetEmulationGet(); +s32 PS4_SYSV_ABI sceNetEmulationSet(); + +void RegisterLib(Core::Loader::SymbolsResolver* sym); + +struct SystemHooks { + std::function is_online; + std::function* mac)> mac_address; + std::function p2p_port; + std::function p2p_started; + std::function p2p_stopped; + std::function public_addr; +}; +void SetSystemHooks(SystemHooks hooks); +SystemHooks GetSystemHooks(); +void SetKernelErrnoHook(void (*hook)(int orbis_errno)); +void ReleaseResolverPool(OrbisNetId rid); + +} // namespace Libraries::Net \ No newline at end of file diff --git a/src/core/libraries/network/net_error.h b/src/core/libraries/net/net_error.h similarity index 66% rename from src/core/libraries/network/net_error.h rename to src/core/libraries/net/net_error.h index 467610fdd..be29fc75d 100644 --- a/src/core/libraries/network/net_error.h +++ b/src/core/libraries/net/net_error.h @@ -6,22 +6,40 @@ // net errno codes constexpr int ORBIS_NET_EPERM = 1; constexpr int ORBIS_NET_ENOENT = 2; +constexpr int ORBIS_NET_ESRCH = 3; constexpr int ORBIS_NET_EINTR = 4; +constexpr int ORBIS_NET_EIO = 5; +constexpr int ORBIS_NET_ENXIO = 6; +constexpr int ORBIS_NET_E2BIG = 7; +constexpr int ORBIS_NET_ENOEXEC = 8; constexpr int ORBIS_NET_EBADF = 9; +constexpr int ORBIS_NET_ECHILD = 10; +constexpr int ORBIS_NET_EDEADLK = 11; constexpr int ORBIS_NET_ENOMEM = 12; constexpr int ORBIS_NET_EACCES = 13; constexpr int ORBIS_NET_EFAULT = 14; constexpr int ORBIS_NET_ENOTBLK = 15; constexpr int ORBIS_NET_EBUSY = 16; constexpr int ORBIS_NET_EEXIST = 17; +constexpr int ORBIS_NET_EXDEV = 18; constexpr int ORBIS_NET_ENODEV = 19; +constexpr int ORBIS_NET_ENOTDIR = 20; +constexpr int ORBIS_NET_EISDIR = 21; constexpr int ORBIS_NET_EINVAL = 22; constexpr int ORBIS_NET_ENFILE = 23; constexpr int ORBIS_NET_EMFILE = 24; +constexpr int ORBIS_NET_ENOTTY = 25; +constexpr int ORBIS_NET_ETXTBSY = 26; +constexpr int ORBIS_NET_EFBIG = 27; constexpr int ORBIS_NET_ENOSPC = 28; +constexpr int ORBIS_NET_ESPIPE = 29; +constexpr int ORBIS_NET_EROFS = 30; +constexpr int ORBIS_NET_EMLINK = 31; constexpr int ORBIS_NET_EPIPE = 32; +constexpr int ORBIS_NET_EDOM = 33; +constexpr int ORBIS_NET_ERANGE = 34; constexpr int ORBIS_NET_EAGAIN = 35; -constexpr int ORBIS_NET_EWOULDBLOCK = 35; +constexpr int ORBIS_NET_EWOULDBLOCK = ORBIS_NET_EAGAIN; constexpr int ORBIS_NET_EINPROGRESS = 36; constexpr int ORBIS_NET_EALREADY = 37; constexpr int ORBIS_NET_ENOTSOCK = 38; @@ -30,7 +48,10 @@ constexpr int ORBIS_NET_EMSGSIZE = 40; constexpr int ORBIS_NET_EPROTOTYPE = 41; constexpr int ORBIS_NET_ENOPROTOOPT = 42; constexpr int ORBIS_NET_EPROTONOSUPPORT = 43; +constexpr int ORBIS_NET_ESOCKTNOSUPPORT = 44; constexpr int ORBIS_NET_EOPNOTSUPP = 45; +constexpr int ORBIS_NET_ENOTSUP = ORBIS_NET_EOPNOTSUPP; +constexpr int ORBIS_NET_EPFNOSUPPORT = 46; constexpr int ORBIS_NET_EAFNOSUPPORT = 47; constexpr int ORBIS_NET_EADDRINUSE = 48; constexpr int ORBIS_NET_EADDRNOTAVAIL = 49; @@ -42,6 +63,7 @@ constexpr int ORBIS_NET_ECONNRESET = 54; constexpr int ORBIS_NET_ENOBUFS = 55; constexpr int ORBIS_NET_EISCONN = 56; constexpr int ORBIS_NET_ENOTCONN = 57; +constexpr int ORBIS_NET_ESHUTDOWN = 58; constexpr int ORBIS_NET_ETOOMANYREFS = 59; constexpr int ORBIS_NET_ETIMEDOUT = 60; constexpr int ORBIS_NET_ECONNREFUSED = 61; @@ -50,13 +72,29 @@ constexpr int ORBIS_NET_ENAMETOOLONG = 63; constexpr int ORBIS_NET_EHOSTDOWN = 64; constexpr int ORBIS_NET_EHOSTUNREACH = 65; constexpr int ORBIS_NET_ENOTEMPTY = 66; +constexpr int ORBIS_NET_EPROCLIM = 67; +constexpr int ORBIS_NET_EUSERS = 68; +constexpr int ORBIS_NET_EDQUOT = 69; +constexpr int ORBIS_NET_ESTALE = 70; +constexpr int ORBIS_NET_EREMOTE = 71; +constexpr int ORBIS_NET_EBADRPC = 72; +constexpr int ORBIS_NET_ERPCMISMATCH = 73; +constexpr int ORBIS_NET_EPROGUNAVAIL = 74; +constexpr int ORBIS_NET_EPROGMISMATCH = 75; constexpr int ORBIS_NET_EPROCUNAVAIL = 76; +constexpr int ORBIS_NET_ENOLCK = 77; +constexpr int ORBIS_NET_ENOSYS = 78; +constexpr int ORBIS_NET_EFTYPE = 79; +constexpr int ORBIS_NET_EAUTH = 80; +constexpr int ORBIS_NET_ENEEDAUTH = 81; constexpr int ORBIS_NET_EIDRM = 82; constexpr int ORBIS_NET_ENOMSG = 83; constexpr int ORBIS_NET_EOVERFLOW = 84; constexpr int ORBIS_NET_ECANCELED = 85; constexpr int ORBIS_NET_EPROTO = 92; constexpr int ORBIS_NET_EADHOC = 160; +constexpr int ORBIS_NET_ERESERVED161 = 161; +constexpr int ORBIS_NET_ERESERVED162 = 162; constexpr int ORBIS_NET_EINACTIVEDISABLED = 163; constexpr int ORBIS_NET_ENODATA = 164; constexpr int ORBIS_NET_EDESC = 165; @@ -64,6 +102,7 @@ constexpr int ORBIS_NET_EDESCTIMEDOUT = 166; constexpr int ORBIS_NET_ENETINTR = 167; constexpr int ORBIS_NET_ENOTINIT = 200; constexpr int ORBIS_NET_ENOLIBMEM = 201; +constexpr int ORBIS_NET_ETLS = 202; constexpr int ORBIS_NET_ECALLBACK = 203; constexpr int ORBIS_NET_EINTERNAL = 204; constexpr int ORBIS_NET_ERETURN = 205; @@ -74,6 +113,7 @@ constexpr int ORBIS_NET_RESOLVER_EINTERNAL = 220; constexpr int ORBIS_NET_RESOLVER_EBUSY = 221; constexpr int ORBIS_NET_RESOLVER_ENOSPACE = 222; constexpr int ORBIS_NET_RESOLVER_EPACKET = 223; +constexpr int ORBIS_NET_RESOLVER_ERESERVED224 = 224; constexpr int ORBIS_NET_RESOLVER_ENODNS = 225; constexpr int ORBIS_NET_RESOLVER_ETIMEDOUT = 226; constexpr int ORBIS_NET_RESOLVER_ENOSUPPORT = 227; @@ -84,24 +124,45 @@ constexpr int ORBIS_NET_RESOLVER_ENOTIMPLEMENTED = 231; constexpr int ORBIS_NET_RESOLVER_ESERVERREFUSED = 232; constexpr int ORBIS_NET_RESOLVER_ENORECORD = 233; constexpr int ORBIS_NET_RESOLVER_EALIGNMENT = 234; +constexpr int ORBIS_NET_RESOLVER_ENOTFOUND = 235; +constexpr int ORBIS_NET_RESOLVER_ENOTINIT = 236; // error codes constexpr int ORBIS_NET_ERROR_BASE = 0x80410100; // not existed used for calculation constexpr int ORBIS_NET_ERROR_EPERM = 0x80410101; constexpr int ORBIS_NET_ERROR_ENOENT = 0x80410102; +constexpr int ORBIS_NET_ERROR_ESRCH = 0x80410103; constexpr int ORBIS_NET_ERROR_EINTR = 0x80410104; +constexpr int ORBIS_NET_ERROR_EIO = 0x80410105; +constexpr int ORBIS_NET_ERROR_ENXIO = 0x80410106; +constexpr int ORBIS_NET_ERROR_E2BIG = 0x80410107; +constexpr int ORBIS_NET_ERROR_ENOEXEC = 0x80410108; constexpr int ORBIS_NET_ERROR_EBADF = 0x80410109; +constexpr int ORBIS_NET_ERROR_ECHILD = 0x8041010a; +constexpr int ORBIS_NET_ERROR_EDEADLK = 0x8041010b; constexpr int ORBIS_NET_ERROR_ENOMEM = 0x8041010c; constexpr int ORBIS_NET_ERROR_EACCES = 0x8041010d; constexpr int ORBIS_NET_ERROR_EFAULT = 0x8041010e; constexpr int ORBIS_NET_ERROR_ENOTBLK = 0x8041010f; +constexpr int ORBIS_NET_ERROR_EBUSY = 0x80410110; constexpr int ORBIS_NET_ERROR_EEXIST = 0x80410111; +constexpr int ORBIS_NET_ERROR_EXDEV = 0x80410112; constexpr int ORBIS_NET_ERROR_ENODEV = 0x80410113; +constexpr int ORBIS_NET_ERROR_ENOTDIR = 0x80410114; +constexpr int ORBIS_NET_ERROR_EISDIR = 0x80410115; constexpr int ORBIS_NET_ERROR_EINVAL = 0x80410116; constexpr int ORBIS_NET_ERROR_ENFILE = 0x80410117; constexpr int ORBIS_NET_ERROR_EMFILE = 0x80410118; +constexpr int ORBIS_NET_ERROR_ENOTTY = 0x80410119; +constexpr int ORBIS_NET_ERROR_ETXTBSY = 0x8041011a; +constexpr int ORBIS_NET_ERROR_EFBIG = 0x8041011b; constexpr int ORBIS_NET_ERROR_ENOSPC = 0x8041011c; +constexpr int ORBIS_NET_ERROR_ESPIPE = 0x8041011d; +constexpr int ORBIS_NET_ERROR_EROFS = 0x8041011e; +constexpr int ORBIS_NET_ERROR_EMLINK = 0x8041011f; constexpr int ORBIS_NET_ERROR_EPIPE = 0x80410120; +constexpr int ORBIS_NET_ERROR_EDOM = 0x80410121; +constexpr int ORBIS_NET_ERROR_ERANGE = 0x80410122; constexpr int ORBIS_NET_ERROR_EAGAIN = 0x80410123; constexpr int ORBIS_NET_ERROR_EWOULDBLOCK = 0x80410123; constexpr int ORBIS_NET_ERROR_EINPROGRESS = 0x80410124; @@ -112,7 +173,9 @@ constexpr int ORBIS_NET_ERROR_EMSGSIZE = 0x80410128; constexpr int ORBIS_NET_ERROR_EPROTOTYPE = 0x80410129; constexpr int ORBIS_NET_ERROR_ENOPROTOOPT = 0x8041012a; constexpr int ORBIS_NET_ERROR_EPROTONOSUPPORT = 0x8041012b; +constexpr int ORBIS_NET_ERROR_ESOCKTNOSUPPORT = 0x8041012c; constexpr int ORBIS_NET_ERROR_EOPNOTSUPP = 0x8041012d; +constexpr int ORBIS_NET_ERROR_ENOTSUP = 0x8041012d; constexpr int ORBIS_NET_ERROR_EPFNOSUPPORT = 0x8041012e; constexpr int ORBIS_NET_ERROR_EAFNOSUPPORT = 0x8041012f; constexpr int ORBIS_NET_ERROR_EADDRINUSE = 0x80410130; @@ -134,12 +197,26 @@ constexpr int ORBIS_NET_ERROR_ENAMETOOLONG = 0x8041013f; constexpr int ORBIS_NET_ERROR_EHOSTDOWN = 0x80410140; constexpr int ORBIS_NET_ERROR_EHOSTUNREACH = 0x80410141; constexpr int ORBIS_NET_ERROR_ENOTEMPTY = 0x80410142; -constexpr int ORBIS_NET_ERROR_EPROCUNAVAIL = 0x8041014C; +constexpr int ORBIS_NET_ERROR_EPROCLIM = 0x80410143; +constexpr int ORBIS_NET_ERROR_EUSERS = 0x80410144; +constexpr int ORBIS_NET_ERROR_EDQUOT = 0x80410145; +constexpr int ORBIS_NET_ERROR_ESTALE = 0x80410146; +constexpr int ORBIS_NET_ERROR_EREMOTE = 0x80410147; +constexpr int ORBIS_NET_ERROR_EBADRPC = 0x80410148; +constexpr int ORBIS_NET_ERROR_ERPCMISMATCH = 0x80410149; +constexpr int ORBIS_NET_ERROR_EPROGUNAVAIL = 0x8041014a; +constexpr int ORBIS_NET_ERROR_EPROGMISMATCH = 0x8041014b; +constexpr int ORBIS_NET_ERROR_EPROCUNAVAIL = 0x8041014c; +constexpr int ORBIS_NET_ERROR_ENOLCK = 0x8041014d; +constexpr int ORBIS_NET_ERROR_ENOSYS = 0x8041014e; +constexpr int ORBIS_NET_ERROR_EFTYPE = 0x8041014f; +constexpr int ORBIS_NET_ERROR_EAUTH = 0x80410150; +constexpr int ORBIS_NET_ERROR_ENEEDAUTH = 0x80410151; constexpr int ORBIS_NET_ERROR_EIDRM = 0x80410152; -constexpr int ORBIS_NET_ERROR_ENOMSG = 0x80410153; +constexpr int ORBIS_NET_ERROR_ENOMS = 0x80410153; constexpr int ORBIS_NET_ERROR_EOVERFLOW = 0x80410154; constexpr int ORBIS_NET_ERROR_ECANCELED = 0x80410155; -constexpr int ORBIS_NET_ERROR_EPROTO = 0x8041015C; +constexpr int ORBIS_NET_ERROR_EPROTO = 0x8041015c; constexpr int ORBIS_NET_ERROR_EADHOC = 0x804101a0; constexpr int ORBIS_NET_ERROR_ERESERVED161 = 0x804101a1; constexpr int ORBIS_NET_ERROR_ERESERVED162 = 0x804101a2; @@ -147,8 +224,10 @@ constexpr int ORBIS_NET_ERROR_EINACTIVEDISABLED = 0x804101a3; constexpr int ORBIS_NET_ERROR_ENODATA = 0x804101a4; constexpr int ORBIS_NET_ERROR_EDESC = 0x804101a5; constexpr int ORBIS_NET_ERROR_EDESCTIMEDOUT = 0x804101a6; +constexpr int ORBIS_NET_ERROR_ENETINTR = 0x804101a7; constexpr int ORBIS_NET_ERROR_ENOTINIT = 0x804101c8; constexpr int ORBIS_NET_ERROR_ENOLIBMEM = 0x804101c9; +constexpr int ORBIS_NET_ERROR_ERESERVED202 = 0x804101ca; constexpr int ORBIS_NET_ERROR_ECALLBACK = 0x804101cb; constexpr int ORBIS_NET_ERROR_EINTERNAL = 0x804101cc; constexpr int ORBIS_NET_ERROR_ERETURN = 0x804101cd; @@ -157,6 +236,7 @@ constexpr int ORBIS_NET_ERROR_RESOLVER_EINTERNAL = 0x804101dc; constexpr int ORBIS_NET_ERROR_RESOLVER_EBUSY = 0x804101dd; constexpr int ORBIS_NET_ERROR_RESOLVER_ENOSPACE = 0x804101de; constexpr int ORBIS_NET_ERROR_RESOLVER_EPACKET = 0x804101df; +constexpr int ORBIS_NET_ERROR_RESOLVER_ERESERVED224 = 0x804101e0; constexpr int ORBIS_NET_ERROR_RESOLVER_ENODNS = 0x804101e1; constexpr int ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT = 0x804101e2; constexpr int ORBIS_NET_ERROR_RESOLVER_ENOSUPPORT = 0x804101e3; @@ -167,3 +247,5 @@ constexpr int ORBIS_NET_ERROR_RESOLVER_ENOTIMPLEMENTED = 0x804101e7; constexpr int ORBIS_NET_ERROR_RESOLVER_ESERVERREFUSED = 0x804101e8; constexpr int ORBIS_NET_ERROR_RESOLVER_ENORECORD = 0x804101e9; constexpr int ORBIS_NET_ERROR_RESOLVER_EALIGNMENT = 0x804101ea; +constexpr int ORBIS_NET_ERROR_RESOLVER_ENOTFOUND = 0x804101eb; +constexpr int ORBIS_NET_ERROR_RESOLVER_ENOTINIT = 0x804101ec; diff --git a/src/core/libraries/net/net_kernel.cpp b/src/core/libraries/net/net_kernel.cpp new file mode 100644 index 000000000..b1063404c --- /dev/null +++ b/src/core/libraries/net/net_kernel.cpp @@ -0,0 +1,339 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "common/logging/log.h" +#include "common/singleton.h" +#include "core/emulator_settings.h" +#include "core/file_sys/fs.h" +#include "core/libraries/kernel/file_system.h" +#include "core/libraries/kernel/kernel.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_error.h" +#include "core/libraries/net/net_kernel.h" +#include "core/libraries/net/net_log.h" +#include "core/libraries/net/net_resolver.h" +#include "core/libraries/net/net_translate.h" +#include "core/libraries/net/net_upnp.h" +#include "core/libraries/net/net_util.h" +#include "core/net/guest_net.h" + +namespace Libraries::Net { + +namespace { + +using FDTable = Common::Singleton; +using Core::Net::Host::Error; + +s64 Posix(s64 result) { + if (result < 0) { + *Libraries::Kernel::__Error() = *sceNetErrnoLoc(); + return -1; + } + return result; +} + +class KeepNetErrno { +public: + KeepNetErrno() : saved_{*sceNetErrnoLoc()} {} + ~KeepNetErrno() { + *sceNetErrnoLoc() = saved_; + } + KeepNetErrno(const KeepNetErrno&) = delete; + KeepNetErrno& operator=(const KeepNetErrno&) = delete; + +private: + s32 saved_; +}; + +s32 PosixError(int orbis_errno, + const std::source_location where = std::source_location::current()) { + *Libraries::Kernel::__Error() = orbis_errno; + LogFailure(orbis_errno, where); + return -1; +} + +bool IsNetObject(s32 fd) { + return Core::Net::GetObjectKind(fd).has_value(); +} + +} // namespace + +void InstallKernelIntegration() { + // Every net object is a Socket entry, Core::Net knows the real kind. + Core::Net::SetIdAllocator({ + .allocate = + [] { + auto* table = FDTable::Instance(); + const int fd = table->CreateHandle(); + auto* file = table->GetFile(fd); + file->type = Core::FileSys::FileType::Socket; + file->m_guest_name = "net"; + file->is_opened = true; + return fd; + }, + .release = [](s32 fd) { FDTable::Instance()->DeleteHandle(fd); }, + }); + SetKernelErrnoHook([](int orbis_errno) { *Libraries::Kernel::__Error() = orbis_errno; }); + SetSystemHooks({ + .is_online = [] { return EmulatorSettings.IsConnectedToNetwork(); }, + .mac_address = + [](std::array* mac) { + auto* netinfo = Common::Singleton::Instance(); + if (!netinfo->RetrieveEthernetAddr()) { + return false; + } + *mac = netinfo->GetEthernetAddr(); + return true; + }, + .p2p_port = + [] { + const int port = EmulatorSettings.GetP2PPort(); + if (port < 0 || port > 0xffff) { + LOG_WARNING(Lib_Net, "p2p_port {} is not a UDP port, using 3658", port); + return u16{3658}; + } + return static_cast(port); + }, + .p2p_started = + [](u16 port) { + if (!EmulatorSettings.IsConnectedToNetwork() || !EmulatorSettings.IsUPnPEnabled()) { + LOG_INFO(Lib_Net, "P2P on UDP port {}: UPnP forwarding off", port); + return; + } + LOG_INFO(Lib_Net, "P2P on UDP port {}: asking the router to forward it (UPnP)", + port); + auto& upnp = UPnPClient::Instance(); + upnp.Start(); // no-op if NP already started it + upnp.MapPortAsync(port); + }, + .p2p_stopped = + [](u16 port) { + LOG_DEBUG(Lib_Net, "P2P on UDP port {} stopped: removing any UPnP mapping", port); + UPnPClient::Instance().UnmapPort(port); + }, + .public_addr = + [] { + // Prefer the router's answer over the NP server's. + if (const u32 upnp = UPnPClient::Instance().GetExternalIp(); upnp != 0) { + return upnp; + } + return Common::Singleton::Instance()->GetExternalIp(); + }, + }); +} + +s32 KernelClose(s32 fd) { + const auto kind = Core::Net::GetObjectKind(fd); + LOG_DEBUG(Lib_Net, "close({}): {}", fd, + !kind ? "not a net object" + : *kind == Core::Net::NetObjectKind::Socket ? "socket" + : *kind == Core::Net::NetObjectKind::Epoll ? "epoll" + : "resolver"); + if (!kind) { + return PosixError(ORBIS_NET_EBADF); + } + if (*kind == Core::Net::NetObjectKind::External) { + const int e = DestroyResolver(fd); + if (e != 0) { + return PosixError(e); + } + ReleaseResolverPool(fd); + return 0; + } + const auto r = Core::Net::CloseObject(fd); + if (r.error == Error::Ok && *kind == Core::Net::NetObjectKind::Socket) { + LogSocketClosed(fd); + } + return r.error == Error::Ok ? 0 : PosixError(ToOrbisErrno(r.error)); +} + +s64 KernelRead(s32 fd, void* buf, u64 nbytes) { + const KeepNetErrno keep; + if (Core::Net::GetObjectKind(fd) != Core::Net::NetObjectKind::Socket) { + return PosixError(ORBIS_NET_EBADF); + } + return Posix(sceNetRecvfrom(fd, buf, nbytes, 0, nullptr, nullptr)); +} + +s64 KernelWrite(s32 fd, const void* buf, u64 nbytes) { + const KeepNetErrno keep; + if (Core::Net::GetObjectKind(fd) != Core::Net::NetObjectKind::Socket) { + return PosixError(ORBIS_NET_EBADF); + } + return Posix(sceNetSendto(fd, buf, nbytes, 0, nullptr, 0)); +} + +s32 KernelFstat(s32 fd, Libraries::Kernel::OrbisKernelStat* sb) { + if (!sb) { + return PosixError(ORBIS_NET_EFAULT); + } + if (!IsNetObject(fd)) { + return PosixError(ORBIS_NET_EBADF); + } + std::memset(sb, 0, sizeof(*sb)); + sb->st_mode = 0140000u | 0777u; + sb->st_nlink = 1; + sb->st_blksize = 4096; + sb->st_size = 0; + return 0; +} + +s64 KernelSelect(std::span entries, s64 timeout_us) { + std::vector sockets; + sockets.reserve(entries.size()); + for (auto& e : entries) { + e.ready = 0; + if (Core::Net::GetObjectKind(e.id) == Core::Net::NetObjectKind::Socket) { + sockets.push_back(e); + } + } + const auto r = Core::Net::SocketSelect(sockets, timeout_us); + if (r.error != Error::Ok) { + return PosixError(static_cast(r.error)); + } + for (const auto& s : sockets) { + for (auto& e : entries) { + if (e.id == s.id) { + e.ready = s.ready; + } + } + } + return r.value; +} + +// libkernel / libScePosix socket calls + +int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetConnect(s, addr, addrlen))); +} + +int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetBind(s, addr, addrlen))); +} + +int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetAccept(s, addr, paddrlen))); +} + +int PS4_SYSV_ABI sys_getpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetGetpeername(s, addr, paddrlen))); +} + +int PS4_SYSV_ABI sys_getsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetGetsockname(s, addr, paddrlen))); +} + +int PS4_SYSV_ABI sys_getsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetGetsockopt(s, level, optname, optval, optlen))); +} + +int PS4_SYSV_ABI sys_listen(OrbisNetId s, int backlog) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetListen(s, backlog))); +} + +int PS4_SYSV_ABI sys_setsockopt(OrbisNetId s, int level, int optname, const void* optval, + u32 optlen) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetSetsockopt(s, level, optname, optval, optlen))); +} + +int PS4_SYSV_ABI sys_shutdown(OrbisNetId s, int how) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetShutdown(s, how))); +} + +int PS4_SYSV_ABI sys_socketex(const char* name, int family, int type, int protocol) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetSocket(name, family, type, protocol))); +} + +int PS4_SYSV_ABI sys_socket(int family, int type, int protocol) { + const KeepNetErrno keep; + return sys_socketex(nullptr, family, type, protocol); +} + +int PS4_SYSV_ABI sys_socketpair(int family, int type, int protocol, int sv[2]) { + LOG_DEBUG(Lib_Net, "family = {}, type = {}, protocol = {}", family, type, protocol); + if (sv == nullptr) { + return PosixError(ORBIS_NET_EFAULT); + } + if (family != ORBIS_NET_AF_UNIX) { + return PosixError(ORBIS_NET_EAFNOSUPPORT); // AF_UNIX only on FreeBSD + } + int error = 0; + const auto kind = ToHostSocketKind(family, type, protocol, &error); + if (!kind) { + return PosixError(error); + } + s32 ids[2]; + const auto r = Core::Net::SocketCreatePair(kind->family, kind->type, kind->protocol, ids); + if (r.error != Error::Ok) { + return PosixError(ToOrbisErrno(r.error)); + } + sv[0] = ids[0]; + sv[1] = ids[1]; + return 0; +} + +int PS4_SYSV_ABI sys_netabort(OrbisNetId s, int flags) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetSocketAbort(s, flags))); +} + +int PS4_SYSV_ABI sys_socketclose(OrbisNetId s) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetSocketClose(s))); +} + +int PS4_SYSV_ABI sys_send(OrbisNetId s, const void* buf, u64 len, int flags) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetSend(s, buf, len, flags))); +} + +int PS4_SYSV_ABI sys_sendto(OrbisNetId s, const void* buf, u64 len, int flags, + const OrbisNetSockaddr* addr, u32 addrlen) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetSendto(s, buf, len, flags, addr, addrlen))); +} + +int PS4_SYSV_ABI sys_sendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags) { + const KeepNetErrno keep; + return static_cast(Posix(sceNetSendmsg(s, msg, flags))); +} + +s64 PS4_SYSV_ABI sys_recv(OrbisNetId s, void* buf, u64 len, int flags) { + const KeepNetErrno keep; + return Posix(sceNetRecv(s, buf, len, flags)); +} + +s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr, + u32* paddrlen) { + const KeepNetErrno keep; + return Posix(sceNetRecvfrom(s, buf, len, flags, addr, paddrlen)); +} + +s64 PS4_SYSV_ABI sys_recvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags) { + const KeepNetErrno keep; + return Posix(sceNetRecvmsg(s, msg, flags)); +} + +int PS4_SYSV_ABI sys_htons(u16 v) { + return sceNetHtons(v); +} + +int PS4_SYSV_ABI sys_htonl(u32 v) { + return static_cast(sceNetHtonl(v)); +} + +} // namespace Libraries::Net diff --git a/src/core/libraries/network/sys_net.h b/src/core/libraries/net/net_kernel.h similarity index 72% rename from src/core/libraries/network/sys_net.h rename to src/core/libraries/net/net_kernel.h index 5749803b5..b6c6f0908 100644 --- a/src/core/libraries/network/sys_net.h +++ b/src/core/libraries/net/net_kernel.h @@ -1,13 +1,31 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include + #include "common/types.h" -#include "net.h" +#include "core/libraries/net/net_types.h" +#include "core/net/guest_net.h" + +namespace Libraries::Kernel { +struct OrbisKernelStat; +} namespace Libraries::Net { +// Called from the kernel's RegisterLib, before any net object exists. +void InstallKernelIntegration(); +s32 KernelClose(s32 fd); +s64 KernelRead(s32 fd, void* buf, u64 nbytes); +s64 KernelWrite(s32 fd, const void* buf, u64 nbytes); +s32 KernelFstat(s32 fd, Libraries::Kernel::OrbisKernelStat* sb); +// Returns the number of ready (fd, direction) pairs. Epolls and resolvers are never ready. +s64 KernelSelect(std::span entries, s64 timeout_us); + +// libkernel / libScePosix socket calls + int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen); int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen); int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen); diff --git a/src/core/libraries/net/net_log.cpp b/src/core/libraries/net/net_log.cpp new file mode 100644 index 000000000..909c4b218 --- /dev/null +++ b/src/core/libraries/net/net_log.cpp @@ -0,0 +1,526 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/logging/log.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_error.h" +#include "core/libraries/net/net_log.h" + +namespace Libraries::Net { + +namespace { + +struct ErrnoEntry { + int value; + const char* name; +}; + +// ORBIS_NET_ERROR_ is 0x80410100 | value. +constexpr ErrnoEntry ErrnoNames[] = { + {1, "EPERM"}, + {2, "ENOENT"}, + {3, "ESRCH"}, + {4, "EINTR"}, + {5, "EIO"}, + {6, "ENXIO"}, + {7, "E2BIG"}, + {8, "ENOEXEC"}, + {9, "EBADF"}, + {10, "ECHILD"}, + {11, "EDEADLK"}, + {12, "ENOMEM"}, + {13, "EACCES"}, + {14, "EFAULT"}, + {15, "ENOTBLK"}, + {16, "EBUSY"}, + {17, "EEXIST"}, + {18, "EXDEV"}, + {19, "ENODEV"}, + {20, "ENOTDIR"}, + {21, "EISDIR"}, + {22, "EINVAL"}, + {23, "ENFILE"}, + {24, "EMFILE"}, + {25, "ENOTTY"}, + {26, "ETXTBSY"}, + {27, "EFBIG"}, + {28, "ENOSPC"}, + {29, "ESPIPE"}, + {30, "EROFS"}, + {31, "EMLINK"}, + {32, "EPIPE"}, + {33, "EDOM"}, + {34, "ERANGE"}, + {35, "EWOULDBLOCK"}, + {36, "EINPROGRESS"}, + {37, "EALREADY"}, + {38, "ENOTSOCK"}, + {39, "EDESTADDRREQ"}, + {40, "EMSGSIZE"}, + {41, "EPROTOTYPE"}, + {42, "ENOPROTOOPT"}, + {43, "EPROTONOSUPPORT"}, + {44, "ESOCKTNOSUPPORT"}, + {45, "EOPNOTSUPP"}, + {46, "EPFNOSUPPORT"}, + {47, "EAFNOSUPPORT"}, + {48, "EADDRINUSE"}, + {49, "EADDRNOTAVAIL"}, + {50, "ENETDOWN"}, + {51, "ENETUNREACH"}, + {52, "ENETRESET"}, + {53, "ECONNABORTED"}, + {54, "ECONNRESET"}, + {55, "ENOBUFS"}, + {56, "EISCONN"}, + {57, "ENOTCONN"}, + {58, "ESHUTDOWN"}, + {59, "ETOOMANYREFS"}, + {60, "ETIMEDOUT"}, + {61, "ECONNREFUSED"}, + {62, "ELOOP"}, + {63, "ENAMETOOLONG"}, + {64, "EHOSTDOWN"}, + {65, "EHOSTUNREACH"}, + {66, "ENOTEMPTY"}, + {67, "EPROCLIM"}, + {68, "EUSERS"}, + {69, "EDQUOT"}, + {70, "ESTALE"}, + {71, "EREMOTE"}, + {72, "EBADRPC"}, + {73, "ERPCMISMATCH"}, + {74, "EPROGUNAVAIL"}, + {75, "EPROGMISMATCH"}, + {76, "EPROCUNAVAIL"}, + {77, "ENOLCK"}, + {78, "ENOSYS"}, + {79, "EFTYPE"}, + {80, "EAUTH"}, + {81, "ENEEDAUTH"}, + {82, "EIDRM"}, + {83, "ENOMS"}, + {84, "EOVERFLOW"}, + {85, "ECANCELED"}, + {92, "EPROTO"}, + {160, "EADHOC"}, + {161, "ERESERVED161"}, + {162, "ERESERVED162"}, + {163, "EINACTIVEDISABLED"}, + {164, "ENODATA"}, + {165, "EDESC"}, + {166, "EDESCTIMEDOUT"}, + {167, "ENETINTR"}, + {200, "ENOTINIT"}, + {201, "ENOLIBMEM"}, + {202, "ERESERVED202"}, + {203, "ECALLBACK"}, + {204, "EINTERNAL"}, + {205, "ERETURN"}, + {206, "ENOALLOCMEM"}, + {220, "RESOLVER_EINTERNAL"}, + {221, "RESOLVER_EBUSY"}, + {222, "RESOLVER_ENOSPACE"}, + {223, "RESOLVER_EPACKET"}, + {224, "RESOLVER_ERESERVED224"}, + {225, "RESOLVER_ENODNS"}, + {226, "RESOLVER_ETIMEDOUT"}, + {227, "RESOLVER_ENOSUPPORT"}, + {228, "RESOLVER_EFORMAT"}, + {229, "RESOLVER_ESERVERFAILURE"}, + {230, "RESOLVER_ENOHOST"}, + {231, "RESOLVER_ENOTIMPLEMENTED"}, + {232, "RESOLVER_ESERVERREFUSED"}, + {233, "RESOLVER_ENORECORD"}, + {234, "RESOLVER_EALIGNMENT"}, + {235, "RESOLVER_ENOTFOUND"}, + {236, "RESOLVER_ENOTINIT"}, +}; + +enum class Severity { Routine, Network, Suspicious }; + +Severity SeverityOf(int e) { + switch (e) { + case ORBIS_NET_EWOULDBLOCK: + case ORBIS_NET_EINPROGRESS: + case ORBIS_NET_EALREADY: + case ORBIS_NET_EISCONN: + case ORBIS_NET_EINTR: + case ORBIS_NET_ENOTBLK: + return Severity::Routine; + case ORBIS_NET_ECONNREFUSED: + case ORBIS_NET_ECONNRESET: + case ORBIS_NET_ECONNABORTED: + case ORBIS_NET_ENOTCONN: + case ORBIS_NET_EPIPE: + case ORBIS_NET_ETIMEDOUT: + case ORBIS_NET_EHOSTUNREACH: + case ORBIS_NET_EHOSTDOWN: + case ORBIS_NET_ENETUNREACH: + case ORBIS_NET_ENETDOWN: + case ORBIS_NET_ENETRESET: + case ORBIS_NET_EADDRINUSE: + case ORBIS_NET_EADDRNOTAVAIL: + case ORBIS_NET_ENOBUFS: + case ORBIS_NET_EACCES: + case ORBIS_NET_ESHUTDOWN: + return Severity::Network; + default: + return e >= 0xdc && e <= 0xec ? Severity::Network : Severity::Suspicious; + } +} + +std::string_view ShortName(std::string_view signature) { + const size_t paren = signature.find('('); + std::string_view head = signature.substr(0, paren); + const size_t start = head.find_last_of(": "); + head = start == std::string_view::npos ? head : head.substr(start + 1); + // Wrapped exports have their body in Impl. + if (head.ends_with("Impl")) { + head.remove_suffix(4); + } + return head; +} + +u64 CountFailure(std::string_view function, int e) { + static std::mutex mutex; + static std::map, u64> counts; + std::scoped_lock lock{mutex}; + return ++counts[{std::string{function}, e}]; +} + +} // namespace + +std::string ErrnoName(int orbis_errno) { + const auto* it = + std::find_if(std::begin(ErrnoNames), std::end(ErrnoNames), + [&](const ErrnoEntry& entry) { return entry.value == orbis_errno; }); + return it != std::end(ErrnoNames) ? it->name : fmt::format("E{}", orbis_errno); +} + +std::string ErrorCodeName(s32 code) { + const u32 u = static_cast(code); + if ((u & 0xffffff00u) == static_cast(ORBIS_NET_ERROR_BASE)) { + return ErrnoName(static_cast(u & 0xff)); + } + return fmt::format("{:#x}", u); +} + +void LogFailure(int orbis_errno, const std::source_location& where) { + const std::string_view function = ShortName(where.function_name()); + const Severity severity = SeverityOf(orbis_errno); + if (severity == Severity::Routine) { + LOG_TRACE(Lib_Net, "{} failed: {}", function, ErrnoName(orbis_errno)); + return; + } + const u64 count = CountFailure(function, orbis_errno); + if (count > 16 && count % 1000 != 0) { + return; + } + const std::string repeat = count > 16 ? fmt::format(", {} times so far", count) : ""; + std::string_view file = where.file_name(); + file.remove_prefix(std::min(file.size(), file.find_last_of("/\\") + 1)); + if (severity == Severity::Network) { + LOG_DEBUG(Lib_Net, "{} failed: {} ({}:{}{})", function, ErrnoName(orbis_errno), file, + where.line(), repeat); + } else { + LOG_WARNING(Lib_Net, "{} failed: {} ({}:{}{})", function, ErrnoName(orbis_errno), file, + where.line(), repeat); + } +} + +namespace { + +struct Traffic { + u64 sent_bytes = 0; + u64 sent_calls = 0; + u64 received_bytes = 0; + u64 received_calls = 0; + std::set peers; +}; + +std::mutex g_traffic_mutex; +std::map g_traffic; // guarded by g_traffic_mutex + +} // namespace + +void CountTraffic(OrbisNetId s, bool sent, s64 bytes) { + std::scoped_lock lock{g_traffic_mutex}; + auto& t = g_traffic[s]; + if (sent) { + t.sent_bytes += static_cast(bytes); + ++t.sent_calls; + } else { + t.received_bytes += static_cast(bytes); + ++t.received_calls; + } +} + +void NoteP2PPeer(OrbisNetId s, bool sent, const OrbisNetSockaddr* addr, u32 len) { + if (addr == nullptr || addr->sa_family != ORBIS_NET_AF_INET || + len < sizeof(OrbisNetSockaddrIn)) { + return; + } + OrbisNetSockaddrIn in; + std::memcpy(&in, addr, sizeof(in)); + if (in.sin_vport == 0) { + return; // not P2P + } + constexpr size_t MaxPeers = 16; + const std::string text = FormatSockaddr(addr, len); + { + std::scoped_lock lock{g_traffic_mutex}; + auto& peers = g_traffic[s].peers; + if (peers.size() >= MaxPeers || !peers.insert((sent ? "> " : "< ") + text).second) { + return; + } + } + LOG_INFO(Lib_Net, "socket {}: first P2P packet {} {}", s, sent ? "to" : "from", text); +} + +void LogSocketClosed(OrbisNetId s) { + Traffic t; + { + std::scoped_lock lock{g_traffic_mutex}; + const auto it = g_traffic.find(s); + if (it != g_traffic.end()) { + t = it->second; + g_traffic.erase(it); + } + } + LOG_DEBUG(Lib_Net, "socket {} closed: sent {} bytes in {} calls, received {} bytes in {} calls", + s, t.sent_bytes, t.sent_calls, t.received_bytes, t.received_calls); +} + +std::string FormatSockaddr(const OrbisNetSockaddr* addr, u32 len) { + if (addr == nullptr) { + return "null"; + } + if (len < 2) { + return fmt::format("(length {})", len); + } + if (addr->sa_family == ORBIS_NET_AF_INET && len >= sizeof(OrbisNetSockaddrIn)) { + OrbisNetSockaddrIn in; + std::memcpy(&in, addr, sizeof(in)); + const u32 ip = sceNetNtohl(in.sin_addr); + std::string text = fmt::format("{}.{}.{}.{}:{}", ip >> 24, (ip >> 16) & 0xff, + (ip >> 8) & 0xff, ip & 0xff, sceNetNtohs(in.sin_port)); + if (in.sin_vport != 0) { + text += fmt::format(" vport {}", sceNetNtohs(in.sin_vport)); + } + return text; + } + if (addr->sa_family == ORBIS_NET_AF_UNIX) { + const char* path = reinterpret_cast(addr) + 2; + return fmt::format("unix:{}", std::string_view{path, strnlen(path, len - 2)}); + } + return fmt::format("family {} (length {})", addr->sa_family, len); +} + +std::string_view SocketTypeName(s32 type) { + switch (type) { + case ORBIS_NET_SOCK_STREAM: + return "STREAM"; + case ORBIS_NET_SOCK_DGRAM: + return "DGRAM"; + case ORBIS_NET_SOCK_RAW: + return "RAW"; + case ORBIS_NET_SOCK_DGRAM_P2P: + return "DGRAM_P2P"; + case ORBIS_NET_SOCK_STREAM_P2P: + return "STREAM_P2P"; + default: + return "unknown"; + } +} + +std::string OptionName(s32 level, s32 name) { + const char* option = nullptr; + const char* level_name = nullptr; + switch (level) { + case ORBIS_NET_SOL_SOCKET: + level_name = "SOL_SOCKET"; + switch (name) { + case ORBIS_NET_SO_REUSEADDR: + option = "SO_REUSEADDR"; + break; + case ORBIS_NET_SO_KEEPALIVE: + option = "SO_KEEPALIVE"; + break; + case ORBIS_NET_SO_BROADCAST: + option = "SO_BROADCAST"; + break; + case ORBIS_NET_SO_LINGER: + option = "SO_LINGER"; + break; + case ORBIS_NET_SO_REUSEPORT: + option = "SO_REUSEPORT"; + break; + case ORBIS_NET_SO_ONESBCAST: + option = "SO_ONESBCAST"; + break; + case ORBIS_NET_SO_USECRYPTO: + option = "SO_USECRYPTO"; + break; + case ORBIS_NET_SO_USESIGNATURE: + option = "SO_USESIGNATURE"; + break; + case ORBIS_NET_SO_SNDBUF: + option = "SO_SNDBUF"; + break; + case ORBIS_NET_SO_RCVBUF: + option = "SO_RCVBUF"; + break; + case ORBIS_NET_SO_ERROR: + option = "SO_ERROR"; + break; + case ORBIS_NET_SO_TYPE: + option = "SO_TYPE"; + break; + case ORBIS_NET_SO_SNDTIMEO: + option = "SO_SNDTIMEO"; + break; + case ORBIS_NET_SO_RCVTIMEO: + option = "SO_RCVTIMEO"; + break; + case ORBIS_NET_SO_ERROR_EX: + option = "SO_ERROR_EX"; + break; + case ORBIS_NET_SO_ACCEPTTIMEO: + option = "SO_ACCEPTTIMEO"; + break; + case ORBIS_NET_SO_CONNECTTIMEO: + option = "SO_CONNECTTIMEO"; + break; + case ORBIS_NET_SO_NBIO: + option = "SO_NBIO"; + break; + case ORBIS_NET_SO_POLICY: + option = "SO_POLICY"; + break; + case ORBIS_NET_SO_NAME: + option = "SO_NAME"; + break; + case ORBIS_NET_SO_PRIORITY: + option = "SO_PRIORITY"; + break; + case 0x1005: + option = "SO_SNDTIMEO (BSD)"; + break; + case 0x1006: + option = "SO_RCVTIMEO (BSD)"; + break; + default: + break; + } + break; + case ORBIS_NET_IPPROTO_IP: + level_name = "IPPROTO_IP"; + switch (name) { + case ORBIS_NET_IP_HDRINCL: + option = "IP_HDRINCL"; + break; + case ORBIS_NET_IP_TOS: + option = "IP_TOS"; + break; + case ORBIS_NET_IP_TTL: + option = "IP_TTL"; + break; + case ORBIS_NET_IP_MULTICAST_IF: + option = "IP_MULTICAST_IF"; + break; + case ORBIS_NET_IP_MULTICAST_TTL: + option = "IP_MULTICAST_TTL"; + break; + case ORBIS_NET_IP_MULTICAST_LOOP: + option = "IP_MULTICAST_LOOP"; + break; + case ORBIS_NET_IP_ADD_MEMBERSHIP: + option = "IP_ADD_MEMBERSHIP"; + break; + case ORBIS_NET_IP_DROP_MEMBERSHIP: + option = "IP_DROP_MEMBERSHIP"; + break; + case ORBIS_NET_IP_TTLCHK: + option = "IP_TTLCHK"; + break; + case ORBIS_NET_IP_MAXTTL: + option = "IP_MAXTTL"; + break; + case ORBIS_NET_IP_DONTFRAG: + option = "IP_DONTFRAG"; + break; + default: + break; + } + break; + case ORBIS_NET_IPPROTO_TCP: + level_name = "IPPROTO_TCP"; + switch (name) { + case ORBIS_NET_TCP_NODELAY: + option = "TCP_NODELAY"; + break; + case ORBIS_NET_TCP_MAXSEG: + option = "TCP_MAXSEG"; + break; + case ORBIS_NET_TCP_MSS_TO_ADVERTISE: + option = "TCP_MSS_TO_ADVERTISE"; + break; + case ORBIS_NET_TCP_KEEPLISTEN: + option = "TCP_KEEPLISTEN"; + break; + default: + break; + } + break; + case ORBIS_NET_IPPROTO_UDP: + level_name = "IPPROTO_UDP"; + if (name == ORBIS_NET_UDP_SND_ON_SUSPEND) { + option = "UDP_SND_ON_SUSPEND"; + } + break; + default: + break; + } + if (level_name == nullptr) { + return fmt::format("level {:#x}/option {:#x}", level, name); + } + if (option == nullptr) { + return fmt::format("{}/option {:#x}", level_name, name); + } + return fmt::format("{}/{}", level_name, option); +} + +std::string EpollEventsName(u32 events) { + static constexpr std::pair Bits[] = { + {ORBIS_NET_EPOLLIN, "IN"}, {ORBIS_NET_EPOLLOUT, "OUT"}, {ORBIS_NET_EPOLLERR, "ERR"}, + {ORBIS_NET_EPOLLHUP, "HUP"}, {ORBIS_NET_EPOLLDESCID, "DESCID"}, + }; + std::string out; + u32 rest = events; + for (const auto& [bit, name] : Bits) { + if (events & bit) { + out += out.empty() ? "" : "|"; + out += name; + rest &= ~bit; + } + } + if (rest != 0) { + out += fmt::format("{}{:#x}", out.empty() ? "" : "|", rest); + } + return out.empty() ? "0" : out; +} + +} // namespace Libraries::Net diff --git a/src/core/libraries/net/net_log.h b/src/core/libraries/net/net_log.h new file mode 100644 index 000000000..a07a4dfd2 --- /dev/null +++ b/src/core/libraries/net/net_log.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/types.h" +#include "core/libraries/net/net_types.h" + +namespace Libraries::Net { + +std::string ErrnoName(int orbis_errno); +std::string ErrorCodeName(s32 code); +void LogFailure(int orbis_errno, const std::source_location& where); +std::string FormatSockaddr(const OrbisNetSockaddr* addr, u32 len); +std::string_view SocketTypeName(s32 type); +std::string OptionName(s32 level, s32 name); +std::string EpollEventsName(u32 events); +void CountTraffic(OrbisNetId s, bool sent, s64 bytes); +void NoteP2PPeer(OrbisNetId s, bool sent, const OrbisNetSockaddr* addr, u32 len); +void LogSocketClosed(OrbisNetId s); + +} // namespace Libraries::Net diff --git a/src/core/libraries/net/net_p2p.cpp b/src/core/libraries/net/net_p2p.cpp new file mode 100644 index 000000000..6cb2fd766 --- /dev/null +++ b/src/core/libraries/net/net_p2p.cpp @@ -0,0 +1,314 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/logging/log.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_log.h" +#include "core/libraries/net/net_p2p.h" +#include "core/libraries/net/net_translate.h" +#include "core/net/guest_net.h" +#include "core/net/p2p_codec.h" + +namespace Libraries::Net { + +namespace { + +using Host::Error; + +enum SignalingChannel : size_t { Signaling = 0, Control = 1, Matching2 = 2, ChannelCount }; + +size_t ClassifySignaling(std::span packet) { + if (packet.size() < 5 || std::memcmp(packet.data(), "SHAD", 4) != 0) { + return Signaling; + } + switch (packet[4]) { + case 0x10: + return Control; + case 0x21: + return Matching2; + default: + return Signaling; + } +} + +struct P2PState { + std::mutex mutex; + std::shared_ptr transport; + std::shared_ptr keyring = std::make_shared(); + u16 configured_port = 3658; // host order + u32 advertised_addr = 0; // network order + std::chrono::steady_clock::time_point retry_after{}; + std::pair last_failure{0, Error::Ok}; +}; + +P2PState& P2PStateInstance() { + static P2PState state; + return state; +} + +void LogBroadcast(std::span packet, const P2P::Endpoint& from) { + char address[INET6_ADDRSTRLEN] = "?"; + const auto* sa = from.Sockaddr(); + if (sa->sa_family == AF_INET) { + inet_ntop(AF_INET, &reinterpret_cast(sa)->sin_addr, address, + sizeof(address)); + } else if (sa->sa_family == AF_INET6) { + inet_ntop(AF_INET6, &reinterpret_cast(sa)->sin6_addr, address, + sizeof(address)); + } + std::string hex; + const size_t shown = std::min(packet.size(), 64); + hex.reserve(shown * 3); + for (size_t i = 0; i < shown; ++i) { + fmt::format_to(std::back_inserter(hex), "{}{:02x}", i == 0 ? "" : " ", packet[i]); + } + LOG_WARNING(Lib_Net, + "P2P broadcast packet received (layout assumed; please report this line): " + "from {}:{}, {} bytes: {}{}", + address, from.Port(), packet.size(), hex, packet.size() > shown ? " ..." : ""); +} + +constexpr u16 P2PPortFallbacks = 16; + +u16 StartLocked(P2PState& state, u16 udp_port, bool allow_fallback) { + if (!Host::Initialize()) { + return 0; + } + P2P::TransportConfig config; + config.signaling_channels = ChannelCount; + config.classify_signaling = ClassifySignaling; + const auto try_port = [&](u16 port, Error* error) { + auto codec = std::make_unique(state.keyring, LogBroadcast); + return P2P::Transport::Create(AF_INET, P2P::Endpoint::IPv4("0.0.0.0", port), + std::move(codec), config, error); + }; + Error error; + auto transport = try_port(udp_port, &error); + for (u16 i = 1; !transport && allow_fallback && error == Error::AddrInUse && udp_port != 0 && + i <= P2PPortFallbacks && udp_port + i <= 0xffff; + ++i) { + transport = try_port(static_cast(udp_port + i), &error); + } + if (!transport) { + if (state.last_failure != std::pair{udp_port, error}) { + state.last_failure = {udp_port, error}; + LOG_ERROR(Lib_Net, "cannot start P2P on UDP port {}: {}", udp_port, + ErrnoName(static_cast(error))); + } else { + LOG_DEBUG(Lib_Net, "cannot start P2P on UDP port {}: {}", udp_port, + ErrnoName(static_cast(error))); + } + return 0; + } + state.last_failure = {0, Error::Ok}; + const u16 port = transport->BoundPort(); + state.transport = transport; + Core::Net::SetP2PTransport(AF_INET, std::move(transport)); + if (udp_port != 0 && port != udp_port) { + LOG_WARNING(Lib_Net, + "UDP port {} is in use (another instance?), P2P transport on UDP port {} " + "instead. Set p2p_port to choose one.", + udp_port, port); + } else { + LOG_INFO(Lib_Net, "P2P transport on UDP port {}", port); + } + return port; +} + +u16 PortToBind(const P2PState& state, const SystemHooks& hooks) { + return hooks.p2p_port ? hooks.p2p_port() : state.configured_port; +} + +std::shared_ptr RunningTransport() { + auto& state = P2PStateInstance(); + const auto hooks = GetSystemHooks(); + std::shared_ptr transport; + u16 started = 0; + { + std::scoped_lock lock{state.mutex}; + const auto now = std::chrono::steady_clock::now(); + if (!state.transport && now >= state.retry_after) { + started = StartLocked(state, PortToBind(state, hooks), true); + if (started == 0) { + state.retry_after = now + std::chrono::seconds{2}; + } + } + transport = state.transport; + } + if (started != 0 && hooks.p2p_started) { + hooks.p2p_started(started); + } + return transport; +} + +int ChannelSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) { + const auto transport = RunningTransport(); + if (!transport || (data == nullptr && len != 0)) { + return -1; + } + sockaddr_in to{}; + to.sin_family = AF_INET; + to.sin_addr.s_addr = dest_addr; + to.sin_port = dest_port; + const auto endpoint = + P2P::Endpoint::FromSockaddr(reinterpret_cast(&to), sizeof(to)); + const Error e = transport->SendSignaling( + {static_cast(data), static_cast(len)}, endpoint); + return e == Error::Ok ? static_cast(len) : -1; +} + +int ChannelRecvFrom(size_t channel, void* buf, u32 len, u32* from_addr, u16* from_port) { + const auto transport = RunningTransport(); + std::vector packet; + P2P::Endpoint from; + if (!transport || !transport->RecvSignaling(channel, &packet, &from)) { + return -1; + } + const u32 n = std::min(len, static_cast(packet.size())); + if (buf != nullptr) { + std::memcpy(buf, packet.data(), n); + } + const auto* in = reinterpret_cast(&from.addr); + if (from_addr != nullptr) { + *from_addr = in->sin_addr.s_addr; + } + if (from_port != nullptr) { + *from_port = in->sin_port; + } + return static_cast(n); +} + +} // namespace + +void ConfigureP2P(u16 udp_port, u32 advertised_addr) { + auto& state = P2PStateInstance(); + std::scoped_lock lock{state.mutex}; + state.configured_port = udp_port; + state.advertised_addr = advertised_addr; + const u32 ip = sceNetNtohl(advertised_addr); + LOG_INFO(Lib_Net, "P2P configured: UDP port {}, advertised address {}.{}.{}.{}", udp_port, + ip >> 24, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); +} + +u16 StartP2P(u16 udp_port) { + StopP2P(); + auto& state = P2PStateInstance(); + u16 started = 0; + { + std::scoped_lock lock{state.mutex}; + started = StartLocked(state, udp_port, false); + state.retry_after = {}; + } + const auto hooks = GetSystemHooks(); + if (started != 0 && hooks.p2p_started) { + hooks.p2p_started(started); + } + return started; +} + +void StopP2P() { + auto& state = P2PStateInstance(); + u16 stopped = 0; + { + std::scoped_lock lock{state.mutex}; + if (state.transport) { + stopped = state.transport->BoundPort(); + } + state.transport.reset(); + state.retry_after = {}; + Core::Net::SetP2PTransport(AF_INET, nullptr); + } + if (stopped != 0) { + LOG_INFO(Lib_Net, "P2P transport on UDP port {} stopped", stopped); + } + const auto hooks = GetSystemHooks(); + if (stopped != 0 && hooks.p2p_stopped) { + hooks.p2p_stopped(stopped); + } +} + +std::shared_ptr GetP2PKeyring() { + auto& state = P2PStateInstance(); + std::scoped_lock lock{state.mutex}; + return state.keyring; +} + +bool EnsureP2PTransport() { + return RunningTransport() != nullptr; +} + +bool P2PTransportIsReady() { + auto& state = P2PStateInstance(); + std::scoped_lock lock{state.mutex}; + return state.transport != nullptr; +} + +u16 GetP2PConfiguredPort() { + const auto hooks = GetSystemHooks(); + auto& state = P2PStateInstance(); + std::scoped_lock lock{state.mutex}; + return PortToBind(state, hooks); +} + +u16 GetP2PBoundPort() { + auto& state = P2PStateInstance(); + std::scoped_lock lock{state.mutex}; + return state.transport ? state.transport->BoundPort() : 0; +} + +u32 GetP2PAdvertisedAddr() { + u32 configured = 0; + { + auto& state = P2PStateInstance(); + std::scoped_lock lock{state.mutex}; + configured = state.advertised_addr; + } + if (configured != 0) { + return configured; + } + const auto hooks = GetSystemHooks(); + return hooks.public_addr ? hooks.public_addr() : 0; +} + +u16 GetP2PAdvertisedPort() { + const u16 bound = GetP2PBoundPort(); + return bound != 0 ? bound : GetP2PConfiguredPort(); +} + +int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) { + return ChannelSendTo(data, len, dest_addr, dest_port); +} + +int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) { + return ChannelRecvFrom(Signaling, buf, len, from_addr, from_port); +} + +int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) { + return ChannelSendTo(data, len, dest_addr, dest_port); +} + +int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) { + return ChannelRecvFrom(Control, buf, len, from_addr, from_port); +} + +int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) { + return ChannelSendTo(data, len, dest_addr, dest_port); +} + +int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) { + return ChannelRecvFrom(Matching2, buf, len, from_addr, from_port); +} + +} // namespace Libraries::Net diff --git a/src/core/libraries/net/net_p2p.h b/src/core/libraries/net/net_p2p.h new file mode 100644 index 000000000..7f6ed4275 --- /dev/null +++ b/src/core/libraries/net/net_p2p.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/types.h" + +namespace Core::Net::P2P { +class Keyring; +} + +namespace Libraries::Net { +void ConfigureP2P(u16 udp_port, u32 advertised_addr); +u16 StartP2P(u16 udp_port); +void StopP2P(); +std::shared_ptr GetP2PKeyring(); + +// Used by NP signaling/matching. +bool EnsureP2PTransport(); +bool P2PTransportIsReady(); +u16 GetP2PConfiguredPort(); // host order +u16 GetP2PBoundPort(); // host order, 0 when not running +u32 GetP2PAdvertisedAddr(); // Network order, 0 if unknown. +u16 GetP2PAdvertisedPort(); +int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port); +int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port); +int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port); +int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port); +int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port); +int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port); + +} // namespace Libraries::Net diff --git a/src/core/libraries/net/net_p2p_ioctl.cpp b/src/core/libraries/net/net_p2p_ioctl.cpp new file mode 100644 index 000000000..cacb13d41 --- /dev/null +++ b/src/core/libraries/net/net_p2p_ioctl.cpp @@ -0,0 +1,145 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/logging/log.h" +#include "core/libraries/net/net_error.h" +#include "core/libraries/net/net_log.h" +#include "core/libraries/net/net_p2p_ioctl.h" +#include "core/libraries/net/net_types.h" +#include "core/net/p2p_codec.h" + +namespace Libraries::Net { + +namespace P2P = Core::Net::P2P; + +namespace { + +constexpr size_t AddressSize = sizeof(OrbisNetSockaddrIn); // IPv4 only +constexpr size_t RecordSize = AddressSize + 20; +constexpr size_t KeyOffset = AddressSize; +constexpr size_t LocalPortOffset = RecordSize - 4; +constexpr size_t FlagsOffset = RecordSize - 2; +constexpr size_t IdOffset = RecordSize - 1; +constexpr u16 DefaultKeyPort = 3658; // loopback port that also gets the default key + +P2P::KeyEntry ReadEntry(const u8* bytes) { + P2P::KeyEntry entry; + std::memcpy(entry.key.value.data(), bytes + KeyOffset, entry.key.value.size()); + std::memcpy(&entry.local_port, bytes + LocalPortOffset, sizeof(entry.local_port)); + entry.flags = bytes[FlagsOffset]; + entry.id = bytes[IdOffset]; + return entry; +} + +bool ReadPeer(const u8* bytes, P2P::Endpoint* peer) { + OrbisNetSockaddrIn in; + std::memcpy(&in, bytes, sizeof(in)); + if (in.sin_family != ORBIS_NET_AF_INET) { + return false; + } + sockaddr_in host{}; + host.sin_family = AF_INET; + host.sin_port = in.sin_port; + std::memcpy(&host.sin_addr, &in.sin_addr, sizeof(in.sin_addr)); + *peer = P2P::Endpoint::FromSockaddr(reinterpret_cast(&host), sizeof(host)); + return true; +} + +void WriteKey(u8* bytes, const std::optional& key) { + if (key) { + std::memcpy(bytes + KeyOffset, key->value.data(), key->value.size()); + } else { + std::memset(bytes + KeyOffset, 0, sizeof(P2P::P2PKey::value)); + } +} + +} // namespace + +int P2PKeyIoctl(P2P::Keyring& keyring, u16 np_port, u64 cmd, void* data) { + const u32 control = static_cast(cmd); + if ((control & 0xff00) != 0x5000) { + return ORBIS_NET_EINVAL; + } + if (control == 0x200050c8) { + LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: enable (no-op)", control); + return 0; + } + if ((control & 0xff) == 0xd9) { + LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: communication ID cleared", control); + keyring.SetCommunicationId(std::nullopt); + return 0; + } + if (data == nullptr) { + return ORBIS_NET_EFAULT; + } + auto* bytes = static_cast(data); + if (control == 0x802450c8 || control == 0x803450c8) { + LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: keys with id {} removed", control, bytes[IdOffset]); + keyring.RemoveKeysWithId(bytes[IdOffset]); + return 0; + } + // Never log key values. + const auto peer_text = [&] { + return FormatSockaddr(reinterpret_cast(bytes), AddressSize); + }; + + switch (control & 0xff) { + case 0xc9: { + const auto entry = ReadEntry(bytes); + LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: default key set (id {})", control, entry.id); + keyring.SetDefaultKey(entry.key); + keyring.AddPeerKey(P2P::Endpoint::IPv4("127.0.0.1", DefaultKeyPort), entry); + return 0; + } + case 0xca: + WriteKey(bytes, keyring.GetDefaultKey()); + return 0; + case 0xcb: { + P2P::Endpoint peer; + if (!ReadPeer(bytes, &peer)) { + return ORBIS_NET_EINVAL; + } + auto entry = ReadEntry(bytes); + if (entry.local_port == 0) { + entry.local_port = np_port; + } + LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: key for peer {} added (id {}, flags {:#x})", control, + peer_text(), entry.id, entry.flags); + keyring.AddPeerKey(peer, entry); + return 0; + } + case 0xcc: { + P2P::Endpoint peer; + if (!ReadPeer(bytes, &peer)) { + return ORBIS_NET_EINVAL; + } + const auto entry = ReadEntry(bytes); + const bool released = keyring.ReleasePeerKey(peer, entry.key, entry.id); + LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: key for peer {} (id {}) {}", control, peer_text(), + entry.id, released ? "released" : "not installed"); + return released ? 0 : ORBIS_NET_ENOENT; + } + case 0xcd: + LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: wildcard key set", control); + keyring.SetWildcardKey(ReadEntry(bytes).key); + return 0; + case 0xce: + WriteKey(bytes, keyring.GetWildcardKey()); + return 0; + case 0xcf: + case 0xd0: + std::memcpy(bytes, &np_port, sizeof(np_port)); + return 0; + case 0xd8: + LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: communication ID set", control); + keyring.SetCommunicationId(P2P::DeriveCommunicationId(std::span(bytes, 16))); + return 0; + default: + LOG_WARNING(Lib_Net, "P2P ioctl {:#x}: unknown command", control); + return ORBIS_NET_EINVAL; + } +} + +} // namespace Libraries::Net diff --git a/src/core/libraries/net/net_p2p_ioctl.h b/src/core/libraries/net/net_p2p_ioctl.h new file mode 100644 index 000000000..4df01f3ed --- /dev/null +++ b/src/core/libraries/net/net_p2p_ioctl.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Core::Net::P2P { +class Keyring; +} + +namespace Libraries::Net { + +// np_port is in network byte order. Returns 0 or a guest errno. +int P2PKeyIoctl(Core::Net::P2P::Keyring& keyring, u16 np_port, u64 cmd, void* data); + +} // namespace Libraries::Net diff --git a/src/core/libraries/net/net_resolver.cpp b/src/core/libraries/net/net_resolver.cpp new file mode 100644 index 000000000..4e0fb4d5f --- /dev/null +++ b/src/core/libraries/net/net_resolver.cpp @@ -0,0 +1,350 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/logging/log.h" +#include "core/libraries/net/net_error.h" +#include "core/libraries/net/net_log.h" +#include "core/libraries/net/net_resolver.h" +#include "core/net/guest_net.h" +#include "core/net/host_net.h" + +#ifndef _WIN32 +#include +#endif + +namespace Libraries::Net { + +namespace { + +using Core::Net::Host::Error; + +// Each kind has its own preserved abort. +enum class LookupKind { Ntoa = 0, Aton = 1 }; + +struct Resolver { + std::string name; + + std::mutex mutex; // guards everything below + bool running = false; + bool destroyed = false; + u64 lookup = 0; // bumped to orphan a running async lookup + s32 status = 0; + std::array pending_abort{}; // indexed by LookupKind +}; + +std::mutex g_mutex; +std::unordered_map> g_resolvers; +std::function g_is_online; + +std::shared_ptr Find(s32 id) { + std::scoped_lock lock{g_mutex}; + const auto it = g_resolvers.find(id); + return it != g_resolvers.end() ? it->second : nullptr; +} + +bool IsOnline() { + std::function check; + { + std::scoped_lock lock{g_mutex}; + check = g_is_online; + } + return !check || check(); +} + +// IPv4 only, at most 10 records. +s32 Lookup(std::string hostname, bool allow_literal, std::vector* addrs) { + if (!hostname.empty() && hostname.back() == '.') { + hostname.pop_back(); + } + if (hostname.compare(0, 9, "localhost") == 0) { + addrs->push_back(htonl(INADDR_LOOPBACK)); + return 0; + } + if (allow_literal) { + in_addr literal{}; + if (inet_pton(AF_INET, hostname.c_str(), &literal) == 1) { + addrs->push_back(literal.s_addr); + return 0; + } + } + if (!IsOnline()) { + return ORBIS_NET_ERROR_RESOLVER_ENODNS; + } + addrinfo hints{}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; // one entry per address + addrinfo* result = nullptr; + const int gai = getaddrinfo(hostname.c_str(), nullptr, &hints, &result); + if (gai != 0) { + LOG_WARNING(Lib_Net, "lookup of {} failed: {}", hostname, gai); + switch (gai) { + case EAI_NONAME: +#if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME + case EAI_NODATA: +#endif + return ORBIS_NET_ERROR_RESOLVER_ENOHOST; + case EAI_AGAIN: + return ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT; + case EAI_FAIL: + return ORBIS_NET_ERROR_RESOLVER_ESERVERFAILURE; + default: + return ORBIS_NET_ERROR_RESOLVER_EINTERNAL; + } + } + for (const addrinfo* ai = result; ai != nullptr && addrs->size() < 10; ai = ai->ai_next) { + if (ai->ai_family != AF_INET || ai->ai_addr == nullptr) { + continue; + } + const u32 addr = reinterpret_cast(ai->ai_addr)->sin_addr.s_addr; + if (std::find(addrs->begin(), addrs->end(), addr) == addrs->end()) { + addrs->push_back(addr); + } + } + freeaddrinfo(result); + return addrs->empty() ? ORBIS_NET_ERROR_RESOLVER_ENORECORD : 0; +} + +void Deliver(const ResolverOutput& output, const std::vector& addrs) { + if (output.addr != nullptr) { + output.addr->inaddr_addr = addrs.front(); + } + if (output.info != nullptr) { + std::memset(output.info, 0, sizeof(*output.info)); + for (size_t i = 0; i < addrs.size(); ++i) { + output.info->addrs[i].u.addr.inaddr_addr = addrs[i]; + output.info->addrs[i].af = ORBIS_NET_AF_INET; + } + output.info->records = static_cast(addrs.size()); + output.info->recordsv4 = static_cast(addrs.size()); + } +} + +} // namespace + +s32 CreateResolver(std::string name) { + const auto r = Core::Net::ReserveExternalId(); + if (r.error != Error::Ok) { + return -static_cast(r.error); + } + const auto id = static_cast(r.value); + auto resolver = std::make_shared(); + resolver->name = std::move(name); + std::scoped_lock lock{g_mutex}; + g_resolvers[id] = std::move(resolver); + return id; +} + +int DestroyResolver(s32 id) { + std::shared_ptr resolver; + { + std::scoped_lock lock{g_mutex}; + const auto it = g_resolvers.find(id); + if (it == g_resolvers.end()) { + return ORBIS_NET_EBADF; + } + resolver = std::move(it->second); + g_resolvers.erase(it); + } + { + std::scoped_lock lock{resolver->mutex}; + resolver->destroyed = true; // keeps a running lookup from writing guest memory + } + Core::Net::ReleaseExternalId(id); + return 0; +} + +int AbortResolver(s32 id, u32 flags) { + const auto resolver = Find(id); + if (!resolver) { + return ORBIS_NET_EBADF; + } + std::scoped_lock lock{resolver->mutex}; + if (resolver->running) { + ++resolver->lookup; // orphan the running thread + resolver->running = false; + resolver->status = ORBIS_NET_ERROR_EINTR; + Core::Net::SignalExternal(id, true); + LOG_DEBUG(Lib_Net, "resolver {}: running lookup aborted", id); + return 0; + } + LOG_DEBUG(Lib_Net, "resolver {}: abort with nothing running, flags {:#x}", id, flags); + if (flags & ORBIS_NET_RESOLVER_ABORT_FLAG_NTOA_PRESERVATION) { + resolver->pending_abort[static_cast(LookupKind::Ntoa)] = true; + } + if (flags & ORBIS_NET_RESOLVER_ABORT_FLAG_ATON_PRESERVATION) { + resolver->pending_abort[static_cast(LookupKind::Aton)] = true; + } + return 0; +} + +namespace { + +template +s32 RunLookup(s32 id, LookupKind kind, bool async, Work work, Deliver deliver) { + const auto resolver = Find(id); + if (!resolver) { + return ORBIS_NET_ERROR_EBADF; + } + u64 lookup = 0; + { + std::scoped_lock lock{resolver->mutex}; + if (resolver->running) { + return ORBIS_NET_ERROR_RESOLVER_EBUSY; + } + if (std::exchange(resolver->pending_abort[static_cast(kind)], false)) { + LOG_DEBUG(Lib_Net, "resolver {}: lookup fails with a preserved abort", id); + resolver->status = ORBIS_NET_ERROR_EINTR; + return ORBIS_NET_ERROR_EINTR; + } + if (async) { + resolver->running = true; + resolver->status = 0; + lookup = ++resolver->lookup; + } + } + if (!async) { + auto [status, result] = work(); + std::scoped_lock lock{resolver->mutex}; + if (status == 0) { + status = deliver(result); + } + resolver->status = status; + return status; + } + std::thread([resolver, id, lookup, work = std::move(work), deliver = std::move(deliver)] { + auto [status, result] = work(); + std::scoped_lock lock{resolver->mutex}; + if (resolver->destroyed || resolver->lookup != lookup) { + return; // stale, the id may have been reused + } + if (status == 0) { + status = deliver(result); + } + resolver->status = status; + resolver->running = false; + Core::Net::SignalExternal(id); + }).detach(); + return 0; +} + +s32 ReverseLookup(u32 addr, std::string* name) { + if (addr == htonl(INADDR_LOOPBACK)) { + *name = "localhost"; + return 0; + } + if (!IsOnline()) { + return ORBIS_NET_ERROR_RESOLVER_ENODNS; + } + sockaddr_in in{}; + in.sin_family = AF_INET; + in.sin_addr.s_addr = addr; + char host[1025]{}; // NI_MAXHOST + const int gai = getnameinfo(reinterpret_cast(&in), sizeof(in), host, + sizeof(host), nullptr, 0, NI_NAMEREQD); + switch (gai) { + case 0: + *name = host; + return 0; + case EAI_NONAME: + return ORBIS_NET_ERROR_RESOLVER_ENOHOST; + case EAI_AGAIN: + return ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT; + case EAI_FAIL: + return ORBIS_NET_ERROR_RESOLVER_ESERVERFAILURE; + default: + return ORBIS_NET_ERROR_RESOLVER_EINTERNAL; + } +} + +} // namespace + +s32 ResolverStartNtoa(s32 id, const char* hostname, ResolverOutput output, bool async, + bool disable_ipaddress) { + if (!Find(id)) { + return ORBIS_NET_ERROR_EBADF; + } + if (hostname == nullptr || (output.addr == nullptr && output.info == nullptr)) { + return ORBIS_NET_ERROR_EINVAL; + } + const size_t length = strnlen(hostname, ORBIS_NET_RESOLVER_HOSTNAME_LEN_MAX + 1); + if (length == 0 || length > static_cast(ORBIS_NET_RESOLVER_HOSTNAME_LEN_MAX)) { + return ORBIS_NET_ERROR_EINVAL; + } + return RunLookup( + id, LookupKind::Ntoa, async, + [host = std::string{hostname}, allow_literal = !disable_ipaddress] { + std::vector addrs; + const s32 status = Lookup(host, allow_literal, &addrs); + if (status == 0) { + std::string list; + for (const u32 a : addrs) { + const u32 ip = ntohl(a); + list += fmt::format("{}{}.{}.{}.{}", list.empty() ? "" : ", ", ip >> 24, + (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); + } + LOG_INFO(Lib_Net, "{} resolved to {}", host, list); + } else { + LOG_INFO(Lib_Net, "{} not resolved: {}", host, ErrorCodeName(status)); + } + return std::pair{status, std::move(addrs)}; + }, + [output](const std::vector& addrs) { + Deliver(output, addrs); + return s32{0}; + }); +} + +s32 ResolverStartAton(s32 id, const OrbisNetInAddr* addr, char* hostname, s32 len, bool async) { + if (!Find(id)) { + return ORBIS_NET_ERROR_EBADF; + } + if (addr == nullptr || hostname == nullptr || len <= 0) { + return ORBIS_NET_ERROR_EINVAL; + } + return RunLookup( + id, LookupKind::Aton, async, + [address = addr->inaddr_addr] { + std::string name; + const s32 status = ReverseLookup(address, &name); + const u32 ip = ntohl(address); + LOG_INFO(Lib_Net, "reverse lookup of {}.{}.{}.{}: {}", ip >> 24, (ip >> 16) & 0xff, + (ip >> 8) & 0xff, ip & 0xff, status == 0 ? name : ErrorCodeName(status)); + return std::pair{status, std::move(name)}; + }, + [hostname, len](const std::string& name) { + if (name.size() + 1 > static_cast(len)) { + return s32{ORBIS_NET_ERROR_RESOLVER_ENOSPACE}; + } + std::memcpy(hostname, name.c_str(), name.size() + 1); + return s32{0}; + }); +} + +int ResolverGetError(s32 id, s32* status) { + const auto resolver = Find(id); + if (!resolver) { + return ORBIS_NET_EBADF; + } + std::scoped_lock lock{resolver->mutex}; + *status = resolver->status; + return 0; +} + +void SetResolverOnlineCheck(std::function is_online) { + std::scoped_lock lock{g_mutex}; + g_is_online = std::move(is_online); +} + +} // namespace Libraries::Net diff --git a/src/core/libraries/net/net_resolver.h b/src/core/libraries/net/net_resolver.h new file mode 100644 index 000000000..d91aaf700 --- /dev/null +++ b/src/core/libraries/net/net_resolver.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/types.h" +#include "core/libraries/net/net_types.h" + +namespace Libraries::Net { + +s32 CreateResolver(std::string name); +int DestroyResolver(s32 id); +int AbortResolver(s32 id, u32 flags); +struct ResolverOutput { + OrbisNetInAddr* addr = nullptr; + OrbisNetResolverInfo* info = nullptr; +}; +s32 ResolverStartNtoa(s32 id, const char* hostname, ResolverOutput output, bool async, + bool disable_ipaddress); +s32 ResolverStartAton(s32 id, const OrbisNetInAddr* addr, char* hostname, s32 len, bool async); +int ResolverGetError(s32 id, s32* status); +void SetResolverOnlineCheck(std::function is_online); + +} // namespace Libraries::Net diff --git a/src/core/libraries/net/net_translate.cpp b/src/core/libraries/net/net_translate.cpp new file mode 100644 index 000000000..6f426e15a --- /dev/null +++ b/src/core/libraries/net/net_translate.cpp @@ -0,0 +1,392 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "core/libraries/net/net_error.h" +#include "core/libraries/net/net_translate.h" + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +namespace Libraries::Net { + +std::optional ToHostFamily(int orbis_family) { + switch (orbis_family) { + case ORBIS_NET_AF_UNIX: + return AF_UNIX; + case ORBIS_NET_AF_INET: + return AF_INET; + case ORBIS_NET_AF_INET6: + return AF_INET6; + default: + return std::nullopt; + } +} + +int ToOrbisFamily(int host_family) { + switch (host_family) { + case AF_UNIX: + return ORBIS_NET_AF_UNIX; + case AF_INET: + return ORBIS_NET_AF_INET; + case AF_INET6: + return ORBIS_NET_AF_INET6; + default: + return 0; + } +} + +std::optional ToHostSocketKind(int family, int type, int protocol, + int* orbis_errno) { + if (family == ORBIS_NET_AF_UNIX) { + if (type != ORBIS_NET_SOCK_STREAM && type != ORBIS_NET_SOCK_DGRAM) { + *orbis_errno = ORBIS_NET_EPROTONOSUPPORT; + return std::nullopt; + } + return HostSocketKind{AF_UNIX, type == ORBIS_NET_SOCK_STREAM ? SOCK_STREAM : SOCK_DGRAM, 0, + false}; + } + // PS4 returns EPROTONOSUPPORT here, not EAFNOSUPPORT. + if (family != ORBIS_NET_AF_INET) { + *orbis_errno = ORBIS_NET_EPROTONOSUPPORT; + return std::nullopt; + } + const auto host_family = ToHostFamily(family); + HostSocketKind kind{*host_family, 0, protocol, false}; + switch (type) { + case ORBIS_NET_SOCK_STREAM: + kind.type = SOCK_STREAM; + break; + case ORBIS_NET_SOCK_DGRAM: + kind.type = SOCK_DGRAM; + break; + case ORBIS_NET_SOCK_RAW: + kind.type = SOCK_RAW; + break; + case ORBIS_NET_SOCK_DGRAM_P2P: + kind.type = SOCK_DGRAM; + kind.p2p = true; + break; + case ORBIS_NET_SOCK_STREAM_P2P: + kind.type = SOCK_STREAM; + kind.p2p = true; + break; + default: + *orbis_errno = ORBIS_NET_EPROTONOSUPPORT; + return std::nullopt; + } + if (type != ORBIS_NET_SOCK_RAW && protocol != 0) { + const int natural = kind.p2p ? -1 + : kind.type == SOCK_STREAM ? ORBIS_NET_IPPROTO_TCP + : ORBIS_NET_IPPROTO_UDP; + if (protocol != natural) { + *orbis_errno = ORBIS_NET_EPROTOTYPE; + return std::nullopt; + } + } + return kind; +} + +int ToOrbisSocketType(int host_type, bool p2p) { + if (host_type == SOCK_STREAM) { + return p2p ? ORBIS_NET_SOCK_STREAM_P2P : ORBIS_NET_SOCK_STREAM; + } + if (host_type == SOCK_DGRAM) { + return p2p ? ORBIS_NET_SOCK_DGRAM_P2P : ORBIS_NET_SOCK_DGRAM; + } + return ORBIS_NET_SOCK_RAW; +} + +int ToHostSockaddr(const OrbisNetSockaddr* addr, u32 addrlen, sockaddr_storage* out, + socklen_t* out_len) { + if (addr == nullptr) { + return ORBIS_NET_EFAULT; + } + if (addrlen < 2) { + return ORBIS_NET_EINVAL; + } + *out = {}; + switch (addr->sa_family) { + case ORBIS_NET_AF_INET: { + if (addrlen < sizeof(OrbisNetSockaddrIn)) { + return ORBIS_NET_EINVAL; + } + OrbisNetSockaddrIn in; + std::memcpy(&in, addr, sizeof(in)); // may be unaligned + auto* host = reinterpret_cast(out); + host->sin_family = AF_INET; + host->sin_port = in.sin_port; + std::memcpy(&host->sin_addr, &in.sin_addr, sizeof(in.sin_addr)); + *out_len = sizeof(sockaddr_in); + return 0; + } + case ORBIS_NET_AF_INET6: { + if (addrlen < sizeof(OrbisNetSockaddrIn6)) { + return ORBIS_NET_EINVAL; + } + OrbisNetSockaddrIn6 in6; + std::memcpy(&in6, addr, sizeof(in6)); + auto* host = reinterpret_cast(out); + host->sin6_family = AF_INET6; + host->sin6_port = in6.sin6_port; + host->sin6_flowinfo = in6.sin6_flowinfo; + std::memcpy(&host->sin6_addr, in6.sin6_addr, sizeof(in6.sin6_addr)); + host->sin6_scope_id = in6.sin6_scope_id; + *out_len = sizeof(sockaddr_in6); + return 0; + } + case ORBIS_NET_AF_UNIX: { + const u32 size = std::min(addrlen, sizeof(OrbisNetSockaddrUn)); + const char* path = reinterpret_cast(addr) + 2; + const size_t path_len = strnlen(path, size - 2); + auto* host = reinterpret_cast(out); + if (path_len >= sizeof(host->sun_path)) { + return ORBIS_NET_ENAMETOOLONG; + } + host->sun_family = AF_UNIX; + std::memcpy(host->sun_path, path, path_len); + host->sun_path[path_len] = '\0'; + *out_len = static_cast(offsetof(sockaddr_un, sun_path) + path_len + 1); + return 0; + } + default: + return ORBIS_NET_EAFNOSUPPORT; + } +} + +namespace { + +void CopyOut(const void* full, u32 full_len, OrbisNetSockaddr* out, u32* addrlen) { + if (out != nullptr) { + std::memcpy(out, full, std::min(*addrlen, full_len)); + } + *addrlen = full_len; +} + +} // namespace + +void ToOrbisSockaddr(const sockaddr* addr, socklen_t len, OrbisNetSockaddr* out, u32* addrlen) { + if (addr->sa_family == AF_INET && len >= static_cast(sizeof(sockaddr_in))) { + const auto* host = reinterpret_cast(addr); + OrbisNetSockaddrIn in{}; + in.sin_len = sizeof(in); + in.sin_family = ORBIS_NET_AF_INET; + in.sin_port = host->sin_port; + std::memcpy(&in.sin_addr, &host->sin_addr, sizeof(in.sin_addr)); + CopyOut(&in, sizeof(in), out, addrlen); + } else if (addr->sa_family == AF_INET6 && len >= static_cast(sizeof(sockaddr_in6))) { + const auto* host = reinterpret_cast(addr); + OrbisNetSockaddrIn6 in6{}; + in6.sin6_len = sizeof(in6); + in6.sin6_family = ORBIS_NET_AF_INET6; + in6.sin6_port = host->sin6_port; + in6.sin6_flowinfo = host->sin6_flowinfo; + std::memcpy(in6.sin6_addr, &host->sin6_addr, sizeof(in6.sin6_addr)); + in6.sin6_scope_id = host->sin6_scope_id; + CopyOut(&in6, sizeof(in6), out, addrlen); + } else if (addr->sa_family == AF_UNIX) { + // Unnamed sockets get an empty path. + const auto* host = reinterpret_cast(addr); + OrbisNetSockaddrUn un{}; + un.sun_len = sizeof(un); + un.sun_family = ORBIS_NET_AF_UNIX; + const auto offset = static_cast(offsetof(sockaddr_un, sun_path)); + if (len > offset) { + const size_t max = std::min(len - offset, sizeof(un.sun_path) - 1); + std::memcpy(un.sun_path, host->sun_path, strnlen(host->sun_path, max)); + } + CopyOut(&un, sizeof(un), out, addrlen); + } else { + *addrlen = 0; + } +} + +int ToP2PAddress(const OrbisNetSockaddr* addr, u32 addrlen, P2PKind kind, P2P::Endpoint* endpoint, + u16* port) { + if (addr == nullptr) { + return ORBIS_NET_EFAULT; + } + if (addrlen != sizeof(OrbisNetSockaddrIn)) { + return ORBIS_NET_EINVAL; + } + if (addr->sa_family != ORBIS_NET_AF_INET) { + return ORBIS_NET_EAFNOSUPPORT; + } + OrbisNetSockaddrIn in; + std::memcpy(&in, addr, sizeof(in)); + sockaddr_in host{}; + host.sin_family = AF_INET; + std::memcpy(&host.sin_addr, &in.sin_addr, sizeof(in.sin_addr)); + if (kind == P2PKind::Datagram) { + host.sin_port = in.sin_port; + *port = ntohs(in.sin_vport); + } else { + host.sin_port = in.sin_vport != 0 ? in.sin_vport : htons(DefaultP2PUdpPort); + *port = ntohs(in.sin_port); + } + *endpoint = P2P::Endpoint::FromSockaddr(reinterpret_cast(&host), sizeof(host)); + return 0; +} + +void ToOrbisP2PAddress(const P2P::Endpoint& endpoint, u16 port, P2PKind kind, OrbisNetSockaddr* out, + u32* addrlen) { + OrbisNetSockaddrIn in{}; + in.sin_len = sizeof(in); + in.sin_family = ORBIS_NET_AF_INET; + u16 udp_port = 0; // network byte order + if (endpoint.Family() == AF_INET) { + const auto* host = reinterpret_cast(&endpoint.addr); + udp_port = host->sin_port; + std::memcpy(&in.sin_addr, &host->sin_addr, sizeof(in.sin_addr)); + } + if (kind == P2PKind::Datagram) { + in.sin_port = udp_port; + in.sin_vport = htons(port); + } else { + in.sin_port = htons(port); + in.sin_vport = udp_port; + } + CopyOut(&in, sizeof(in), out, addrlen); +} + +HostMsgFlags ToHostMsgFlags(int orbis_flags) { + constexpr int kNoSignal = 0x20000; // FreeBSD MSG_NOSIGNAL, always implied + HostMsgFlags out{}; + int rest = orbis_flags; + if ((rest & ORBIS_NET_MSG_PEEKLEN) == ORBIS_NET_MSG_PEEKLEN) { + out.peeklen = true; + rest &= ~(ORBIS_NET_MSG_PEEKLEN & ~ORBIS_NET_MSG_PEEK); + } + if (rest & ORBIS_NET_MSG_PEEK) { + out.host |= MSG_PEEK; + out.peek = true; + rest &= ~ORBIS_NET_MSG_PEEK; + } + if (rest & ORBIS_NET_MSG_WAITALL) { + out.host |= MSG_WAITALL; // host socket is non-blocking, we loop + out.waitall = true; + rest &= ~ORBIS_NET_MSG_WAITALL; + } + if (rest & ORBIS_NET_MSG_DONTWAIT) { + out.dontwait = true; + rest &= ~ORBIS_NET_MSG_DONTWAIT; + } + if (rest & ORBIS_NET_MSG_USECRYPTO) { + out.crypto = true; + rest &= ~ORBIS_NET_MSG_USECRYPTO; + } + if (rest & ORBIS_NET_MSG_USESIGNATURE) { + out.signature = true; + rest &= ~ORBIS_NET_MSG_USESIGNATURE; + } + rest &= ~kNoSignal; + out.unsupported = rest; // e.g. MSG_OOB + return out; +} + +std::optional ToHostOption(int level, int name) { + switch (level) { + case ORBIS_NET_SOL_SOCKET: + switch (name) { + case ORBIS_NET_SO_KEEPALIVE: + return HostOption{SOL_SOCKET, SO_KEEPALIVE, OptionValue::Int}; + case ORBIS_NET_SO_BROADCAST: + return HostOption{SOL_SOCKET, SO_BROADCAST, OptionValue::Int}; + case ORBIS_NET_SO_SNDBUF: + return HostOption{SOL_SOCKET, SO_SNDBUF, OptionValue::Int}; + case ORBIS_NET_SO_RCVBUF: + return HostOption{SOL_SOCKET, SO_RCVBUF, OptionValue::Int}; + case ORBIS_NET_SO_LINGER: + return HostOption{SOL_SOCKET, Core::Net::Host::LingerOption, OptionValue::Linger}; + default: + return std::nullopt; + } + case ORBIS_NET_IPPROTO_IP: + // Values differ per host (IP_TTL is 2 on Linux), map by name. + switch (name) { + case ORBIS_NET_IP_HDRINCL: + return HostOption{IPPROTO_IP, IP_HDRINCL, OptionValue::Int}; + case ORBIS_NET_IP_TOS: + return HostOption{IPPROTO_IP, IP_TOS, OptionValue::Int}; + case ORBIS_NET_IP_TTL: + return HostOption{IPPROTO_IP, IP_TTL, OptionValue::Int}; + case ORBIS_NET_IP_MULTICAST_IF: + return HostOption{IPPROTO_IP, IP_MULTICAST_IF, OptionValue::Raw}; + case ORBIS_NET_IP_MULTICAST_TTL: + return HostOption{IPPROTO_IP, IP_MULTICAST_TTL, OptionValue::Int}; + case ORBIS_NET_IP_MULTICAST_LOOP: + return HostOption{IPPROTO_IP, IP_MULTICAST_LOOP, OptionValue::Int}; + case ORBIS_NET_IP_ADD_MEMBERSHIP: + return HostOption{IPPROTO_IP, IP_ADD_MEMBERSHIP, OptionValue::Raw}; + case ORBIS_NET_IP_DROP_MEMBERSHIP: + return HostOption{IPPROTO_IP, IP_DROP_MEMBERSHIP, OptionValue::Raw}; + default: + return std::nullopt; + } + case ORBIS_NET_IPPROTO_TCP: + switch (name) { + case ORBIS_NET_TCP_NODELAY: + return HostOption{IPPROTO_TCP, TCP_NODELAY, OptionValue::Int}; +#ifdef TCP_MAXSEG + case ORBIS_NET_TCP_MAXSEG: + return HostOption{IPPROTO_TCP, TCP_MAXSEG, OptionValue::Int}; +#endif + default: + return std::nullopt; + } + default: + return std::nullopt; + } +} + +bool IsReservedPort(u16 port) { + return (port >= 1 && port <= 1023) || port == 5353 || (port >= 8540 && port <= 8579) || + (port >= 9293 && port <= 9310) || port >= 40000; +} + +bool IsReservedP2PVport(u16 vport) { + return vport == 5353 || vport >= 32768; +} + +s32 DefaultSendBuffer(int host_type) { + return host_type == SOCK_STREAM ? 32768 : 9216; +} + +s32 DefaultReceiveBuffer(int host_type) { + return host_type == SOCK_STREAM ? 65536 : 40 * 1024; +} + +u32 ToHostEpollEvents(u32 orbis_events) { + u32 out = 0; + out |= (orbis_events & ORBIS_NET_EPOLLIN) ? Host::EvIn : 0u; + out |= (orbis_events & ORBIS_NET_EPOLLOUT) ? Host::EvOut : 0u; + out |= (orbis_events & ORBIS_NET_EPOLLERR) ? Host::EvErr : 0u; + out |= (orbis_events & ORBIS_NET_EPOLLHUP) ? Host::EvHup : 0u; + return out; +} + +u32 ToOrbisEpollEvents(u32 host_events) { + u32 out = 0; + out |= (host_events & Host::EvIn) ? ORBIS_NET_EPOLLIN : 0u; + out |= (host_events & Host::EvOut) ? ORBIS_NET_EPOLLOUT : 0u; + out |= (host_events & Host::EvErr) ? ORBIS_NET_EPOLLERR : 0u; + out |= (host_events & Host::EvHup) ? ORBIS_NET_EPOLLHUP : 0u; + out |= (host_events & Host::EvDescId) ? ORBIS_NET_EPOLLDESCID : 0u; + return out; +} + +int ToOrbisErrno(Host::Error error) { + if (error == Host::Error::NotSock) { + return ORBIS_NET_EBADF; + } + return static_cast(error); // already FreeBSD errno values +} + +} // namespace Libraries::Net diff --git a/src/core/libraries/net/net_translate.h b/src/core/libraries/net/net_translate.h new file mode 100644 index 000000000..91e45c30f --- /dev/null +++ b/src/core/libraries/net/net_translate.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "core/libraries/net/net_types.h" +#include "core/net/host_net.h" +#include "core/net/p2p_transport.h" + +namespace Libraries::Net { + +namespace Host = Core::Net::Host; +namespace P2P = Core::Net::P2P; + +std::optional ToHostFamily(int orbis_family); +int ToOrbisFamily(int host_family); + +struct HostSocketKind { + int family; + int type; + int protocol; + bool p2p; +}; + +std::optional ToHostSocketKind(int family, int type, int protocol, + int* orbis_errno); +int ToOrbisSocketType(int host_type, bool p2p); +int ToHostSockaddr(const OrbisNetSockaddr* addr, u32 addrlen, sockaddr_storage* out, + socklen_t* out_len); +void ToOrbisSockaddr(const sockaddr* addr, socklen_t len, OrbisNetSockaddr* out, u32* addrlen); +enum class P2PKind { Datagram, Stream }; +inline constexpr u16 DefaultP2PUdpPort = 3658; +int ToP2PAddress(const OrbisNetSockaddr* addr, u32 addrlen, P2PKind kind, P2P::Endpoint* endpoint, + u16* port); +void ToOrbisP2PAddress(const P2P::Endpoint& endpoint, u16 port, P2PKind kind, OrbisNetSockaddr* out, + u32* addrlen); + +struct HostMsgFlags { + int host = 0; + bool dontwait = false; + bool peek = false; + bool peeklen = false; + bool waitall = false; + bool crypto = false; + bool signature = false; + int unsupported = 0; +}; +HostMsgFlags ToHostMsgFlags(int orbis_flags); + +enum class OptionValue { + Int, + Linger, + Raw, +}; + +struct HostOption { + int level; + int name; + OptionValue value; +}; + +std::optional ToHostOption(int level, int name); +bool IsReservedPort(u16 port); +bool IsReservedP2PVport(u16 vport); +s32 DefaultSendBuffer(int host_type); // TCP 32768, UDP and RAW 9216 +s32 DefaultReceiveBuffer(int host_type); // TCP 65536, UDP and RAW 40960 +u32 ToHostEpollEvents(u32 orbis_events); +u32 ToOrbisEpollEvents(u32 host_events); +int ToOrbisErrno(Host::Error error); + +} // namespace Libraries::Net diff --git a/src/core/libraries/net/net_types.h b/src/core/libraries/net/net_types.h new file mode 100644 index 000000000..56127942d --- /dev/null +++ b/src/core/libraries/net/net_types.h @@ -0,0 +1,337 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Libraries::Net { + +using OrbisNetId = s32; + +enum OrbisNetFamily : u32 { + ORBIS_NET_AF_UNIX = 1, + ORBIS_NET_AF_INET = 2, + ORBIS_NET_AF_INET6 = 28, +}; + +enum OrbisNetSocketType : u32 { + ORBIS_NET_SOCK_STREAM = 1, + ORBIS_NET_SOCK_DGRAM = 2, + ORBIS_NET_SOCK_RAW = 3, + ORBIS_NET_SOCK_DGRAM_P2P = 6, + ORBIS_NET_SOCK_STREAM_P2P = 10, +}; + +enum OrbisNetProtocol : u32 { + ORBIS_NET_IPPROTO_IP = 0, + ORBIS_NET_IPPROTO_ICMP = 1, + ORBIS_NET_IPPROTO_IGMP = 2, + ORBIS_NET_IPPROTO_TCP = 6, + ORBIS_NET_IPPROTO_UDP = 17, + ORBIS_NET_IPPROTO_IPV6 = 41, + ORBIS_NET_SOL_SOCKET = 0xFFFF, +}; + +enum OrbisNetSocketIpOption : u32 { + ORBIS_NET_IP_HDRINCL = 2, + ORBIS_NET_IP_TOS = 3, + ORBIS_NET_IP_TTL = 4, + ORBIS_NET_IP_MULTICAST_IF = 9, + ORBIS_NET_IP_MULTICAST_TTL = 10, + ORBIS_NET_IP_MULTICAST_LOOP = 11, + ORBIS_NET_IP_ADD_MEMBERSHIP = 12, + ORBIS_NET_IP_DROP_MEMBERSHIP = 13, + ORBIS_NET_IP_TTLCHK = 30, + ORBIS_NET_IP_MAXTTL = 31, + ORBIS_NET_IP_DONTFRAG = 67, +}; + +enum OrbisNetSocketTcpOption : u32 { + ORBIS_NET_TCP_NODELAY = 1, + ORBIS_NET_TCP_MAXSEG = 2, + ORBIS_NET_TCP_MSS_TO_ADVERTISE = 3, + ORBIS_NET_TCP_KEEPLISTEN = 128, +}; + +enum OrbisNetSocketUdpOption : u32 { + ORBIS_NET_UDP_SND_ON_SUSPEND = 128, +}; + +// SO_POLICY / SO_PRIORITY ranges +inline constexpr s32 ORBIS_NET_SOCK_POLICY_NUM_MIN = 0; +inline constexpr s32 ORBIS_NET_SOCK_POLICY_NUM_MAX = 15; +inline constexpr s32 ORBIS_NET_SOCK_POLICY_NA = -1; // not applicable +inline constexpr s32 ORBIS_NET_SOCK_PRIORITY_NUM_USER_MIN = 8; +inline constexpr s32 ORBIS_NET_SOCK_PRIORITY_NUM_USER_MAX = 23; + +// sceNetSocketAbort flags +inline constexpr u32 ORBIS_NET_SOCKET_ABORT_FLAG_RCV_PRESERVATION = 0x1; +inline constexpr u32 ORBIS_NET_SOCKET_ABORT_FLAG_SND_PRESERVATION = 0x2; +inline constexpr u32 ORBIS_NET_SOCKET_ABORT_FLAG_SND_PRESERVATION_AGAIN = 0x4; + +inline constexpr u16 ORBIS_NET_ADHOC_PORT = 3658; + +enum OrbisNetSocketSoOption : u32 { + ORBIS_NET_SO_REUSEADDR = 0x00000004, + ORBIS_NET_SO_KEEPALIVE = 0x00000008, + ORBIS_NET_SO_BROADCAST = 0x00000020, + ORBIS_NET_SO_LINGER = 0x00000080, + ORBIS_NET_SO_REUSEPORT = 0x00000200, + ORBIS_NET_SO_ONESBCAST = 0x00010000, + ORBIS_NET_SO_USECRYPTO = 0x00020000, + ORBIS_NET_SO_USESIGNATURE = 0x00040000, + ORBIS_NET_SO_SNDBUF = 0x1001, + ORBIS_NET_SO_RCVBUF = 0x1002, + ORBIS_NET_SO_ERROR = 0x1007, + ORBIS_NET_SO_TYPE = 0x1008, + ORBIS_NET_SO_SNDTIMEO = 0x1105, + ORBIS_NET_SO_RCVTIMEO = 0x1106, + ORBIS_NET_SO_ERROR_EX = 0x1107, + ORBIS_NET_SO_ACCEPTTIMEO = 0x1108, + ORBIS_NET_SO_CONNECTTIMEO = 0x1109, + ORBIS_NET_SO_NBIO = 0x1200, + ORBIS_NET_SO_POLICY = 0x1201, + ORBIS_NET_SO_NAME = 0x1202, + ORBIS_NET_SO_PRIORITY = 0x1203, +}; + +enum OrbisNetFlags : u32 { + ORBIS_NET_MSG_PEEK = 0x00000002, + ORBIS_NET_MSG_WAITALL = 0x00000040, + ORBIS_NET_MSG_DONTWAIT = 0x00000080, + ORBIS_NET_MSG_USECRYPTO = 0x00100000, + ORBIS_NET_MSG_USESIGNATURE = 0x00200000, + ORBIS_NET_MSG_PEEKLEN = (0x00400000 | ORBIS_NET_MSG_PEEK), +}; + +enum OrbisNetShutdown : s32 { + ORBIS_NET_SHUT_RD = 0, + ORBIS_NET_SHUT_WR = 1, + ORBIS_NET_SHUT_RDWR = 2, +}; + +enum OrbisNetEpollFlag : u32 { + ORBIS_NET_EPOLL_CTL_ADD = 1, + ORBIS_NET_EPOLL_CTL_MOD = 2, + ORBIS_NET_EPOLL_CTL_DEL = 3, +}; + +enum OrbisNetEpollEvents : u32 { + ORBIS_NET_EPOLLIN = 0x1, + ORBIS_NET_EPOLLOUT = 0x2, + ORBIS_NET_EPOLLERR = 0x8, + ORBIS_NET_EPOLLHUP = 0x10, + ORBIS_NET_EPOLLDESCID = 0x10000, +}; + +struct OrbisNetSockaddr { + u8 sa_len; + u8 sa_family; + char sa_data[14]; +}; + +struct OrbisNetSockaddrIn { + u8 sin_len; + u8 sin_family; + u16 sin_port; // network byte order + u32 sin_addr; // network byte order + u16 sin_vport; // P2P virtual port, network byte order + char sin_zero[6]; +}; + +struct OrbisNetSockaddrIn6 { + u8 sin6_len; + u8 sin6_family; + u16 sin6_port; // network byte order + u32 sin6_flowinfo; + u8 sin6_addr[16]; + u32 sin6_scope_id; +}; + +struct OrbisNetLinger { + s32 l_onoff; + s32 l_linger; +}; + +union OrbisNetEpollData { + void* ptr; + u32 data_u32; + s32 fd; + u64 data_u64; +}; + +struct OrbisNetEpollEvent { + u32 events; + u32 pad; + u64 ident; + OrbisNetEpollData data; +}; + +struct OrbisNetInAddr { + u32 inaddr_addr; // network byte order +}; +// Host byte order +inline constexpr u32 ORBIS_NET_INADDR_ANY = 0x00000000; +inline constexpr u32 ORBIS_NET_INADDR_LOOPBACK = 0x7f000001; +inline constexpr u32 ORBIS_NET_INADDR_BROADCAST = 0xffffffff; +inline constexpr u32 ORBIS_NET_INADDR_UNSPEC_GROUP = 0xe0000000; +inline constexpr u32 ORBIS_NET_INADDR_AUTOIP = 0xa9fe0000; +inline constexpr u32 ORBIS_NET_IN_CLASSD_NET = 0xf0000000; +inline constexpr u32 ORBIS_NET_IN_AUTOIP_NET = 0xffff0000; +inline constexpr int ORBIS_NET_INET_ADDRSTRLEN = 16; +inline constexpr int ORBIS_NET_ETHER_ADDRSTRLEN = 18; + +// Option value for ORBIS_NET_UDP_SND_ON_SUSPEND. +struct OrbisNetUdpSndOnSuspend { + s32 onoff; + struct OrbisNetSockaddr* addr; + u32 addrlen; + void* data; + u32 datalen; +}; +inline constexpr u32 ORBIS_NET_UDP_SND_ON_SUSPEND_DATALEN_MAX = 512; + +struct OrbisNetMemoryPoolStats { + u64 pool_size; + u64 max_inuse_size; + u64 current_inuse_size; + s32 reserved; +}; + +struct OrbisNetStatisticsInfo { + s32 kernel_mem_free_size; + s32 kernel_mem_free_min; + s32 packet_count; + s32 packet_qos_count; + s32 libnet_mem_free_size; + s32 libnet_mem_free_min; +}; + +struct OrbisNetSockaddrUn { + u8 sun_len; + u8 sun_family; + char sun_path[104]; +}; + +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; +}; + +enum OrbisNetResolverFlag : u32 { + ORBIS_NET_RESOLVER_ASYNC = 0x1, + ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS = 0x10000, +}; +inline constexpr u32 ORBIS_NET_RESOLVER_ABORT_FLAG_NTOA_PRESERVATION = 0x1; +inline constexpr u32 ORBIS_NET_RESOLVER_ABORT_FLAG_ATON_PRESERVATION = 0x2; +inline constexpr int ORBIS_NET_RESOLVER_HOSTNAME_LEN_MAX = 255; + +union OrbisNetAddrUnion { + OrbisNetInAddr addr; + u8 addr6[16]; +}; + +struct OrbisNetResolverAddr { + OrbisNetAddrUnion u; + u32 af; + u32 pad[3]; +}; + +struct OrbisNetResolverInfo { + OrbisNetResolverAddr addrs[10]; + u32 records; + u32 recordsv4; + u32 pad[14]; +}; + +inline constexpr int ORBIS_NET_DEBUG_NAME_LEN_MAX = 31; + +enum OrbisNetSockInfoState : s32 { + ORBIS_NET_SOCKINFO_STATE_UNKNOWN = 0, + ORBIS_NET_SOCKINFO_STATE_CLOSED = 1, + ORBIS_NET_SOCKINFO_STATE_OPENED = 2, + ORBIS_NET_SOCKINFO_STATE_LISTEN = 3, + ORBIS_NET_SOCKINFO_STATE_SYN_SENT = 4, + ORBIS_NET_SOCKINFO_STATE_SYN_RECEIVED = 5, + ORBIS_NET_SOCKINFO_STATE_ESTABLISHED = 6, + ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_1 = 7, + ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_2 = 8, + ORBIS_NET_SOCKINFO_STATE_CLOSE_WAIT = 9, + ORBIS_NET_SOCKINFO_STATE_CLOSING = 10, + ORBIS_NET_SOCKINFO_STATE_LAST_ACK = 11, + ORBIS_NET_SOCKINFO_STATE_TIME_WAIT = 12, +}; + +enum OrbisNetSockInfoFlags : s32 { + ORBIS_NET_SOCKINFO_F_SELF = 0x00000001, + ORBIS_NET_SOCKINFO_F_KERNEL = 0x00000002, + ORBIS_NET_SOCKINFO_F_OTHERS = 0x00000004, + ORBIS_NET_SOCKINFO_F_RECV_WAIT = 0x00010000, + ORBIS_NET_SOCKINFO_F_SEND_WAIT = 0x00020000, + ORBIS_NET_SOCKINFO_F_RECV_EWAIT = 0x00040000, + ORBIS_NET_SOCKINFO_F_SEND_EWAIT = 0x00080000, + ORBIS_NET_SOCKINFO_F_NONBLOCK = 0x00200000, + ORBIS_NET_SOCKINFO_F_ALL = 0x006F0007, +}; + +struct OrbisNetSockInfo { + char name[ORBIS_NET_DEBUG_NAME_LEN_MAX + 1]; + s32 pid; + OrbisNetId s; + s8 socket_type; + s8 policy; + s8 priority; + s8 reserved8; + s32 recv_queue_length; + s32 send_queue_length; + OrbisNetInAddr local_adr; + OrbisNetInAddr remote_adr; + u16 local_port; // network byte order + u16 remote_port; + u16 local_vport; + u16 remote_vport; + s32 state; + s32 flags; + s32 tx_bps; + s32 rx_bps; + s32 max_tx_bps; + s32 max_rx_bps; + s32 tx_vbps; + s32 rx_vbps; + s32 recv_buffer_size; + s32 send_buffer_size; + s32 reserved6[8]; + s32 tx_drops; + s32 rx_drops; + s32 tx_wait; + s32 reserved[2]; +}; + +struct OrbisNetEtherAddr { + u8 data[6]; +}; + +static_assert(sizeof(OrbisNetSockaddr) == 16); +static_assert(sizeof(OrbisNetSockaddrIn) == 16); +static_assert(sizeof(OrbisNetSockaddrIn6) == 28); +static_assert(sizeof(OrbisNetEpollEvent) == 24); +static_assert(sizeof(OrbisNetSockaddrUn) == 106); +static_assert(sizeof(OrbisNetMsghdr) == 48); +static_assert(sizeof(OrbisNetResolverInfo) == 384); +static_assert(sizeof(OrbisNetSockInfo) == 160); +static_assert(sizeof(OrbisNetUdpSndOnSuspend) == 40); +static_assert(sizeof(OrbisNetMemoryPoolStats) == 32); +static_assert(sizeof(OrbisNetStatisticsInfo) == 24); + +} // namespace Libraries::Net diff --git a/src/core/libraries/network/net_upnp.cpp b/src/core/libraries/net/net_upnp.cpp similarity index 80% rename from src/core/libraries/network/net_upnp.cpp rename to src/core/libraries/net/net_upnp.cpp index a014737b4..7c093ab04 100644 --- a/src/core/libraries/network/net_upnp.cpp +++ b/src/core/libraries/net/net_upnp.cpp @@ -25,6 +25,11 @@ UPnPClient& UPnPClient::Instance() { UPnPClient::~UPnPClient() { if (m_thread.joinable()) m_thread.join(); + if (m_map_thread.joinable()) + m_map_thread.join(); + // Mappings have no lease time: leave nothing behind on the router. + if (const u16 port = m_mapped_port.exchange(0); port != 0) + RemoveMapping(port); if (m_available.load()) FreeUPNPUrls(&m_urls); } @@ -136,6 +141,31 @@ void UPnPClient::AddMapping(u16 port) { } } +void UPnPClient::MapPortAsync(u16 port) { + std::scoped_lock lock{m_map_mutex}; + if (m_map_thread.joinable()) + m_map_thread.join(); + m_map_thread = std::thread([this, port] { + if (!WaitReady(10'000)) { + LOG_INFO(Lib_Net, "UPNP: no IGD, UDP port {} is not forwarded", port); + return; + } + const u16 previous = m_mapped_port.exchange(port); + if (previous != 0 && previous != port) + RemoveMapping(previous); + AddMapping(port); + }); +} + +void UPnPClient::UnmapPort(u16 port) { + std::scoped_lock lock{m_map_mutex}; + if (m_map_thread.joinable()) + m_map_thread.join(); + u16 expected = port; + if (m_mapped_port.compare_exchange_strong(expected, 0)) + RemoveMapping(port); +} + void UPnPClient::RemoveMapping(u16 port) { if (!m_available.load()) return; diff --git a/src/core/libraries/network/net_upnp.h b/src/core/libraries/net/net_upnp.h similarity index 78% rename from src/core/libraries/network/net_upnp.h rename to src/core/libraries/net/net_upnp.h index 6fdc92409..6d9fcc7cb 100644 --- a/src/core/libraries/network/net_upnp.h +++ b/src/core/libraries/net/net_upnp.h @@ -40,6 +40,12 @@ public: void AddMapping(u16 port); void RemoveMapping(u16 port); + // Forwards `port` once discovery has finished (waiting up to 10 s for it), on a background + // thread so the caller never waits for the router. Replaces an earlier request's mapping. + void MapPortAsync(u16 port); + // Removes what MapPortAsync forwarded, if it is `port`. + void UnmapPort(u16 port); + private: UPnPClient() = default; ~UPnPClient(); @@ -54,6 +60,7 @@ private: std::atomic m_available{false}; std::atomic m_external_ip_nbo{0}; std::atomic m_external_port{0}; + std::atomic m_mapped_port{0}; // forwarded by MapPortAsync, removed on exit char m_external_ip_str[16]{}; char m_lan_addr[64]{}; @@ -63,6 +70,8 @@ private: std::mutex m_mutex; std::condition_variable m_cv; std::thread m_thread; + std::mutex m_map_mutex; // serializes MapPortAsync / UnmapPort + std::thread m_map_thread; }; } // namespace Libraries::Net diff --git a/src/core/libraries/network/net_util.cpp b/src/core/libraries/net/net_util.cpp similarity index 99% rename from src/core/libraries/network/net_util.cpp rename to src/core/libraries/net/net_util.cpp index 0dda3c21b..24d3755d6 100644 --- a/src/core/libraries/network/net_util.cpp +++ b/src/core/libraries/net/net_util.cpp @@ -39,8 +39,8 @@ typedef int net_socket; #include "common/assert.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" -#include "net.h" -#include "net_error.h" +#include "core/libraries/net/net_error.h" +#include "core/libraries/net/net_types.h" #include "net_util.h" namespace NetUtil { diff --git a/src/core/libraries/network/net_util.h b/src/core/libraries/net/net_util.h similarity index 100% rename from src/core/libraries/network/net_util.h rename to src/core/libraries/net/net_util.h diff --git a/src/core/libraries/network/http.cpp b/src/core/libraries/network/http.cpp index 36f45139e..5b0fda932 100644 --- a/src/core/libraries/network/http.cpp +++ b/src/core/libraries/network/http.cpp @@ -2583,6 +2583,10 @@ int PS4_SYSV_ABI sceHttpsDisableOptionPrivate(int id, u32 sslFlags) { int PS4_SYSV_ABI sceHttpsEnableOption(int id, u32 sslFlags) { LOG_INFO(Lib_Http, "called id={}, sslFlags={:#x}", id, sslFlags); + if (EmulatorSettings.IsForcedHttpsDisabled()) { + LOG_INFO(Lib_Http, "HTTPS is forced disabled, disabling options for id={}", id); + return sceHttpsDisableOption(id, sslFlags); + } std::lock_guard lock(g_state.m_mutex); if (!g_state.inited) { LOG_ERROR(Lib_Http, "Not initialized"); diff --git a/src/core/libraries/network/net.cpp b/src/core/libraries/network/net.cpp deleted file mode 100644 index 79e3a1c58..000000000 --- a/src/core/libraries/network/net.cpp +++ /dev/null @@ -1,2193 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#ifdef WIN32 -#define _WINSOCK_DEPRECATED_NO_WARNINGS -#include -#include -#include -#else -#include -#include -#endif - -#include -#include -#include "common/assert.h" -#include "common/error.h" -#include "common/logging/log.h" -#include "common/singleton.h" -#include "core/file_sys/fs.h" -#include "core/libraries/error_codes.h" -#include "core/libraries/libs.h" -#include "core/libraries/network/net.h" -#include "net_epoll.h" -#include "net_error.h" -#include "net_resolver.h" -#include "net_util.h" -#include "netctl.h" -#include "sockets.h" -#include "sys_net.h" - -namespace Libraries::Net { - -using FDTable = Common::Singleton; - -static thread_local int32_t net_errno = 0; - -static bool g_isNetInitialized = true; // TODO init it properly - -static int ConvertFamilies(int family) { - switch (family) { - case ORBIS_NET_AF_INET: - return AF_INET; - case ORBIS_NET_AF_INET6: - return AF_INET6; - default: - UNREACHABLE_MSG("unsupported socket family {}", family); - } -} - -auto NetErrorHandler(auto f) -> decltype(f()) { - auto result = 0; - int err; - int positiveErr; - - do { - result = f(); - - if (result >= 0) { - return result; // Success - } - - err = *Libraries::Kernel::__Error(); // Standard errno - - // Convert to positive error for comparison - positiveErr = (err < 0) ? -err : err; - - if ((positiveErr & 0xfff0000) != 0) { - // Unknown/fatal error range - *sceNetErrnoLoc() = ORBIS_NET_ERETURN; - return -positiveErr; - } - - // Retry if interrupted - } while (positiveErr == ORBIS_NET_EINTR); - - if (positiveErr == ORBIS_NET_ENOTSOCK) { - result = -ORBIS_NET_EBADF; - } else if (positiveErr == ORBIS_NET_ENETINTR) { - result = -ORBIS_NET_EINTR; - } else { - result = -positiveErr; - } - - *sceNetErrnoLoc() = -result; - - return (-result) | ORBIS_NET_ERROR_BASE; // Convert to official ORBIS_NET_ERROR code -} - -// Returns the number of bytes currently sitting in the socket's send or receive queue. -static s32 GetQueueLength(net_socket sock, bool send_queue) { -#ifdef _WIN32 - if (!send_queue) { - u_long available = 0; - if (::ioctlsocket(sock, FIONREAD, &available) == 0) { - return static_cast(available); - } - } - // Windows has no way to query the send queue depth. - return 0; -#elif defined(__APPLE__) - if (send_queue) { - int pending = 0; - socklen_t len = sizeof(pending); - if (::getsockopt(sock, SOL_SOCKET, SO_NWRITE, &pending, &len) == 0) { - return pending; - } - return 0; - } - int available = 0; - return ::ioctl(sock, FIONREAD, &available) == 0 ? available : 0; -#else - int pending = 0; - return ::ioctl(sock, send_queue ? TIOCOUTQ : FIONREAD, &pending) == 0 ? pending : 0; -#endif -} - -// Datagram sockets have no state machine, so they are always reported as opened. -static s32 GetSocketState(net_socket sock, int socket_type) { - if (socket_type != ORBIS_NET_SOCK_STREAM && socket_type != ORBIS_NET_SOCK_STREAM_P2P) { - return ORBIS_NET_SOCKINFO_STATE_OPENED; - } - -#if defined(__linux__) - tcp_info tcp{}; - socklen_t tcp_len = sizeof(tcp); - if (::getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tcp, &tcp_len) == 0) { - switch (tcp.tcpi_state) { - case TCP_ESTABLISHED: - return ORBIS_NET_SOCKINFO_STATE_ESTABLISHED; - case TCP_SYN_SENT: - return ORBIS_NET_SOCKINFO_STATE_SYN_SENT; - case TCP_SYN_RECV: - return ORBIS_NET_SOCKINFO_STATE_SYN_RECEIVED; - case TCP_FIN_WAIT1: - return ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_1; - case TCP_FIN_WAIT2: - return ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_2; - case TCP_TIME_WAIT: - return ORBIS_NET_SOCKINFO_STATE_TIME_WAIT; - case TCP_CLOSE: - return ORBIS_NET_SOCKINFO_STATE_CLOSED; - case TCP_CLOSE_WAIT: - return ORBIS_NET_SOCKINFO_STATE_CLOSE_WAIT; - case TCP_LAST_ACK: - return ORBIS_NET_SOCKINFO_STATE_LAST_ACK; - case TCP_LISTEN: - return ORBIS_NET_SOCKINFO_STATE_LISTEN; - case TCP_CLOSING: - return ORBIS_NET_SOCKINFO_STATE_CLOSING; - default: - break; - } - } -#elif defined(_WIN32) - TCP_INFO_v0 tcp{}; - DWORD version = 0; - DWORD returned = 0; - if (::WSAIoctl(sock, SIO_TCP_INFO, &version, static_cast(sizeof(version)), &tcp, - static_cast(sizeof(tcp)), &returned, nullptr, nullptr) == 0) { - switch (tcp.State) { - case TCPSTATE_CLOSED: - return ORBIS_NET_SOCKINFO_STATE_CLOSED; - case TCPSTATE_LISTEN: - return ORBIS_NET_SOCKINFO_STATE_LISTEN; - case TCPSTATE_SYN_SENT: - return ORBIS_NET_SOCKINFO_STATE_SYN_SENT; - case TCPSTATE_SYN_RCVD: - return ORBIS_NET_SOCKINFO_STATE_SYN_RECEIVED; - case TCPSTATE_ESTABLISHED: - return ORBIS_NET_SOCKINFO_STATE_ESTABLISHED; - case TCPSTATE_FIN_WAIT_1: - return ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_1; - case TCPSTATE_FIN_WAIT_2: - return ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_2; - case TCPSTATE_CLOSE_WAIT: - return ORBIS_NET_SOCKINFO_STATE_CLOSE_WAIT; - case TCPSTATE_CLOSING: - return ORBIS_NET_SOCKINFO_STATE_CLOSING; - case TCPSTATE_LAST_ACK: - return ORBIS_NET_SOCKINFO_STATE_LAST_ACK; - case TCPSTATE_TIME_WAIT: - return ORBIS_NET_SOCKINFO_STATE_TIME_WAIT; - default: - break; - } - } -#endif - - // No way to read the real state (or the query failed), so infer a plausible one. - int accepting = 0; - socklen_t accepting_len = sizeof(accepting); - if (::getsockopt(sock, SOL_SOCKET, SO_ACCEPTCONN, reinterpret_cast(&accepting), - &accepting_len) == 0 && - accepting != 0) { - return ORBIS_NET_SOCKINFO_STATE_LISTEN; - } - - sockaddr_in peer{}; - socklen_t peer_len = sizeof(peer); - if (::getpeername(sock, reinterpret_cast(&peer), &peer_len) == 0) { - return ORBIS_NET_SOCKINFO_STATE_ESTABLISHED; - } - return ORBIS_NET_SOCKINFO_STATE_CLOSED; -} - -static void FillSockInfo(OrbisNetId id, Core::FileSys::File* file, OrbisNetSockInfo* info) { - memset(info, 0, sizeof(OrbisNetSockInfo)); - - file->m_guest_name.copy(info->name, ORBIS_NET_DEBUG_NAME_LEN_MAX); - info->s = id; - if (!file->socket) { - return; - } - info->socket_type = static_cast(file->socket->socket_type); - info->flags = ORBIS_NET_SOCKINFO_F_SELF; - info->state = ORBIS_NET_SOCKINFO_STATE_UNKNOWN; - - const auto native = file->socket->Native(); - if (!native.has_value()) { - info->state = ORBIS_NET_SOCKINFO_STATE_OPENED; - return; - } - const net_socket sock = *native; -#ifndef _WIN32 - const int socket_flags = ::fcntl(sock, F_GETFL, 0); - if (socket_flags != -1 && (socket_flags & O_NONBLOCK) != 0) { - info->flags |= ORBIS_NET_SOCKINFO_F_NONBLOCK; - } -#endif - sockaddr_in local{}; - socklen_t local_len = sizeof(local); - if (::getsockname(sock, reinterpret_cast(&local), &local_len) == 0 && - local.sin_family == AF_INET) { - info->local_adr.inaddr_addr = local.sin_addr.s_addr; - info->local_port = local.sin_port; - } - - sockaddr_in remote{}; - socklen_t remote_len = sizeof(remote); - if (::getpeername(sock, reinterpret_cast(&remote), &remote_len) == 0 && - remote.sin_family == AF_INET) { - info->remote_adr.inaddr_addr = remote.sin_addr.s_addr; - info->remote_port = remote.sin_port; - } - - info->recv_queue_length = GetQueueLength(sock, false); - info->send_queue_length = GetQueueLength(sock, true); - info->state = GetSocketState(sock, file->socket->socket_type); - - int buffer_size = 0; - socklen_t buffer_size_len = sizeof(buffer_size); - if (::getsockopt(sock, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&buffer_size), - &buffer_size_len) == 0) { - info->recv_buffer_size = buffer_size; - } - buffer_size_len = sizeof(buffer_size); - if (::getsockopt(sock, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&buffer_size), - &buffer_size_len) == 0) { - info->send_buffer_size = buffer_size; - } - - // The remaining fields (v ports, bandwidth counters and network emulation drop counters) have - // no equivalent on the host and are left zeroed. -} - -int PS4_SYSV_ABI in6addr_any() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI in6addr_loopback() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sce_net_dummy() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sce_net_in6addr_any() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sce_net_in6addr_linklocal_allnodes() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sce_net_in6addr_linklocal_allrouters() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sce_net_in6addr_loopback() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sce_net_in6addr_nodelocal_allnodes() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -OrbisNetId PS4_SYSV_ABI sceNetAccept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_accept(s, addr, paddrlen); }); -} - -int PS4_SYSV_ABI sceNetAddrConfig6GetInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetAddrConfig6Start() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetAddrConfig6Stop() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetAllocateAllRouteInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetBandwidthControlGetDataTraffic() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetBandwidthControlGetDefaultParam() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetBandwidthControlGetIfParam() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetBandwidthControlGetPolicy() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetBandwidthControlSetDefaultParam() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetBandwidthControlSetIfParam() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetBandwidthControlSetPolicy() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetBind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_bind(s, addr, addrlen); }); -} - -int PS4_SYSV_ABI sceNetClearDnsCache() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigAddArp() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigAddArpWithInterface() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigAddIfaddr() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigAddMRoute() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigAddRoute() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigAddRoute6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigAddRouteWithInterface() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigCleanUpAllInterfaces() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigDelArp() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigDelArpWithInterface() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigDelDefaultRoute() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigDelDefaultRoute6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigDelIfaddr() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigDelIfaddr6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigDelMRoute() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigDelRoute() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigDelRoute6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigDownInterface() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigEtherGetLinkMode() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigEtherPostPlugInOutEvent() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigEtherSetLinkMode() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigFlushRoute() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigGetDefaultRoute() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigGetDefaultRoute6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigGetIfaddr() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigGetIfaddr6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigRoutingShowRoutingConfig() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigRoutingShowtCtlVar() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigRoutingStart() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigRoutingStop() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigSetDefaultRoute() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigSetDefaultRoute6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigSetDefaultScope() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigSetIfaddr() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigSetIfaddr6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigSetIfaddr6WithFlags() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigSetIfFlags() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigSetIfLinkLocalAddr6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigSetIfmtu() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigUnsetIfFlags() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigUpInterface() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigUpInterfaceWithFlags() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanAdhocClearWakeOnWlan() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanAdhocCreate() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanAdhocGetWakeOnWlanInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanAdhocJoin() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanAdhocLeave() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanAdhocScanJoin() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanAdhocSetExtInfoElement() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanAdhocSetWakeOnWlan() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanApStart() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanApStop() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanQuery() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStart() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStop() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanDiagGetDeviceInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanDiagSetAntenna() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanDiagSetTxFixedRate() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanGetDeviceConfig() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanInfraGetRssiInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanInfraLeave() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanInfraScanJoin() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanScan() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConfigWlanSetDeviceConfig() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetConnect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_connect(s, addr, addrlen); }); -} - -int PS4_SYSV_ABI sceNetControl() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDhcpdStart() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDhcpdStop() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDhcpGetAutoipInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDhcpGetInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDhcpGetInfoEx() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDhcpStart() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDhcpStop() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDumpAbort() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDumpCreate() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDumpDestroy() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDumpRead() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDuplicateIpStart() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetDuplicateIpStop() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetEpollAbort() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, OrbisNetId id, - OrbisNetEpollEvent* event) { - auto file = FDTable::Instance()->GetEpoll(epollid); - if (!file) { - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - } - auto epoll = file->epoll; - LOG_WARNING(Lib_Net, "called, epollid = {} ({}), op = {}, id = {}", epollid, epoll->name, - magic_enum::enum_name(op), id); - - auto find_id = [&](OrbisNetId id) { - return std::ranges::find_if(epoll->events, [&](auto& el) { return el.first == id; }); - }; - - switch (op) { - case ORBIS_NET_EPOLL_CTL_ADD: { - if (event == nullptr) { - *sceNetErrnoLoc() = ORBIS_NET_EINVAL; - return ORBIS_NET_ERROR_EINVAL; - } - if (find_id(id) != epoll->events.end()) { - *sceNetErrnoLoc() = ORBIS_NET_EEXIST; - return ORBIS_NET_ERROR_EEXIST; - } - - auto file = FDTable::Instance()->GetFile(id); - if (!file) { - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "file id is invalid = {}", id); - return ORBIS_NET_ERROR_EBADF; - } - - switch (file->type) { - case Core::FileSys::FileType::Socket: { - auto native_handle = file->socket->Native(); - if (!native_handle) { - // P2P socket, cannot be added to epoll - LOG_ERROR(Lib_Net, "P2P socket cannot be added to epoll (unimplemented)"); - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - } - -#ifndef __FreeBSD__ - epoll_event native_event = {.events = ConvertEpollEventsIn(event->events), - .data = {.fd = id}}; - ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_ADD, *native_handle, &native_event) == 0); - epoll->events.emplace_back(id, *event); -#endif - break; - } - case Core::FileSys::FileType::Resolver: { - epoll->async_resolutions.emplace_back(id); - epoll->events.emplace_back(id, *event); - break; - } - default: { - LOG_ERROR(Lib_Net, "file type {} ({}) passed", magic_enum::enum_name(file->type.load()), - file->m_guest_name); - break; - } - } - break; - } - case ORBIS_NET_EPOLL_CTL_MOD: { - if (event == nullptr) { - *sceNetErrnoLoc() = ORBIS_NET_EINVAL; - return ORBIS_NET_ERROR_EINVAL; - } - - const auto it = find_id(id); - if (it == epoll->events.end()) { - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - } - - auto file = FDTable::Instance()->GetFile(id); - if (!file) { - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "file id is invalid = {}", id); - return ORBIS_NET_ERROR_EBADF; - } - - switch (file->type) { - case Core::FileSys::FileType::Socket: { - auto native_handle = file->socket->Native(); - if (!native_handle) { - // P2P socket, cannot be modified in epoll - LOG_ERROR(Lib_Net, "P2P socket cannot be modified in epoll (unimplemented)"); - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - } - -#ifndef __FreeBSD__ - epoll_event native_event = {.events = ConvertEpollEventsIn(event->events), - .data = {.fd = id}}; - ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_MOD, *native_handle, &native_event) == 0); - *it = {id, *event}; -#endif - break; - } - default: - LOG_ERROR(Lib_Net, "file type {} ({}) passed", magic_enum::enum_name(file->type.load()), - file->m_guest_name); - break; - } - break; - } - case ORBIS_NET_EPOLL_CTL_DEL: { - if (event != nullptr) { - *sceNetErrnoLoc() = ORBIS_NET_EINVAL; - return ORBIS_NET_ERROR_EINVAL; - } - - auto it = find_id(id); - if (it == epoll->events.end()) { - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - } - - auto file = FDTable::Instance()->GetFile(id); - if (!file) { - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "file id is invalid = {}", id); - return ORBIS_NET_ERROR_EBADF; - } - - switch (file->type) { - case Core::FileSys::FileType::Socket: { - auto native_handle = file->socket->Native(); - if (!native_handle) { - // P2P socket, cannot be removed from epoll - LOG_ERROR(Lib_Net, "P2P socket cannot be removed from epoll (unimplemented)"); - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - } -#ifndef __FreeBSD__ - ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_DEL, *native_handle, nullptr) == 0); - epoll->events.erase(it); -#endif - break; - } - case Core::FileSys::FileType::Resolver: { - std::erase(epoll->async_resolutions, id); - epoll->events.erase(it); - break; - } - default: - LOG_ERROR(Lib_Net, "file type {} ({}) passed", magic_enum::enum_name(file->type.load()), - file->m_guest_name); - break; - } - break; - } - default: - *sceNetErrnoLoc() = ORBIS_NET_EINVAL; - return ORBIS_NET_ERROR_EINVAL; - } - - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetEpollCreate(const char* name, int flags) { - LOG_INFO(Lib_Net, "called, name = {}, flags = {}", name, flags); - if (flags != 0) { - *sceNetErrnoLoc() = ORBIS_NET_EINVAL; - return ORBIS_NET_ERROR_EINVAL; - } - - auto fd = FDTable::Instance()->CreateHandle(); - auto* epoll = FDTable::Instance()->GetFile(fd); - epoll->is_opened = true; - epoll->type = Core::FileSys::FileType::Epoll; - epoll->epoll = std::make_shared(name); - epoll->m_guest_name = name; - return fd; -} - -int PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId epollid) { - auto file = FDTable::Instance()->GetEpoll(epollid); - if (!file) { - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - } - - LOG_DEBUG(Lib_Net, "called, epollid = {} ({})", epollid, file->epoll->name); - - file->epoll->Destroy(); - FDTable::Instance()->DeleteHandle(epollid); - - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events, int maxevents, - int timeout) { -#ifdef __FreeBSD__ - return 0; -#else - auto file = FDTable::Instance()->GetEpoll(epollid); - if (!file) { - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - } - auto epoll = file->epoll; - LOG_DEBUG(Lib_Net, "called, epollid = {} ({}), maxevents = {}, timeout = {}", epollid, - epoll->name, maxevents, timeout); - - int sockets_waited_on = (epoll->events.size() - epoll->async_resolutions.size()) > 0; - - std::vector native_events{static_cast(maxevents)}; - int result = ORBIS_OK; - if (sockets_waited_on) { -#ifdef __linux__ - const timespec epoll_timeout{.tv_sec = timeout / 1000000, - .tv_nsec = (timeout % 1000000) * 1000}; - result = epoll_pwait2(epoll->epoll_fd, native_events.data(), maxevents, - timeout < 0 ? nullptr : &epoll_timeout, nullptr); -#else - result = epoll_wait(epoll->epoll_fd, native_events.data(), maxevents, - timeout < 0 ? timeout : timeout / 1000); -#endif - } - - int i = 0; - if (result < 0) { - LOG_ERROR(Lib_Net, "epoll_wait failed with {}", Common::GetLastErrorMsg()); - switch (errno) { - case EINTR: - *sceNetErrnoLoc() = ORBIS_NET_EINTR; - return ORBIS_NET_ERROR_EINTR; - case EINVAL: - *sceNetErrnoLoc() = ORBIS_NET_EINVAL; - return ORBIS_NET_ERROR_EINVAL; - case EBADF: - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - default: - *sceNetErrnoLoc() = ORBIS_NET_EINTERNAL; - return ORBIS_NET_ERROR_EINTERNAL; - } - } else if (result == 0) { - LOG_TRACE(Lib_Net, "timed out"); - } else { - for (; i < result; ++i) { - const auto& current_event = native_events[i]; - LOG_DEBUG(Lib_Net, "native_event[{}] = ( .events = {}, .data = {:#x} )", i, - current_event.events, current_event.data.u64); - const auto it = std::ranges::find_if( - epoll->events, [&](auto& el) { return el.first == current_event.data.fd; }); - ASSERT(it != epoll->events.end()); - events[i] = { - .events = ConvertEpollEventsOut(current_event.events), - .ident = static_cast(current_event.data.fd), - .data = it->second.data, - }; - LOG_DEBUG(Lib_Net, "event[{}] = ( .events = {:#x}, .ident = {}, .data = {:#x} )", i, - events[i].events, events[i].ident, events[i].data.data_u64); - } - } - - if (result >= 0) { - while (!epoll->async_resolutions.empty()) { - if (i == maxevents) { - break; - } - auto rid = epoll->async_resolutions.front(); - epoll->async_resolutions.pop_front(); - auto file = FDTable::Instance()->GetResolver(rid); - if (!file) { - LOG_ERROR(Lib_Net, "resolver {} does not exist", rid); - continue; - } - - file->resolver->Resolve(); - if (file->resolver->resolution_error != ORBIS_OK) { - // Resolution failed, shouldn't appear. - continue; - } - - const auto it = - std::ranges::find_if(epoll->events, [&](auto& el) { return el.first == rid; }); - ASSERT(it != epoll->events.end()); - events[i] = { - .events = ORBIS_NET_EPOLLDESCID, - .ident = static_cast(rid), - .data = it->second.data, - }; - LOG_DEBUG(Lib_Net, "event[{}] = ( .events = {:#x}, .ident = {}, .data = {:#x} )", i, - events[i].events, events[i].ident, events[i].data.data_u64); - ++i; - } - } - return i; -#endif -} - -int* PS4_SYSV_ABI sceNetErrnoLoc() { - LOG_TRACE(Lib_Net, "called"); - return &net_errno; -} - -int PS4_SYSV_ABI sceNetEtherNtostr() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetEtherStrton() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetEventCallbackCreate() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetEventCallbackDestroy() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetEventCallbackGetError() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetEventCallbackWaitCB() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetFreeAllRouteInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetArpInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetDns6Info() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetDnsInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetIfList() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetIfListOnce() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetIfName() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetIfnameNumList() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetMacAddress(Libraries::NetCtl::OrbisNetEtherAddr* addr, int flags) { - if (addr == nullptr) { - LOG_ERROR(Lib_Net, "addr is null!"); - return ORBIS_NET_EINVAL; - } - LOG_DEBUG(Lib_Net, "called"); - - auto* netinfo = Common::Singleton::Instance(); - netinfo->RetrieveEthernetAddr(); - memcpy(addr->data, netinfo->GetEthernetAddr().data(), 6); - - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetMemoryPoolStats() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetNameToIndex() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_getpeername(s, addr, paddrlen); }); -} - -int PS4_SYSV_ABI sceNetGetRandom() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetRouteInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetSockInfo(OrbisNetId s, OrbisNetSockInfo* info, int n, int flags) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - if (info == nullptr) { - if (s >= 0) { - *sceNetErrnoLoc() = ORBIS_NET_EINVAL; - return ORBIS_NET_ERROR_EINVAL; - } - const int count = static_cast(FDTable::Instance()->GetSocketHandles().size()); - LOG_DEBUG(Lib_Net, "queried socket count = {}", count); - return count; - } - - if (n <= 0) { - *sceNetErrnoLoc() = ORBIS_NET_EINVAL; - return ORBIS_NET_ERROR_EINVAL; - } - - if (s >= 0) { - auto* file = FDTable::Instance()->GetSocket(s); - if (!file) { - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - } - FillSockInfo(s, file, info); - LOG_DEBUG(Lib_Net, "s = {} ({}), state = {}, flags = {:#x}", s, file->m_guest_name, - info->state, info->flags); - return 1; - } - - int written = 0; - for (const auto id : FDTable::Instance()->GetSocketHandles()) { - if (written >= n) { - break; - } - auto* file = FDTable::Instance()->GetSocket(id); - if (!file) { - continue; - } - FillSockInfo(id, file, &info[written]); - written++; - } - LOG_DEBUG(Lib_Net, "enumerated {} of at most {} sockets (flags = {:#x})", written, n, flags); - return written; -} - -int PS4_SYSV_ABI sceNetGetSockInfo6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_getsockname(s, addr, paddrlen); }); -} - -int PS4_SYSV_ABI sceNetGetsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_getsockopt(s, level, optname, optval, optlen); }); -} - -int PS4_SYSV_ABI sceNetGetStatisticsInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetStatisticsInfoInternal() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetGetSystemTime() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -u32 PS4_SYSV_ABI sceNetHtonl(u32 host32) { - return htonl(host32); -} - -u64 PS4_SYSV_ABI sceNetHtonll(u64 host64) { - return HTONLL(host64); -} - -u16 PS4_SYSV_ABI sceNetHtons(u16 host16) { - return htons(host16); -} - -#if defined(WIN32) || defined(__linux__) -// there isn't a strlcpy function in windows/glibc so implement one -u64 strlcpy(char* dst, const char* src, u64 size) { - u64 src_len = strlen(src); - - if (size > 0) { - u64 copy_len = (src_len >= size) ? (size - 1) : src_len; - memcpy(dst, src, copy_len); - dst[copy_len] = '\0'; - } - - return src_len; -} - -#endif - -const char* freebsd_inet_ntop4(const unsigned char* src, char* dst, u64 size) { - static const char fmt[] = "%u.%u.%u.%u"; - char tmp[sizeof "255.255.255.255"]; - int l; - - l = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]); - if (l <= 0 || (socklen_t)l >= size) { - return nullptr; - } - strlcpy(dst, tmp, size); - return (dst); -} - -const char* freebsd_inet_ntop6(const unsigned char* src, char* dst, u64 size) { - /* - * Note that int32_t and int16_t need only be "at least" large enough - * to contain a value of the specified size. On some systems, like - * Crays, there is no such thing as an integer variable with 16 bits. - * Keep this in mind if you think this function should have been coded - * to use pointer overlays. All the world's not a VAX. - */ - char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp; - struct { - int base, len; - } best, cur; -#define NS_IN6ADDRSZ 16 -#define NS_INT16SZ 2 - u_int words[NS_IN6ADDRSZ / NS_INT16SZ]; - int i; - - /* - * Preprocess: - * Copy the input (bytewise) array into a wordwise array. - * Find the longest run of 0x00's in src[] for :: shorthanding. - */ - memset(words, '\0', sizeof words); - for (i = 0; i < NS_IN6ADDRSZ; i++) - words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); - best.base = -1; - best.len = 0; - cur.base = -1; - cur.len = 0; - for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { - if (words[i] == 0) { - if (cur.base == -1) - cur.base = i, cur.len = 1; - else - cur.len++; - } else { - if (cur.base != -1) { - if (best.base == -1 || cur.len > best.len) - best = cur; - cur.base = -1; - } - } - } - if (cur.base != -1) { - if (best.base == -1 || cur.len > best.len) - best = cur; - } - if (best.base != -1 && best.len < 2) - best.base = -1; - - /* - * Format the result. - */ - tp = tmp; - for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { - /* Are we inside the best run of 0x00's? */ - if (best.base != -1 && i >= best.base && i < (best.base + best.len)) { - if (i == best.base) - *tp++ = ':'; - continue; - } - /* Are we following an initial run of 0x00s or any real hex? */ - if (i != 0) - *tp++ = ':'; - /* Is this address an encapsulated IPv4? */ - if (i == 6 && best.base == 0 && - (best.len == 6 || (best.len == 7 && words[7] != 0x0001) || - (best.len == 5 && words[5] == 0xffff))) { - if (!freebsd_inet_ntop4(src + 12, tp, sizeof tmp - (tp - tmp))) - return nullptr; - tp += strlen(tp); - break; - } - tp += snprintf(tp, sizeof(tmp) - (tp - tmp), "%x", words[i]); - } - /* Was it a trailing run of 0x00's? */ - if (best.base != -1 && (best.base + best.len) == (NS_IN6ADDRSZ / NS_INT16SZ)) - *tp++ = ':'; - *tp++ = '\0'; - - /* - * Check for overflow, copy, and we're done. - */ - if ((u64)(tp - tmp) > size) { - return nullptr; - } - strcpy(dst, tmp); - return (dst); -} -const char* PS4_SYSV_ABI sceNetInetNtop(int af, const void* src, char* dst, u32 size) { - if (!(src && dst)) { - *sceNetErrnoLoc() = ORBIS_NET_ENOSPC; - LOG_ERROR(Lib_Net, "returned ORBIS_NET_ENOSPC"); - return nullptr; - } - - const char* returnvalue = nullptr; - switch (af) { - case ORBIS_NET_AF_INET: - returnvalue = freebsd_inet_ntop4((const unsigned char*)src, dst, size); - break; - case ORBIS_NET_AF_INET6: - returnvalue = freebsd_inet_ntop6((const unsigned char*)src, dst, size); - break; - default: - *sceNetErrnoLoc() = ORBIS_NET_EAFNOSUPPORT; - LOG_ERROR(Lib_Net, "returned ORBIS_NET_EAFNOSUPPORT"); - return nullptr; - } - if (returnvalue == nullptr) { - *sceNetErrnoLoc() = ORBIS_NET_ENOSPC; - LOG_ERROR(Lib_Net, "returned ORBIS_NET_ENOSPC"); - } else { - LOG_DEBUG(Lib_Net, "{}: {}", magic_enum::enum_name((OrbisNetFamily)af), dst); - } - return returnvalue; -} - -int PS4_SYSV_ABI sceNetInetNtopWithScopeId() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetInetPton(int af, const char* src, void* dst) { -#ifdef WIN32 - int res = InetPtonA(ConvertFamilies(af), src, dst); -#else - int res = inet_pton(ConvertFamilies(af), src, dst); -#endif - if (res < 0) { - UNREACHABLE_MSG("af = {}, src = {}, dst = {}", af, src, fmt::ptr(dst)); - } - return res; -} - -int PS4_SYSV_ABI sceNetInetPtonEx(int af, const char* src, void* dst, int flags) { - LOG_WARNING(Lib_Net, "ignored flags, redirecting to sceNetInetPton"); - return sceNetInetPton(af, src, dst); -} - -int PS4_SYSV_ABI sceNetInetPtonWithScopeId() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetInfoDumpStart() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetInfoDumpStop() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetInit() { - LOG_ERROR(Lib_Net, "(DUMMY) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetInitParam() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetIoctl() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetListen(OrbisNetId s, int backlog) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_listen(s, backlog); }); -} - -int PS4_SYSV_ABI sceNetMemoryAllocate() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetMemoryFree() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -u32 PS4_SYSV_ABI sceNetNtohl(u32 net32) { - return ntohl(net32); -} - -int PS4_SYSV_ABI sceNetNtohll() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -u16 PS4_SYSV_ABI sceNetNtohs(u16 net16) { - return ntohs(net16); -} - -int PS4_SYSV_ABI sceNetPoolCreate(const char* name, int size, int flags) { - LOG_ERROR(Lib_Net, "(DUMMY) name = {} size = {} flags = {} ", std::string(name), size, flags); - static s32 id = 1; - return id++; -} - -int PS4_SYSV_ABI sceNetPoolDestroy() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetPppoeStart() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetPppoeStop() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetRecv(OrbisNetId s, void* buf, u64 len, int flags) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler( - [&] { return sys_recvfrom(s, buf, len, flags | 0x40000000, nullptr, 0); }); -} - -int PS4_SYSV_ABI sceNetRecvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr, - u32* paddrlen) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler( - [&] { return sys_recvfrom(s, buf, len, flags | 0x40000000, addr, paddrlen); }); -} - -int PS4_SYSV_ABI sceNetRecvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_recvmsg(s, msg, flags | 0x40000000); }); -} - -int PS4_SYSV_ABI sceNetResolverAbort() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetResolverConnect() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetResolverConnectAbort() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetResolverConnectCreate() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetResolverConnectDestroy() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) { - const char* safe_name = name ? name : ""; - LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", safe_name, poolid, flags); - - if (flags != 0) { - *sceNetErrnoLoc() = ORBIS_NET_EINVAL; - return ORBIS_NET_ERROR_EINVAL; - } - - auto fd = FDTable::Instance()->CreateHandle(); - auto* resolver = FDTable::Instance()->GetFile(fd); - resolver->is_opened = true; - resolver->type = Core::FileSys::FileType::Resolver; - resolver->resolver = std::make_shared(safe_name, poolid, flags); - resolver->m_guest_name = safe_name; - return fd; -} - -int PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId resolverid) { - LOG_ERROR(Lib_Net, "(STUBBED) called rid = {}", resolverid); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId resolverid, s32* status) { - if (!status) { - LOG_ERROR(Lib_Net, "status == nullptr"); - *sceNetErrnoLoc() = ORBIS_NET_EINVAL; - return ORBIS_NET_ERROR_EINVAL; - } - - auto file = FDTable::Instance()->GetResolver(resolverid); - if (!file) { - LOG_ERROR(Lib_Net, "invalid resolverid {}", resolverid); - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - } - - *status = file->resolver->resolution_error; - LOG_INFO(Lib_Net, "called rid = {}, error = {:#x}", resolverid, static_cast(*status)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetResolverStartAton() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetResolverStartAton6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* hostname, - OrbisNetInAddr* addr, int timeout, int retry, int flags) { - LOG_INFO(Lib_Net, - "called, resolverid = {}, hostname = {}, timeout = {}, retry = {}, flags = {}", - resolverid, hostname, timeout, retry, flags); - - auto file = FDTable::Instance()->GetResolver(resolverid); - if (!file) { - LOG_ERROR(Lib_Net, "invalid resolverid {}", resolverid); - *sceNetErrnoLoc() = ORBIS_NET_EBADF; - return ORBIS_NET_ERROR_EBADF; - } - - if (!EmulatorSettings.IsConnectedToNetwork()) { - *sceNetErrnoLoc() = ORBIS_NET_RESOLVER_ENODNS; - file->resolver->resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS; - return ORBIS_NET_ERROR_RESOLVER_ENODNS; - } - - if ((flags & ORBIS_NET_RESOLVER_ASYNC) != 0) { - return file->resolver->ResolveAsync(hostname, addr, timeout, retry, flags); - } - - auto* netinfo = Common::Singleton::Instance(); - auto ret = netinfo->ResolveHostname(hostname, addr); - - if (ret != 0) { - *sceNetErrnoLoc() = ret; - ret = -ret | ORBIS_NET_ERROR_BASE; - } - - return ret; -} - -int PS4_SYSV_ABI sceNetResolverStartNtoa6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecords(OrbisNetId resolverid, const char* hostname, - OrbisNetResolverInfo* info, int timeout, - int retry, int flags) { - LOG_WARNING(Lib_Net, "redirected to sceNetResolverStartNtoa"); - - OrbisNetInAddr addr{}; - auto result = sceNetResolverStartNtoa(resolverid, hostname, &addr, timeout, retry, flags); - - if (result == ORBIS_OK) { - info->addrs[0] = {.u = {.addr = addr}, .af = ORBIS_NET_AF_INET}; - info->records = 1; - info->recordsv4 = 1; - } - - return result; -} - -int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecordsEx() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetSend(OrbisNetId s, const void* buf, u64 len, int flags) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_sendto(s, buf, len, flags | 0x40020000, nullptr, 0); }); -} - -int PS4_SYSV_ABI sceNetSendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_sendmsg(s, msg, flags | 0x40020000); }); -} - -int PS4_SYSV_ABI sceNetSendto(OrbisNetId s, const void* buf, u64 len, int flags, - const OrbisNetSockaddr* addr, u32 addrlen) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler( - [&] { return sys_sendto(s, buf, len, flags | 0x40020000, addr, addrlen); }); -} - -int PS4_SYSV_ABI sceNetSetDns6Info() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetSetDns6InfoToKernel() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetSetDnsInfo() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetSetDnsInfoToKernel() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetSetsockopt(OrbisNetId s, int level, int optname, const void* optval, - u32 optlen) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_setsockopt(s, level, optname, optval, optlen); }); -} - -int PS4_SYSV_ABI sceNetShowIfconfig() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowIfconfigForBuffer() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowIfconfigWithMemory() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowNetstat() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowNetstatEx() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowNetstatExForBuffer() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowNetstatForBuffer() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowNetstatWithMemory() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowPolicy() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowPolicyWithMemory() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowRoute() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowRoute6() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowRoute6ForBuffer() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowRoute6WithMemory() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowRouteForBuffer() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShowRouteWithMemory() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetShutdown(OrbisNetId s, int how) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_shutdown(s, how); }); -} - -OrbisNetId PS4_SYSV_ABI sceNetSocket(const char* name, int family, int type, int protocol) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_socketex(name, family, type, protocol); }); -} - -int PS4_SYSV_ABI sceNetSocketAbort(OrbisNetId s, int flags) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_netabort(s, flags); }); -} - -int PS4_SYSV_ABI sceNetSocketClose(OrbisNetId s) { - if (!g_isNetInitialized) { - return ORBIS_NET_ERROR_ENOTINIT; - } - - return NetErrorHandler([&] { return sys_socketclose(s); }); -} - -int PS4_SYSV_ABI sceNetSyncCreate() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetSyncDestroy() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetSyncGet() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetSyncSignal() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetSyncWait() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetSysctl() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetTerm() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetThreadCreate() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetThreadExit() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetThreadJoin() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetUsleep() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI Func_0E707A589F751C68() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetEmulationGet() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNetEmulationSet() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return ORBIS_OK; -} - -void RegisterLib(Core::Loader::SymbolsResolver* sym) { - LIB_FUNCTION("ZRAJo-A-ukc", "libSceNet", 1, "libSceNet", in6addr_any); - LIB_FUNCTION("XCuA-GqjA-k", "libSceNet", 1, "libSceNet", in6addr_loopback); - LIB_FUNCTION("VZgoeBxPXUQ", "libSceNet", 1, "libSceNet", sce_net_dummy); - LIB_FUNCTION("GAtITrgxKDE", "libSceNet", 1, "libSceNet", sce_net_in6addr_any); - LIB_FUNCTION("84MgU4MMTLQ", "libSceNet", 1, "libSceNet", sce_net_in6addr_linklocal_allnodes); - LIB_FUNCTION("2uSWyOKYc1M", "libSceNet", 1, "libSceNet", sce_net_in6addr_linklocal_allrouters); - LIB_FUNCTION("P3AeWBvPrkg", "libSceNet", 1, "libSceNet", sce_net_in6addr_loopback); - LIB_FUNCTION("PgNI+j4zxzM", "libSceNet", 1, "libSceNet", sce_net_in6addr_nodelocal_allnodes); - LIB_FUNCTION("PIWqhn9oSxc", "libSceNet", 1, "libSceNet", sceNetAccept); - LIB_FUNCTION("BTUvkWzrP68", "libSceNet", 1, "libSceNet", sceNetAddrConfig6GetInfo); - LIB_FUNCTION("3qG7UJy2Fq8", "libSceNet", 1, "libSceNet", sceNetAddrConfig6Start); - LIB_FUNCTION("P+0ePpDfUAQ", "libSceNet", 1, "libSceNet", sceNetAddrConfig6Stop); - LIB_FUNCTION("PcdLABhYga4", "libSceNet", 1, "libSceNet", sceNetAllocateAllRouteInfo); - LIB_FUNCTION("xHq87H78dho", "libSceNet", 1, "libSceNet", sceNetBandwidthControlGetDataTraffic); - LIB_FUNCTION("c8IRpl4L74I", "libSceNet", 1, "libSceNet", sceNetBandwidthControlGetDefaultParam); - LIB_FUNCTION("b9Ft65tqvLk", "libSceNet", 1, "libSceNet", sceNetBandwidthControlGetIfParam); - LIB_FUNCTION("PDkapOwggRw", "libSceNet", 1, "libSceNet", sceNetBandwidthControlGetPolicy); - LIB_FUNCTION("P4zZXE7bpsA", "libSceNet", 1, "libSceNet", sceNetBandwidthControlSetDefaultParam); - LIB_FUNCTION("g4DKkzV2qC4", "libSceNet", 1, "libSceNet", sceNetBandwidthControlSetIfParam); - LIB_FUNCTION("7Z1hhsEmkQU", "libSceNet", 1, "libSceNet", sceNetBandwidthControlSetPolicy); - LIB_FUNCTION("bErx49PgxyY", "libSceNet", 1, "libSceNet", sceNetBind); - LIB_FUNCTION("eyLyLJrdEOU", "libSceNet", 1, "libSceNet", sceNetClearDnsCache); - LIB_FUNCTION("Ea2NaVMQNO8", "libSceNet", 1, "libSceNet", sceNetConfigAddArp); - LIB_FUNCTION("0g0qIuPN3ZQ", "libSceNet", 1, "libSceNet", sceNetConfigAddArpWithInterface); - LIB_FUNCTION("ge7g15Sqhks", "libSceNet", 1, "libSceNet", sceNetConfigAddIfaddr); - LIB_FUNCTION("FDHr4Iz7dQU", "libSceNet", 1, "libSceNet", sceNetConfigAddMRoute); - LIB_FUNCTION("Cyjl1yzi1qY", "libSceNet", 1, "libSceNet", sceNetConfigAddRoute); - LIB_FUNCTION("Bu+L5r1lKRg", "libSceNet", 1, "libSceNet", sceNetConfigAddRoute6); - LIB_FUNCTION("wIGold7Lro0", "libSceNet", 1, "libSceNet", sceNetConfigAddRouteWithInterface); - LIB_FUNCTION("MzA1YrRE6rA", "libSceNet", 1, "libSceNet", sceNetConfigCleanUpAllInterfaces); - LIB_FUNCTION("HJt+4x-CnY0", "libSceNet", 1, "libSceNet", sceNetConfigDelArp); - LIB_FUNCTION("xTcttXJ3Utg", "libSceNet", 1, "libSceNet", sceNetConfigDelArpWithInterface); - LIB_FUNCTION("RuVwHEW6dM4", "libSceNet", 1, "libSceNet", sceNetConfigDelDefaultRoute); - LIB_FUNCTION("UMlVCy7RX1s", "libSceNet", 1, "libSceNet", sceNetConfigDelDefaultRoute6); - LIB_FUNCTION("0239JNsI6PE", "libSceNet", 1, "libSceNet", sceNetConfigDelIfaddr); - LIB_FUNCTION("hvCXMwd45oc", "libSceNet", 1, "libSceNet", sceNetConfigDelIfaddr6); - LIB_FUNCTION("5Yl1uuh5i-A", "libSceNet", 1, "libSceNet", sceNetConfigDelMRoute); - LIB_FUNCTION("QO7+2E3cD-U", "libSceNet", 1, "libSceNet", sceNetConfigDelRoute); - LIB_FUNCTION("4wDGvfhmkmk", "libSceNet", 1, "libSceNet", sceNetConfigDelRoute6); - LIB_FUNCTION("3WzWV86AJ3w", "libSceNet", 1, "libSceNet", sceNetConfigDownInterface); - LIB_FUNCTION("mOUkgTaSkJU", "libSceNet", 1, "libSceNet", sceNetConfigEtherGetLinkMode); - LIB_FUNCTION("pF3Vy1iZ5bs", "libSceNet", 1, "libSceNet", sceNetConfigEtherPostPlugInOutEvent); - LIB_FUNCTION("QltDK6wWqF0", "libSceNet", 1, "libSceNet", sceNetConfigEtherSetLinkMode); - LIB_FUNCTION("18KNgSvYx+Y", "libSceNet", 1, "libSceNet", sceNetConfigFlushRoute); - LIB_FUNCTION("lFJb+BlPK1c", "libSceNet", 1, "libSceNet", sceNetConfigGetDefaultRoute); - LIB_FUNCTION("mCLdiNIKtW0", "libSceNet", 1, "libSceNet", sceNetConfigGetDefaultRoute6); - LIB_FUNCTION("ejwa0hWWhDs", "libSceNet", 1, "libSceNet", sceNetConfigGetIfaddr); - LIB_FUNCTION("FU6NK4RHQVE", "libSceNet", 1, "libSceNet", sceNetConfigGetIfaddr6); - LIB_FUNCTION("vbZLomImmEE", "libSceNet", 1, "libSceNet", sceNetConfigRoutingShowRoutingConfig); - LIB_FUNCTION("a6sS6iSE0IA", "libSceNet", 1, "libSceNet", sceNetConfigRoutingShowtCtlVar); - LIB_FUNCTION("eszLdtIMfQE", "libSceNet", 1, "libSceNet", sceNetConfigRoutingStart); - LIB_FUNCTION("toi8xxcSfJ0", "libSceNet", 1, "libSceNet", sceNetConfigRoutingStop); - LIB_FUNCTION("EAl7xvi7nXg", "libSceNet", 1, "libSceNet", sceNetConfigSetDefaultRoute); - LIB_FUNCTION("4zLOHbt3UFk", "libSceNet", 1, "libSceNet", sceNetConfigSetDefaultRoute6); - LIB_FUNCTION("yaVAdLDxUj0", "libSceNet", 1, "libSceNet", sceNetConfigSetDefaultScope); - LIB_FUNCTION("8Kh+1eidI3c", "libSceNet", 1, "libSceNet", sceNetConfigSetIfaddr); - LIB_FUNCTION("QJbV3vfBQ8Q", "libSceNet", 1, "libSceNet", sceNetConfigSetIfaddr6); - LIB_FUNCTION("POrSEl8zySw", "libSceNet", 1, "libSceNet", sceNetConfigSetIfaddr6WithFlags); - LIB_FUNCTION("0sesmAYH3Lk", "libSceNet", 1, "libSceNet", sceNetConfigSetIfFlags); - LIB_FUNCTION("uNTluLfYgS8", "libSceNet", 1, "libSceNet", sceNetConfigSetIfLinkLocalAddr6); - LIB_FUNCTION("s31rYkpIMMQ", "libSceNet", 1, "libSceNet", sceNetConfigSetIfmtu); - LIB_FUNCTION("tvdzQkm+UaY", "libSceNet", 1, "libSceNet", sceNetConfigUnsetIfFlags); - LIB_FUNCTION("oGEBX0eXGFs", "libSceNet", 1, "libSceNet", sceNetConfigUpInterface); - LIB_FUNCTION("6HNbayHPL7c", "libSceNet", 1, "libSceNet", sceNetConfigUpInterfaceWithFlags); - LIB_FUNCTION("6A6EweB3Dto", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocClearWakeOnWlan); - LIB_FUNCTION("ZLdJyQJUMkM", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocCreate); - LIB_FUNCTION("Yr3UeApLWTY", "libSceNet", 1, "libSceNet", - sceNetConfigWlanAdhocGetWakeOnWlanInfo); - LIB_FUNCTION("Xma8yHmV+TQ", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocJoin); - LIB_FUNCTION("K4o48GTNbSc", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocLeave); - LIB_FUNCTION("ZvKgNrrLCCQ", "libSceNet", 1, "libSceNet", - sceNetConfigWlanAdhocPspEmuClearWakeOnWlan); - LIB_FUNCTION("1j4DZ5dXbeQ", "libSceNet", 1, "libSceNet", - sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo); - LIB_FUNCTION("C-+JPjaEhdA", "libSceNet", 1, "libSceNet", - sceNetConfigWlanAdhocPspEmuSetWakeOnWlan); - LIB_FUNCTION("7xYdUWg1WdY", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocScanJoin); - LIB_FUNCTION("Q7ee2Uav5f8", "libSceNet", 1, "libSceNet", - sceNetConfigWlanAdhocSetExtInfoElement); - LIB_FUNCTION("xaOTiuxIQNY", "libSceNet", 1, "libSceNet", sceNetConfigWlanAdhocSetWakeOnWlan); - LIB_FUNCTION("QlRJWya+dtE", "libSceNet", 1, "libSceNet", sceNetConfigWlanApStart); - LIB_FUNCTION("6uYcvVjH7Ms", "libSceNet", 1, "libSceNet", sceNetConfigWlanApStop); - LIB_FUNCTION("MDbg-oAj8Aw", "libSceNet", 1, "libSceNet", sceNetConfigWlanBackgroundScanQuery); - LIB_FUNCTION("cMA8f6jI6s0", "libSceNet", 1, "libSceNet", sceNetConfigWlanBackgroundScanStart); - LIB_FUNCTION("3T5aIe-7L84", "libSceNet", 1, "libSceNet", sceNetConfigWlanBackgroundScanStop); - LIB_FUNCTION("+3KMyS93TOs", "libSceNet", 1, "libSceNet", sceNetConfigWlanDiagGetDeviceInfo); - LIB_FUNCTION("9oiOWQ5FMws", "libSceNet", 1, "libSceNet", sceNetConfigWlanDiagSetAntenna); - LIB_FUNCTION("fHr45B97n0U", "libSceNet", 1, "libSceNet", sceNetConfigWlanDiagSetTxFixedRate); - LIB_FUNCTION("PNDDxnqqtk4", "libSceNet", 1, "libSceNet", sceNetConfigWlanGetDeviceConfig); - LIB_FUNCTION("Pkx0lwWVzmQ", "libSceNet", 1, "libSceNet", sceNetConfigWlanInfraGetRssiInfo); - LIB_FUNCTION("IkBCxG+o4Nk", "libSceNet", 1, "libSceNet", sceNetConfigWlanInfraLeave); - LIB_FUNCTION("273-I-zD8+8", "libSceNet", 1, "libSceNet", sceNetConfigWlanInfraScanJoin); - LIB_FUNCTION("-Mi5hNiWC4c", "libSceNet", 1, "libSceNet", sceNetConfigWlanScan); - LIB_FUNCTION("U1q6DrPbY6k", "libSceNet", 1, "libSceNet", sceNetConfigWlanSetDeviceConfig); - LIB_FUNCTION("OXXX4mUk3uk", "libSceNet", 1, "libSceNet", sceNetConnect); - LIB_FUNCTION("lDTIbqNs0ps", "libSceNet", 1, "libSceNet", sceNetControl); - LIB_FUNCTION("Q6T-zIblNqk", "libSceNet", 1, "libSceNet", sceNetDhcpdStart); - LIB_FUNCTION("xwWm8jzrpeM", "libSceNet", 1, "libSceNet", sceNetDhcpdStop); - LIB_FUNCTION("KhQxhlEslo0", "libSceNet", 1, "libSceNet", sceNetDhcpGetAutoipInfo); - LIB_FUNCTION("ix4LWXd12F0", "libSceNet", 1, "libSceNet", sceNetDhcpGetInfo); - LIB_FUNCTION("DrZuCQDnm3w", "libSceNet", 1, "libSceNet", sceNetDhcpGetInfoEx); - LIB_FUNCTION("Wzv6dngR-DQ", "libSceNet", 1, "libSceNet", sceNetDhcpStart); - LIB_FUNCTION("6AN7OlSMWk0", "libSceNet", 1, "libSceNet", sceNetDhcpStop); - LIB_FUNCTION("+ezgWao0wo8", "libSceNet", 1, "libSceNet", sceNetDumpAbort); - LIB_FUNCTION("bghgkeLKq1Q", "libSceNet", 1, "libSceNet", sceNetDumpCreate); - LIB_FUNCTION("xZ54Il-u1vs", "libSceNet", 1, "libSceNet", sceNetDumpDestroy); - LIB_FUNCTION("YWTpt45PxbI", "libSceNet", 1, "libSceNet", sceNetDumpRead); - LIB_FUNCTION("TwjkDIPdZ1Q", "libSceNet", 1, "libSceNet", sceNetDuplicateIpStart); - LIB_FUNCTION("QCbvCx9HL30", "libSceNet", 1, "libSceNet", sceNetDuplicateIpStop); - LIB_FUNCTION("w21YgGGNtBk", "libSceNet", 1, "libSceNet", sceNetEpollAbort); - LIB_FUNCTION("ZVw46bsasAk", "libSceNet", 1, "libSceNet", sceNetEpollControl); - LIB_FUNCTION("SF47kB2MNTo", "libSceNet", 1, "libSceNet", sceNetEpollCreate); - LIB_FUNCTION("Inp1lfL+Jdw", "libSceNet", 1, "libSceNet", sceNetEpollDestroy); - LIB_FUNCTION("drjIbDbA7UQ", "libSceNet", 1, "libSceNet", sceNetEpollWait); - LIB_FUNCTION("HQOwnfMGipQ", "libSceNet", 1, "libSceNet", sceNetErrnoLoc); - LIB_FUNCTION("v6M4txecCuo", "libSceNet", 1, "libSceNet", sceNetEtherNtostr); - LIB_FUNCTION("b-bFZvNV59I", "libSceNet", 1, "libSceNet", sceNetEtherStrton); - LIB_FUNCTION("cWGGXoeZUzA", "libSceNet", 1, "libSceNet", sceNetEventCallbackCreate); - LIB_FUNCTION("jzP0MoZpYnI", "libSceNet", 1, "libSceNet", sceNetEventCallbackDestroy); - LIB_FUNCTION("tB3BB8AsrjU", "libSceNet", 1, "libSceNet", sceNetEventCallbackGetError); - LIB_FUNCTION("5isaotjMWlA", "libSceNet", 1, "libSceNet", sceNetEventCallbackWaitCB); - LIB_FUNCTION("2ee14ktE1lw", "libSceNet", 1, "libSceNet", sceNetFreeAllRouteInfo); - LIB_FUNCTION("q8j9OSdnN1Y", "libSceNet", 1, "libSceNet", sceNetGetArpInfo); - LIB_FUNCTION("wmoIm94hqik", "libSceNet", 1, "libSceNet", sceNetGetDns6Info); - LIB_FUNCTION("nCL0NyZsd5A", "libSceNet", 1, "libSceNet", sceNetGetDnsInfo); - LIB_FUNCTION("HoV-GJyx7YY", "libSceNet", 1, "libSceNet", sceNetGetIfList); - LIB_FUNCTION("ahiOMqoYYMc", "libSceNet", 1, "libSceNet", sceNetGetIfListOnce); - LIB_FUNCTION("0MT2l3uIX7c", "libSceNet", 1, "libSceNet", sceNetGetIfName); - LIB_FUNCTION("5lrSEHdqyos", "libSceNet", 1, "libSceNet", sceNetGetIfnameNumList); - LIB_FUNCTION("6Oc0bLsIYe0", "libSceNet", 1, "libSceNet", sceNetGetMacAddress); - LIB_FUNCTION("rMyh97BU5pY", "libSceNet", 1, "libSceNet", sceNetGetMemoryPoolStats); - LIB_FUNCTION("+S-2-jlpaBo", "libSceNet", 1, "libSceNet", sceNetGetNameToIndex); - LIB_FUNCTION("TCkRD0DWNLg", "libSceNet", 1, "libSceNet", sceNetGetpeername); - LIB_FUNCTION("G3O2j9f5z00", "libSceNet", 1, "libSceNet", sceNetGetRandom); - LIB_FUNCTION("6Nx1hIQL9h8", "libSceNet", 1, "libSceNet", sceNetGetRouteInfo); - LIB_FUNCTION("hLuXdjHnhiI", "libSceNet", 1, "libSceNet", sceNetGetSockInfo); - LIB_FUNCTION("Cidi9Y65mP8", "libSceNet", 1, "libSceNet", sceNetGetSockInfo6); - LIB_FUNCTION("hoOAofhhRvE", "libSceNet", 1, "libSceNet", sceNetGetsockname); - LIB_FUNCTION("xphrZusl78E", "libSceNet", 1, "libSceNet", sceNetGetsockopt); - LIB_FUNCTION("GA5ZDaLtUBE", "libSceNet", 1, "libSceNet", sceNetGetStatisticsInfo); - LIB_FUNCTION("9mIcUExH34w", "libSceNet", 1, "libSceNet", sceNetGetStatisticsInfoInternal); - LIB_FUNCTION("p2vxsE2U3RQ", "libSceNet", 1, "libSceNet", sceNetGetSystemTime); - LIB_FUNCTION("9T2pDF2Ryqg", "libSceNet", 1, "libSceNet", sceNetHtonl); - LIB_FUNCTION("3CHi1K1wsCQ", "libSceNet", 1, "libSceNet", sceNetHtonll); - LIB_FUNCTION("iWQWrwiSt8A", "libSceNet", 1, "libSceNet", sceNetHtons); - LIB_FUNCTION("9vA2aW+CHuA", "libSceNet", 1, "libSceNet", sceNetInetNtop); - LIB_FUNCTION("Eh+Vqkrrc00", "libSceNet", 1, "libSceNet", sceNetInetNtopWithScopeId); - LIB_FUNCTION("8Kcp5d-q1Uo", "libSceNet", 1, "libSceNet", sceNetInetPton); - LIB_FUNCTION("Xn2TA2QhxHc", "libSceNet", 1, "libSceNet", sceNetInetPtonEx); - LIB_FUNCTION("b+LixqREH6A", "libSceNet", 1, "libSceNet", sceNetInetPtonWithScopeId); - LIB_FUNCTION("cYW1ISGlOmo", "libSceNet", 1, "libSceNet", sceNetInfoDumpStart); - LIB_FUNCTION("XfV-XBCuhDo", "libSceNet", 1, "libSceNet", sceNetInfoDumpStop); - LIB_FUNCTION("Nlev7Lg8k3A", "libSceNet", 1, "libSceNet", sceNetInit); - LIB_FUNCTION("6MojQ8uFHEI", "libSceNet", 1, "libSceNet", sceNetInitParam); - LIB_FUNCTION("ghqRRVQxqKo", "libSceNet", 1, "libSceNet", sceNetIoctl); - LIB_FUNCTION("kOj1HiAGE54", "libSceNet", 1, "libSceNet", sceNetListen); - LIB_FUNCTION("HKIa-WH0AZ4", "libSceNet", 1, "libSceNet", sceNetMemoryAllocate); - LIB_FUNCTION("221fvqVs+sQ", "libSceNet", 1, "libSceNet", sceNetMemoryFree); - LIB_FUNCTION("pQGpHYopAIY", "libSceNet", 1, "libSceNet", sceNetNtohl); - LIB_FUNCTION("tOrRi-v3AOM", "libSceNet", 1, "libSceNet", sceNetNtohll); - LIB_FUNCTION("Rbvt+5Y2iEw", "libSceNet", 1, "libSceNet", sceNetNtohs); - LIB_FUNCTION("dgJBaeJnGpo", "libSceNet", 1, "libSceNet", sceNetPoolCreate); - LIB_FUNCTION("K7RlrTkI-mw", "libSceNet", 1, "libSceNet", sceNetPoolDestroy); - LIB_FUNCTION("QGOqGPnk5a4", "libSceNet", 1, "libSceNet", sceNetPppoeStart); - LIB_FUNCTION("FIV95WE1EuE", "libSceNet", 1, "libSceNet", sceNetPppoeStop); - LIB_FUNCTION("9wO9XrMsNhc", "libSceNet", 1, "libSceNet", sceNetRecv); - LIB_FUNCTION("304ooNZxWDY", "libSceNet", 1, "libSceNet", sceNetRecvfrom); - LIB_FUNCTION("wvuUDv0jrMI", "libSceNet", 1, "libSceNet", sceNetRecvmsg); - LIB_FUNCTION("AzqoBha7js4", "libSceNet", 1, "libSceNet", sceNetResolverAbort); - LIB_FUNCTION("JQk8ck8vnPY", "libSceNet", 1, "libSceNet", sceNetResolverConnect); - LIB_FUNCTION("bonnMiDoOZg", "libSceNet", 1, "libSceNet", sceNetResolverConnectAbort); - LIB_FUNCTION("V5q6gvEJpw4", "libSceNet", 1, "libSceNet", sceNetResolverConnectCreate); - LIB_FUNCTION("QFPjG6rqeZg", "libSceNet", 1, "libSceNet", sceNetResolverConnectDestroy); - LIB_FUNCTION("C4UgDHHPvdw", "libSceNet", 1, "libSceNet", sceNetResolverCreate); - LIB_FUNCTION("kJlYH5uMAWI", "libSceNet", 1, "libSceNet", sceNetResolverDestroy); - LIB_FUNCTION("J5i3hiLJMPk", "libSceNet", 1, "libSceNet", sceNetResolverGetError); - LIB_FUNCTION("Apb4YDxKsRI", "libSceNet", 1, "libSceNet", sceNetResolverStartAton); - LIB_FUNCTION("zvzWA5IZMsg", "libSceNet", 1, "libSceNet", sceNetResolverStartAton6); - LIB_FUNCTION("Nd91WaWmG2w", "libSceNet", 1, "libSceNet", sceNetResolverStartNtoa); - LIB_FUNCTION("zl35YNs9jnI", "libSceNet", 1, "libSceNet", sceNetResolverStartNtoa6); - LIB_FUNCTION("RCCY01Xd+58", "libSceNet", 1, "libSceNet", - sceNetResolverStartNtoaMultipleRecords); - LIB_FUNCTION("sT4nBQKUPqM", "libSceNet", 1, "libSceNet", - sceNetResolverStartNtoaMultipleRecordsEx); - LIB_FUNCTION("beRjXBn-z+o", "libSceNet", 1, "libSceNet", sceNetSend); - LIB_FUNCTION("2eKbgcboJso", "libSceNet", 1, "libSceNet", sceNetSendmsg); - LIB_FUNCTION("gvD1greCu0A", "libSceNet", 1, "libSceNet", sceNetSendto); - LIB_FUNCTION("15Ywg-ZsSl0", "libSceNet", 1, "libSceNet", sceNetSetDns6Info); - LIB_FUNCTION("E3oH1qsdqCA", "libSceNet", 1, "libSceNet", sceNetSetDns6InfoToKernel); - LIB_FUNCTION("B-M6KjO8-+w", "libSceNet", 1, "libSceNet", sceNetSetDnsInfo); - LIB_FUNCTION("8s+T0bJeyLQ", "libSceNet", 1, "libSceNet", sceNetSetDnsInfoToKernel); - LIB_FUNCTION("2mKX2Spso7I", "libSceNet", 1, "libSceNet", sceNetSetsockopt); - LIB_FUNCTION("k1V1djYpk7k", "libSceNet", 1, "libSceNet", sceNetShowIfconfig); - LIB_FUNCTION("j6pkkO2zJtg", "libSceNet", 1, "libSceNet", sceNetShowIfconfigForBuffer); - LIB_FUNCTION("E8dTcvQw3hg", "libSceNet", 1, "libSceNet", sceNetShowIfconfigWithMemory); - LIB_FUNCTION("WxislcDAW5I", "libSceNet", 1, "libSceNet", sceNetShowNetstat); - LIB_FUNCTION("rX30iWQqqzg", "libSceNet", 1, "libSceNet", sceNetShowNetstatEx); - LIB_FUNCTION("vjwKTGa21f0", "libSceNet", 1, "libSceNet", sceNetShowNetstatExForBuffer); - LIB_FUNCTION("mqoB+LN0pW8", "libSceNet", 1, "libSceNet", sceNetShowNetstatForBuffer); - LIB_FUNCTION("H5WHYRfDkR0", "libSceNet", 1, "libSceNet", sceNetShowNetstatWithMemory); - LIB_FUNCTION("tk0p0JmiBkM", "libSceNet", 1, "libSceNet", sceNetShowPolicy); - LIB_FUNCTION("dbrSNEuZfXI", "libSceNet", 1, "libSceNet", sceNetShowPolicyWithMemory); - LIB_FUNCTION("cEMX1VcPpQ8", "libSceNet", 1, "libSceNet", sceNetShowRoute); - LIB_FUNCTION("fCa7-ihdRdc", "libSceNet", 1, "libSceNet", sceNetShowRoute6); - LIB_FUNCTION("nTJqXsbSS1I", "libSceNet", 1, "libSceNet", sceNetShowRoute6ForBuffer); - LIB_FUNCTION("TCZyE2YI1uM", "libSceNet", 1, "libSceNet", sceNetShowRoute6WithMemory); - LIB_FUNCTION("n-IAZb7QB1Y", "libSceNet", 1, "libSceNet", sceNetShowRouteForBuffer); - LIB_FUNCTION("0-XSSp1kEFM", "libSceNet", 1, "libSceNet", sceNetShowRouteWithMemory); - LIB_FUNCTION("TSM6whtekok", "libSceNet", 1, "libSceNet", sceNetShutdown); - LIB_FUNCTION("Q4qBuN-c0ZM", "libSceNet", 1, "libSceNet", sceNetSocket); - LIB_FUNCTION("zJGf8xjFnQE", "libSceNet", 1, "libSceNet", sceNetSocketAbort); - LIB_FUNCTION("45ggEzakPJQ", "libSceNet", 1, "libSceNet", sceNetSocketClose); - LIB_FUNCTION("6AJE2jKg-c0", "libSceNet", 1, "libSceNet", sceNetSyncCreate); - LIB_FUNCTION("atGfzCaXMak", "libSceNet", 1, "libSceNet", sceNetSyncDestroy); - LIB_FUNCTION("sAleh-BoxLA", "libSceNet", 1, "libSceNet", sceNetSyncGet); - LIB_FUNCTION("Z-8Jda650Vk", "libSceNet", 1, "libSceNet", sceNetSyncSignal); - LIB_FUNCTION("NP5gxDeYhIM", "libSceNet", 1, "libSceNet", sceNetSyncWait); - LIB_FUNCTION("3zRdT3O2Kxo", "libSceNet", 1, "libSceNet", sceNetSysctl); - LIB_FUNCTION("cTGkc6-TBlI", "libSceNet", 1, "libSceNet", sceNetTerm); - LIB_FUNCTION("j-Op3ibRJaQ", "libSceNet", 1, "libSceNet", sceNetThreadCreate); - LIB_FUNCTION("KirVfZbqniw", "libSceNet", 1, "libSceNet", sceNetThreadExit); - LIB_FUNCTION("pRbEzaV30qI", "libSceNet", 1, "libSceNet", sceNetThreadJoin); - LIB_FUNCTION("bjrzRLFali0", "libSceNet", 1, "libSceNet", sceNetUsleep); - LIB_FUNCTION("DnB6WJ91HGg", "libSceNet", 1, "libSceNet", Func_0E707A589F751C68); - LIB_FUNCTION("JK1oZe4UysY", "libSceNetDebug", 1, "libSceNet", sceNetEmulationGet); - LIB_FUNCTION("pfn3Fha1ydc", "libSceNetDebug", 1, "libSceNet", sceNetEmulationSet); -}; - -} // namespace Libraries::Net diff --git a/src/core/libraries/network/net.h b/src/core/libraries/network/net.h deleted file mode 100644 index f8f11e67d..000000000 --- a/src/core/libraries/network/net.h +++ /dev/null @@ -1,560 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/assert.h" -#include "common/types.h" -#include "netctl.h" - -namespace Core::Loader { -class SymbolsResolver; -} - -// Define our own htonll and ntohll because its not available in some systems/platforms -#ifndef HTONLL -#define HTONLL(x) (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32)) -#endif -#ifndef NTOHLL -#define NTOHLL(x) (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32)) -#endif - -namespace Libraries::Net { - -static int ConvertFamilies(int family); - -enum OrbisNetFamily : u32 { - ORBIS_NET_AF_UNIX = 1, - ORBIS_NET_AF_INET = 2, - ORBIS_NET_AF_INET6 = 28, -}; -enum OrbisNetSocketType : u32 { - ORBIS_NET_SOCK_STREAM = 1, - ORBIS_NET_SOCK_DGRAM = 2, - ORBIS_NET_SOCK_RAW = 3, - ORBIS_NET_SOCK_DGRAM_P2P = 6, - ORBIS_NET_SOCK_STREAM_P2P = 10 -}; - -enum OrbisNetProtocol : u32 { - ORBIS_NET_IPPROTO_IP = 0, - ORBIS_NET_IPPROTO_ICMP = 1, - ORBIS_NET_IPPROTO_IGMP = 2, - ORBIS_NET_IPPROTO_TCP = 6, - ORBIS_NET_IPPROTO_UDP = 17, - ORBIS_NET_IPPROTO_IPV6 = 41, - ORBIS_NET_SOL_SOCKET = 0xFFFF -}; - -constexpr std::string_view NameOf(OrbisNetProtocol p) { - switch (p) { - case ORBIS_NET_IPPROTO_IP: - return "ORBIS_NET_IPPROTO_IP"; - case ORBIS_NET_IPPROTO_ICMP: - return "ORBIS_NET_IPPROTO_ICMP"; - case ORBIS_NET_IPPROTO_IGMP: - return "ORBIS_NET_IPPROTO_IGMP"; - case ORBIS_NET_IPPROTO_TCP: - return "ORBIS_NET_IPPROTO_TCP"; - case ORBIS_NET_IPPROTO_UDP: - return "ORBIS_NET_IPPROTO_UDP"; - case ORBIS_NET_IPPROTO_IPV6: - return "ORBIS_NET_IPPROTO_IPV6"; - case ORBIS_NET_SOL_SOCKET: - return "ORBIS_NET_SOL_SOCKET"; - default: - UNREACHABLE_MSG("{}", (u32)p); - } -} - -enum OrbisNetSocketIpOption : u32 { - ORBIS_NET_IP_HDRINCL = 2, - ORBIS_NET_IP_TOS = 3, - ORBIS_NET_IP_TTL = 4, - ORBIS_NET_IP_MULTICAST_IF = 9, - ORBIS_NET_IP_MULTICAST_TTL = 10, - ORBIS_NET_IP_MULTICAST_LOOP = 11, - ORBIS_NET_IP_ADD_MEMBERSHIP = 12, - ORBIS_NET_IP_DROP_MEMBERSHIP = 13, - ORBIS_NET_IP_TTLCHK = 30, - ORBIS_NET_IP_MAXTTL = 31, -}; - -enum OrbisNetSocketTcpOption : u32 { - ORBIS_NET_TCP_NODELAY = 1, - ORBIS_NET_TCP_MAXSEG = 2, - ORBIS_NET_TCP_MSS_TO_ADVERTISE = 3, -}; - -enum OrbisNetSocketSoOption : u32 { - ORBIS_NET_SO_REUSEADDR = 0x00000004, - ORBIS_NET_SO_KEEPALIVE = 0x00000008, - ORBIS_NET_SO_BROADCAST = 0x00000020, - ORBIS_NET_SO_LINGER = 0x00000080, - ORBIS_NET_SO_REUSEPORT = 0x00000200, - ORBIS_NET_SO_ONESBCAST = 0x00010000, - ORBIS_NET_SO_USECRYPTO = 0x00020000, - ORBIS_NET_SO_USESIGNATURE = 0x00040000, - ORBIS_NET_SO_SNDBUF = 0x1001, - ORBIS_NET_SO_RCVBUF = 0x1002, - ORBIS_NET_SO_ERROR = 0x1007, - ORBIS_NET_SO_TYPE = 0x1008, - ORBIS_NET_SO_SNDTIMEO = 0x1105, - ORBIS_NET_SO_RCVTIMEO = 0x1106, - ORBIS_NET_SO_ERROR_EX = 0x1107, - ORBIS_NET_SO_ACCEPTTIMEO = 0x1108, - ORBIS_NET_SO_CONNECTTIMEO = 0x1109, - ORBIS_NET_SO_NBIO = 0x1200, - ORBIS_NET_SO_POLICY = 0x1201, - ORBIS_NET_SO_NAME = 0x1202, - ORBIS_NET_SO_PRIORITY = 0x1203 -}; - -enum OrbisNetFlags : u32 { - ORBIS_NET_MSG_PEEK = 0x00000002, - ORBIS_NET_MSG_WAITALL = 0x00000040, - ORBIS_NET_MSG_DONTWAIT = 0x00000080, - ORBIS_NET_MSG_USECRYPTO = 0x00100000, - ORBIS_NET_MSG_USESIGNATURE = 0x00200000, - ORBIS_NET_MSG_PEEKLEN = (0x00400000 | ORBIS_NET_MSG_PEEK) -}; - -constexpr std::string_view NameOf(OrbisNetSocketSoOption o) { - switch (o) { - case ORBIS_NET_SO_REUSEADDR: - return "ORBIS_NET_SO_REUSEADDR"; - case ORBIS_NET_SO_KEEPALIVE: - return "ORBIS_NET_SO_KEEPALIVE"; - case ORBIS_NET_SO_BROADCAST: - return "ORBIS_NET_SO_BROADCAST"; - case ORBIS_NET_SO_LINGER: - return "ORBIS_NET_SO_LINGER"; - case ORBIS_NET_SO_REUSEPORT: - return "ORBIS_NET_SO_REUSEPORT"; - case ORBIS_NET_SO_ONESBCAST: - return "ORBIS_NET_SO_ONESBCAST"; - case ORBIS_NET_SO_USECRYPTO: - return "ORBIS_NET_SO_USECRYPTO"; - case ORBIS_NET_SO_USESIGNATURE: - return "ORBIS_NET_SO_USESIGNATURE"; - case ORBIS_NET_SO_SNDBUF: - return "ORBIS_NET_SO_SNDBUF"; - case ORBIS_NET_SO_RCVBUF: - return "ORBIS_NET_SO_RCVBUF"; - case ORBIS_NET_SO_ERROR: - return "ORBIS_NET_SO_ERROR"; - case ORBIS_NET_SO_TYPE: - return "ORBIS_NET_SO_TYPE"; - case ORBIS_NET_SO_SNDTIMEO: - return "ORBIS_NET_SO_SNDTIMEO"; - case ORBIS_NET_SO_RCVTIMEO: - return "ORBIS_NET_SO_RCVTIMEO"; - case ORBIS_NET_SO_ERROR_EX: - return "ORBIS_NET_SO_ERROR_EX"; - case ORBIS_NET_SO_ACCEPTTIMEO: - return "ORBIS_NET_SO_ACCEPTTIMEO"; - case ORBIS_NET_SO_CONNECTTIMEO: - return "ORBIS_NET_SO_CONNECTTIMEO"; - case ORBIS_NET_SO_NBIO: - return "ORBIS_NET_SO_NBIO"; - case ORBIS_NET_SO_POLICY: - return "ORBIS_NET_SO_POLICY"; - case ORBIS_NET_SO_NAME: - return "ORBIS_NET_SO_NAME"; - case ORBIS_NET_SO_PRIORITY: - return "ORBIS_NET_SO_PRIORITY"; - default: - UNREACHABLE_MSG("{}", (u32)o); - } -} - -enum OrbisNetEpollFlag : u32 { - ORBIS_NET_EPOLL_CTL_ADD = 1, - ORBIS_NET_EPOLL_CTL_MOD = 2, - ORBIS_NET_EPOLL_CTL_DEL = 3, -}; - -enum OrbisNetEpollEvents : u32 { - ORBIS_NET_EPOLLIN = 0x1, - ORBIS_NET_EPOLLOUT = 0x2, - ORBIS_NET_EPOLLERR = 0x8, - ORBIS_NET_EPOLLHUP = 0x10, - ORBIS_NET_EPOLLDESCID = 0x10000, -}; - -enum OrbisNetResolverFlag : u32 { - ORBIS_NET_RESOLVER_ASYNC = 0x1, - ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS = 0x10000, -}; - -using OrbisNetId = s32; - -struct OrbisNetSockaddr { - u8 sa_len; - u8 sa_family; - char sa_data[14]; -}; - -struct OrbisNetSockaddrIn { - u8 sin_len; - u8 sin_family; - u16 sin_port; - u32 sin_addr; - u16 sin_vport; - char sin_zero[6]; -}; - -using OrbisNetInAddr_t = u32; - -struct OrbisNetInAddr { - OrbisNetInAddr_t inaddr_addr; -}; - -struct OrbisNetSockaddrUn { - u8 sun_len; - u8 sun_family; // AF_UNIX - char sun_path[104]; -}; - -struct OrbisNetIovec { - void* iov_base; - u64 iov_len; -}; - -struct OrbisNetMsghdr { - void* msg_name; - u32 msg_namelen; - OrbisNetIovec* msg_iov; - int msg_iovlen; - void* msg_control; - u32 msg_controllen; - int msg_flags; -}; - -union OrbisNetEpollData { - void* ptr; - u32 data_u32; - int fd; - u64 data_u64; -}; - -struct OrbisNetEpollEvent { - u32 events; - u32 pad; - u64 ident; - OrbisNetEpollData data; -}; - -union OrbisNetAddrUnion { - OrbisNetInAddr addr; - u8 addr6[16]; -}; - -struct OrbisNetResolverAddr { - OrbisNetAddrUnion u; - u32 af; - u32 pad[3]; -}; - -struct OrbisNetResolverInfo { - OrbisNetResolverAddr addrs[10]; - u32 records; - u32 recordsv4; - u32 pad[14]; -}; - -constexpr int ORBIS_NET_DEBUG_NAME_LEN_MAX = 31; - -enum OrbisNetSockInfoState : s32 { - ORBIS_NET_SOCKINFO_STATE_UNKNOWN = 0, - ORBIS_NET_SOCKINFO_STATE_CLOSED = 1, - ORBIS_NET_SOCKINFO_STATE_OPENED = 2, - ORBIS_NET_SOCKINFO_STATE_LISTEN = 3, - ORBIS_NET_SOCKINFO_STATE_SYN_SENT = 4, - ORBIS_NET_SOCKINFO_STATE_SYN_RECEIVED = 5, - ORBIS_NET_SOCKINFO_STATE_ESTABLISHED = 6, - ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_1 = 7, - ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_2 = 8, - ORBIS_NET_SOCKINFO_STATE_CLOSE_WAIT = 9, - ORBIS_NET_SOCKINFO_STATE_CLOSING = 10, - ORBIS_NET_SOCKINFO_STATE_LAST_ACK = 11, - ORBIS_NET_SOCKINFO_STATE_TIME_WAIT = 12, -}; - -enum OrbisNetSockInfoFlags : s32 { - ORBIS_NET_SOCKINFO_F_SELF = 0x00000001, - ORBIS_NET_SOCKINFO_F_KERNEL = 0x00000002, - ORBIS_NET_SOCKINFO_F_OTHERS = 0x00000004, - ORBIS_NET_SOCKINFO_F_RECV_WAIT = 0x00010000, - ORBIS_NET_SOCKINFO_F_SEND_WAIT = 0x00020000, - ORBIS_NET_SOCKINFO_F_RECV_EWAIT = 0x00040000, - ORBIS_NET_SOCKINFO_F_SEND_EWAIT = 0x00080000, - ORBIS_NET_SOCKINFO_F_NONBLOCK = 0x00200000, - ORBIS_NET_SOCKINFO_F_ALL = 0x006F0007, -}; - -constexpr int ORBIS_NET_SOCKINFO_TIME_WAIT = 0x00000040; - -struct OrbisNetSockInfo { - char name[ORBIS_NET_DEBUG_NAME_LEN_MAX + 1]; - s32 pid; - OrbisNetId s; - s8 socket_type; - s8 policy; - s8 priority; - s8 reserved8; - s32 recv_queue_length; - s32 send_queue_length; - OrbisNetInAddr local_adr; - OrbisNetInAddr remote_adr; - u16 local_port; - u16 remote_port; - u16 local_vport; - u16 remote_vport; - s32 state; - s32 flags; - s32 tx_bps; - s32 rx_bps; - s32 max_tx_bps; - s32 max_rx_bps; - s32 tx_vbps; - s32 rx_vbps; - s32 recv_buffer_size; - s32 send_buffer_size; - s32 reserved6[8]; - s32 tx_drops; - s32 rx_drops; - s32 tx_wait; - s32 reserved[2]; -}; - -int PS4_SYSV_ABI in6addr_any(); -int PS4_SYSV_ABI in6addr_loopback(); -int PS4_SYSV_ABI sce_net_dummy(); -int PS4_SYSV_ABI sce_net_in6addr_any(); -int PS4_SYSV_ABI sce_net_in6addr_linklocal_allnodes(); -int PS4_SYSV_ABI sce_net_in6addr_linklocal_allrouters(); -int PS4_SYSV_ABI sce_net_in6addr_loopback(); -int PS4_SYSV_ABI sce_net_in6addr_nodelocal_allnodes(); -OrbisNetId PS4_SYSV_ABI sceNetAccept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen); -int PS4_SYSV_ABI sceNetAddrConfig6GetInfo(); -int PS4_SYSV_ABI sceNetAddrConfig6Start(); -int PS4_SYSV_ABI sceNetAddrConfig6Stop(); -int PS4_SYSV_ABI sceNetAllocateAllRouteInfo(); -int PS4_SYSV_ABI sceNetBandwidthControlGetDataTraffic(); -int PS4_SYSV_ABI sceNetBandwidthControlGetDefaultParam(); -int PS4_SYSV_ABI sceNetBandwidthControlGetIfParam(); -int PS4_SYSV_ABI sceNetBandwidthControlGetPolicy(); -int PS4_SYSV_ABI sceNetBandwidthControlSetDefaultParam(); -int PS4_SYSV_ABI sceNetBandwidthControlSetIfParam(); -int PS4_SYSV_ABI sceNetBandwidthControlSetPolicy(); -int PS4_SYSV_ABI sceNetBind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen); -int PS4_SYSV_ABI sceNetClearDnsCache(); -int PS4_SYSV_ABI sceNetConfigAddArp(); -int PS4_SYSV_ABI sceNetConfigAddArpWithInterface(); -int PS4_SYSV_ABI sceNetConfigAddIfaddr(); -int PS4_SYSV_ABI sceNetConfigAddMRoute(); -int PS4_SYSV_ABI sceNetConfigAddRoute(); -int PS4_SYSV_ABI sceNetConfigAddRoute6(); -int PS4_SYSV_ABI sceNetConfigAddRouteWithInterface(); -int PS4_SYSV_ABI sceNetConfigCleanUpAllInterfaces(); -int PS4_SYSV_ABI sceNetConfigDelArp(); -int PS4_SYSV_ABI sceNetConfigDelArpWithInterface(); -int PS4_SYSV_ABI sceNetConfigDelDefaultRoute(); -int PS4_SYSV_ABI sceNetConfigDelDefaultRoute6(); -int PS4_SYSV_ABI sceNetConfigDelIfaddr(); -int PS4_SYSV_ABI sceNetConfigDelIfaddr6(); -int PS4_SYSV_ABI sceNetConfigDelMRoute(); -int PS4_SYSV_ABI sceNetConfigDelRoute(); -int PS4_SYSV_ABI sceNetConfigDelRoute6(); -int PS4_SYSV_ABI sceNetConfigDownInterface(); -int PS4_SYSV_ABI sceNetConfigEtherGetLinkMode(); -int PS4_SYSV_ABI sceNetConfigEtherPostPlugInOutEvent(); -int PS4_SYSV_ABI sceNetConfigEtherSetLinkMode(); -int PS4_SYSV_ABI sceNetConfigFlushRoute(); -int PS4_SYSV_ABI sceNetConfigGetDefaultRoute(); -int PS4_SYSV_ABI sceNetConfigGetDefaultRoute6(); -int PS4_SYSV_ABI sceNetConfigGetIfaddr(); -int PS4_SYSV_ABI sceNetConfigGetIfaddr6(); -int PS4_SYSV_ABI sceNetConfigRoutingShowRoutingConfig(); -int PS4_SYSV_ABI sceNetConfigRoutingShowtCtlVar(); -int PS4_SYSV_ABI sceNetConfigRoutingStart(); -int PS4_SYSV_ABI sceNetConfigRoutingStop(); -int PS4_SYSV_ABI sceNetConfigSetDefaultRoute(); -int PS4_SYSV_ABI sceNetConfigSetDefaultRoute6(); -int PS4_SYSV_ABI sceNetConfigSetDefaultScope(); -int PS4_SYSV_ABI sceNetConfigSetIfaddr(); -int PS4_SYSV_ABI sceNetConfigSetIfaddr6(); -int PS4_SYSV_ABI sceNetConfigSetIfaddr6WithFlags(); -int PS4_SYSV_ABI sceNetConfigSetIfFlags(); -int PS4_SYSV_ABI sceNetConfigSetIfLinkLocalAddr6(); -int PS4_SYSV_ABI sceNetConfigSetIfmtu(); -int PS4_SYSV_ABI sceNetConfigUnsetIfFlags(); -int PS4_SYSV_ABI sceNetConfigUpInterface(); -int PS4_SYSV_ABI sceNetConfigUpInterfaceWithFlags(); -int PS4_SYSV_ABI sceNetConfigWlanAdhocClearWakeOnWlan(); -int PS4_SYSV_ABI sceNetConfigWlanAdhocCreate(); -int PS4_SYSV_ABI sceNetConfigWlanAdhocGetWakeOnWlanInfo(); -int PS4_SYSV_ABI sceNetConfigWlanAdhocJoin(); -int PS4_SYSV_ABI sceNetConfigWlanAdhocLeave(); -int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan(); -int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo(); -int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan(); -int PS4_SYSV_ABI sceNetConfigWlanAdhocScanJoin(); -int PS4_SYSV_ABI sceNetConfigWlanAdhocSetExtInfoElement(); -int PS4_SYSV_ABI sceNetConfigWlanAdhocSetWakeOnWlan(); -int PS4_SYSV_ABI sceNetConfigWlanApStart(); -int PS4_SYSV_ABI sceNetConfigWlanApStop(); -int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanQuery(); -int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStart(); -int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStop(); -int PS4_SYSV_ABI sceNetConfigWlanDiagGetDeviceInfo(); -int PS4_SYSV_ABI sceNetConfigWlanDiagSetAntenna(); -int PS4_SYSV_ABI sceNetConfigWlanDiagSetTxFixedRate(); -int PS4_SYSV_ABI sceNetConfigWlanGetDeviceConfig(); -int PS4_SYSV_ABI sceNetConfigWlanInfraGetRssiInfo(); -int PS4_SYSV_ABI sceNetConfigWlanInfraLeave(); -int PS4_SYSV_ABI sceNetConfigWlanInfraScanJoin(); -int PS4_SYSV_ABI sceNetConfigWlanScan(); -int PS4_SYSV_ABI sceNetConfigWlanSetDeviceConfig(); -int PS4_SYSV_ABI sceNetConnect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen); -int PS4_SYSV_ABI sceNetControl(); -int PS4_SYSV_ABI sceNetDhcpdStart(); -int PS4_SYSV_ABI sceNetDhcpdStop(); -int PS4_SYSV_ABI sceNetDhcpGetAutoipInfo(); -int PS4_SYSV_ABI sceNetDhcpGetInfo(); -int PS4_SYSV_ABI sceNetDhcpGetInfoEx(); -int PS4_SYSV_ABI sceNetDhcpStart(); -int PS4_SYSV_ABI sceNetDhcpStop(); -int PS4_SYSV_ABI sceNetDumpAbort(); -int PS4_SYSV_ABI sceNetDumpCreate(); -int PS4_SYSV_ABI sceNetDumpDestroy(); -int PS4_SYSV_ABI sceNetDumpRead(); -int PS4_SYSV_ABI sceNetDuplicateIpStart(); -int PS4_SYSV_ABI sceNetDuplicateIpStop(); -int PS4_SYSV_ABI sceNetEpollAbort(); -int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, OrbisNetId id, - OrbisNetEpollEvent* event); -int PS4_SYSV_ABI sceNetEpollCreate(const char* name, int flags); -int PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId epollid); -int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events, int maxevents, - int timeout); -int* PS4_SYSV_ABI sceNetErrnoLoc(); -int PS4_SYSV_ABI sceNetEtherNtostr(); -int PS4_SYSV_ABI sceNetEtherStrton(); -int PS4_SYSV_ABI sceNetEventCallbackCreate(); -int PS4_SYSV_ABI sceNetEventCallbackDestroy(); -int PS4_SYSV_ABI sceNetEventCallbackGetError(); -int PS4_SYSV_ABI sceNetEventCallbackWaitCB(); -int PS4_SYSV_ABI sceNetFreeAllRouteInfo(); -int PS4_SYSV_ABI sceNetGetArpInfo(); -int PS4_SYSV_ABI sceNetGetDns6Info(); -int PS4_SYSV_ABI sceNetGetDnsInfo(); -int PS4_SYSV_ABI sceNetGetIfList(); -int PS4_SYSV_ABI sceNetGetIfListOnce(); -int PS4_SYSV_ABI sceNetGetIfName(); -int PS4_SYSV_ABI sceNetGetIfnameNumList(); -int PS4_SYSV_ABI sceNetGetMacAddress(Libraries::NetCtl::OrbisNetEtherAddr* addr, int flags); -int PS4_SYSV_ABI sceNetGetMemoryPoolStats(); -int PS4_SYSV_ABI sceNetGetNameToIndex(); -int PS4_SYSV_ABI sceNetGetpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen); -int PS4_SYSV_ABI sceNetGetRandom(); -int PS4_SYSV_ABI sceNetGetRouteInfo(); -int PS4_SYSV_ABI sceNetGetSockInfo(OrbisNetId s, OrbisNetSockInfo* info, int n, int flags); -int PS4_SYSV_ABI sceNetGetSockInfo6(); -int PS4_SYSV_ABI sceNetGetsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen); -int PS4_SYSV_ABI sceNetGetsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen); -int PS4_SYSV_ABI sceNetGetStatisticsInfo(); -int PS4_SYSV_ABI sceNetGetStatisticsInfoInternal(); -int PS4_SYSV_ABI sceNetGetSystemTime(); -u32 PS4_SYSV_ABI sceNetHtonl(u32 host32); -u64 PS4_SYSV_ABI sceNetHtonll(u64 host64); -u16 PS4_SYSV_ABI sceNetHtons(u16 host16); -const char* PS4_SYSV_ABI sceNetInetNtop(int af, const void* src, char* dst, u32 size); -int PS4_SYSV_ABI sceNetInetNtopWithScopeId(); -int PS4_SYSV_ABI sceNetInetPton(int af, const char* src, void* dst); -int PS4_SYSV_ABI sceNetInetPtonEx(int af, const char* src, void* dst, int flags); -int PS4_SYSV_ABI sceNetInetPtonWithScopeId(); -int PS4_SYSV_ABI sceNetInfoDumpStart(); -int PS4_SYSV_ABI sceNetInfoDumpStop(); -int PS4_SYSV_ABI sceNetInit(); -int PS4_SYSV_ABI sceNetInitParam(); -int PS4_SYSV_ABI sceNetIoctl(); -int PS4_SYSV_ABI sceNetListen(OrbisNetId s, int backlog); -int PS4_SYSV_ABI sceNetMemoryAllocate(); -int PS4_SYSV_ABI sceNetMemoryFree(); -u32 PS4_SYSV_ABI sceNetNtohl(u32 net32); -int PS4_SYSV_ABI sceNetNtohll(); -u16 PS4_SYSV_ABI sceNetNtohs(u16 net16); -int PS4_SYSV_ABI sceNetPoolCreate(const char* name, int size, int flags); -int PS4_SYSV_ABI sceNetPoolDestroy(); -int PS4_SYSV_ABI sceNetPppoeStart(); -int PS4_SYSV_ABI sceNetPppoeStop(); -int PS4_SYSV_ABI sceNetRecv(OrbisNetId s, void* buf, u64 len, int flags); -int PS4_SYSV_ABI sceNetRecvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr, - u32* paddrlen); -int PS4_SYSV_ABI sceNetRecvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags); -int PS4_SYSV_ABI sceNetResolverAbort(); -int PS4_SYSV_ABI sceNetResolverConnect(); -int PS4_SYSV_ABI sceNetResolverConnectAbort(); -int PS4_SYSV_ABI sceNetResolverConnectCreate(); -int PS4_SYSV_ABI sceNetResolverConnectDestroy(); -int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags); -int PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId resolverid); -int PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId resolverid, s32* status); -int PS4_SYSV_ABI sceNetResolverStartAton(); -int PS4_SYSV_ABI sceNetResolverStartAton6(); -int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* hostname, - OrbisNetInAddr* addr, int timeout, int retry, int flags); -int PS4_SYSV_ABI sceNetResolverStartNtoa6(); -int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecords(OrbisNetId resolverid, const char* hostname, - OrbisNetResolverInfo* info, int timeout, - int retry, int flags); -int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecordsEx(); -int PS4_SYSV_ABI sceNetSend(OrbisNetId s, const void* buf, u64 len, int flags); -int PS4_SYSV_ABI sceNetSendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags); -int PS4_SYSV_ABI sceNetSendto(OrbisNetId s, const void* buf, u64 len, int flags, - const OrbisNetSockaddr* addr, u32 addrlen); -int PS4_SYSV_ABI sceNetSetDns6Info(); -int PS4_SYSV_ABI sceNetSetDns6InfoToKernel(); -int PS4_SYSV_ABI sceNetSetDnsInfo(); -int PS4_SYSV_ABI sceNetSetDnsInfoToKernel(); -int PS4_SYSV_ABI sceNetSetsockopt(OrbisNetId s, int level, int optname, const void* optval, - u32 optlen); -int PS4_SYSV_ABI sceNetShowIfconfig(); -int PS4_SYSV_ABI sceNetShowIfconfigForBuffer(); -int PS4_SYSV_ABI sceNetShowIfconfigWithMemory(); -int PS4_SYSV_ABI sceNetShowNetstat(); -int PS4_SYSV_ABI sceNetShowNetstatEx(); -int PS4_SYSV_ABI sceNetShowNetstatExForBuffer(); -int PS4_SYSV_ABI sceNetShowNetstatForBuffer(); -int PS4_SYSV_ABI sceNetShowNetstatWithMemory(); -int PS4_SYSV_ABI sceNetShowPolicy(); -int PS4_SYSV_ABI sceNetShowPolicyWithMemory(); -int PS4_SYSV_ABI sceNetShowRoute(); -int PS4_SYSV_ABI sceNetShowRoute6(); -int PS4_SYSV_ABI sceNetShowRoute6ForBuffer(); -int PS4_SYSV_ABI sceNetShowRoute6WithMemory(); -int PS4_SYSV_ABI sceNetShowRouteForBuffer(); -int PS4_SYSV_ABI sceNetShowRouteWithMemory(); -int PS4_SYSV_ABI sceNetShutdown(OrbisNetId s, int how); -OrbisNetId PS4_SYSV_ABI sceNetSocket(const char* name, int family, int type, int protocol); -int PS4_SYSV_ABI sceNetSocketAbort(OrbisNetId s, int flags); -int PS4_SYSV_ABI sceNetSocketClose(OrbisNetId s); -int PS4_SYSV_ABI sceNetSyncCreate(); -int PS4_SYSV_ABI sceNetSyncDestroy(); -int PS4_SYSV_ABI sceNetSyncGet(); -int PS4_SYSV_ABI sceNetSyncSignal(); -int PS4_SYSV_ABI sceNetSyncWait(); -int PS4_SYSV_ABI sceNetSysctl(); -int PS4_SYSV_ABI sceNetTerm(); -int PS4_SYSV_ABI sceNetThreadCreate(); -int PS4_SYSV_ABI sceNetThreadExit(); -int PS4_SYSV_ABI sceNetThreadJoin(); -int PS4_SYSV_ABI sceNetUsleep(); -int PS4_SYSV_ABI Func_0E707A589F751C68(); -int PS4_SYSV_ABI sceNetEmulationGet(); -int PS4_SYSV_ABI sceNetEmulationSet(); - -void RegisterLib(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::Net diff --git a/src/core/libraries/network/net_epoll.cpp b/src/core/libraries/network/net_epoll.cpp deleted file mode 100644 index f151d0b55..000000000 --- a/src/core/libraries/network/net_epoll.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include - -#include "common/assert.h" -#include "common/types.h" -#include "net_epoll.h" - -namespace Libraries::Net { - -u32 ConvertEpollEventsIn(u32 orbis_events) { - u32 ret = 0; - -#ifndef __FreeBSD__ - if ((orbis_events & ORBIS_NET_EPOLLIN) != 0) { - ret |= EPOLLIN; - } - if ((orbis_events & ORBIS_NET_EPOLLOUT) != 0) { - ret |= EPOLLOUT; - } -#endif - - return ret; -} - -u32 ConvertEpollEventsOut(u32 epoll_events) { - u32 ret = 0; - -#ifndef __FreeBSD__ - if ((epoll_events & EPOLLIN) != 0) { - ret |= ORBIS_NET_EPOLLIN; - } - if ((epoll_events & EPOLLOUT) != 0) { - ret |= ORBIS_NET_EPOLLOUT; - } - if ((epoll_events & EPOLLERR) != 0) { - ret |= ORBIS_NET_EPOLLERR; - } - if ((epoll_events & EPOLLHUP) != 0) { - ret |= ORBIS_NET_EPOLLHUP; - } -#endif - - return ret; -} - -int EpollTable::CreateHandle(const char* name) { - std::scoped_lock lock{m_mutex}; - - if (auto it = std::ranges::find_if(epolls, [](const Epoll& e) { return e.Destroyed(); }); - it != epolls.end()) { - *it = Epoll(name); - const auto ret = std::distance(epolls.begin(), it); - LOG_DEBUG(Lib_Net, "epollid = {}", ret); - return ret; - } else { - epolls.emplace_back(name); - const auto ret = epolls.size() - 1; - LOG_DEBUG(Lib_Net, "epollid = {}", ret); - return ret; - } -} - -void EpollTable::DeleteHandle(int d) { - UNREACHABLE(); -} - -Epoll* EpollTable::GetEpoll(int epollid) { - std::scoped_lock lock{m_mutex}; - if (epollid >= epolls.size() || epolls[epollid].Destroyed()) { - return nullptr; - } - - return &epolls[epollid]; -} - -} // namespace Libraries::Net diff --git a/src/core/libraries/network/net_epoll.h b/src/core/libraries/network/net_epoll.h deleted file mode 100644 index 17716b36e..000000000 --- a/src/core/libraries/network/net_epoll.h +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" -#include "core/libraries/network/net.h" - -#include -#include -#include - -#ifdef _WIN32 -#include -#endif - -#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) -// ADD libepoll-shim if using freebsd! -#include -#include -#endif - -namespace Libraries::Net { - -#ifdef _WIN32 -using epoll_handle = HANDLE; -#else -using epoll_handle = int; -#endif - -struct Epoll { - std::vector> events{}; - std::string name; - epoll_handle epoll_fd; - std::deque async_resolutions{}; - - explicit Epoll(const char* name_) : name(name_), epoll_fd(epoll_create1(0)) { -#ifdef _WIN32 - ASSERT(epoll_fd != nullptr); -#else - ASSERT(epoll_fd != -1); -#endif - if (name_ == nullptr) { - name = "anon"; - } - } - - bool Destroyed() const noexcept { - return destroyed; - } - - void Destroy() noexcept { - events.clear(); -#ifdef _WIN32 - epoll_close(epoll_fd); - epoll_fd = nullptr; -#else - close(epoll_fd); - epoll_fd = -1; -#endif - name = ""; - destroyed = true; - } - -private: - bool destroyed{}; -}; - -u32 ConvertEpollEventsIn(u32 orbis_events); -u32 ConvertEpollEventsOut(u32 epoll_events); - -class EpollTable { -public: - EpollTable() = default; - virtual ~EpollTable() = default; - - int CreateHandle(const char* name); - void DeleteHandle(int d); - Epoll* GetEpoll(int d); - -private: - std::vector epolls; - std::mutex m_mutex; -}; - -} // namespace Libraries::Net diff --git a/src/core/libraries/network/net_obj.cpp b/src/core/libraries/network/net_obj.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/core/libraries/network/net_obj.h b/src/core/libraries/network/net_obj.h deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/core/libraries/network/net_resolver.cpp b/src/core/libraries/network/net_resolver.cpp deleted file mode 100644 index a66f1519b..000000000 --- a/src/core/libraries/network/net_resolver.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/assert.h" -#include "common/singleton.h" -#include "common/types.h" -#include "core/emulator_settings.h" -#include "core/libraries/error_codes.h" -#include "net_error.h" -#include "net_resolver.h" -#include "net_util.h" - -namespace Libraries::Net { - -int Resolver::ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry, - int flags) { - std::scoped_lock lock{m_mutex}; - - if (async_resolution) { - *sceNetErrnoLoc() = ORBIS_NET_RESOLVER_EBUSY; - return ORBIS_NET_ERROR_RESOLVER_EBUSY; - } - - async_resolution = AsyncResolution{hostname, addr, timeout, retry, flags}; - - return ORBIS_OK; -} - -void Resolver::Resolve() { - if (!EmulatorSettings.IsConnectedToNetwork()) { - resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS; - return; - } - - if (async_resolution) { - auto* netinfo = Common::Singleton::Instance(); - auto ret = netinfo->ResolveHostname(async_resolution->hostname, async_resolution->addr); - resolution_error = ret; - if (ret != ORBIS_OK) { - // Resolver errors are stored as ORBIS_NET_ERROR values. - resolution_error = -ret | ORBIS_NET_ERROR_BASE; - } - } else { - LOG_ERROR(Lib_Net, "async resolution has not been set-up"); - } -} - -} // namespace Libraries::Net diff --git a/src/core/libraries/network/net_resolver.h b/src/core/libraries/network/net_resolver.h deleted file mode 100644 index 4c5c2ece8..000000000 --- a/src/core/libraries/network/net_resolver.h +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" -#include "core/libraries/network/net.h" - -#include -#include - -namespace Libraries::Net { - -struct Resolver { -public: - Resolver(const char* name, int poolid, int flags) : name(name), poolid(poolid), flags(flags) {} - - int ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry, int flags); - void Resolve(); - - int resolution_error = ORBIS_OK; - -private: - struct AsyncResolution { - const char* hostname; - OrbisNetInAddr* addr; - int timeout; - int retry; - int flags; - }; - - std::string name; - int poolid; - int flags; - std::optional async_resolution{}; - std::mutex m_mutex; -}; - -} // namespace Libraries::Net \ No newline at end of file diff --git a/src/core/libraries/network/netctl.cpp b/src/core/libraries/network/netctl.cpp index f0bd459fd..b4a6a1ee3 100644 --- a/src/core/libraries/network/netctl.cpp +++ b/src/core/libraries/network/netctl.cpp @@ -17,9 +17,9 @@ #include "core/emulator_settings.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" +#include "core/libraries/net/net_util.h" #include "core/libraries/network/net_ctl_codes.h" #include "core/libraries/network/netctl.h" -#include "net_util.h" namespace Libraries::NetCtl { diff --git a/src/core/libraries/network/p2p_sockets.cpp b/src/core/libraries/network/p2p_sockets.cpp deleted file mode 100644 index 4f2485100..000000000 --- a/src/core/libraries/network/p2p_sockets.cpp +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include "core/libraries/kernel/kernel.h" -#include "net.h" -#include "net_error.h" -#include "sockets.h" - -namespace Libraries::Net { - -int P2PSocket::Close() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return 0; -} - -int P2PSocket::Shutdown(int how) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return 0; -} - -int P2PSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return 0; -} - -int P2PSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return 0; -} - -int P2PSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return 0; -} - -int P2PSocket::Listen(int backlog) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return 0; -} - -int P2PSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - *Libraries::Kernel::__Error() = ORBIS_NET_EAGAIN; - return -1; -} - -int P2PSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to, - u32 tolen) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - *Libraries::Kernel::__Error() = ORBIS_NET_EAGAIN; - return -1; -} - -int P2PSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - *Libraries::Kernel::__Error() = ORBIS_NET_EAGAIN; - return -1; -} - -int P2PSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - *Libraries::Kernel::__Error() = ORBIS_NET_EAGAIN; - return -1; -} - -SocketPtr P2PSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - *Libraries::Kernel::__Error() = ORBIS_NET_EAGAIN; - return nullptr; -} - -int P2PSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return 0; -} - -int P2PSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return 0; -} - -int P2PSocket::GetPeerName(OrbisNetSockaddr* addr, u32* namelen) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return 0; -} - -int P2PSocket::fstat(Libraries::Kernel::OrbisKernelStat* stat) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return 0; -} - -u16 GetP2PConfiguredPort() { - return 0; -} - -u32 GetP2PAdvertisedAddr() { - return 0; -} - -bool EnsureP2PTransport() { - return false; -} - -bool P2PTransportIsReady() { - return false; -} - -int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) { - return -1; -} - -int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) { - return -1; -} - -int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) { - return -1; -} - -int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) { - return -1; -} - -int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) { - return -1; -} - -int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) { - return -1; -} - -} // namespace Libraries::Net diff --git a/src/core/libraries/network/posix_sockets.cpp b/src/core/libraries/network/posix_sockets.cpp deleted file mode 100644 index f1d8030e7..000000000 --- a/src/core/libraries/network/posix_sockets.cpp +++ /dev/null @@ -1,716 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include "common/error.h" -#include "core/libraries/kernel/file_system.h" -#include "core/libraries/kernel/kernel.h" -#include "net.h" -#ifndef _WIN32 -#include -#endif -#include "net_error.h" -#include "sockets.h" - -namespace Libraries::Net { - -#ifdef _WIN32 -#define ERROR_CASE(errname) \ - case (WSA##errname): \ - *Libraries::Kernel::__Error() = ORBIS_NET_##errname; \ - return -1; -#else -#define ERROR_CASE(errname) \ - case (errname): \ - *Libraries::Kernel::__Error() = ORBIS_NET_##errname; \ - return -1; -#endif - -static int ConvertReturnErrorCode(int retval) { - if (retval < 0) { -#ifdef _WIN32 - switch (WSAGetLastError()) { -#else - switch (errno) { -#endif -#ifndef _WIN32 // These errorcodes don't exist in WinSock - ERROR_CASE(EPERM) - ERROR_CASE(ENOENT) - // ERROR_CASE(ESRCH) - // ERROR_CASE(EIO) - // ERROR_CASE(ENXIO) - // ERROR_CASE(E2BIG) - // ERROR_CASE(ENOEXEC) - // ERROR_CASE(EDEADLK) - ERROR_CASE(ENOMEM) - // ERROR_CASE(ECHILD) - // ERROR_CASE(EBUSY) - ERROR_CASE(EEXIST) - // ERROR_CASE(EXDEV) - ERROR_CASE(ENODEV) - // ERROR_CASE(ENOTDIR) - // ERROR_CASE(EISDIR) - ERROR_CASE(ENFILE) - // ERROR_CASE(ENOTTY) - // ERROR_CASE(ETXTBSY) - // ERROR_CASE(EFBIG) - ERROR_CASE(ENOSPC) - // ERROR_CASE(ESPIPE) - // ERROR_CASE(EROFS) - // ERROR_CASE(EMLINK) - ERROR_CASE(EPIPE) - // ERROR_CASE(EDOM) - // ERROR_CASE(ERANGE) - // ERROR_CASE(ENOLCK) - // ERROR_CASE(ENOSYS) - // ERROR_CASE(EIDRM) - // ERROR_CASE(EOVERFLOW) - // ERROR_CASE(EILSEQ) - // ERROR_CASE(ENOTSUP) - ERROR_CASE(ECANCELED) - // ERROR_CASE(EBADMSG) - ERROR_CASE(ENODATA) - // ERROR_CASE(ENOSR) - // ERROR_CASE(ENOSTR) - // ERROR_CASE(ETIME) -#endif - ERROR_CASE(EINTR) - ERROR_CASE(EBADF) - ERROR_CASE(EACCES) - ERROR_CASE(EFAULT) - ERROR_CASE(EINVAL) - ERROR_CASE(EMFILE) - ERROR_CASE(EWOULDBLOCK) - ERROR_CASE(EINPROGRESS) - ERROR_CASE(EALREADY) - ERROR_CASE(ENOTSOCK) - ERROR_CASE(EDESTADDRREQ) - ERROR_CASE(EMSGSIZE) - ERROR_CASE(EPROTOTYPE) - ERROR_CASE(ENOPROTOOPT) - ERROR_CASE(EPROTONOSUPPORT) -#if defined(__APPLE__) || defined(_WIN32) - ERROR_CASE(EOPNOTSUPP) -#endif - ERROR_CASE(EAFNOSUPPORT) - ERROR_CASE(EADDRINUSE) - ERROR_CASE(EADDRNOTAVAIL) - ERROR_CASE(ENETDOWN) - ERROR_CASE(ENETUNREACH) - ERROR_CASE(ENETRESET) - ERROR_CASE(ECONNABORTED) - ERROR_CASE(ECONNRESET) - ERROR_CASE(ENOBUFS) - ERROR_CASE(EISCONN) - ERROR_CASE(ENOTCONN) - ERROR_CASE(ETIMEDOUT) - ERROR_CASE(ECONNREFUSED) - ERROR_CASE(ELOOP) - ERROR_CASE(ENAMETOOLONG) - ERROR_CASE(EHOSTUNREACH) - ERROR_CASE(ENOTEMPTY) - } - *Libraries::Kernel::__Error() = ORBIS_NET_EINTERNAL; - return -1; - } - // if it is 0 or positive return it as it is - return retval; -} - -static int ConvertLevels(int level) { - switch (level) { - case ORBIS_NET_SOL_SOCKET: - return SOL_SOCKET; - case ORBIS_NET_IPPROTO_IP: - return IPPROTO_IP; - case ORBIS_NET_IPPROTO_TCP: - return IPPROTO_TCP; - case ORBIS_NET_IPPROTO_IPV6: - return IPPROTO_IPV6; - } - return -1; -} - -static void convertOrbisNetSockaddrToPosix(const OrbisNetSockaddr* src, sockaddr* dst) { - if (src == nullptr || dst == nullptr) - return; - memset(dst, 0, sizeof(sockaddr)); - const OrbisNetSockaddrIn* src_in = (const OrbisNetSockaddrIn*)src; - sockaddr_in* dst_in = (sockaddr_in*)dst; - dst_in->sin_family = src_in->sin_family; - dst_in->sin_port = src_in->sin_port; - memcpy(&dst_in->sin_addr, &src_in->sin_addr, 4); -} - -static void convertPosixSockaddrToOrbis(sockaddr* src, OrbisNetSockaddr* dst) { - if (src == nullptr || dst == nullptr) - return; - memset(dst, 0, sizeof(OrbisNetSockaddr)); - OrbisNetSockaddrIn* dst_in = (OrbisNetSockaddrIn*)dst; - sockaddr_in* src_in = (sockaddr_in*)src; - dst_in->sin_family = static_cast(src_in->sin_family); - dst_in->sin_port = src_in->sin_port; - memcpy(&dst_in->sin_addr, &src_in->sin_addr, 4); -} - -bool PosixSocket::IsValid() const { -#ifdef _WIN32 - return sock != INVALID_SOCKET; -#else - return sock != -1; -#endif -} - -int PosixSocket::Close() { - std::scoped_lock lock{m_mutex}; -#ifdef _WIN32 - auto out = closesocket(sock); -#else - auto out = ::close(sock); -#endif - return ConvertReturnErrorCode(out); -} - -int PosixSocket::Shutdown(int how) { - return ConvertReturnErrorCode(::shutdown(sock, how)); -} - -int PosixSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) { - std::scoped_lock lock{m_mutex}; - sockaddr addr2; - convertOrbisNetSockaddrToPosix(addr, &addr2); - return ConvertReturnErrorCode(::bind(sock, &addr2, sizeof(sockaddr_in))); -} - -int PosixSocket::Listen(int backlog) { - std::scoped_lock lock{m_mutex}; - return ConvertReturnErrorCode(::listen(sock, backlog)); -} - -static int convertOrbisFlagsToPosix(int sock_type, int sce_flags) { - int posix_flags = 0; - - if (sce_flags & ORBIS_NET_MSG_PEEK) - posix_flags |= MSG_PEEK; -#ifndef _WIN32 - if (sce_flags & ORBIS_NET_MSG_DONTWAIT) - posix_flags |= MSG_DONTWAIT; -#endif - // MSG_WAITALL is only valid for stream sockets - if ((sce_flags & ORBIS_NET_MSG_WAITALL) && - ((sock_type == ORBIS_NET_SOCK_STREAM) || (sock_type == ORBIS_NET_SOCK_STREAM_P2P))) - posix_flags |= MSG_WAITALL; - - return posix_flags; -} - -// On Windows, MSG_DONTWAIT is not handled natively by recv/send. -// This function uses select() with zero timeout to simulate non-blocking behavior. -static int socket_is_ready(int sock, bool is_read = true) { - fd_set fds{}; - FD_ZERO(&fds); - FD_SET(sock, &fds); - timeval timeout{0, 0}; - int res = - select(sock + 1, is_read ? &fds : nullptr, is_read ? nullptr : &fds, nullptr, &timeout); - if (res == 0) { - *Libraries::Kernel::__Error() = ORBIS_NET_EWOULDBLOCK; - return -1; - } else if (res < 0) { - return ConvertReturnErrorCode(res); - } - return res; -} - -int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) { - std::scoped_lock lock{m_mutex}; - -#ifdef _WIN32 - int totalSent = 0; - bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0; - bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0; - - // stream socket with multiple buffers - bool use_wsamsg = - (socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) && - msg->msg_iovlen > 1; - - for (int i = 0; i < msg->msg_iovlen; ++i) { - char* buf = (char*)msg->msg_iov[i].iov_base; - size_t remaining = msg->msg_iov[i].iov_len; - - while (remaining > 0) { - if (dontWait) { - int ready = socket_is_ready(sock, false); - if (ready <= 0) - return ready; - } - - int sent = 0; - if (use_wsamsg) { - // only call WSASendMsg if we have multiple buffers - LPFN_WSASENDMSG wsasendmsg = nullptr; - GUID guid = WSAID_WSASENDMSG; - DWORD bytes = 0; - if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), - &wsasendmsg, sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) { - // fallback to send() - sent = ::send(sock, buf, remaining, 0); - } else { - DWORD bytesSent = 0; - int res = wsasendmsg( - sock, reinterpret_cast(const_cast(msg)), 0, - &bytesSent, nullptr, nullptr); - if (res == SOCKET_ERROR) - return ConvertReturnErrorCode(WSAGetLastError()); - sent = bytesSent; - } - } else { - sent = ::send(sock, buf, remaining, 0); - if (sent == SOCKET_ERROR) - return ConvertReturnErrorCode(WSAGetLastError()); - } - - totalSent += sent; - remaining -= sent; - buf += sent; - - if (!waitAll) - break; - } - } - - return totalSent; - -#else - int native_flags = convertOrbisFlagsToPosix(socket_type, flags); - int res = sendmsg(sock, reinterpret_cast(msg), native_flags); - return ConvertReturnErrorCode(res); -#endif -} - -int PosixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to, - u32 tolen) { - std::scoped_lock lock{m_mutex}; - int res = 0; -#ifdef _WIN32 - if (flags & ORBIS_NET_MSG_DONTWAIT) { - res = socket_is_ready(sock, false); - if (res <= 0) - return res; - } -#endif - const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags); - if (to == nullptr) { - res = send(sock, (const char*)msg, len, posix_flags); - } else { - sockaddr addr{}; - convertOrbisNetSockaddrToPosix(to, &addr); - res = sendto(sock, (const char*)msg, len, posix_flags, &addr, tolen); - } - return ConvertReturnErrorCode(res); -} - -int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) { - std::scoped_lock lock{receive_mutex}; - -#ifdef _WIN32 - int totalReceived = 0; - bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0; - bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0; - - // stream socket with multiple buffers - bool use_wsarecvmsg = - (socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) && - msg->msg_iovlen > 1; - - for (int i = 0; i < msg->msg_iovlen; ++i) { - char* buf = (char*)msg->msg_iov[i].iov_base; - size_t remaining = msg->msg_iov[i].iov_len; - - while (remaining > 0) { - // emulate DONTWAIT - if (dontWait) { - int ready = socket_is_ready(sock, true); - if (ready <= 0) - return ready; // returns ORBIS_NET_ERROR_EWOULDBLOCK or error - } - - int received = 0; - if (use_wsarecvmsg) { - // only call WSARecvMsg if multiple buffers + stream - LPFN_WSARECVMSG wsarecvmsg = nullptr; - GUID guid = WSAID_WSARECVMSG; - DWORD bytes = 0; - if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), - &wsarecvmsg, sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) { - // fallback to recv() - received = ::recv(sock, buf, remaining, 0); - if (received == SOCKET_ERROR) - return ConvertReturnErrorCode(WSAGetLastError()); - } else { - DWORD bytesReceived = 0; - int res = wsarecvmsg(sock, reinterpret_cast(msg), &bytesReceived, - nullptr, nullptr); - if (res == SOCKET_ERROR) - return ConvertReturnErrorCode(WSAGetLastError()); - received = bytesReceived; - } - } else { - // fallback to recv() for UDP or single-buffer - received = ::recv(sock, buf, remaining, 0); - if (received == SOCKET_ERROR) - return ConvertReturnErrorCode(WSAGetLastError()); - } - - totalReceived += received; - remaining -= received; - buf += received; - - // stop after first receive if WAITALL is not set - if (!waitAll) - break; - } - } - - return totalReceived; - -#else - int native_flags = convertOrbisFlagsToPosix(socket_type, flags); - int res = recvmsg(sock, reinterpret_cast(msg), native_flags); - return ConvertReturnErrorCode(res); -#endif -} - -int PosixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, - u32* fromlen) { - std::scoped_lock lock{receive_mutex}; - int res = 0; -#ifdef _WIN32 - if (flags & ORBIS_NET_MSG_DONTWAIT) { - res = socket_is_ready(sock); - if (res <= 0) - return res; - } -#endif - const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags); - if (from == nullptr) { - res = recv(sock, (char*)buf, len, posix_flags); - } else { - sockaddr addr{}; - socklen_t addrlen = sizeof(addr); - res = recvfrom(sock, (char*)buf, len, posix_flags, &addr, - (fromlen && *fromlen <= sizeof(addr) ? (socklen_t*)fromlen : &addrlen)); - if (res > 0) - convertPosixSockaddrToOrbis(&addr, from); - } - - return ConvertReturnErrorCode(res); -} - -SocketPtr PosixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) { - std::scoped_lock lock{m_mutex}; - sockaddr addr2; - socklen_t len = sizeof(addr2); - net_socket new_socket = ::accept(sock, &addr2, &len); -#ifdef _WIN32 - if (new_socket != INVALID_SOCKET) { -#else - if (new_socket >= 0) { -#endif - if (addr && addrlen) { - convertPosixSockaddrToOrbis(&addr2, addr); - *addrlen = sizeof(OrbisNetSockaddrIn); - } - return std::make_shared(new_socket); - } - ConvertReturnErrorCode(new_socket); - return nullptr; -} - -int PosixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) { - std::scoped_lock lock{m_mutex}; - sockaddr addr2; - convertOrbisNetSockaddrToPosix(addr, &addr2); - int result = ::connect(sock, &addr2, sizeof(sockaddr_in)); -#ifdef _WIN32 - // Winsock returns EWOULDBLOCK where real hardware returns EINPROGRESS - // Step in here on errors to address this. - if (result == -1) { - if (WSAGetLastError() == WSAEWOULDBLOCK) { - WSASetLastError(WSAEINPROGRESS); - } - } -#endif - LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result, - result == -1 ? Common::GetLastErrorMsg() : "none"); - return ConvertReturnErrorCode(result); -} - -int PosixSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) { - std::scoped_lock lock{m_mutex}; - sockaddr addr; - convertOrbisNetSockaddrToPosix(name, &addr); - if (name != nullptr) { - *namelen = sizeof(sockaddr_in); - } - int res = getsockname(sock, &addr, (socklen_t*)namelen); - if (res >= 0) { - convertPosixSockaddrToOrbis(&addr, name); - *namelen = sizeof(OrbisNetSockaddrIn); - } - return ConvertReturnErrorCode(res); -} - -#define CASE_SETSOCKOPT(opt) \ - case ORBIS_NET_##opt: \ - return ConvertReturnErrorCode( \ - setsockopt(sock, native_level, opt, (const char*)optval, optlen)) - -#define CASE_SETSOCKOPT_VALUE(opt, value) \ - case opt: \ - if (optlen != sizeof(*value)) { \ - *Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \ - return -1; \ - } \ - memcpy(value, optval, optlen); \ - return 0 - -int PosixSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) { - std::scoped_lock lock{m_mutex}; - s32 native_level = ConvertLevels(level); - ::linger native_linger; - if (native_level == SOL_SOCKET) { - switch (optname) { - CASE_SETSOCKOPT(SO_REUSEADDR); - CASE_SETSOCKOPT(SO_KEEPALIVE); - CASE_SETSOCKOPT(SO_BROADCAST); - CASE_SETSOCKOPT(SO_SNDBUF); - CASE_SETSOCKOPT(SO_RCVBUF); - CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_CONNECTTIMEO, &sockopt_so_connecttimeo); - CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_REUSEPORT, &sockopt_so_reuseport); - CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_USECRYPTO, &sockopt_so_usecrypto); - CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_USESIGNATURE, &sockopt_so_usesignature); - case ORBIS_NET_SO_SNDTIMEO: - case ORBIS_NET_SO_RCVTIMEO: { - if (optlen != sizeof(int)) { - *Libraries::Kernel::__Error() = ORBIS_NET_ERROR_EFAULT; - return -1; - } - std::vector val; - const auto optname_nat = (optname == ORBIS_NET_SO_SNDTIMEO) ? SO_SNDTIMEO : SO_RCVTIMEO; - int timeout_us = *(const int*)optval; -#ifdef _WIN32 - DWORD timeout = timeout_us / 1000; -#else - timeval timeout{.tv_sec = timeout_us / 1000000, .tv_usec = timeout_us % 1000000}; -#endif - val.insert(val.end(), (char*)&timeout, (char*)&timeout + sizeof(timeout)); - optlen = sizeof(timeout); - return ConvertReturnErrorCode( - setsockopt(sock, native_level, optname_nat, val.data(), optlen)); - } - case ORBIS_NET_SO_ONESBCAST: { - - if (optlen != sizeof(sockopt_so_onesbcast)) { - *Libraries::Kernel::__Error() = ORBIS_NET_ERROR_EFAULT; - return -1; - } - memcpy(&sockopt_so_onesbcast, optval, optlen); - return ConvertReturnErrorCode( - setsockopt(sock, native_level, SO_BROADCAST, (const char*)optval, optlen)); - } - case ORBIS_NET_SO_TYPE: - case ORBIS_NET_SO_ERROR: { - *Libraries::Kernel::__Error() = ORBIS_NET_ENOPROTOOPT; - return -1; - } - case ORBIS_NET_SO_LINGER: { - if (socket_type != ORBIS_NET_SOCK_STREAM) { - *Libraries::Kernel::__Error() = ORBIS_NET_EPROCUNAVAIL; - return -1; - } - if (optlen < sizeof(OrbisNetLinger)) { - LOG_ERROR(Lib_Net, "size missmatched! optlen = {} OrbisNetLinger={}", optlen, - sizeof(OrbisNetLinger)); - *Libraries::Kernel::__Error() = ORBIS_NET_EINVAL; - return -1; - } - - const void* native_val = &native_linger; - u32 native_len = sizeof(native_linger); - native_linger.l_onoff = reinterpret_cast(optval)->l_onoff; - native_linger.l_linger = reinterpret_cast(optval)->l_linger; - return ConvertReturnErrorCode( - setsockopt(sock, native_level, SO_LINGER, (const char*)native_val, native_len)); - } - - case ORBIS_NET_SO_NAME: - *Libraries::Kernel::__Error() = ORBIS_NET_EINVAL; - return -1; // don't support set for name - case ORBIS_NET_SO_NBIO: { - if (optlen < sizeof(sockopt_so_nbio)) { - *Libraries::Kernel::__Error() = ORBIS_NET_EINVAL; - return -1; - } else { - memcpy(&sockopt_so_nbio, optval, sizeof(sockopt_so_nbio)); - } -#ifdef _WIN32 - static_assert(sizeof(u_long) == sizeof(sockopt_so_nbio), - "type used for ioctlsocket value does not have the expected size"); - return ConvertReturnErrorCode(ioctlsocket(sock, FIONBIO, (u_long*)&sockopt_so_nbio)); -#else - return ConvertReturnErrorCode(ioctl(sock, FIONBIO, &sockopt_so_nbio)); -#endif - } - case ORBIS_NET_SO_ACCEPTTIMEO: - LOG_ERROR(Lib_Net, "Unhandled option ORBIS_NET_SO_ACCEPTTIMEO"); - return ORBIS_OK; - } - } else if (native_level == IPPROTO_IP) { - switch (optname) { - // CASE_SETSOCKOPT(IP_HDRINCL); - CASE_SETSOCKOPT(IP_TOS); - CASE_SETSOCKOPT(IP_TTL); - CASE_SETSOCKOPT(IP_MULTICAST_IF); - CASE_SETSOCKOPT(IP_MULTICAST_TTL); - CASE_SETSOCKOPT(IP_MULTICAST_LOOP); - CASE_SETSOCKOPT(IP_ADD_MEMBERSHIP); - CASE_SETSOCKOPT(IP_DROP_MEMBERSHIP); - CASE_SETSOCKOPT_VALUE(ORBIS_NET_IP_TTLCHK, &sockopt_ip_ttlchk); - CASE_SETSOCKOPT_VALUE(ORBIS_NET_IP_MAXTTL, &sockopt_ip_maxttl); - case ORBIS_NET_IP_HDRINCL: { - if (socket_type != ORBIS_NET_SOCK_RAW) { - *Libraries::Kernel::__Error() = ORBIS_NET_EPROCUNAVAIL; - return -1; - } - return ConvertReturnErrorCode( - setsockopt(sock, native_level, optname, (const char*)optval, optlen)); - } - } - } else if (native_level == IPPROTO_TCP) { - switch (optname) { - CASE_SETSOCKOPT(TCP_NODELAY); - CASE_SETSOCKOPT(TCP_MAXSEG); - CASE_SETSOCKOPT_VALUE(ORBIS_NET_TCP_MSS_TO_ADVERTISE, &sockopt_tcp_mss_to_advertise); - } - } - - UNREACHABLE_MSG("Unknown level ={} optname ={}", level, optname); - return 0; -} - -#define CASE_GETSOCKOPT(opt) \ - case ORBIS_NET_##opt: { \ - socklen_t optlen_temp = *optlen; \ - auto retval = ConvertReturnErrorCode( \ - getsockopt(sock, native_level, opt, (char*)optval, &optlen_temp)); \ - *optlen = optlen_temp; \ - return retval; \ - } -#define CASE_GETSOCKOPT_VALUE(opt, value) \ - case opt: \ - if (*optlen < sizeof(value)) { \ - *optlen = sizeof(value); \ - *Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \ - return -1; \ - } \ - *optlen = sizeof(value); \ - *(decltype(value)*)optval = value; \ - return 0; - -int PosixSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) { - std::scoped_lock lock{m_mutex}; - s32 native_level = ConvertLevels(level); - if (native_level == SOL_SOCKET) { - switch (optname) { - CASE_GETSOCKOPT(SO_REUSEADDR); - CASE_GETSOCKOPT(SO_KEEPALIVE); - CASE_GETSOCKOPT(SO_BROADCAST); - CASE_GETSOCKOPT(SO_LINGER); - CASE_GETSOCKOPT(SO_SNDBUF); - CASE_GETSOCKOPT(SO_RCVBUF); - CASE_GETSOCKOPT(SO_SNDTIMEO); - CASE_GETSOCKOPT(SO_RCVTIMEO); - CASE_GETSOCKOPT(SO_ERROR); - CASE_GETSOCKOPT(SO_TYPE); - CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_NBIO, sockopt_so_nbio); - CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_CONNECTTIMEO, sockopt_so_connecttimeo); - CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_REUSEPORT, sockopt_so_reuseport); - CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_ONESBCAST, sockopt_so_onesbcast); - CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_USECRYPTO, sockopt_so_usecrypto); - CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_USESIGNATURE, sockopt_so_usesignature); - CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_NAME, - (char)0); // writes an empty string to the output buffer - case ORBIS_NET_SO_ERROR_EX: { - socklen_t optlen_temp = *optlen; - auto retval = ConvertReturnErrorCode( - getsockopt(sock, level, SO_ERROR, (char*)optval, &optlen_temp)); - *optlen = optlen_temp; - if (retval < 0) { - s32 r = *Libraries::Kernel::__Error(); - *Libraries::Kernel::__Error() = 0; - return r; - } - return retval; - } - } - } else if (native_level == IPPROTO_IP) { - switch (optname) { - CASE_GETSOCKOPT(IP_HDRINCL); - CASE_GETSOCKOPT(IP_TOS); - CASE_GETSOCKOPT(IP_TTL); - CASE_GETSOCKOPT(IP_MULTICAST_IF); - CASE_GETSOCKOPT(IP_MULTICAST_TTL); - CASE_GETSOCKOPT(IP_MULTICAST_LOOP); - CASE_GETSOCKOPT(IP_ADD_MEMBERSHIP); - CASE_GETSOCKOPT(IP_DROP_MEMBERSHIP); - CASE_GETSOCKOPT_VALUE(ORBIS_NET_IP_TTLCHK, sockopt_ip_ttlchk); - CASE_GETSOCKOPT_VALUE(ORBIS_NET_IP_MAXTTL, sockopt_ip_maxttl); - } - } else if (native_level == IPPROTO_TCP) { - switch (optname) { - CASE_GETSOCKOPT(TCP_NODELAY); - CASE_GETSOCKOPT(TCP_MAXSEG); - CASE_GETSOCKOPT_VALUE(ORBIS_NET_TCP_MSS_TO_ADVERTISE, sockopt_tcp_mss_to_advertise); - } - } - UNREACHABLE_MSG("Unknown level ={} optname ={}", level, optname); - return 0; -} - -int PosixSocket::GetPeerName(OrbisNetSockaddr* name, u32* namelen) { - std::scoped_lock lock{m_mutex}; - - sockaddr addr; - convertOrbisNetSockaddrToPosix(name, &addr); - if (name != nullptr) { - *namelen = sizeof(sockaddr_in); - } - int res = ::getpeername(sock, &addr, (socklen_t*)namelen); - if (res >= 0) { - convertPosixSockaddrToOrbis(&addr, name); - *namelen = sizeof(OrbisNetSockaddrIn); - } - return ConvertReturnErrorCode(res); -} - -int PosixSocket::fstat(Libraries::Kernel::OrbisKernelStat* sb) { -#ifdef _WIN32 - LOG_ERROR(Lib_Net, "(STUBBED) called"); - sb->st_mode = 0000777u | 0140000u; - return 0; -#else - struct stat st{}; - int result = ::fstat(sock, &st); - sb->st_mode = 0000777u | 0140000u; - sb->st_size = st.st_size; - sb->st_blocks = st.st_blocks; - sb->st_blksize = st.st_blksize; - // sb->st_flags = st.st_flags; - return ConvertReturnErrorCode(result); -#endif -} - -} // namespace Libraries::Net diff --git a/src/core/libraries/network/sockets.h b/src/core/libraries/network/sockets.h deleted file mode 100644 index 03ff1d0c8..000000000 --- a/src/core/libraries/network/sockets.h +++ /dev/null @@ -1,197 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#ifdef _WIN32 -#define _WINSOCK_DEPRECATED_NO_WARNINGS -#include -#include -#include -#include -#include -typedef SOCKET net_socket; -typedef int socklen_t; -#ifndef LPFN_WSASENDMSG -typedef INT(PASCAL* LPFN_WSASENDMSG)(SOCKET s, LPWSAMSG lpMsg, DWORD dwFlags, - LPDWORD lpNumberOfBytesSent, LPWSAOVERLAPPED lpOverlapped, - LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); -#endif -#ifndef WSAID_WSASENDMSG -static const GUID WSAID_WSASENDMSG = { - 0xa441e712, 0x754f, 0x43ca, {0x84, 0xa7, 0x0d, 0xee, 0x44, 0xcf, 0x60, 0x6d}}; -#endif -#ifndef LPFN_WSARECVMSG -typedef INT(PASCAL* LPFN_WSARECVMSG)(SOCKET s, LPWSAMSG lpMsg, LPDWORD lpdwNumberOfBytesRecvd, - LPWSAOVERLAPPED lpOverlapped, - LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); -#endif - -#ifndef WSAID_WSARECVMSG -static const GUID WSAID_WSARECVMSG = { - 0xf689d7c8, 0x6f1f, 0x436b, {0x8a, 0x53, 0xe5, 0x4f, 0xe3, 0x51, 0xc3, 0x22}}; -#endif -#else -#include -#include -#include -#include -#include -#include -#include -#include -typedef int net_socket; -#endif -#include -#include -#include -#include "net.h" - -namespace Libraries::Kernel { -struct OrbisKernelStat; -} - -namespace Libraries::Net { - -struct Socket; - -typedef std::shared_ptr SocketPtr; - -struct OrbisNetLinger { - s32 l_onoff; - s32 l_linger; -}; -struct Socket { - explicit Socket(int domain, int type, int protocol) : socket_type(type) {} - virtual ~Socket() = default; - virtual bool IsValid() const = 0; - virtual int Close() = 0; - virtual int Shutdown(int how) = 0; - virtual int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) = 0; - virtual int GetSocketOptions(int level, int optname, void* optval, u32* optlen) = 0; - virtual int Bind(const OrbisNetSockaddr* addr, u32 addrlen) = 0; - virtual int Listen(int backlog) = 0; - virtual int SendMessage(const OrbisNetMsghdr* msg, int flags) = 0; - virtual int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to, - u32 tolen) = 0; - virtual SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) = 0; - virtual int ReceiveMessage(OrbisNetMsghdr* msg, int flags) = 0; - virtual int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, - u32* fromlen) = 0; - virtual int Connect(const OrbisNetSockaddr* addr, u32 namelen) = 0; - virtual int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) = 0; - virtual int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) = 0; - virtual int fstat(Libraries::Kernel::OrbisKernelStat* stat) = 0; - virtual std::optional Native() = 0; - std::mutex m_mutex; - std::mutex receive_mutex; - int socket_type; -}; - -struct PosixSocket : public Socket { - net_socket sock; - int sockopt_so_connecttimeo = 0; - int sockopt_so_reuseport = 0; - int sockopt_so_onesbcast = 0; - int sockopt_so_usecrypto = 0; - int sockopt_so_usesignature = 0; - int sockopt_so_nbio = 0; - int sockopt_ip_ttlchk = 0; - int sockopt_ip_maxttl = 0; - int sockopt_tcp_mss_to_advertise = 0; - int socket_type; - explicit PosixSocket(int domain, int type, int protocol) - : Socket(domain, type, protocol), sock(socket(domain, type, protocol)) { - socket_type = type; - } - explicit PosixSocket(net_socket sock) : Socket(0, 0, 0), sock(sock) {} - bool IsValid() const override; - int Close() override; - int Shutdown(int how) override; - int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override; - int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override; - int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override; - int Listen(int backlog) override; - int SendMessage(const OrbisNetMsghdr* msg, int flags) override; - int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to, - u32 tolen) override; - int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override; - int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override; - SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override; - int Connect(const OrbisNetSockaddr* addr, u32 namelen) override; - int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override; - int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override; - int fstat(Libraries::Kernel::OrbisKernelStat* stat) override; - std::optional Native() override { - return sock; - } -}; - -struct P2PSocket : public Socket { - explicit P2PSocket(int domain, int type, int protocol) : Socket(domain, type, protocol) {} - bool IsValid() const override { - return true; - } - int Close() override; - int Shutdown(int how) override; - int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override; - int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override; - int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override; - int Listen(int backlog) override; - int SendMessage(const OrbisNetMsghdr* msg, int flags) override; - int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to, - u32 tolen) override; - int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override; - int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override; - SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override; - int Connect(const OrbisNetSockaddr* addr, u32 namelen) override; - int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override; - int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override; - int fstat(Libraries::Kernel::OrbisKernelStat* stat) override; - std::optional Native() override { - return {}; - } -}; - -struct UnixSocket : public Socket { - net_socket sock; - int socket_type; - explicit UnixSocket(int domain, int type, int protocol) - : Socket(domain, type, protocol), sock(socket(domain, type, protocol)) { - socket_type = type; - } - explicit UnixSocket(net_socket sock) : Socket(0, 0, 0), sock(sock) {} - bool IsValid() const override; - int Close() override; - int Shutdown(int how) override; - int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override; - int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override; - int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override; - int Listen(int backlog) override; - int SendMessage(const OrbisNetMsghdr* msg, int flags) override; - int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to, - u32 tolen) override; - int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override; - int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override; - SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override; - int Connect(const OrbisNetSockaddr* addr, u32 namelen) override; - int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override; - int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override; - int fstat(Libraries::Kernel::OrbisKernelStat* stat) override; - std::optional Native() override { - return sock; - } -}; - -u16 GetP2PConfiguredPort(); -u32 GetP2PAdvertisedAddr(); -bool EnsureP2PTransport(); -bool P2PTransportIsReady(); -int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port); -int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port); -int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port); -int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port); -int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port); -int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port); - -} // namespace Libraries::Net diff --git a/src/core/libraries/network/sys_net.cpp b/src/core/libraries/network/sys_net.cpp deleted file mode 100644 index c2fa120ee..000000000 --- a/src/core/libraries/network/sys_net.cpp +++ /dev/null @@ -1,452 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include -#include "common/error.h" -#include "common/singleton.h" -#include "core/file_sys/fs.h" -#include "net_error.h" -#include "sockets.h" -#include "sys_net.h" - -namespace Libraries::Net { - -using FDTable = Common::Singleton; - -int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name); - int returncode = file->socket->Connect(addr, addrlen); - if (returncode >= 0) { - return returncode; - } - u32 error = *Libraries::Kernel::__Error(); - // Don't log EINPROGRESS or EISCONN, these are normal to see from non-blocking communication. - if (error != ORBIS_NET_EINPROGRESS && error != ORBIS_NET_EISCONN) { - LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error); - } - return -1; -} - -int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name); - int returncode = file->socket->Bind(addr, addrlen); - if (returncode >= 0) { - return returncode; - } - LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error()); - return -1; -} - -int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name); - auto new_sock = file->socket->Accept(addr, paddrlen); - if (!new_sock) { - u32 error = *Libraries::Kernel::__Error(); - // Don't log EWOULDBLOCK, this is normal to see from non-blocking communication. - if (error != ORBIS_NET_EWOULDBLOCK) { - LOG_ERROR(Lib_Net, - "s = {} ({}) returned error code creating new socket for accepting: {}", s, - file->m_guest_name, error); - } - return -1; - } - auto fd = FDTable::Instance()->CreateHandle(); - auto* new_file = FDTable::Instance()->GetFile(fd); - new_file->is_opened = true; - new_file->type = Core::FileSys::FileType::Socket; - new_file->socket = new_sock; - return fd; -} - -int PS4_SYSV_ABI sys_getpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name); - int returncode = file->socket->GetPeerName(addr, paddrlen); - if (returncode >= 0) { - return returncode; - } - LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error()); - return -1; -} - -int PS4_SYSV_ABI sys_getsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name); - int returncode = file->socket->GetSocketAddress(addr, paddrlen); - if (returncode >= 0) { - return returncode; - } - LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error()); - return -1; -} - -int PS4_SYSV_ABI sys_getsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - auto name = [&] -> std::string_view { - switch (level) { - case ORBIS_NET_SOL_SOCKET: - return NameOf((OrbisNetSocketSoOption)optname); - case ORBIS_NET_IPPROTO_IP: - return magic_enum::enum_name((OrbisNetSocketIpOption)optname); - case ORBIS_NET_IPPROTO_TCP: - return magic_enum::enum_name((OrbisNetSocketTcpOption)optname); - default: - return std::string_view{"unknown"}; - } - }(); - LOG_DEBUG(Lib_Net, "s = {} ({}), level = {}, optname = {}", s, file->m_guest_name, - NameOf((OrbisNetProtocol)level), name); - int returncode = file->socket->GetSocketOptions(level, optname, optval, optlen); - if (returncode >= 0) { - if (optname == ORBIS_NET_SO_ERROR) { - LOG_DEBUG(Lib_Net, "so_error = {}", *reinterpret_cast(optval)); - } - return returncode; - } - LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error()); - return -1; -} - -int PS4_SYSV_ABI sys_listen(OrbisNetId s, int backlog) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({}), backlog = {}", s, file->m_guest_name, backlog); - int returncode = file->socket->Listen(backlog); - if (returncode >= 0) { - return returncode; - } - LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error()); - return -1; -} - -int PS4_SYSV_ABI sys_setsockopt(OrbisNetId s, int level, int optname, const void* optval, - u32 optlen) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - auto name = [&] -> std::string_view { - switch (level) { - case ORBIS_NET_SOL_SOCKET: - return NameOf((OrbisNetSocketSoOption)optname); - case ORBIS_NET_IPPROTO_IP: - return magic_enum::enum_name((OrbisNetSocketIpOption)optname); - case ORBIS_NET_IPPROTO_TCP: - return magic_enum::enum_name((OrbisNetSocketTcpOption)optname); - default: - return std::string_view{"unknown"}; - } - }(); - LOG_DEBUG(Lib_Net, "s = {} ({}), level = {}, optname = {}", s, file->m_guest_name, - NameOf((OrbisNetProtocol)level), name); - int returncode = file->socket->SetSocketOptions(level, optname, optval, optlen); - if (returncode >= 0) { - return returncode; - } - LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error()); - return -1; -} - -int PS4_SYSV_ABI sys_shutdown(OrbisNetId s, int how) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({}), how = {}", s, file->m_guest_name, how); - int returncode = file->socket->Shutdown(how); - if (returncode >= 0) { - return returncode; - } - LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error()); - return -1; -} - -int PS4_SYSV_ABI sys_socketex(const char* name, int family, int type, int protocol) { - auto sname = name ? name : "anon"; - LOG_INFO(Lib_Net, "name = {} family = {} type = {} protocol = {}", std::string(sname), - magic_enum::enum_name((OrbisNetFamily)family), - magic_enum::enum_name((OrbisNetSocketType)type), protocol); - SocketPtr socket; - switch (type) { - case ORBIS_NET_SOCK_STREAM: - case ORBIS_NET_SOCK_DGRAM: - if (family == ORBIS_NET_AF_UNIX) { - socket = std::make_shared(family, type, protocol); - break; - } - [[fallthrough]]; - case ORBIS_NET_SOCK_RAW: - socket = std::make_shared(family, type, protocol); - break; - case ORBIS_NET_SOCK_DGRAM_P2P: - case ORBIS_NET_SOCK_STREAM_P2P: - socket = std::make_shared(family, type, protocol); - break; - default: - UNREACHABLE_MSG("Unknown type {}", type); - } - if (!socket->IsValid()) { - *Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT; - return -1; - } - - auto fd = FDTable::Instance()->CreateHandle(); - auto* sock = FDTable::Instance()->GetFile(fd); - sock->is_opened = true; - sock->type = Core::FileSys::FileType::Socket; - sock->socket = socket; - sock->m_guest_name = sname; - return fd; -} - -int PS4_SYSV_ABI sys_socket(int family, int type, int protocol) { - return sys_socketex(nullptr, family, type, protocol); -} - -#ifdef _WIN32 -int socketpair(int family, int type, int protocol, net_socket fd[2]) { - if (family != AF_UNIX && family != AF_INET) { - *Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT; - return -1; - } - if (type != SOCK_STREAM) { - *Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT; - return -1; - } - - SOCKET listener = INVALID_SOCKET; - SOCKET sock1 = INVALID_SOCKET; - SOCKET sock2 = INVALID_SOCKET; - sockaddr_in addr{}; - int addrlen = sizeof(addr); - - listener = socket(AF_INET, SOCK_STREAM, 0); - if (listener == INVALID_SOCKET) - goto fail1; - - ZeroMemory(&addr, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - addr.sin_port = 0; - - if (bind(listener, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) - goto fail1; - if (listen(listener, 1) == SOCKET_ERROR) - goto fail1; - - if (getsockname(listener, (sockaddr*)&addr, &addrlen) == SOCKET_ERROR) - goto fail1; - - sock1 = socket(AF_INET, SOCK_STREAM, 0); - if (sock1 == INVALID_SOCKET) - goto fail2; - - if (connect(sock1, (sockaddr*)&addr, addrlen) == SOCKET_ERROR) - goto fail2; - - sock2 = accept(listener, nullptr, nullptr); - if (sock2 == INVALID_SOCKET) - goto fail3; - - closesocket(listener); - fd[0] = sock1; - fd[1] = sock2; - return 0; - -fail3: - closesocket(sock2); -fail2: - closesocket(sock1); -fail1: - closesocket(listener); - return -1; -} -#endif - -int PS4_SYSV_ABI sys_socketpair(int family, int type, int protocol, int sv[2]) { - if (sv == nullptr) { - *Libraries::Kernel::__Error() = ORBIS_NET_EINVAL; - return -1; - } - if (family != ORBIS_NET_AF_UNIX) { - *Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT; - return -1; - } - - net_socket fd[2]; - if (socketpair(family, type, protocol, fd) != 0) { - LOG_ERROR(Lib_Net, "socketpair failed with {}", Common::GetLastErrorMsg()); - return -1; - } - - auto fd1 = FDTable::Instance()->CreateHandle(); - auto fd2 = FDTable::Instance()->CreateHandle(); - auto* sock = FDTable::Instance()->GetFile(fd1); - sock->is_opened = true; - sock->type = Core::FileSys::FileType::Socket; - sock->socket = std::make_shared(fd[0]); - sock->m_guest_name = "anon_sock0"; - sock = FDTable::Instance()->GetFile(fd2); - sock->is_opened = true; - sock->type = Core::FileSys::FileType::Socket; - sock->socket = std::make_shared(fd[1]); - sock->m_guest_name = "anon_sock1"; - sv[0] = fd1; - sv[1] = fd2; - return 0; -} - -int PS4_SYSV_ABI sys_netabort(OrbisNetId s, int flags) { - LOG_ERROR(Lib_Net, "(STUBBED) called"); - return -1; -} - -int PS4_SYSV_ABI sys_socketclose(OrbisNetId s) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name); - int returncode = file->socket->Close(); - if (returncode >= 0) { - FDTable::Instance()->DeleteHandle(s); - return returncode; - } - LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error()); - return -1; -} - -int PS4_SYSV_ABI sys_send(OrbisNetId s, const void* buf, u64 len, int flags) { - return sys_sendto(s, buf, len, flags, nullptr, 0); -} - -int PS4_SYSV_ABI sys_sendto(OrbisNetId s, const void* buf, u64 len, int flags, - const OrbisNetSockaddr* addr, u32 addrlen) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({}), len = {}, flags = {:#x}", s, file->m_guest_name, len, flags); - int returncode = file->socket->SendPacket(buf, len, flags, addr, addrlen); - if (returncode >= 0) { - return returncode; - } - LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error()); - return -1; -} - -int PS4_SYSV_ABI sys_sendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({}), flags = {:#x}", s, file->m_guest_name, flags); - int returncode = file->socket->SendMessage(msg, flags); - if (returncode >= 0) { - return returncode; - } - LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error()); - return -1; -} - -s64 PS4_SYSV_ABI sys_recv(OrbisNetId s, void* buf, u64 len, int flags) { - return sys_recvfrom(s, buf, len, flags, nullptr, nullptr); -} - -s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr, - u32* paddrlen) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({}), len = {}, flags = {:#x}", s, file->m_guest_name, len, flags); - int returncode = file->socket->ReceivePacket(buf, len, flags, addr, paddrlen); - if (returncode >= 0) { - return returncode; - } - // Don't log EWOULDBLOCK, this is normal to see from non-blocking communication. - u32 error = *Libraries::Kernel::__Error(); - if (error != ORBIS_NET_EWOULDBLOCK) { - LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error); - } - return -1; -} - -s64 PS4_SYSV_ABI sys_recvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags) { - auto file = FDTable::Instance()->GetSocket(s); - if (!file) { - *Libraries::Kernel::__Error() = ORBIS_NET_EBADF; - LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); - return -1; - } - LOG_DEBUG(Lib_Net, "s = {} ({}), flags = {:#x}", s, file->m_guest_name, flags); - int returncode = file->socket->ReceiveMessage(msg, flags); - if (returncode >= 0) { - return returncode; - } - LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, - (u32)*Libraries::Kernel::__Error()); - return -1; -} - -int PS4_SYSV_ABI sys_htons(u16 v) { - return ((v << 8) & 0xff00) | ((v >> 8) & 0x00ff); -} - -int PS4_SYSV_ABI sys_htonl(u32 v) { - return (v << 24) | ((v & 0xff00) << 8) | ((v >> 8) & 0xff00) | (v >> 24); -} - -} // namespace Libraries::Net diff --git a/src/core/libraries/network/unix_sockets.cpp b/src/core/libraries/network/unix_sockets.cpp deleted file mode 100644 index 467587c4d..000000000 --- a/src/core/libraries/network/unix_sockets.cpp +++ /dev/null @@ -1,397 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include "common/error.h" -#include "core/libraries/kernel/file_system.h" -#include "core/libraries/kernel/kernel.h" -#include "net.h" -#ifndef _WIN32 -#include -#include -#endif -#include "net_error.h" -#include "sockets.h" - -namespace Libraries::Net { - -#ifdef _WIN32 -#define ERROR_CASE(errname) \ - case (WSA##errname): \ - *Libraries::Kernel::__Error() = ORBIS_NET_##errname; \ - return -1; -#else -#define ERROR_CASE(errname) \ - case (errname): \ - *Libraries::Kernel::__Error() = ORBIS_NET_##errname; \ - return -1; -#endif - -static int ConvertReturnErrorCode(int retval) { - if (retval < 0) { -#ifdef _WIN32 - switch (WSAGetLastError()) { -#else - switch (errno) { -#endif -#ifndef _WIN32 // These errorcodes don't exist in WinSock - ERROR_CASE(EPERM) - ERROR_CASE(ENOENT) - // ERROR_CASE(ESRCH) - // ERROR_CASE(EIO) - // ERROR_CASE(ENXIO) - // ERROR_CASE(E2BIG) - // ERROR_CASE(ENOEXEC) - // ERROR_CASE(EDEADLK) - ERROR_CASE(ENOMEM) - // ERROR_CASE(ECHILD) - // ERROR_CASE(EBUSY) - ERROR_CASE(EEXIST) - // ERROR_CASE(EXDEV) - ERROR_CASE(ENODEV) - // ERROR_CASE(ENOTDIR) - // ERROR_CASE(EISDIR) - ERROR_CASE(ENFILE) - // ERROR_CASE(ENOTTY) - // ERROR_CASE(ETXTBSY) - // ERROR_CASE(EFBIG) - ERROR_CASE(ENOSPC) - // ERROR_CASE(ESPIPE) - // ERROR_CASE(EROFS) - // ERROR_CASE(EMLINK) - ERROR_CASE(EPIPE) - // ERROR_CASE(EDOM) - // ERROR_CASE(ERANGE) - // ERROR_CASE(ENOLCK) - // ERROR_CASE(ENOSYS) - // ERROR_CASE(EIDRM) - // ERROR_CASE(EOVERFLOW) - // ERROR_CASE(EILSEQ) - // ERROR_CASE(ENOTSUP) - ERROR_CASE(ECANCELED) - // ERROR_CASE(EBADMSG) - ERROR_CASE(ENODATA) - // ERROR_CASE(ENOSR) - // ERROR_CASE(ENOSTR) - // ERROR_CASE(ETIME) -#endif - ERROR_CASE(EINTR) - ERROR_CASE(EBADF) - ERROR_CASE(EACCES) - ERROR_CASE(EFAULT) - ERROR_CASE(EINVAL) - ERROR_CASE(EMFILE) - ERROR_CASE(EWOULDBLOCK) - ERROR_CASE(EINPROGRESS) - ERROR_CASE(EALREADY) - ERROR_CASE(ENOTSOCK) - ERROR_CASE(EDESTADDRREQ) - ERROR_CASE(EMSGSIZE) - ERROR_CASE(EPROTOTYPE) - ERROR_CASE(ENOPROTOOPT) - ERROR_CASE(EPROTONOSUPPORT) -#if defined(__APPLE__) || defined(_WIN32) - ERROR_CASE(EOPNOTSUPP) -#endif - ERROR_CASE(EAFNOSUPPORT) - ERROR_CASE(EADDRINUSE) - ERROR_CASE(EADDRNOTAVAIL) - ERROR_CASE(ENETDOWN) - ERROR_CASE(ENETUNREACH) - ERROR_CASE(ENETRESET) - ERROR_CASE(ECONNABORTED) - ERROR_CASE(ECONNRESET) - ERROR_CASE(ENOBUFS) - ERROR_CASE(EISCONN) - ERROR_CASE(ENOTCONN) - ERROR_CASE(ETIMEDOUT) - ERROR_CASE(ECONNREFUSED) - ERROR_CASE(ELOOP) - ERROR_CASE(ENAMETOOLONG) - ERROR_CASE(EHOSTUNREACH) - ERROR_CASE(ENOTEMPTY) - } - *Libraries::Kernel::__Error() = ORBIS_NET_EINTERNAL; - return -1; - } - // if it is 0 or positive return it as it is - return retval; -} - -static int ConvertLevels(int level) { - switch (level) { - case ORBIS_NET_SOL_SOCKET: - return SOL_SOCKET; - case ORBIS_NET_IPPROTO_IP: - return IPPROTO_IP; - case ORBIS_NET_IPPROTO_TCP: - return IPPROTO_TCP; - case ORBIS_NET_IPPROTO_IPV6: - return IPPROTO_IPV6; - } - return -1; -} - -static void convertOrbisNetSockaddrToUnix(const OrbisNetSockaddr* src, sockaddr_un* dst) { - if (src == nullptr || dst == nullptr) - return; - memset(dst, 0, sizeof(sockaddr_un)); - const OrbisNetSockaddrUn* src_in = (const OrbisNetSockaddrUn*)src; - sockaddr_un* dst_in = (sockaddr_un*)dst; - dst_in->sun_family = src_in->sun_family; - memcpy(&dst_in->sun_path, &src_in->sun_path, src_in->sun_len); -} - -static void convertUnixSockaddrToOrbis(sockaddr* src, OrbisNetSockaddr* dst) { - if (src == nullptr || dst == nullptr) - return; - memset(dst, 0, sizeof(OrbisNetSockaddrUn)); - OrbisNetSockaddrUn* dst_in = (OrbisNetSockaddrUn*)dst; - sockaddr_un* src_in = (sockaddr_un*)src; - dst_in->sun_len = strnlen(src_in->sun_path, 108); - dst_in->sun_family = src_in->sun_family; - memcpy(&dst_in->sun_path, &src_in->sun_path, dst_in->sun_len); -} - -bool UnixSocket::IsValid() const { -#ifdef _WIN32 - return sock != INVALID_SOCKET; -#else - return sock != -1; -#endif -} - -int UnixSocket::Close() { - std::scoped_lock lock{m_mutex}; -#ifdef _WIN32 - auto out = closesocket(sock); -#else - auto out = ::close(sock); -#endif - return ConvertReturnErrorCode(out); -} - -int UnixSocket::Shutdown(int how) { - return ConvertReturnErrorCode(::shutdown(sock, how)); -} - -int UnixSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) { - std::scoped_lock lock{m_mutex}; - sockaddr_un addr2; - convertOrbisNetSockaddrToUnix(addr, &addr2); - return ConvertReturnErrorCode(::bind(sock, (const sockaddr*)&addr2, sizeof(sockaddr_un))); -} - -int UnixSocket::Listen(int backlog) { - std::scoped_lock lock{m_mutex}; - return ConvertReturnErrorCode(::listen(sock, backlog)); -} - -int UnixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) { - std::scoped_lock lock{m_mutex}; -#ifdef _WIN32 - DWORD bytesSent = 0; - LPFN_WSASENDMSG wsasendmsg = nullptr; - GUID guid = WSAID_WSASENDMSG; - DWORD bytes = 0; - - if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsasendmsg, - sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) { - return ConvertReturnErrorCode(-1); - } - - int res = wsasendmsg(sock, reinterpret_cast(const_cast(msg)), flags, - &bytesSent, nullptr, nullptr); - - if (res == SOCKET_ERROR) { - return ConvertReturnErrorCode(-1); - } - return static_cast(bytesSent); -#else - int res = sendmsg(sock, reinterpret_cast(msg), flags); - return ConvertReturnErrorCode(res); -#endif -} - -int UnixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to, - u32 tolen) { - std::scoped_lock lock{m_mutex}; - LOG_ERROR(Lib_Net, "called"); - if (to != nullptr) { - sockaddr_un addr; - convertOrbisNetSockaddrToUnix(to, &addr); - return ConvertReturnErrorCode( - sendto(sock, (const char*)msg, len, flags, (sockaddr*)&addr, sizeof(sockaddr_un))); - } else { - return ConvertReturnErrorCode(send(sock, (const char*)msg, len, flags)); - } -} - -int UnixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) { - std::scoped_lock lock{receive_mutex}; -#ifdef _WIN32 - LPFN_WSARECVMSG wsarecvmsg = nullptr; - GUID guid = WSAID_WSARECVMSG; - DWORD bytes = 0; - - if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsarecvmsg, - sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) { - return ConvertReturnErrorCode(-1); - } - - DWORD bytesReceived = 0; - int res = wsarecvmsg(sock, reinterpret_cast(msg), &bytesReceived, nullptr, nullptr); - - if (res == SOCKET_ERROR) { - return ConvertReturnErrorCode(-1); - } - return static_cast(bytesReceived); -#else - int res = recvmsg(sock, reinterpret_cast(msg), flags); - return ConvertReturnErrorCode(res); -#endif -} - -int UnixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) { - std::scoped_lock lock{receive_mutex}; - LOG_ERROR(Lib_Net, "called"); - if (from != nullptr) { - sockaddr_un addr; - int res = recvfrom(sock, (char*)buf, len, flags, (sockaddr*)&addr, (socklen_t*)fromlen); - convertUnixSockaddrToOrbis((sockaddr*)&addr, from); - *fromlen = sizeof(OrbisNetSockaddrUn); - return ConvertReturnErrorCode(res); - } else { - return ConvertReturnErrorCode(recv(sock, (char*)buf, len, flags)); - } -} - -SocketPtr UnixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) { - std::scoped_lock lock{m_mutex}; - sockaddr_un addr2; - socklen_t len = sizeof(addr2); - net_socket new_socket = ::accept(sock, (sockaddr*)&addr2, &len); -#ifdef _WIN32 - if (new_socket != INVALID_SOCKET) { -#else - if (new_socket >= 0) { -#endif - if (addr && addrlen) { - convertUnixSockaddrToOrbis((sockaddr*)&addr2, addr); - *addrlen = sizeof(OrbisNetSockaddrUn); - } - return std::make_shared(new_socket); - } - ConvertReturnErrorCode(new_socket); - return nullptr; -} - -int UnixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) { - std::scoped_lock lock{m_mutex}; - sockaddr_un addr2; - convertOrbisNetSockaddrToUnix(addr, &addr2); - int result = 0; - do { - result = ::connect(sock, (sockaddr*)&addr2, sizeof(sockaddr_un)); - LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result, - result == -1 ? Common::GetLastErrorMsg() : "none"); - } while (result == -1 && (errno == EINTR || errno == EINPROGRESS)); - return ConvertReturnErrorCode(result); -} - -int UnixSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) { - std::scoped_lock lock{m_mutex}; - - sockaddr_un addr; - convertOrbisNetSockaddrToUnix(name, &addr); - if (name != nullptr) { - *namelen = sizeof(sockaddr_un); - } - int res = getsockname(sock, (sockaddr*)&addr, (socklen_t*)namelen); - if (res >= 0) { - convertUnixSockaddrToOrbis((sockaddr*)&addr, name); - *namelen = sizeof(OrbisNetSockaddrUn); - } - return ConvertReturnErrorCode(res); -} - -#define CASE_SETSOCKOPT(opt) \ - case ORBIS_NET_##opt: \ - return ConvertReturnErrorCode( \ - setsockopt(sock, native_level, opt, (const char*)optval, optlen)) - -#define CASE_SETSOCKOPT_VALUE(opt, value) \ - case opt: \ - if (optlen != sizeof(*value)) { \ - *Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \ - return -1; \ - } \ - memcpy(value, optval, optlen); \ - return 0 - -int UnixSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) { - std::scoped_lock lock{m_mutex}; - LOG_ERROR(Lib_Net, "Unknown level = {} optname = {}", level, optname); - return -1; -} - -#define CASE_GETSOCKOPT(opt) \ - case ORBIS_NET_##opt: { \ - socklen_t optlen_temp = *optlen; \ - auto retval = ConvertReturnErrorCode( \ - getsockopt(sock, native_level, opt, (char*)optval, &optlen_temp)); \ - *optlen = optlen_temp; \ - return retval; \ - } -#define CASE_GETSOCKOPT_VALUE(opt, value) \ - case opt: \ - if (*optlen < sizeof(value)) { \ - *optlen = sizeof(value); \ - *Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \ - return -1; \ - } \ - *optlen = sizeof(value); \ - *(decltype(value)*)optval = value; \ - return 0; - -int UnixSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) { - std::scoped_lock lock{m_mutex}; - LOG_ERROR(Lib_Net, "Unknown level = {} optname = {}", level, optname); - return ConvertReturnErrorCode(-1); -} - -int UnixSocket::GetPeerName(OrbisNetSockaddr* name, u32* namelen) { - std::scoped_lock lock{m_mutex}; - LOG_DEBUG(Lib_Net, "called"); - - sockaddr_un addr; - convertOrbisNetSockaddrToUnix(name, &addr); - if (name != nullptr) { - *namelen = sizeof(sockaddr_un); - } - int res = ::getpeername(sock, (sockaddr*)&addr, (socklen_t*)namelen); - if (res >= 0) { - convertUnixSockaddrToOrbis((sockaddr*)&addr, name); - *namelen = sizeof(OrbisNetSockaddrUn); - } - return ConvertReturnErrorCode(res); -} - -int UnixSocket::fstat(Libraries::Kernel::OrbisKernelStat* sb) { -#ifdef _WIN32 - LOG_ERROR(Lib_Net, "(STUBBED) called"); - sb->st_mode = 0000777u | 0140000u; - return 0; -#else - struct stat st{}; - int result = ::fstat(sock, &st); - sb->st_mode = 0000777u | 0140000u; - sb->st_size = st.st_size; - sb->st_blocks = st.st_blocks; - sb->st_blksize = st.st_blksize; - // sb->st_flags = st.st_flags; - return ConvertReturnErrorCode(result); -#endif -} - -} // namespace Libraries::Net diff --git a/src/core/libraries/np/np_handler.cpp b/src/core/libraries/np/np_handler.cpp index 99fe06bc5..976786d9b 100644 --- a/src/core/libraries/np/np_handler.cpp +++ b/src/core/libraries/np/np_handler.cpp @@ -12,9 +12,12 @@ #include "common/string_util.h" #include "core/emulator_settings.h" #include "core/libraries/invitation_dialog/invitation_dialog.h" -#include "core/libraries/network/net_upnp.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_p2p.h" +#include "core/libraries/net/net_upnp.h" #include "core/libraries/np/np_error.h" #include "core/libraries/np/np_manager.h" +#include "core/libraries/np/np_matching2/np_matching2_internal.h" #include "core/libraries/np/np_matching2/np_matching2_mm.h" #include "core/libraries/np/np_score/np_score.h" #include "core/libraries/np/np_web_api/np_web_api.h" @@ -28,11 +31,50 @@ namespace Libraries::Np { +NpHandler::NpHandler() + : m_matching2_contexts( + std::unique_ptr(new NpMatching2::ContextManager())), + m_matching2_state(std::make_unique()) {} + +NpHandler::~NpHandler() = default; + NpHandler& NpHandler::GetInstance() { static NpHandler s_instance; return s_instance; } +NpMatching2::ContextManager& NpHandler::GetMatching2ContextManager() { + return *m_matching2_contexts; +} + +NpMatching2::NpMatching2State& NpHandler::GetMatching2State() { + return *m_matching2_state; +} + +NpHandler::Matching2CacheGuard NpHandler::LockMatching2Cache( + NpMatching2::OrbisNpMatching2ContextId ctx_id) { + std::shared_ptr cache; + { + std::lock_guard lock(m_mutex_matching2_cache); + auto& entry = m_matching2_cache[ctx_id]; + if (!entry) { + entry = std::make_shared(); + } + cache = entry; + } + return Matching2CacheGuard(std::move(cache)); +} + +void NpHandler::ResetMatching2Cache(NpMatching2::OrbisNpMatching2ContextId ctx_id) { + std::lock_guard lock(m_mutex_matching2_cache); + m_matching2_cache.erase(ctx_id); +} + +void NpHandler::ResetMatching2Caches() { + std::lock_guard lock(m_mutex_matching2_cache); + m_matching2_cache.clear(); +} + std::pair NpHandler::ParseServerAddress() const { const std::string server_str = EmulatorSettings.GetShadNetServer(); u16 port = 31313; // default port @@ -193,6 +235,9 @@ void NpHandler::Shutdown() { if (m_worker_thread.joinable()) m_worker_thread.join(); + // P2P goes with NP: closes the UDP port and removes its UPnP forwarding. + Net::StopP2P(); + LOG_INFO(NpHandler, "Shutdown complete"); } @@ -549,17 +594,17 @@ bool NpHandler::SendSessionInvitation(s32 user_id, const std::string& session_id const std::vector& to, const std::string& message) { if (session_id.empty() || to.empty()) { - LOG_ERROR(NpHandler, "SendSessionInvitation: empty session_id or recipient list"); + LOG_ERROR(NpHandler, "empty session_id or recipient list"); return false; } const std::string base_url = EmulatorSettings.GetShadNetWebApiServer(); if (base_url.empty()) { - LOG_ERROR(NpHandler, "SendSessionInvitation: WebAPI server address is empty"); + LOG_ERROR(NpHandler, "WebAPI server address is empty"); return false; } const std::string token = GetBearerToken(user_id); if (token.empty()) { - LOG_ERROR(NpHandler, "SendSessionInvitation: no bearer token for user_id={}", user_id); + LOG_ERROR(NpHandler, "no bearer token for user_id={}", user_id); return false; } @@ -599,15 +644,14 @@ bool NpHandler::SendSessionInvitation(s32 user_id, const std::string& session_id const httplib::Headers headers = {{"Authorization", "Bearer " + token}}; const auto res = cli.Post(path.c_str(), headers, body, content_type.c_str()); if (!res) { - LOG_ERROR(NpHandler, "SendSessionInvitation: POST {} failed (no response)", path); + LOG_ERROR(NpHandler, "POST {} failed (no response)", path); return false; } if (res->status != 200 && res->status != 204) { - LOG_ERROR(NpHandler, "SendSessionInvitation: POST {} -> HTTP {}", path, res->status); + LOG_ERROR(NpHandler, "POST {} -> HTTP {}", path, res->status); return false; } - LOG_INFO(NpHandler, "SendSessionInvitation: sent invite to {} recipient(s) for session '{}'", - to.size(), session_id); + LOG_INFO(NpHandler, "sent invite to {} recipient(s) for session '{}'", to.size(), session_id); return true; } @@ -688,35 +732,14 @@ bool NpHandler::AcceptSessionInvitation(s32 user_id, const std::string& invitati // Whatever surface handled it (RECV dialog or the emulator prompt), retire the other one. ImGui::InvitationPrompt::Dismiss(invitation_id); if (!found) { - LOG_ERROR(NpHandler, "AcceptSessionInvitation: no pending invite '{}' for user_id={}", - invitation_id, user_id); + LOG_ERROR(NpHandler, "no pending invite '{}' for user_id={}", invitation_id, user_id); return false; } // Raise the join event now that the user has explicitly accepted (via the RECV dialog or the // emulator's system-UI equivalent). PostSessionInvitationEvent(user_id, inv.session_id, invitation_id, inv.to_npid, inv.from_npid, inv.from_account_id); - // Consume it server-side (PUT usedFlag=true). - const std::string base_url = EmulatorSettings.GetShadNetWebApiServer(); - const std::string token = GetBearerToken(user_id); - if (base_url.empty() || token.empty()) { - LOG_ERROR(NpHandler, "AcceptSessionInvitation: no WebAPI server/token for user_id={}", - user_id); - return false; - } - httplib::Client cli(base_url); - cli.set_connection_timeout(5); - cli.set_read_timeout(10); - const httplib::Headers headers = {{"Authorization", "Bearer " + token}}; - const std::string path = "/v1/users/me/invitations/" + invitation_id; - const auto res = cli.Put(path.c_str(), headers, "{\"usedFlag\":true}", "application/json"); - if (!res || (res->status != 200 && res->status != 204)) { - LOG_ERROR(NpHandler, "AcceptSessionInvitation: PUT {} failed ({})", path, - res ? res->status : 0); - return false; - } - LOG_INFO(NpHandler, "AcceptSessionInvitation: consumed '{}' session='{}'", invitation_id, - inv.session_id); + LOG_INFO(NpHandler, "accepted '{}' session='{}'", invitation_id, inv.session_id); return true; } @@ -732,8 +755,7 @@ void NpHandler::DeclineSessionInvitation(s32 user_id, const std::string& invitat v.begin(), v.end(), [&](const PendingInvitation& p) { return p.invitation_id == invitation_id; }), v.end()); - LOG_INFO(NpHandler, "DeclineSessionInvitation: dismissed '{}' for user_id={}", invitation_id, - user_id); + LOG_INFO(NpHandler, "dismissed '{}' for user_id={}", invitation_id, user_id); } u32 NpHandler::GetLocalIpAddr(s32 user_id) const { @@ -1117,7 +1139,7 @@ std::string NpHandler::GetNpCommId(s32 service_label) const { com_id = ids[idx]; if (static_cast(service_label) >= ids.size()) { LOG_WARNING(NpHandler, - "GetNpCommId: service_label={} >= npbind entry count {} — falling back " + "service_label={} >= npbind entry count {} — falling back " "to index 0", service_label, ids.size()); } @@ -1139,13 +1161,13 @@ s32 NpHandler::RecordScore(s32 user_id, s32 service_label, u32 boardId, s32 pcId std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_WARNING(NpHandler, "RecordScore: user_id={} not connected", user_id); + LOG_WARNING(NpHandler, "user_id={} not connected", user_id); return ORBIS_NP_ERROR_SIGNED_OUT; } client = it->second; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "RecordScore: user_id={} not authenticated", user_id); + LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id); return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; } @@ -1189,7 +1211,7 @@ s32 NpHandler::RecordScore(s32 user_id, s32 service_label, u32 boardId, s32 pcId m_pending_score.emplace(pkt_id, std::move(pending)); } LOG_INFO(NpHandler, - "RecordScore: user_id={} service_label={} board={} pcId={} score={} commentLen={} " + "user_id={} service_label={} board={} pcId={} score={} commentLen={} " "gameInfoSize={} pkt_id={} com_id='{}'", user_id, service_label, boardId, pcId, score, commentLen, gameInfoSize, pkt_id, com_id); @@ -1204,13 +1226,13 @@ s32 NpHandler::RecordGameData(s32 user_id, s32 service_label, u32 boardId, s32 p std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_WARNING(NpHandler, "RecordGameData: user_id={} not connected", user_id); + LOG_WARNING(NpHandler, "user_id={} not connected", user_id); return ORBIS_NP_ERROR_SIGNED_OUT; } client = it->second; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "RecordGameData: user_id={} not authenticated", user_id); + LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id); return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; } @@ -1250,7 +1272,7 @@ s32 NpHandler::RecordGameData(s32 user_id, s32 service_label, u32 boardId, s32 p m_pending_score.emplace(pkt_id, std::move(pending)); } LOG_INFO(NpHandler, - "RecordGameData: user_id={} service_label={} board={} pcId={} score={} dataSize={} " + "user_id={} service_label={} board={} pcId={} score={} dataSize={} " "pkt_id={} com_id='{}'", user_id, service_label, boardId, pcId, score, size, pkt_id, com_id); return ORBIS_OK; @@ -1264,13 +1286,13 @@ s32 NpHandler::GetGameData(s32 user_id, s32 service_label, u32 boardId, const st std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_WARNING(NpHandler, "GetGameData: user_id={} not connected", user_id); + LOG_WARNING(NpHandler, "user_id={} not connected", user_id); return ORBIS_NP_ERROR_SIGNED_OUT; } client = it->second; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "GetGameData: user_id={} not authenticated", user_id); + LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id); return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; } @@ -1310,7 +1332,7 @@ s32 NpHandler::GetGameData(s32 user_id, s32 service_label, u32 boardId, const st m_pending_score.emplace(pkt_id, std::move(pending)); } LOG_INFO(NpHandler, - "GetGameData: user_id={} service_label={} board={} npId='{}' pcId={} recvSize={} " + "user_id={} service_label={} board={} npId='{}' pcId={} recvSize={} " "pkt_id={} com_id='{}'", user_id, service_label, boardId, npId, pcId, recvSize, pkt_id, com_id); return ORBIS_OK; @@ -1324,13 +1346,13 @@ s32 NpHandler::GetGameDataByAccountId(s32 user_id, s32 service_label, u32 boardI std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_WARNING(NpHandler, "GetGameDataByAccountId: user_id={} not connected", user_id); + LOG_WARNING(NpHandler, "user_id={} not connected", user_id); return ORBIS_NP_ERROR_SIGNED_OUT; } client = it->second; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "GetGameDataByAccountId: user_id={} not authenticated", user_id); + LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id); return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; } @@ -1371,7 +1393,7 @@ s32 NpHandler::GetGameDataByAccountId(s32 user_id, s32 service_label, u32 boardI m_pending_score.emplace(pkt_id, std::move(pending)); } LOG_INFO(NpHandler, - "GetGameDataByAccountId: user_id={} service_label={} board={} accountId={} pcId={} " + "user_id={} service_label={} board={} accountId={} pcId={} " "recvSize={} pkt_id={} com_id='{}'", user_id, service_label, boardId, accountId, pcId, recvSize, pkt_id, com_id); return ORBIS_OK; @@ -1385,13 +1407,13 @@ s32 NpHandler::GetBoardInfo(s32 user_id, s32 service_label, u32 boardId, std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_WARNING(NpHandler, "GetBoardInfo: user_id={} not connected", user_id); + LOG_WARNING(NpHandler, "user_id={} not connected", user_id); return ORBIS_NP_ERROR_SIGNED_OUT; } client = it->second; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "GetBoardInfo: user_id={} not authenticated", user_id); + LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id); return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; } @@ -1419,8 +1441,8 @@ s32 NpHandler::GetBoardInfo(s32 user_id, s32 service_label, u32 boardId, pending.user_id = user_id; m_pending_score.emplace(pkt_id, std::move(pending)); } - LOG_INFO(NpHandler, "GetBoardInfo: user_id={} service_label={} board={} pkt_id={} com_id='{}'", - user_id, service_label, boardId, pkt_id, com_id); + LOG_INFO(NpHandler, "user_id={} service_label={} board={} pkt_id={} com_id='{}'", user_id, + service_label, boardId, pkt_id, com_id); return ORBIS_OK; } @@ -1434,8 +1456,7 @@ s32 NpHandler::GetRankingByNpId(s32 user_id, s32 service_label, u32 boardId, std::shared_ptr req) { // pcIds must either be empty (use 0 for everything) or match npIds in size. if (!pcIds.empty() && pcIds.size() != npIds.size()) { - LOG_ERROR(NpHandler, "GetRankingByNpId: pcIds size {} != npIds size {}", pcIds.size(), - npIds.size()); + LOG_ERROR(NpHandler, "pcIds size {} != npIds size {}", pcIds.size(), npIds.size()); return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; } @@ -1445,13 +1466,13 @@ s32 NpHandler::GetRankingByNpId(s32 user_id, s32 service_label, u32 boardId, std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_WARNING(NpHandler, "GetRankingByNpId: user_id={} not connected", user_id); + LOG_WARNING(NpHandler, "user_id={} not connected", user_id); return ORBIS_NP_ERROR_SIGNED_OUT; } client = it->second; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "GetRankingByNpId: user_id={} not authenticated", user_id); + LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id); return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; } @@ -1500,7 +1521,7 @@ s32 NpHandler::GetRankingByNpId(s32 user_id, s32 service_label, u32 boardId, m_pending_score.emplace(pkt_id, std::move(pending)); } LOG_INFO(NpHandler, - "GetRankingByNpId: user_id={} service_label={} board={} npIdCount={} " + "user_id={} service_label={} board={} npIdCount={} " "withPcId={} withComment={} withGameInfo={} pkt_id={} com_id='{}'", user_id, service_label, boardId, npIds.size(), !pcIds.empty(), commentArray != nullptr, infoArray != nullptr, pkt_id, com_id); @@ -1518,13 +1539,13 @@ s32 NpHandler::GetRankingByRange(s32 user_id, s32 service_label, u32 boardId, u3 std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_WARNING(NpHandler, "GetRankingByRange: user_id={} not connected", user_id); + LOG_WARNING(NpHandler, "user_id={} not connected", user_id); return ORBIS_NP_ERROR_SIGNED_OUT; } client = it->second; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "GetRankingByRange: user_id={} not authenticated", user_id); + LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id); return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; } @@ -1569,7 +1590,7 @@ s32 NpHandler::GetRankingByRange(s32 user_id, s32 service_label, u32 boardId, u3 m_pending_score.emplace(pkt_id, std::move(pending)); } LOG_INFO(NpHandler, - "GetRankingByRange: user_id={} service_label={} board={} startRank={} numRanks={} " + "user_id={} service_label={} board={} startRank={} numRanks={} " "withComment={} withGameInfo={} pkt_id={} com_id='{}'", user_id, service_label, boardId, startSerialRank, arrayNum, commentArray != nullptr, infoArray != nullptr, pkt_id, com_id); @@ -1587,13 +1608,13 @@ s32 NpHandler::GetRankingByRangeA(s32 user_id, s32 service_label, u32 boardId, u std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_WARNING(NpHandler, "GetRankingByRangeA: user_id={} not connected", user_id); + LOG_WARNING(NpHandler, "user_id={} not connected", user_id); return ORBIS_NP_ERROR_SIGNED_OUT; } client = it->second; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "GetRankingByRangeA: user_id={} not authenticated", user_id); + LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id); return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; } @@ -1641,7 +1662,7 @@ s32 NpHandler::GetRankingByRangeA(s32 user_id, s32 service_label, u32 boardId, u m_pending_score.emplace(pkt_id, std::move(pending)); } LOG_INFO(NpHandler, - "GetRankingByRangeA: user_id={} service_label={} board={} startRank={} numRanks={} " + "user_id={} service_label={} board={} startRank={} numRanks={} " "withComment={} withGameInfo={} pkt_id={} com_id='{}'", user_id, service_label, boardId, startSerialRank, arrayNum, commentArray != nullptr, infoArray != nullptr, pkt_id, com_id); @@ -1661,7 +1682,7 @@ s32 NpHandler::GetRankingByAccountId(s32 user_id, s32 service_label, u32 boardId std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_WARNING(NpHandler, "GetRankingByAccountId: user_id={} not connected", user_id); + LOG_WARNING(NpHandler, "user_id={} not connected", user_id); return ORBIS_NP_ERROR_SIGNED_OUT; } client = it->second; @@ -1715,7 +1736,7 @@ s32 NpHandler::GetRankingByAccountId(s32 user_id, s32 service_label, u32 boardId m_pending_score.emplace(pkt_id, std::move(pending)); } LOG_INFO(NpHandler, - "GetRankingByAccountId: user_id={} service_label={} board={} accountIdCount={} " + "user_id={} service_label={} board={} accountIdCount={} " "withComment={} withGameInfo={} pkt_id={} com_id='{}'", user_id, service_label, boardId, accountIds.size(), commentArray != nullptr, infoArray != nullptr, pkt_id, com_id); @@ -1733,13 +1754,13 @@ s32 NpHandler::GetFriendsRanking(s32 user_id, s32 service_label, u32 boardId, bo std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_WARNING(NpHandler, "GetFriendsRanking: user_id={} not connected", user_id); + LOG_WARNING(NpHandler, "user_id={} not connected", user_id); return ORBIS_NP_ERROR_SIGNED_OUT; } client = it->second; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "GetFriendsRanking: user_id={} not authenticated", user_id); + LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id); return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; } @@ -1784,7 +1805,7 @@ s32 NpHandler::GetFriendsRanking(s32 user_id, s32 service_label, u32 boardId, bo m_pending_score.emplace(pkt_id, std::move(pending)); } LOG_INFO(NpHandler, - "GetFriendsRanking: user_id={} service_label={} board={} includeSelf={} " + "user_id={} service_label={} board={} includeSelf={} " "arrayNum={} withComment={} withGameInfo={} pkt_id={} com_id='{}'", user_id, service_label, boardId, includeSelf, arrayNum, commentArray != nullptr, infoArray != nullptr, pkt_id, com_id); @@ -1802,13 +1823,13 @@ s32 NpHandler::GetFriendsRankingA(s32 user_id, s32 service_label, u32 boardId, b std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_WARNING(NpHandler, "GetFriendsRankingA: user_id={} not connected", user_id); + LOG_WARNING(NpHandler, "user_id={} not connected", user_id); return ORBIS_NP_ERROR_SIGNED_OUT; } client = it->second; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "GetFriendsRankingA: user_id={} not authenticated", user_id); + LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id); return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; } @@ -1855,7 +1876,7 @@ s32 NpHandler::GetFriendsRankingA(s32 user_id, s32 service_label, u32 boardId, b m_pending_score.emplace(pkt_id, std::move(pending)); } LOG_INFO(NpHandler, - "GetFriendsRankingA: user_id={} service_label={} board={} includeSelf={} " + "user_id={} service_label={} board={} includeSelf={} " "arrayNum={} withComment={} withGameInfo={} pkt_id={} com_id='{}'", user_id, service_label, boardId, includeSelf, arrayNum, commentArray != nullptr, infoArray != nullptr, pkt_id, com_id); @@ -1880,7 +1901,7 @@ static u32 FillPlainRankArrayFromProto(const shadnet::GetScoreResponse& resp, u6 std::memcpy(out.npId.handle.data, npid.data(), cp); out.npId.handle.term = 0; LOG_INFO(NpHandler, - "FillPlainRankArrayFromProto: out[{}] (resp[{}]) npid='{}' (len={}) rank={} " + "out[{}] (resp[{}]) npid='{}' (len={}) rank={} " "score={}", out_i, i, npid, npid.size(), r.rank(), r.score()); out.pcId = r.pcid(); @@ -1926,7 +1947,7 @@ static u32 FillPlainRankArrayAFromProto(const shadnet::GetScoreResponse& resp, u std::memcpy(out.onlineId.data, npid.data(), cp); out.onlineId.term = 0; LOG_INFO(NpHandler, - "FillPlainRankArrayAFromProto: out[{}] (resp[{}]) npid='{}' (len={}) rank={} " + "out[{}] (resp[{}]) npid='{}' (len={}) rank={} " "score={} accountId={}", out_i, i, npid, npid.size(), r.rank(), r.score(), r.accountid()); out.pcId = r.pcid(); @@ -1963,7 +1984,7 @@ static u32 FillPlayerRankArrayAFromProto(const shadnet::GetScoreResponse& resp, const int n_req = static_cast(requestedCount); if (n_resp != n_req) { LOG_WARNING(NpHandler, - "FillPlayerRankArrayAFromProto: response count {} != request count {} — " + "response count {} != request count {} — " "will fill up to min() and leave extras at hasData=0", n_resp, n_req); } @@ -2015,7 +2036,7 @@ static u32 FillRankArrayFromProto(const shadnet::GetScoreResponse& resp, const int n_req = static_cast(requestedNpIds.size()); if (n_resp != n_req) { LOG_WARNING(NpHandler, - "FillRankArrayFromProto: response count {} != request count {} — " + "response count {} != request count {} — " "will fill up to min() and leave extras at hasData=0", n_resp, n_req); } @@ -2782,24 +2803,22 @@ void NpHandler::ReportTrophyUnlock(s32 user_id, s32 service_label, s32 trophy_id std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_DEBUG(NpHandler, "ReportTrophyUnlock: no shadNet session for user_id={}; skipping", - user_id); + LOG_DEBUG(NpHandler, "no shadNet session for user_id={}; skipping", user_id); return; } client = it->second; } if (!client) { - LOG_DEBUG(NpHandler, "ReportTrophyUnlock: null client for user_id={}; skipping", user_id); + LOG_DEBUG(NpHandler, "null client for user_id={}; skipping", user_id); return; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "ReportTrophyUnlock: not authenticated; trophy {} not mirrored", - trophy_id); + LOG_WARNING(NpHandler, "not authenticated; trophy {} not mirrored", trophy_id); return; } if (!client->IsTrophiesEnabled()) { LOG_WARNING(NpHandler, - "ReportTrophyUnlock: server has not advertised trophy support; trophy {} " + "server has not advertised trophy support; trophy {} " "not mirrored", trophy_id); return; @@ -2808,15 +2827,15 @@ void NpHandler::ReportTrophyUnlock(s32 user_id, s32 service_label, s32 trophy_id const std::string com_id = GetNpCommId(service_label); if (!IsValidNpCommId(com_id)) { LOG_WARNING(NpHandler, - "ReportTrophyUnlock: no valid NP Communication ID for service_label={}; " + "no valid NP Communication ID for service_label={}; " "skipping trophy {}", service_label, trophy_id); return; } const u64 pkt_id = client->UnlockTrophy(com_id, trophy_id, timestamp); - LOG_INFO(NpHandler, "ReportTrophyUnlock: user_id={} trophy={} com_id='{}' pkt_id={}", user_id, - trophy_id, com_id, pkt_id); + LOG_INFO(NpHandler, "user_id={} trophy={} com_id='{}' pkt_id={}", user_id, trophy_id, com_id, + pkt_id); } void NpHandler::SyncTrophies( @@ -2827,30 +2846,28 @@ void NpHandler::SyncTrophies( std::lock_guard lock(m_mutex_clients); auto it = m_clients.find(user_id); if (it == m_clients.end()) { - LOG_DEBUG(NpHandler, "SyncTrophies: no shadNet session for user_id={}; skipping", - user_id); + LOG_DEBUG(NpHandler, "no shadNet session for user_id={}; skipping", user_id); return; } client = it->second; } if (!client) { - LOG_DEBUG(NpHandler, "SyncTrophies: null client for user_id={}; skipping", user_id); + LOG_DEBUG(NpHandler, "null client for user_id={}; skipping", user_id); return; } if (!client->IsAuthenticated()) { - LOG_WARNING(NpHandler, "SyncTrophies: not authenticated; skipping sync"); + LOG_WARNING(NpHandler, "not authenticated; skipping sync"); return; } if (!client->IsTrophiesEnabled()) { - LOG_WARNING(NpHandler, "SyncTrophies: server has not advertised trophy support; " + LOG_WARNING(NpHandler, "server has not advertised trophy support; " "skipping sync"); return; } const std::string com_id = GetNpCommId(service_label); if (!IsValidNpCommId(com_id)) { - LOG_WARNING(NpHandler, - "SyncTrophies: no valid NP Communication ID for service_label={}; skipping", + LOG_WARNING(NpHandler, "no valid NP Communication ID for service_label={}; skipping", service_label); return; } @@ -2860,7 +2877,7 @@ void NpHandler::SyncTrophies( std::lock_guard lock(m_mutex_pending_trophy); m_pending_trophy.emplace(pkt_id, std::move(on_merged)); } - LOG_INFO(NpHandler, "SyncTrophies: user_id={} uploading {} com_id='{}' pkt_id={}", user_id, + LOG_INFO(NpHandler, "user_id={} uploading {} com_id='{}' pkt_id={}", user_id, local_trophies.size(), com_id, pkt_id); } @@ -3024,7 +3041,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, std::lock_guard lock(m_mutex_pending_score); auto it = m_pending_score.find(pkt_id); if (it == m_pending_score.end()) { - LOG_WARNING(NpHandler, "OnScoreReply: no pending request for pkt_id={} cmd={}", pkt_id, + LOG_WARNING(NpHandler, "no pending request for pkt_id={} cmd={}", pkt_id, static_cast(cmd)); return; } @@ -3060,8 +3077,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, default: break; } - LOG_WARNING(NpHandler, - "OnScoreReply: user_id={} pkt_id={} cmd={} server_error={} → orbis_err={:#x}", + LOG_WARNING(NpHandler, "user_id={} pkt_id={} cmd={} server_error={} → orbis_err={:#x}", user_id, pkt_id, static_cast(cmd), static_cast(error), orbis_err); req->SetResult(orbis_err); return; @@ -3075,8 +3091,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, const u32 rank = static_cast(body[0]) | (static_cast(body[1]) << 8) | (static_cast(body[2]) << 16) | (static_cast(body[3]) << 24); *req->tmpRankOut = rank; - LOG_INFO(NpHandler, "OnScoreReply: RecordScore user_id={} pkt_id={} rank={}", user_id, - pkt_id, rank); + LOG_INFO(NpHandler, "RecordScore user_id={} pkt_id={} rank={}", user_id, pkt_id, rank); } req->SetResult(ORBIS_OK); break; @@ -3087,8 +3102,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, // the caller. Server-side errors are mapped to ErrorType values // (NotFound / ScoreInvalid / ScoreHasData) which arrive as the // packet's error byte; if we got here, error == NoError. - LOG_INFO(NpHandler, "OnScoreReply: RecordScoreData user_id={} pkt_id={} ok", user_id, - pkt_id); + LOG_INFO(NpHandler, "RecordScoreData user_id={} pkt_id={} ok", user_id, pkt_id); req->SetResult(ORBIS_OK); break; } @@ -3099,7 +3113,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, ? "GetScoreData" : "GetScoreGameDataByAccId"; if (body.size() < 4) { - LOG_ERROR(NpHandler, "OnScoreReply: {} body too small ({})", cmd_name, body.size()); + LOG_ERROR(NpHandler, "{} body too small ({})", cmd_name, body.size()); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } @@ -3107,8 +3121,8 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, (static_cast(body[2]) << 16) | (static_cast(body[3]) << 24); if (static_cast(4) + stored_size > body.size()) { - LOG_ERROR(NpHandler, "OnScoreReply: {} blob size {} exceeds body size {}", cmd_name, - stored_size, body.size()); + LOG_ERROR(NpHandler, "{} blob size {} exceeds body size {}", cmd_name, stored_size, + body.size()); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } @@ -3124,20 +3138,19 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, const u64 copy_n = std::min(static_cast(stored_size), pending.recvSize); std::memcpy(pending.dataOut, body.data() + 4, static_cast(copy_n)); if (copy_n < stored_size) { - LOG_WARNING(NpHandler, - "OnScoreReply: {} truncated user_id={} pkt_id={} stored={} recv={}", + LOG_WARNING(NpHandler, "{} truncated user_id={} pkt_id={} stored={} recv={}", cmd_name, user_id, pkt_id, stored_size, pending.recvSize); } } - LOG_INFO(NpHandler, "OnScoreReply: {} user_id={} pkt_id={} storedSize={} recvSize={}", - cmd_name, user_id, pkt_id, stored_size, pending.recvSize); + LOG_INFO(NpHandler, "{} user_id={} pkt_id={} storedSize={} recvSize={}", cmd_name, user_id, + pkt_id, stored_size, pending.recvSize); req->SetResult(ORBIS_OK); break; } case ShadNet::CommandType::GetBoardInfos: { if (body.size() < 4) { - LOG_ERROR(NpHandler, "OnScoreReply: GetBoardInfos body too small ({})", body.size()); + LOG_ERROR(NpHandler, "GetBoardInfos body too small ({})", body.size()); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } @@ -3145,14 +3158,14 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, (static_cast(body[2]) << 16) | (static_cast(body[3]) << 24); if (static_cast(4) + proto_size > body.size()) { - LOG_ERROR(NpHandler, "OnScoreReply: GetBoardInfos proto size {} exceeds body size {}", - proto_size, body.size()); + LOG_ERROR(NpHandler, "GetBoardInfos proto size {} exceeds body size {}", proto_size, + body.size()); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } shadnet::BoardInfo bi; if (!bi.ParseFromArray(body.data() + 4, static_cast(proto_size))) { - LOG_ERROR(NpHandler, "OnScoreReply: GetBoardInfos proto parse failed"); + LOG_ERROR(NpHandler, "GetBoardInfos proto parse failed"); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } @@ -3164,7 +3177,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, pending.boardInfo->uploadSizeLimit = bi.uploadsizelimit(); } LOG_INFO(NpHandler, - "OnScoreReply: GetBoardInfos user_id={} pkt_id={} rankLimit={} updateMode={} " + "GetBoardInfos user_id={} pkt_id={} rankLimit={} updateMode={} " "sortMode={} uploadNumLimit={} uploadSizeLimit={}", user_id, pkt_id, bi.ranklimit(), bi.updatemode(), bi.sortmode(), bi.uploadnumlimit(), bi.uploadsizelimit()); @@ -3175,7 +3188,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, case ShadNet::CommandType::GetScoreNpid: { // Reply body = u32 LE proto size + GetScoreResponse proto bytes. if (body.size() < 4) { - LOG_ERROR(NpHandler, "OnScoreReply: GetScoreNpid body too small ({})", body.size()); + LOG_ERROR(NpHandler, "GetScoreNpid body too small ({})", body.size()); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } @@ -3183,14 +3196,14 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, (static_cast(body[2]) << 16) | (static_cast(body[3]) << 24); if (static_cast(4) + proto_size > body.size()) { - LOG_ERROR(NpHandler, "OnScoreReply: GetScoreNpid proto size {} exceeds body size {}", - proto_size, body.size()); + LOG_ERROR(NpHandler, "GetScoreNpid proto size {} exceeds body size {}", proto_size, + body.size()); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } shadnet::GetScoreResponse resp; if (!resp.ParseFromArray(body.data() + 4, static_cast(proto_size))) { - LOG_ERROR(NpHandler, "OnScoreReply: GetScoreNpid proto parse failed"); + LOG_ERROR(NpHandler, "GetScoreNpid proto parse failed"); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } @@ -3202,9 +3215,8 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, if (pending.totalRecord != nullptr) { *pending.totalRecord = resp.totalrecord(); } - LOG_INFO(NpHandler, - "OnScoreReply: GetScoreNpid user_id={} pkt_id={} found={}/{} totalRecord={}", - user_id, pkt_id, found, pending.arrayNum, resp.totalrecord()); + LOG_INFO(NpHandler, "GetScoreNpid user_id={} pkt_id={} found={}/{} totalRecord={}", user_id, + pkt_id, found, pending.arrayNum, resp.totalrecord()); req->SetResult(static_cast(found)); break; } @@ -3213,8 +3225,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, // Index-aligned with the request's accountIds[] (empty-npid sentinel // marks "no score on this board"), same contract as GetScoreNpid. if (body.size() < 4) { - LOG_ERROR(NpHandler, "OnScoreReply: GetScoreAccountId body too small ({})", - body.size()); + LOG_ERROR(NpHandler, "GetScoreAccountId body too small ({})", body.size()); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } @@ -3222,15 +3233,14 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, (static_cast(body[2]) << 16) | (static_cast(body[3]) << 24); if (static_cast(4) + proto_size > body.size()) { - LOG_ERROR(NpHandler, - "OnScoreReply: GetScoreAccountId proto size {} exceeds body size {}", - proto_size, body.size()); + LOG_ERROR(NpHandler, "GetScoreAccountId proto size {} exceeds body size {}", proto_size, + body.size()); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } shadnet::GetScoreResponse resp; if (!resp.ParseFromArray(body.data() + 4, static_cast(proto_size))) { - LOG_ERROR(NpHandler, "OnScoreReply: GetScoreAccountId proto parse failed"); + LOG_ERROR(NpHandler, "GetScoreAccountId proto parse failed"); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } @@ -3243,8 +3253,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, if (pending.totalRecord != nullptr) { *pending.totalRecord = resp.totalrecord(); } - LOG_INFO(NpHandler, - "OnScoreReply: GetScoreAccountId user_id={} pkt_id={} found={}/{} totalRecord={}", + LOG_INFO(NpHandler, "GetScoreAccountId user_id={} pkt_id={} found={}/{} totalRecord={}", user_id, pkt_id, found, pending.arrayNum, resp.totalrecord()); req->SetResult(static_cast(found)); break; @@ -3255,7 +3264,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, const char* cmd_name = (cmd == ShadNet::CommandType::GetScoreRange) ? "GetScoreRange" : "GetScoreFriends"; if (body.size() < 4) { - LOG_ERROR(NpHandler, "OnScoreReply: {} body too small ({})", cmd_name, body.size()); + LOG_ERROR(NpHandler, "{} body too small ({})", cmd_name, body.size()); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } @@ -3263,14 +3272,14 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, (static_cast(body[2]) << 16) | (static_cast(body[3]) << 24); if (static_cast(4) + proto_size > body.size()) { - LOG_ERROR(NpHandler, "OnScoreReply: {} proto size {} exceeds body size {}", cmd_name, - proto_size, body.size()); + LOG_ERROR(NpHandler, "{} proto size {} exceeds body size {}", cmd_name, proto_size, + body.size()); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } shadnet::GetScoreResponse resp; if (!resp.ParseFromArray(body.data() + 4, static_cast(proto_size))) { - LOG_ERROR(NpHandler, "OnScoreReply: {} proto parse failed", cmd_name); + LOG_ERROR(NpHandler, "{} proto parse failed", cmd_name); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } @@ -3288,16 +3297,15 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, if (pending.totalRecord != nullptr) { *pending.totalRecord = resp.totalrecord(); } - LOG_INFO(NpHandler, "OnScoreReply: {} user_id={} pkt_id={} found={}/{} totalRecord={}{}", - cmd_name, user_id, pkt_id, found, pending.arrayNum, resp.totalrecord(), + LOG_INFO(NpHandler, "{} user_id={} pkt_id={} found={}/{} totalRecord={}{}", cmd_name, + user_id, pkt_id, found, pending.arrayNum, resp.totalrecord(), pending.aRankArray != nullptr ? " (A-variant)" : ""); req->SetResult(static_cast(found)); break; } default: - LOG_WARNING(NpHandler, "OnScoreReply: unexpected cmd={} pkt_id={}", static_cast(cmd), - pkt_id); + LOG_WARNING(NpHandler, "unexpected cmd={} pkt_id={}", static_cast(cmd), pkt_id); req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); break; } @@ -3310,7 +3318,7 @@ void NpHandler::OnTusReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, std::lock_guard lock(m_mutex_pending_tus); auto it = m_pending_tus.find(pkt_id); if (it == m_pending_tus.end()) { - LOG_WARNING(NpHandler, "OnTusReply: no pending request for pkt_id={} cmd={}", pkt_id, + LOG_WARNING(NpHandler, "no pending request for pkt_id={} cmd={}", pkt_id, static_cast(cmd)); return; } @@ -3338,8 +3346,7 @@ void NpHandler::OnTusReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, default: break; } - LOG_WARNING(NpHandler, - "OnTusReply: user_id={} pkt_id={} cmd={} server_error={} -> orbis {:#x}", + LOG_WARNING(NpHandler, "user_id={} pkt_id={} cmd={} server_error={} -> orbis {:#x}", user_id, pkt_id, static_cast(cmd), static_cast(error), static_cast(orbis_err)); req->SetResult(orbis_err); @@ -3603,7 +3610,7 @@ void NpHandler::OnTusReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, } default: // Set*/Delete* carry no payload to fill. - LOG_DEBUG(NpHandler, "OnTusReply: cmd={} ok (no payload)", static_cast(cmd)); + LOG_DEBUG(NpHandler, "cmd={} ok (no payload)", static_cast(cmd)); break; } req->SetResult(ORBIS_OK); diff --git a/src/core/libraries/np/np_handler.h b/src/core/libraries/np/np_handler.h index bcef98b76..842cc1383 100644 --- a/src/core/libraries/np/np_handler.h +++ b/src/core/libraries/np/np_handler.h @@ -19,8 +19,10 @@ #include "common/types.h" #include "core/libraries/np/np_manager.h" +#include "core/libraries/np/np_matching2/np_matching2_cache.h" #include "core/libraries/np/np_score/np_score.h" #include "core/libraries/np/np_score/np_score_ctx.h" +#include "core/libraries/np/np_signaling/np_signaling_state.h" #include "core/libraries/np/np_tus/np_tus.h" #include "core/libraries/np/np_tus/np_tus_ctx.h" #include "core/libraries/np/np_types.h" @@ -30,8 +32,42 @@ namespace Libraries::Np { +namespace NpMatching2 { +class ContextManager; +struct NpMatching2State; +} // namespace NpMatching2 + class NpHandler { public: + class Matching2CacheGuard { + public: + Matching2CacheGuard(Matching2CacheGuard&&) noexcept = default; + Matching2CacheGuard& operator=(Matching2CacheGuard&&) noexcept = default; + Matching2CacheGuard(const Matching2CacheGuard&) = delete; + Matching2CacheGuard& operator=(const Matching2CacheGuard&) = delete; + + NpMatching2::Matching2ContextCache* operator->() { + return m_cache.get(); + } + + const NpMatching2::Matching2ContextCache* operator->() const { + return m_cache.get(); + } + + NpMatching2::Matching2ContextCache& operator*() { + return *m_cache; + } + + private: + friend class NpHandler; + + explicit Matching2CacheGuard(std::shared_ptr cache) + : m_cache(std::move(cache)), m_lock(m_cache->mutex) {} + + std::shared_ptr m_cache; + std::unique_lock m_lock; + }; + static NpHandler& GetInstance(); NpHandler(const NpHandler&) = delete; @@ -322,9 +358,23 @@ public: s32 RegisterStateCallback(StateCallback cb, void* userdata); void UnregisterStateCallback(s32 handle); + Matching2CacheGuard LockMatching2Cache(NpMatching2::OrbisNpMatching2ContextId ctx_id); + void ResetMatching2Cache(NpMatching2::OrbisNpMatching2ContextId ctx_id); + void ResetMatching2Caches(); + NpMatching2::ContextManager& GetMatching2ContextManager(); + NpMatching2::NpMatching2State& GetMatching2State(); + + NpSignaling::NpSignalingState& GetSignalingState() { + return m_signaling_state; + } + + const NpSignaling::NpSignalingState& GetSignalingState() const { + return m_signaling_state; + } + private: - NpHandler() = default; - ~NpHandler() = default; + NpHandler(); + ~NpHandler(); /// Connect one user. Blocks until connected+authenticated or failed. bool ConnectUser(s32 user_id, const std::string& host, u16 port, const std::string& npid, @@ -476,6 +526,14 @@ private: // Friend state per user, seeded from LoginReply and updated by notifications/actions. mutable std::mutex m_mutex_friend_state; std::map m_friend_state; + + mutable std::mutex m_mutex_matching2_cache; + std::map> + m_matching2_cache; + std::unique_ptr m_matching2_contexts; + std::unique_ptr m_matching2_state; + NpSignaling::NpSignalingState m_signaling_state; }; -} // namespace Libraries::Np \ No newline at end of file +} // namespace Libraries::Np diff --git a/src/core/libraries/np/np_manager.h b/src/core/libraries/np/np_manager.h index 45b55f2a4..a87556c70 100644 --- a/src/core/libraries/np/np_manager.h +++ b/src/core/libraries/np/np_manager.h @@ -111,6 +111,8 @@ void NotifyNpStateFromUserServiceEvent(Libraries::UserService::OrbisUserServiceE s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpId* np_id); +s32 PS4_SYSV_ABI sceNpGetAccountIdA(Libraries::UserService::OrbisUserServiceUserId user_id, + u64* account_id); s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpOnlineId* online_id); s32 PS4_SYSV_ABI sceNpCheckNpAvailabilityA(s32 req_id, diff --git a/src/core/libraries/np/np_matching2/np_matching2.cpp b/src/core/libraries/np/np_matching2/np_matching2.cpp index bcc3800d4..135e1181c 100644 --- a/src/core/libraries/np/np_matching2/np_matching2.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -9,7 +10,7 @@ #include "core/emulator_settings.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" -#include "core/libraries/network/net.h" +#include "core/libraries/net/net.h" #include "core/libraries/np/np_handler.h" #include "core/libraries/np/np_manager.h" #include "core/libraries/np/np_matching2/np_matching2.h" @@ -17,6 +18,7 @@ #include "core/libraries/np/np_matching2/np_matching2_mm.h" #include "core/libraries/np/np_matching2/np_matching2_signaling.h" #include "core/libraries/np/np_types.h" +#include "core/libraries/np/signaling_handler.h" #include "core/libraries/system/userservice.h" namespace Libraries::Np::NpMatching2 { @@ -35,7 +37,12 @@ int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContext return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - return ContextManager::Instance().CreateContext(param->npId, param->serviceLabel, ctxId); + const int rc = NpHandler::GetInstance().GetMatching2ContextManager().CreateContext( + param->npId, param->serviceLabel, ctxId); + if (rc == ORBIS_OK) { + SignalingHandler::AddContextRef(); + } + return rc; } int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContextParameterA* param, @@ -55,7 +62,12 @@ int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContex const Libraries::Np::OrbisNpId np_id = Libraries::Np::NpHandler::GetInstance().GetNpId(param->userId); - return ContextManager::Instance().CreateContext(&np_id, param->serviceLabel, ctxId, true); + const int rc = NpHandler::GetInstance().GetMatching2ContextManager().CreateContext( + &np_id, param->serviceLabel, ctxId, true); + if (rc == ORBIS_OK) { + SignalingHandler::AddContextRef(); + } + return rc; } int PS4_SYSV_ABI sceNpMatching2CreateJoinRoom(OrbisNpMatching2ContextId ctxId, @@ -71,7 +83,7 @@ int PS4_SYSV_ABI sceNpMatching2CreateJoinRoom(OrbisNpMatching2ContextId ctxId, LOG_ERROR(Lib_NpMatching2, "request or requestId null"); return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -97,7 +109,7 @@ int PS4_SYSV_ABI sceNpMatching2JoinRoom(OrbisNpMatching2ContextId ctxId, LOG_ERROR(Lib_NpMatching2, "request or requestId null"); return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -125,7 +137,7 @@ int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId, return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -147,7 +159,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterContextCallback(OrbisNpMatching2ContextCa return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - ContextManager::Instance().ApplyContextCallback(callback, userdata); + NpHandler::GetInstance().GetMatching2ContextManager().ApplyContextCallback(callback, userdata); return ORBIS_OK; } @@ -159,7 +171,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterLobbyEventCallback( LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -179,7 +191,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterRoomEventCallback(OrbisNpMatching2Context LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -199,7 +211,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2Context LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -207,6 +219,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2Context ctx->signaling_callback = callback; ctx->signaling_callback_arg = userdata; + SignalingHandler::StartMatching2(*ctx); return ORBIS_OK; } @@ -224,7 +237,7 @@ int PS4_SYSV_ABI sceNpMatching2ContextStart(OrbisNpMatching2ContextId ctxId, u64 return ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT; } - const s32 rc = ContextManager::Instance().Start(ctxId); + const s32 rc = NpHandler::GetInstance().GetMatching2ContextManager().Start(ctxId); if (rc != ORBIS_OK) { return rc; } @@ -241,14 +254,19 @@ int PS4_SYSV_ABI sceNpMatching2ContextStop(OrbisNpMatching2ContextId ctxId) { return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - const s32 rc = ContextManager::Instance().Stop(ctxId); + const s32 rc = NpHandler::GetInstance().GetMatching2ContextManager().Stop(ctxId); if (rc != ORBIS_OK) { return rc; } + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); + if (ctx && ctx->signaling_callback) { + SignalingHandler::StopMatching2(*ctx, + ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_MYSELF); + } const s32 submit_rc = MmContextStop(ctxId); if (submit_rc != ORBIS_OK) { - ContextManager::Instance().CompleteStop(ctxId); + NpHandler::GetInstance().GetMatching2ContextManager().CompleteStop(ctxId, ctx); return submit_rc; } return ORBIS_OK; @@ -276,7 +294,6 @@ int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* p InitEventDispatcher(); SetInitialized(true); - g_state.initialized.store(true); return ORBIS_OK; } @@ -290,10 +307,15 @@ int PS4_SYSV_ABI sceNpMatching2Terminate() { } SetInitialized(false); - g_state.initialized.store(false); TermEventDispatcher(); - ContextManager::Instance().Reset(); + NpHandler::GetInstance().GetMatching2ContextManager().Reset(); + { + auto& state = NpHandler::GetInstance().GetMatching2State(); + std::lock_guard lock(state.mutex); + state.next_request_id = 1; + } + SignalingHandler::ReleaseContextRef(); return ORBIS_OK; } @@ -310,12 +332,16 @@ int PS4_SYSV_ABI sceNpMatching2SetDefaultRequestOptParam( LOG_ERROR(Lib_NpMatching2, "requestOpt null"); return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; } + LOG_INFO(Lib_NpMatching2, "ctx={} opt={} callback={:#x} arg={} timeout={} appId={}", ctxId, + fmt::ptr(requestOpt), reinterpret_cast(requestOpt->callback), + fmt::ptr(requestOpt->arg), requestOpt->timeout, requestOpt->appId); + ctx->default_request_callback = requestOpt->callback; ctx->default_request_callback_arg = requestOpt->arg; @@ -334,6 +360,11 @@ int PS4_SYSV_ABI sceNpMatching2GetServerId(OrbisNpMatching2ContextId ctxId, LOG_ERROR(Lib_NpMatching2, "serverId null"); return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); + if (!ctx) { + LOG_ERROR(Lib_NpMatching2, "invalid context id"); + return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; + } *serverId = 1; @@ -356,7 +387,7 @@ int PS4_SYSV_ABI sceNpMatching2GetWorldInfoList(OrbisNpMatching2ContextId ctxId, return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -383,7 +414,7 @@ int PS4_SYSV_ABI sceNpMatching2LeaveRoom(OrbisNpMatching2ContextId ctxId, LOG_ERROR(Lib_NpMatching2, "request or requestId null"); return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -411,7 +442,7 @@ int PS4_SYSV_ABI sceNpMatching2SearchRoom(OrbisNpMatching2ContextId ctxId, return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -439,7 +470,7 @@ int PS4_SYSV_ABI sceNpMatching2SetUserInfo(OrbisNpMatching2ContextId ctxId, return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -466,7 +497,7 @@ int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId, return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -482,7 +513,8 @@ int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId, int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal( OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SetRoomDataExternalRequest* request, OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { - LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, request = {}, requestOpt = {}, caller = {}", + ctxId, fmt::ptr(request), fmt::ptr(requestOpt), fmt::ptr(__builtin_return_address(0))); if (!IsInitialized()) { LOG_ERROR(Lib_NpMatching2, "not initialized"); @@ -493,7 +525,7 @@ int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal( return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -509,7 +541,8 @@ int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal( int PS4_SYSV_ABI sceNpMatching2SetRoomDataInternal( OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SetRoomDataInternalRequest* request, OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { - LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, request = {}, requestOpt = {}, caller = {}", + ctxId, fmt::ptr(request), fmt::ptr(requestOpt), fmt::ptr(__builtin_return_address(0))); if (!IsInitialized()) { LOG_ERROR(Lib_NpMatching2, "not initialized"); @@ -520,7 +553,7 @@ int PS4_SYSV_ABI sceNpMatching2SetRoomDataInternal( return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -546,7 +579,7 @@ s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2Cont return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -555,8 +588,9 @@ s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2Cont constexpr s32 kInactive = 0; constexpr s32 kPending = 1; constexpr s32 kActive = 2; - const auto room_it = ctx->room_cache.find(roomId); - const bool in_room = room_it != ctx->room_cache.end() && + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx->ctx_id); + const auto room_it = cache->rooms.find(roomId); + const bool in_room = room_it != cache->rooms.end() && room_it->second.members.find(memberId) != room_it->second.members.end(); if (!in_room) { @@ -567,8 +601,8 @@ s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2Cont return ORBIS_OK; } - auto peer_it = ctx->peers.find(memberId); - if (peer_it == ctx->peers.end()) { + auto peer_it = cache->peers.find(memberId); + if (peer_it == cache->peers.end()) { PeerInfo pi{}; pi.member_id = memberId; pi.status = kPending; @@ -578,7 +612,7 @@ s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2Cont pi.port = member_it->second.port; pi.online_id = member_it->second.np_id.handle; } - peer_it = ctx->peers.emplace(memberId, pi).first; + peer_it = cache->peers.emplace(memberId, pi).first; } const s32 status = peer_it->second.status == kActive ? kActive : kPending; @@ -609,10 +643,11 @@ int PS4_SYSV_ABI sceNpMatching2DestroyContext(OrbisNpMatching2ContextId ctxId) { LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - if (!ContextManager::Instance().Destroy(ctxId)) { + if (!NpHandler::GetInstance().GetMatching2ContextManager().Destroy(ctxId)) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; } + SignalingHandler::ReleaseContextRef(); return ORBIS_OK; } @@ -625,7 +660,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterLobbyMessageCallback( LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -638,13 +673,14 @@ int PS4_SYSV_ABI sceNpMatching2RegisterLobbyMessageCallback( int PS4_SYSV_ABI sceNpMatching2RegisterRoomMessageCallback( OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RoomMessageCallback callback, void* userdata) { - LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, callback = {}, userdata = {}", ctxId, + fmt::ptr(callback), userdata); if (!IsInitialized()) { LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -709,7 +745,7 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomDataExternalList( return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -736,7 +772,7 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomDataInternal( return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -746,11 +782,14 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomDataInternal( const OrbisNpMatching2RequestId reqId = AllocRequestId(); *requestId = reqId; - void* request_data = BuildGetRoomDataInternalPayload(*ctx, request->roomId); + auto request_payload_owner = std::make_shared(); + void* request_data = + BuildGetRoomDataInternalPayload(*ctx, *request_payload_owner, request->roomId); PendingEvent ev{}; ev.type = PendingEvent::REQUEST_CB; ev.ctx_id = ctxId; + ev.context_owner = ctx; ev.fire_at = std::chrono::steady_clock::now(); ev.req_id = reqId; ev.req_event = ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_INTERNAL; @@ -759,6 +798,7 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomDataInternal( ev.request_cb = request_cb.callback; ev.request_cb_arg = request_cb.arg; ev.request_data = request_data; + ev.payload_owner = std::move(request_payload_owner); ScheduleEvent(std::move(ev)); return ORBIS_OK; } @@ -777,7 +817,7 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataExternalList( return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -790,8 +830,49 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataExternalList( return ORBIS_OK; } -int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataInternal() { - LOG_INFO(Lib_NpMatching2, "called"); +int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataInternal( + OrbisNpMatching2ContextId ctxId, + const OrbisNpMatching2GetRoomMemberDataInternalRequest* request, + const OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + + if (!IsInitialized()) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); + return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; + } + if (!request || !requestId || (request->attrIdNum > 0 && !request->attrId)) { + LOG_ERROR(Lib_NpMatching2, "request or requestId null"); + return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + } + + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); + if (!ctx) { + LOG_ERROR(Lib_NpMatching2, "invalid context id"); + return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; + } + + StoreRequestCallback(ctx, requestOpt); + const OrbisNpMatching2RequestId reqId = AllocRequestId(); + *requestId = reqId; + + auto request_payload_owner = std::make_shared(); + void* request_data = + BuildGetRoomMemberDataInternalPayload(*ctx, *request_payload_owner, *request); + + PendingEvent ev{}; + ev.type = PendingEvent::REQUEST_CB; + ev.ctx_id = ctxId; + ev.context_owner = ctx; + ev.fire_at = std::chrono::steady_clock::now(); + ev.req_id = reqId; + ev.req_event = ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_INTERNAL_A; + ev.error_code = 0; + const RequestCallbackInfo request_cb = ConsumeRequestCallback(ctx); + ev.request_cb = request_cb.callback; + ev.request_cb_arg = request_cb.arg; + ev.request_data = request_data; + ev.payload_owner = std::move(request_payload_owner); + ScheduleEvent(std::move(ev)); return ORBIS_OK; } @@ -810,7 +891,7 @@ int PS4_SYSV_ABI sceNpMatching2GetUserInfoListA(OrbisNpMatching2ContextId ctxId, return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -838,7 +919,7 @@ int PS4_SYSV_ABI sceNpMatching2GetUserInfoList(OrbisNpMatching2ContextId ctxId, return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -876,7 +957,7 @@ int PS4_SYSV_ABI sceNpMatching2JoinRoomA(OrbisNpMatching2ContextId ctxId, LOG_ERROR(Lib_NpMatching2, "request or requestId null"); return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -904,7 +985,7 @@ int PS4_SYSV_ABI sceNpMatching2KickoutRoomMember(OrbisNpMatching2ContextId ctxId return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -938,8 +1019,32 @@ int PS4_SYSV_ABI sceNpMatching2SetLobbyMemberDataInternal() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpMatching2SetRoomMemberDataInternal() { - LOG_INFO(Lib_NpMatching2, "called"); +int PS4_SYSV_ABI sceNpMatching2SetRoomMemberDataInternal( + OrbisNpMatching2ContextId ctxId, + const OrbisNpMatching2SetRoomMemberDataInternalRequest* request, + const OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + + if (!IsInitialized()) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); + return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; + } + if (!request || !requestId || + (request->roomMemberBinAttrInternalNum > 0 && !request->roomMemberBinAttrInternal)) { + LOG_ERROR(Lib_NpMatching2, "request or requestId null"); + return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + } + + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); + if (!ctx) { + LOG_ERROR(Lib_NpMatching2, "invalid context id"); + return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; + } + + StoreRequestCallback(ctx, requestOpt); + const OrbisNpMatching2RequestId reqId = AllocRequestId(); + *requestId = reqId; + MmSetRoomMemberDataInternal(ctxId, reqId, *request); return ORBIS_OK; } @@ -966,7 +1071,7 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo(OrbisNpMatching2ContextId ct return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -978,13 +1083,13 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo(OrbisNpMatching2ContextId ct *reqId = request_id; auto request_payload_owner = std::make_shared(); - ctx->request_payload_override = request_payload_owner.get(); - void* request_data = BuildSignalingGetPingInfoPayload(*ctx, request->roomId); - ctx->request_payload_override = nullptr; + void* request_data = + BuildSignalingGetPingInfoPayload(*ctx, *request_payload_owner, request->roomId); PendingEvent ev{}; ev.type = PendingEvent::REQUEST_CB; ev.ctx_id = ctxId; + ev.context_owner = ctx; ev.fire_at = std::chrono::steady_clock::now(); ev.req_id = request_id; ev.req_event = ORBIS_NP_MATCHING2_REQUEST_EVENT_SIGNALING_GET_PING_INFO; @@ -993,7 +1098,7 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo(OrbisNpMatching2ContextId ct ev.request_cb = request_cb.callback; ev.request_cb_arg = request_cb.arg; ev.request_data = request_data; - ev.request_payload_owner = std::move(request_payload_owner); + ev.payload_owner = std::move(request_payload_owner); ScheduleEvent(std::move(ev)); return ORBIS_OK; } @@ -1013,7 +1118,7 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfoA(OrbisNpMatching2Conte LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -1032,7 +1137,7 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfo(OrbisNpMatching2Contex LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - ContextObject* ctx = ContextManager::Instance().Get(ctxId); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId); if (!ctx) { LOG_ERROR(Lib_NpMatching2, "invalid context id"); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; @@ -1041,10 +1146,13 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfo(OrbisNpMatching2Contex return FillMatching2ConnectionInfo(*ctx, roomId, memberId, infoType, connInfo, false); } -int PS4_SYSV_ABI sceNpMatching2SignalingGetLocalNetInfo(OrbisNpMatching2ContextId ctxId, - void* info) { - LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpMatching2SignalingGetLocalNetInfo(void* info) { + LOG_INFO(Lib_NpMatching2, "called"); + if (!IsInitialized()) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); + return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; + } + return FillMatching2LocalNetInfo(info); } int PS4_SYSV_ABI sceNpMatching2SignalingGetPeerNetInfoResult() { diff --git a/src/core/libraries/np/np_matching2/np_matching2.h b/src/core/libraries/np/np_matching2/np_matching2.h index c7c85e96a..b5f62c70c 100644 --- a/src/core/libraries/np/np_matching2/np_matching2.h +++ b/src/core/libraries/np/np_matching2/np_matching2.h @@ -12,12 +12,142 @@ class SymbolsResolver; namespace Libraries::Np::NpMatching2 { -int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* param); -int PS4_SYSV_ABI sceNpMatching2Terminate(); int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContextParameter* param, OrbisNpMatching2ContextId* ctxId); int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContextParameterA* param, OrbisNpMatching2ContextId* ctxId); +int PS4_SYSV_ABI sceNpMatching2CreateJoinRoom(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2CreateJoinRoomRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2JoinRoom(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2JoinRoomRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2CreateJoinRoomRequestA* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2RegisterContextCallback(OrbisNpMatching2ContextCallback callback, + void* userdata); +int PS4_SYSV_ABI sceNpMatching2RegisterLobbyEventCallback( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2LobbyEventCallback callback, void* userdata); +int PS4_SYSV_ABI sceNpMatching2RegisterRoomEventCallback(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2RoomEventCallback callback, + void* userdata); +int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2SignalingCallback callback, + void* userdata); +int PS4_SYSV_ABI sceNpMatching2ContextStart(OrbisNpMatching2ContextId ctxId, u64 timeout); +int PS4_SYSV_ABI sceNpMatching2ContextStop(OrbisNpMatching2ContextId ctxId); +int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* param); +int PS4_SYSV_ABI sceNpMatching2Terminate(); +int PS4_SYSV_ABI sceNpMatching2SetDefaultRequestOptParam( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RequestOptParam* requestOpt); +int PS4_SYSV_ABI sceNpMatching2GetServerId(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2ServerId* serverId); +int PS4_SYSV_ABI sceNpMatching2GetWorldInfoList(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2GetWorldInfoListRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2LeaveRoom(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2LeaveRoomRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2SearchRoom(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2SearchRoomRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2SetUserInfo(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2SetUserInfoRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId, void* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SetRoomDataExternalRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2SetRoomDataInternal( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SetRoomDataInternalRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId); +s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2RoomId roomId, + OrbisNpMatching2RoomMemberId memberId, + s32* connStatus, void* peerAddr, + u16* peerPort); +int PS4_SYSV_ABI sceNpMatching2AbortContextStart(OrbisNpMatching2ContextId ctxId); +int PS4_SYSV_ABI sceNpMatching2DestroyContext(OrbisNpMatching2ContextId ctxId); +int PS4_SYSV_ABI sceNpMatching2RegisterLobbyMessageCallback( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2LobbyMessageCallback callback, void* userdata); +int PS4_SYSV_ABI sceNpMatching2RegisterRoomMessageCallback( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RoomMessageCallback callback, void* userdata); +int PS4_SYSV_ABI sceNpMatching2GetMemoryInfo(); +int PS4_SYSV_ABI sceNpMatching2GetSslMemoryInfo(); +int PS4_SYSV_ABI sceNpMatching2GetRoomMemberIdListLocal(); +int PS4_SYSV_ABI sceNpMatching2GetRoomPasswordLocal(); +int PS4_SYSV_ABI sceNpMatching2GetSignalingOptParamLocal(); +int PS4_SYSV_ABI sceNpMatching2GetLobbyInfoList(); +int PS4_SYSV_ABI sceNpMatching2GetLobbyMemberDataInternal(); +int PS4_SYSV_ABI sceNpMatching2GetLobbyMemberDataInternalList(); +int PS4_SYSV_ABI sceNpMatching2GetRoomDataExternalList( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2GetRoomDataExternalListRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2GetRoomDataInternal( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2GetRoomDataInternalRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataExternalList( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2GetRoomMemberDataExternalListRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataInternal( + OrbisNpMatching2ContextId ctxId, + const OrbisNpMatching2GetRoomMemberDataInternalRequest* request, + const OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2GetUserInfoListA(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2GetUserInfoListRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2GetUserInfoList(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2GetUserInfoListRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2GrantRoomOwner(OrbisNpMatching2ContextId ctxId, void* reqParam, + void* optParam, s32* reqId); +int PS4_SYSV_ABI sceNpMatching2JoinLobby(OrbisNpMatching2ContextId ctxId, void* reqParam, + void* optParam, s32* reqId); +int PS4_SYSV_ABI sceNpMatching2JoinRoomA(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2JoinRoomRequestA* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2KickoutRoomMember(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2KickoutRoomMemberRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2LeaveLobby(); +int PS4_SYSV_ABI sceNpMatching2SendLobbyChatMessage(); +int PS4_SYSV_ABI sceNpMatching2SendRoomChatMessage(OrbisNpMatching2ContextId ctxId, void* reqParam, + void* optParam, s32* reqId); +int PS4_SYSV_ABI sceNpMatching2SetLobbyMemberDataInternal(); +int PS4_SYSV_ABI sceNpMatching2SetRoomMemberDataInternal( + OrbisNpMatching2ContextId ctxId, + const OrbisNpMatching2SetRoomMemberDataInternalRequest* request, + const OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId); +int PS4_SYSV_ABI sceNpMatching2SetSignalingOptParam(); +int PS4_SYSV_ABI sceNpMatching2SignalingGetPeerNetInfo(); +int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo(OrbisNpMatching2ContextId ctxId, + const void* reqParam, const void* optParam, + s32* reqId); +int PS4_SYSV_ABI sceNpMatching2SignalingCancelPeerNetInfo(); +int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfoA(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2RoomId roomId, + OrbisNpMatching2RoomMemberId memberId, + u32 infoType, void* connInfo); +int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfo(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2RoomId roomId, + OrbisNpMatching2RoomMemberId memberId, + u32 infoType, void* connInfo); +int PS4_SYSV_ABI sceNpMatching2SignalingGetLocalNetInfo(void* info); +int PS4_SYSV_ABI sceNpMatching2SignalingGetPeerNetInfoResult(); void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Np::NpMatching2 diff --git a/src/core/libraries/np/np_matching2/np_matching2_cache.h b/src/core/libraries/np/np_matching2/np_matching2_cache.h new file mode 100644 index 000000000..f1b5d67f4 --- /dev/null +++ b/src/core/libraries/np/np_matching2/np_matching2_cache.h @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/types.h" +#include "core/libraries/np/np_matching2/np_matching2.h" +#include "core/libraries/np/np_types.h" + +namespace Libraries::Np::NpMatching2 { + +struct PeerInfo { + u32 addr = 0; + u16 port = 0; + OrbisNpMatching2RoomMemberId member_id = 0; + s32 conn_id = 0; + s32 status = 0; + Libraries::Np::OrbisNpOnlineId online_id{}; + bool sent_established = false; + u32 ping_us = 0; +}; + +struct MemberBinCache { + OrbisNpMatching2AttributeId id = 0; + u64 update_date = 0; + std::vector data; +}; + +struct MemberCache { + Libraries::Np::OrbisNpId np_id{}; + Libraries::Np::OrbisNpAccountId account_id = 0; + Libraries::Np::OrbisNpPlatformType platform = Libraries::Np::OrbisNpPlatformType::None; + u64 join_date = 0; + OrbisNpMatching2RoomMemberId member_id = 0; + OrbisNpMatching2TeamId team_id = 0; + OrbisNpMatching2RoomGroupId group_id = 0; + OrbisNpMatching2NatType nat_type = 0; + OrbisNpMatching2Flags flag_attr = 0; + u32 addr = 0; + u16 port = 0; + std::map bins; +}; + +struct RoomCache { + u32 num_slots = 0; + u64 mask_password = 0; + OrbisNpMatching2SignalingType signaling_type = ORBIS_NP_MATCHING2_SIGNALING_TYPE_MESH; + OrbisNpMatching2RoomMemberId signaling_main_member = 0; + OrbisNpMatching2ServerId server_id = 0; + OrbisNpMatching2WorldId world_id = 0; + OrbisNpMatching2LobbyId lobby_id = 0; + OrbisNpMatching2RoomId room_id = 0; + u16 max_slot = 0; + u16 public_slots = 0; + u16 private_slots = 0; + u16 open_public_slots = 0; + u16 open_private_slots = 0; + u64 passwd_slot_mask = 0; + u64 joined_slot_mask = 0; + OrbisNpMatching2Flags flags = 0; + std::vector bin_attrs_internal; + std::vector> bin_buffers; + std::map groups; + std::map members; + bool owner = false; +}; + +struct Matching2ContextCache { + mutable std::recursive_mutex mutex; + std::map peers; + std::map rooms; + + void Reset() { + peers.clear(); + rooms.clear(); + } +}; + +} // namespace Libraries::Np::NpMatching2 diff --git a/src/core/libraries/np/np_matching2/np_matching2_internal.cpp b/src/core/libraries/np/np_matching2/np_matching2_internal.cpp index d3c42ae51..538f6b170 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_internal.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2_internal.cpp @@ -4,27 +4,24 @@ #include #include #include +#include +#include #include #include #include "common/logging/log.h" #include "common/thread.h" -#include "core/libraries/network/net.h" +#include "core/libraries/net/net.h" #include "core/libraries/np/np_common.h" #include "core/libraries/np/np_error.h" +#include "core/libraries/np/np_handler.h" #include "core/libraries/np/np_matching2/np_matching2_internal.h" #include "shadnet.pb.h" namespace Libraries::Np::NpMatching2 { -NpMatching2State g_state; - namespace { -CallbackPayload& RequestPayload(ContextObject& ctx) { - return ctx.request_payload_override ? *ctx.request_payload_override : ctx.request_payload; -} - u32 IpStringToAddr(std::string_view ip) { u32 a, b, c, d; if (std::sscanf(std::string(ip).c_str(), "%u.%u.%u.%u", &a, &b, &c, &d) != 4) { @@ -33,6 +30,33 @@ u32 IpStringToAddr(std::string_view ip) { return a | (b << 8) | (c << 16) | (d << 24); } +std::string HexPreview(const void* data, size_t size, size_t max_bytes = 32) { + if (!data || size == 0) { + return {}; + } + + const auto* bytes = static_cast(data); + const size_t count = std::min(size, max_bytes); + std::string out; + out.reserve(count * 3 + 4); + for (size_t i = 0; i < count; ++i) { + char buf[4]{}; + std::snprintf(buf, sizeof(buf), "%02x", bytes[i]); + if (i != 0) { + out.push_back(' '); + } + out.append(buf); + } + if (size > count) { + out.append(" ..."); + } + return out; +} + +std::string HexPreview(std::string_view data, size_t max_bytes = 32) { + return HexPreview(data.data(), data.size(), max_bytes); +} + template void ReserveExternalRoomPayloadStorage(CallbackPayload& p, const Reply& resp) { size_t groups = 0; @@ -63,14 +87,27 @@ void ReserveExternalRoomPayloadStorage(CallbackPayload& p, const Reply& resp) { } // namespace -void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx, +void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx, CallbackPayload& p, const shadnet::CreateJoinRoomResponse& resp) { - CallbackPayload& p = RequestPayload(ctx); const auto& rd = resp.room_data(); + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + + LOG_DEBUG(Lib_NpMatching2, + "Room payload: room={} me={} owner={} max={} public/open={}/{} " + "private/open={}/{} flags={:#x} passwdMask={:#x} joinedMask={:#x} groups={} " + "internalBinAttrs={} members={}", + rd.room_id(), resp.me_member_id(), resp.owner_member_id(), rd.max_slot(), + rd.public_slots(), rd.open_public_slots(), rd.private_slots(), + rd.open_private_slots(), rd.flags(), rd.passwd_slot_mask(), rd.joined_slot_mask(), + rd.groups_size(), rd.bin_attrs_internal_size(), resp.members_size()); p.room_groups.resize(rd.groups_size()); for (int i = 0; i < rd.groups_size(); ++i) { const auto& g = rd.groups(i); + LOG_DEBUG(Lib_NpMatching2, + " roomGroup[{}] id={} slots={} members={} hasPasswd={} hasLabel={} labelSize={}", + i, g.group_id(), g.slot_count(), g.num_members(), g.has_passwd(), g.has_label(), + g.label().size()); auto& dst = p.room_groups[i]; dst.id = static_cast(g.group_id()); dst.hasPasswd = g.has_passwd(); @@ -85,6 +122,10 @@ void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx, p.room_bin_attrs.resize(rd.bin_attrs_internal_size()); for (int i = 0; i < rd.bin_attrs_internal_size(); ++i) { const auto& a = rd.bin_attrs_internal(i); + LOG_DEBUG(Lib_NpMatching2, + " roomInternalBin[{}] id={:#x} updateMember={} updateDate={} size={} data={}", i, + a.attr_id(), a.update_member_id(), a.update_date(), a.data().size(), + HexPreview(a.data(), 128)); p.bin_buffers.emplace_back(a.data().begin(), a.data().end()); auto& buf = p.bin_buffers.back(); auto& dst = p.room_bin_attrs[i]; @@ -116,11 +157,11 @@ void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx, room.internalBinAttrs = p.room_bin_attrs.size(); if (ctx.room_id != 0 && ctx.room_id != room.roomId) { - ctx.room_cache.erase(ctx.room_id); + cache->rooms.erase(ctx.room_id); } - ctx.peers.clear(); + cache->peers.clear(); - RoomCache& rc = ctx.room_cache[room.roomId]; + RoomCache& rc = cache->rooms[room.roomId]; rc = RoomCache{}; rc.num_slots = room.maxSlot; rc.mask_password = room.passwdSlotMask; @@ -167,6 +208,12 @@ void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx, const int member_count = resp.members_size(); for (int i = 0; i < member_count; ++i) { const auto& m = resp.members(i); + LOG_DEBUG(Lib_NpMatching2, + " member[{}] id={} npid='{}' owner={} team={} nat={} flags={:#x} group={} " + "addr={}:{} account={} platform={} bins={}", + i, m.member_id(), m.npid(), m.is_owner(), m.team_id(), m.nat_type(), + m.flag_attr(), m.group_id(), m.addr(), m.port(), m.account_id(), m.platform(), + m.bin_attrs_internal_size()); MemberCache& mc = rc.members[static_cast(m.member_id())]; mc = MemberCache{}; mc.member_id = static_cast(m.member_id()); @@ -181,6 +228,8 @@ void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx, mc.account_id = static_cast(m.account_id()); mc.platform = static_cast(m.platform()); for (const auto& a : m.bin_attrs_internal()) { + LOG_INFO(Lib_NpMatching2, " memberBin id={:#x} updateDate={} size={} data={}", + a.attr_id(), a.update_date(), a.data().size(), HexPreview(a.data(), 128)); MemberBinCache& b = mc.bins[a.attr_id()]; b.id = static_cast(a.attr_id()); b.update_date = a.update_date(); @@ -192,7 +241,7 @@ void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx, peer.addr = mc.addr; peer.port = mc.port; SetNpOnlineId(peer.online_id, m.npid()); - ctx.peers[mc.member_id] = peer; + cache->peers[mc.member_id] = peer; } } } @@ -231,10 +280,10 @@ OrbisNpMatching2RoomMemberBinAttrInternal* AppendMemberBinAttrs( return count == 0 ? nullptr : &p.member_bin_attrs[first]; } -void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp) { - CallbackPayload& p = RequestPayload(ctx); +void* BuildCreateJoinRoomPayload(ContextObject& ctx, CallbackPayload& p, + const shadnet::CreateJoinRoomResponse& resp) { p.Reset(); - BuildCreateJoinRoomPayloadCommon(ctx, resp); + BuildCreateJoinRoomPayloadCommon(ctx, p, resp); const int member_count = resp.members_size(); p.member_data.resize(member_count); @@ -278,10 +327,10 @@ void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRo return p.request_data; } -void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp) { - CallbackPayload& p = RequestPayload(ctx); +void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, CallbackPayload& p, + const shadnet::CreateJoinRoomResponse& resp) { p.Reset(); - BuildCreateJoinRoomPayloadCommon(ctx, resp); + BuildCreateJoinRoomPayloadCommon(ctx, p, resp); const int member_count = resp.members_size(); p.member_data_a.resize(member_count); @@ -326,16 +375,17 @@ void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinR return p.request_data; } -void* BuildLeaveRoomPayload(ContextObject& ctx, const shadnet::LeaveRoomReply& resp) { - CallbackPayload& p = RequestPayload(ctx); +void* BuildLeaveRoomPayload(ContextObject& ctx, CallbackPayload& p, + const shadnet::LeaveRoomReply& resp) { p.Reset(); p.leave_room_response = std::make_unique(); p.leave_room_response->roomId = static_cast(resp.room_id()); if (ctx.room_id == p.leave_room_response->roomId) { - ctx.room_cache.erase(ctx.room_id); - ctx.peers.clear(); + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + cache->rooms.erase(ctx.room_id); + cache->peers.clear(); ctx.room_id = 0; ctx.my_member_id = 0; ctx.is_room_owner = false; @@ -345,8 +395,8 @@ void* BuildLeaveRoomPayload(ContextObject& ctx, const shadnet::LeaveRoomReply& r return p.request_data; } -void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldInfoListReply& resp) { - CallbackPayload& p = RequestPayload(ctx); +void* BuildGetWorldInfoListPayload(ContextObject& ctx, CallbackPayload& p, + const shadnet::GetWorldInfoListReply& resp) { p.Reset(); p.world_list.resize(resp.worlds_size()); @@ -373,15 +423,38 @@ void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldIn return p.request_data; } -void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply& resp) { - CallbackPayload& p = RequestPayload(ctx); +void* BuildSearchRoomPayload(ContextObject& ctx, CallbackPayload& p, + const shadnet::SearchRoomReply& resp) { p.Reset(); ReserveExternalRoomPayloadStorage(p, resp); const int room_count = resp.rooms_size(); + LOG_INFO(Lib_NpMatching2, "Search response rooms={}", room_count); p.room_data_external.resize(room_count); for (int i = 0; i < room_count; ++i) { const auto& r = resp.rooms(i); + LOG_DEBUG(Lib_NpMatching2, + " room[{}] id={} max={} cur={} public/open={}/{} private/open={}/{} " + "flags={:#x} groups={} searchInt={} searchBin={} extBin={}", + i, r.room_id(), r.max_slot(), r.cur_members(), r.public_slots(), + r.open_public_slots(), r.private_slots(), r.open_private_slots(), r.flags(), + r.groups_size(), r.external_search_int_attrs_size(), + r.external_search_bin_attrs_size(), r.external_bin_attrs_size()); + for (int a = 0; a < r.external_search_int_attrs_size(); ++a) { + const auto& src = r.external_search_int_attrs(a); + LOG_DEBUG(Lib_NpMatching2, " searchInt[{}] id={:#x} value={}", a, src.attr_id(), + src.attr_value()); + } + for (int a = 0; a < r.external_search_bin_attrs_size(); ++a) { + const auto& src = r.external_search_bin_attrs(a); + LOG_DEBUG(Lib_NpMatching2, " searchBin[{}] id={:#x} size={} data={}", a, + src.attr_id(), src.data().size(), HexPreview(src.data(), 128)); + } + for (int a = 0; a < r.external_bin_attrs_size(); ++a) { + const auto& src = r.external_bin_attrs(a); + LOG_DEBUG(Lib_NpMatching2, " extBin[{}] id={:#x} size={} data={}", a, src.attr_id(), + src.data().size(), HexPreview(src.data(), 128)); + } auto& dst = p.room_data_external[i]; dst = OrbisNpMatching2RoomDataExternal{}; dst.next = (i + 1 < room_count) ? &p.room_data_external[i + 1] : nullptr; @@ -477,15 +550,41 @@ void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply& return p.request_data; } -void* BuildSearchRoomPayloadA(ContextObject& ctx, const shadnet::SearchRoomReply& resp) { - CallbackPayload& p = RequestPayload(ctx); +void* BuildSearchRoomPayloadA(ContextObject& ctx, CallbackPayload& p, + const shadnet::SearchRoomReply& resp) { p.Reset(); ReserveExternalRoomPayloadStorage(p, resp); const int room_count = resp.rooms_size(); + LOG_INFO(Lib_NpMatching2, "Search response rooms={} range start={} total={} results={}", + room_count, resp.range_start(), resp.range_total(), resp.range_result()); p.room_data_external_a.resize(room_count); for (int i = 0; i < room_count; ++i) { const auto& r = resp.rooms(i); + LOG_DEBUG(Lib_NpMatching2, + " roomA[{}] id={} max={} cur={} public/open={}/{} private/open={}/{} " + "flags={:#x} groups={} searchInt={} searchBin={} extBin={} owner='{}' " + "account={} platform={}", + i, r.room_id(), r.max_slot(), r.cur_members(), r.public_slots(), + r.open_public_slots(), r.private_slots(), r.open_private_slots(), r.flags(), + r.groups_size(), r.external_search_int_attrs_size(), + r.external_search_bin_attrs_size(), r.external_bin_attrs_size(), r.owner_npid(), + r.owner_account_id(), r.owner_platform()); + for (int a = 0; a < r.external_search_int_attrs_size(); ++a) { + const auto& src = r.external_search_int_attrs(a); + LOG_DEBUG(Lib_NpMatching2, " searchIntA[{}] id={:#x} value={}", a, src.attr_id(), + src.attr_value()); + } + for (int a = 0; a < r.external_search_bin_attrs_size(); ++a) { + const auto& src = r.external_search_bin_attrs(a); + LOG_DEBUG(Lib_NpMatching2, " searchBinA[{}] id={:#x} size={} data={}", a, + src.attr_id(), src.data().size(), HexPreview(src.data(), 128)); + } + for (int a = 0; a < r.external_bin_attrs_size(); ++a) { + const auto& src = r.external_bin_attrs(a); + LOG_DEBUG(Lib_NpMatching2, " extBinA[{}] id={:#x} size={} data={}", a, src.attr_id(), + src.data().size(), HexPreview(src.data(), 128)); + } auto& dst = p.room_data_external_a[i]; dst = OrbisNpMatching2RoomDataExternalA{}; dst.next = (i + 1 < room_count) ? &p.room_data_external_a[i + 1] : nullptr; @@ -581,16 +680,38 @@ void* BuildSearchRoomPayloadA(ContextObject& ctx, const shadnet::SearchRoomReply return p.request_data; } -void* BuildGetRoomDataExternalListPayload(ContextObject& ctx, +void* BuildGetRoomDataExternalListPayload(ContextObject& ctx, CallbackPayload& p, const shadnet::GetRoomDataExternalListReply& resp) { - CallbackPayload& p = RequestPayload(ctx); p.Reset(); ReserveExternalRoomPayloadStorage(p, resp); const int room_count = resp.rooms_size(); + LOG_INFO(Lib_NpMatching2, "getRoomDataExternalList response rooms={}", room_count); p.room_data_external.resize(room_count); for (int i = 0; i < room_count; ++i) { const auto& r = resp.rooms(i); + LOG_DEBUG(Lib_NpMatching2, + " room[{}] id={} max={} cur={} public/open={}/{} private/open={}/{} " + "flags={:#x} groups={} searchInt={} searchBin={} extBin={}", + i, r.room_id(), r.max_slot(), r.cur_members(), r.public_slots(), + r.open_public_slots(), r.private_slots(), r.open_private_slots(), r.flags(), + r.groups_size(), r.external_search_int_attrs_size(), + r.external_search_bin_attrs_size(), r.external_bin_attrs_size()); + for (int a = 0; a < r.external_search_int_attrs_size(); ++a) { + const auto& src = r.external_search_int_attrs(a); + LOG_DEBUG(Lib_NpMatching2, " searchInt[{}] id={:#x} value={}", a, src.attr_id(), + src.attr_value()); + } + for (int a = 0; a < r.external_search_bin_attrs_size(); ++a) { + const auto& src = r.external_search_bin_attrs(a); + LOG_DEBUG(Lib_NpMatching2, " searchBin[{}] id={:#x} size={} data={}", a, + src.attr_id(), src.data().size(), HexPreview(src.data(), 128)); + } + for (int a = 0; a < r.external_bin_attrs_size(); ++a) { + const auto& src = r.external_bin_attrs(a); + LOG_DEBUG(Lib_NpMatching2, " extBin[{}] id={:#x} size={} data={}", a, src.attr_id(), + src.data().size(), HexPreview(src.data(), 128)); + } auto& dst = p.room_data_external[i]; dst = OrbisNpMatching2RoomDataExternal{}; dst.next = (i + 1 < room_count) ? &p.room_data_external[i + 1] : nullptr; @@ -685,9 +806,8 @@ void* BuildGetRoomDataExternalListPayload(ContextObject& ctx, return p.request_data; } -void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx, +void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx, CallbackPayload& p, const shadnet::GetRoomDataExternalListReply& resp) { - CallbackPayload& p = RequestPayload(ctx); p.Reset(); ReserveExternalRoomPayloadStorage(p, resp); @@ -790,8 +910,8 @@ void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx, } void* BuildGetRoomMemberDataExternalListPayload( - ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp) { - CallbackPayload& p = RequestPayload(ctx); + ContextObject& ctx, CallbackPayload& p, + const shadnet::GetRoomMemberDataExternalListReply& resp) { p.Reset(); const int member_count = resp.members_size(); @@ -819,8 +939,8 @@ void* BuildGetRoomMemberDataExternalListPayload( } void* BuildGetRoomMemberDataExternalListPayloadA( - ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp) { - CallbackPayload& p = RequestPayload(ctx); + ContextObject& ctx, CallbackPayload& p, + const shadnet::GetRoomMemberDataExternalListReply& resp) { p.Reset(); const int member_count = resp.members_size(); @@ -849,8 +969,8 @@ void* BuildGetRoomMemberDataExternalListPayloadA( return p.request_data; } -void* BuildGetUserInfoListPayload(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp) { - CallbackPayload& p = RequestPayload(ctx); +void* BuildGetUserInfoListPayload(ContextObject& ctx, CallbackPayload& p, + const shadnet::GetUserInfoListReply& resp) { p.Reset(); size_t total_bin_attrs = 0; @@ -899,8 +1019,8 @@ void* BuildGetUserInfoListPayload(ContextObject& ctx, const shadnet::GetUserInfo return p.request_data; } -void* BuildGetUserInfoListPayloadA(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp) { - CallbackPayload& p = RequestPayload(ctx); +void* BuildGetUserInfoListPayloadA(ContextObject& ctx, CallbackPayload& p, + const shadnet::GetUserInfoListReply& resp) { p.Reset(); size_t total_bin_attrs = 0; @@ -951,14 +1071,15 @@ void* BuildGetUserInfoListPayloadA(ContextObject& ctx, const shadnet::GetUserInf return p.request_data; } -void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId room_id) { - const auto rc_it = ctx.room_cache.find(room_id); - if (rc_it == ctx.room_cache.end()) { +void* BuildGetRoomDataInternalPayload(ContextObject& ctx, CallbackPayload& p, + OrbisNpMatching2RoomId room_id) { + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + const auto rc_it = cache->rooms.find(room_id); + if (rc_it == cache->rooms.end()) { return nullptr; } const RoomCache& rc = rc_it->second; - CallbackPayload& p = RequestPayload(ctx); p.Reset(); p.room_groups.clear(); @@ -1042,6 +1163,102 @@ void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId return p.request_data; } +void* BuildGetRoomMemberDataInternalPayload( + ContextObject& ctx, CallbackPayload& p, + const OrbisNpMatching2GetRoomMemberDataInternalRequest& request) { + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + const auto room_it = cache->rooms.find(request.roomId); + if (room_it == cache->rooms.end()) { + return nullptr; + } + const RoomCache& room = room_it->second; + const auto member_it = room.members.find(request.memberId); + if (member_it == room.members.end()) { + return nullptr; + } + const MemberCache& member = member_it->second; + + p.Reset(); + if (member.group_id != 0) { + const auto group_it = room.groups.find(member.group_id); + if (group_it != room.groups.end()) { + p.room_groups.push_back(group_it->second); + } + } + + std::vector ids; + if (request.attrIdNum == 0) { + for (const auto& entry : member.bins) { + ids.push_back(entry.first); + } + } else { + ids.assign(request.attrId, request.attrId + request.attrIdNum); + } + p.member_bin_attrs.reserve(ids.size()); + p.bin_buffers.reserve(ids.size()); + for (const OrbisNpMatching2AttributeId id : ids) { + const auto bin_it = member.bins.find(id); + auto& dst = p.member_bin_attrs.emplace_back(); + dst = {}; + dst.binAttr.id = id; + if (bin_it == member.bins.end()) { + p.bin_buffers.emplace_back(64, u8{0}); + dst.binAttr.data = p.bin_buffers.back().data(); + dst.binAttr.dataSize = 0; + continue; + } + p.bin_buffers.push_back(bin_it->second.data); + auto& data = p.bin_buffers.back(); + dst.lastUpdate.tick = bin_it->second.update_date; + dst.binAttr.data = data.empty() ? nullptr : data.data(); + dst.binAttr.dataSize = data.size(); + } + + if (ctx.a_variant) { + p.member_data_a.resize(1); + auto& out_member = p.member_data_a.front(); + out_member = {}; + out_member.joinDateTicks.tick = member.join_date; + out_member.user.accountId = member.account_id; + out_member.user.platform = member.platform; + out_member.onlineId = member.np_id.handle; + out_member.memberId = member.member_id; + out_member.teamId = member.team_id; + out_member.natType = member.nat_type; + out_member.flags = member.flag_attr; + out_member.roomGroup = p.room_groups.empty() ? nullptr : p.room_groups.data(); + out_member.roomMemberInternalBinAttr = + p.member_bin_attrs.empty() ? nullptr : p.member_bin_attrs.data(); + out_member.roomMemberInternalBinAttrs = p.member_bin_attrs.size(); + + p.room_member_data_internal_response_a = + std::make_unique(); + p.room_member_data_internal_response_a->roomMemberDataInternal = &out_member; + p.request_data = p.room_member_data_internal_response_a.get(); + return p.request_data; + } + + p.member_data.resize(1); + auto& out_member = p.member_data.front(); + out_member = {}; + out_member.joinDate = member.join_date; + out_member.npId = member.np_id; + out_member.memberId = member.member_id; + out_member.teamId = member.team_id; + out_member.natType = member.nat_type; + out_member.flagAttr = member.flag_attr; + out_member.roomGroup = p.room_groups.empty() ? nullptr : p.room_groups.data(); + out_member.roomMemberBinAttrInternal = + p.member_bin_attrs.empty() ? nullptr : p.member_bin_attrs.data(); + out_member.roomMemberBinAttrInternalNum = p.member_bin_attrs.size(); + + p.room_member_data_internal_response = + std::make_unique(); + p.room_member_data_internal_response->roomMemberDataInternal = &out_member; + p.request_data = p.room_member_data_internal_response.get(); + return p.request_data; +} + void* BuildRoomMessagePayload(CallbackPayload& p, bool a_variant, OrbisNpMatching2CastType castType, const std::vector& dstMembers, const MemberCache* srcMember, const std::vector& msg) { @@ -1060,28 +1277,38 @@ void* BuildRoomMessagePayload(CallbackPayload& p, bool a_variant, OrbisNpMatchin p.room_message_data = msg; void* msg_ptr = p.room_message_data.empty() ? nullptr : p.room_message_data.data(); - if (a_variant) { - p.room_message_src_addr = std::make_unique(); - p.room_message_src_online_id = std::make_unique(); - *p.room_message_src_addr = {}; - *p.room_message_src_online_id = {}; - if (srcMember) { - p.room_message_src_addr->accountId = srcMember->account_id; - p.room_message_src_addr->platform = srcMember->platform; - *p.room_message_src_online_id = srcMember->np_id.handle; - } + LOG_INFO(Lib_NpMatching2, + "variant={} cast={} dstN={} srcMember={} srcMid={} " + "srcAccount={} srcPlatform={} msg={} msgLen={} preview=[{}]", + a_variant ? "A" : "regular", castType, dstMembers.size(), fmt::ptr(srcMember), + srcMember ? srcMember->member_id : 0, srcMember ? srcMember->account_id : 0, + srcMember ? static_cast(srcMember->platform) : 0, fmt::ptr(msg_ptr), msg.size(), + HexPreview(msg.data(), msg.size(), 32)); + if (a_variant) { p.room_message_info_a = std::make_unique(); auto& info = *p.room_message_info_a; info = {}; info.filtered = false; info.castType = castType; + if (srcMember) { + info.srcMember.accountId = srcMember->account_id; + info.srcMember.platform = srcMember->platform; + info.srcMemberOnlineId = srcMember->np_id.handle; + } info.dst = p.room_message_dst.get(); - info.srcMember = p.room_message_src_addr.get(); - info.srcOnlineId = p.room_message_src_online_id.get(); info.msg = msg_ptr; - info.msgLen = static_cast(p.room_message_data.size()); + info.msgLen = p.room_message_data.size(); p.room_message_callback_data = p.room_message_info_a.get(); + LOG_INFO(Lib_NpMatching2, + "RoomMessageInfoA: info={} dst={} srcMember={} msg={} " + "msgLen={} account={} platform={} onlineId='{}'", + fmt::ptr(&info), fmt::ptr(info.dst), fmt::ptr(&info.srcMember), fmt::ptr(info.msg), + info.msgLen, info.srcMember.accountId, static_cast(info.srcMember.platform), + srcMember ? std::string_view(srcMember->np_id.handle.data, + strnlen(srcMember->np_id.handle.data, + Libraries::Np::ORBIS_NP_ONLINEID_MAX_LENGTH)) + : std::string_view{}); return p.room_message_callback_data; } @@ -1101,19 +1328,21 @@ void* BuildRoomMessagePayload(CallbackPayload& p, bool a_variant, OrbisNpMatchin info.msg = msg_ptr; info.msgLen = static_cast(p.room_message_data.size()); p.room_message_callback_data = p.room_message_info.get(); + LOG_DEBUG(Lib_NpMatching2, "Payload: info={} dst={} srcNpId={} msg={} msgLen={} onlineId='{}'", + fmt::ptr(&info), fmt::ptr(info.dst), fmt::ptr(info.srcMember), fmt::ptr(info.msg), + info.msgLen, + srcMember ? std::string_view(srcMember->np_id.handle.data, + strnlen(srcMember->np_id.handle.data, + Libraries::Np::ORBIS_NP_ONLINEID_MAX_LENGTH)) + : std::string_view{}); return p.room_message_callback_data; } -ContextManager& ContextManager::Instance() { - static ContextManager instance; - return instance; -} - -ContextObject* ContextManager::GetLocked(OrbisNpMatching2ContextId ctx_id) { - if (ctx_id == 0 || ctx_id > kMaxContexts || !m_used[ctx_id]) { - return nullptr; +std::shared_ptr ContextManager::GetLocked(OrbisNpMatching2ContextId ctx_id) { + if (ctx_id == 0 || ctx_id > kMaxContexts) { + return {}; } - return &m_contexts[ctx_id]; + return m_contexts[ctx_id]; } s32 ContextManager::CreateContext(const OrbisNpId* owner_np_id, OrbisNpServiceLabel service_label, @@ -1128,7 +1357,7 @@ s32 ContextManager::CreateContext(const OrbisNpId* owner_np_id, OrbisNpServiceLa for (u32 i = 0; i < kMaxContexts; ++i) { const OrbisNpMatching2ContextId candidate = static_cast(((m_next_id - 1 + i) % kMaxContexts) + 1); - if (!m_used[candidate]) { + if (!m_contexts[candidate]) { id = candidate; break; } @@ -1138,70 +1367,67 @@ s32 ContextManager::CreateContext(const OrbisNpId* owner_np_id, OrbisNpServiceLa } m_next_id = static_cast((id % kMaxContexts) + 1); - ContextObject& ctx = m_contexts[id]; - ctx.Reset(); - ctx.ctx_id = id; - ctx.a_variant = a_variant; - ctx.service_label = service_label; + auto ctx = std::make_shared(); + NpHandler::GetInstance().ResetMatching2Cache(id); + ctx->ctx_id = id; + ctx->a_variant = a_variant; + ctx->service_label = service_label; + NpHandler::GetInstance().LockMatching2Cache(id)->Reset(); if (owner_np_id) { - ctx.owner_np_id = *owner_np_id; - ctx.online_id = owner_np_id->handle; + ctx->owner_np_id = *owner_np_id; + ctx->online_id = owner_np_id->handle; } - ctx.context_callback = m_pending_context_callback; - ctx.context_callback_arg = m_pending_context_callback_arg; - m_used[id] = true; + ctx->context_callback = m_pending_context_callback; + ctx->context_callback_arg = m_pending_context_callback_arg; + m_contexts[id] = ctx; *out_ctx_id = id; - LOG_DEBUG(Lib_NpMatching2, "context{} created: id={} online_id={} serviceLabel={:#x}", - a_variant ? "A" : "", id, ctx.online_id.data, service_label); + LOG_INFO(Lib_NpMatching2, "context{} created: id={} online_id={} serviceLabel={:#x}", + a_variant ? "A" : "", id, ctx->online_id.data, service_label); return ORBIS_OK; } -bool ContextManager::Check(OrbisNpMatching2ContextId ctx_id) { - std::lock_guard lock(m_mutex); - return GetLocked(ctx_id) != nullptr; -} - -ContextObject* ContextManager::Get(OrbisNpMatching2ContextId ctx_id) { +std::shared_ptr ContextManager::Get(OrbisNpMatching2ContextId ctx_id) { std::lock_guard lock(m_mutex); return GetLocked(ctx_id); } bool ContextManager::Destroy(OrbisNpMatching2ContextId ctx_id) { std::lock_guard lock(m_mutex); - ContextObject* ctx = GetLocked(ctx_id); + auto ctx = GetLocked(ctx_id); if (!ctx) { return false; } if (ctx->stop_pending) { ctx->destroy_pending = true; - LOG_DEBUG(Lib_NpMatching2, "context destroy deferred until stop callback: id={}", ctx_id); + LOG_INFO(Lib_NpMatching2, "context destroy deferred until stop callback: id={}", ctx_id); return true; } - m_contexts[ctx_id].Reset(); - m_used[ctx_id] = false; - LOG_DEBUG(Lib_NpMatching2, "context destroyed: id={}", ctx_id); + NpHandler::GetInstance().ResetMatching2Cache(ctx_id); + m_contexts[ctx_id].reset(); + LOG_INFO(Lib_NpMatching2, "context destroyed: id={}", ctx_id); return true; } -void ContextManager::CompleteStop(OrbisNpMatching2ContextId ctx_id) { +void ContextManager::CompleteStop(OrbisNpMatching2ContextId ctx_id, + const std::shared_ptr& expected_context) { std::lock_guard lock(m_mutex); - ContextObject* ctx = GetLocked(ctx_id); - if (!ctx) { + auto ctx = GetLocked(ctx_id); + if (!ctx || ctx != expected_context) { return; } ctx->stop_pending = false; if (!ctx->destroy_pending) { return; } - m_contexts[ctx_id].Reset(); - m_used[ctx_id] = false; - LOG_DEBUG(Lib_NpMatching2, "context destroyed after stop callback: id={}", ctx_id); + NpHandler::GetInstance().ResetMatching2Cache(ctx_id); + m_contexts[ctx_id].reset(); + LOG_INFO(Lib_NpMatching2, "context destroyed after stop callback: id={}", ctx_id); } s32 ContextManager::Start(OrbisNpMatching2ContextId ctx_id) { std::lock_guard lock(m_mutex); - ContextObject* ctx = GetLocked(ctx_id); + auto ctx = GetLocked(ctx_id); if (!ctx) { return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; } @@ -1211,13 +1437,13 @@ s32 ContextManager::Start(OrbisNpMatching2ContextId ctx_id) { ctx->started = true; ctx->stop_pending = false; ctx->destroy_pending = false; - LOG_DEBUG(Lib_NpMatching2, "context started: id={}", ctx_id); + LOG_INFO(Lib_NpMatching2, "context started: id={}", ctx_id); return ORBIS_OK; } s32 ContextManager::Stop(OrbisNpMatching2ContextId ctx_id) { std::lock_guard lock(m_mutex); - ContextObject* ctx = GetLocked(ctx_id); + auto ctx = GetLocked(ctx_id); if (!ctx) { return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; } @@ -1226,7 +1452,7 @@ s32 ContextManager::Stop(OrbisNpMatching2ContextId ctx_id) { } ctx->started = false; ctx->stop_pending = true; - LOG_DEBUG(Lib_NpMatching2, "context stopped: id={}", ctx_id); + LOG_INFO(Lib_NpMatching2, "context stopped: id={}", ctx_id); return ORBIS_OK; } @@ -1235,44 +1461,42 @@ void ContextManager::ApplyContextCallback(OrbisNpMatching2ContextCallback callba m_pending_context_callback = callback; m_pending_context_callback_arg = arg; for (u32 id = 1; id <= kMaxContexts; ++id) { - if (m_used[id]) { - m_contexts[id].context_callback = callback; - m_contexts[id].context_callback_arg = arg; + if (m_contexts[id]) { + m_contexts[id]->context_callback = callback; + m_contexts[id]->context_callback_arg = arg; } } } void ContextManager::Reset() { std::lock_guard lock(m_mutex); - for (auto& ctx : m_contexts) { - ctx.Reset(); - } - m_used = {}; + m_contexts = {}; + NpHandler::GetInstance().ResetMatching2Caches(); m_next_id = 1; m_pending_context_callback = nullptr; m_pending_context_callback_arg = nullptr; } OrbisNpMatching2RequestId AllocRequestId() { - std::lock_guard lock(g_state.mutex); - const OrbisNpMatching2RequestId id = g_state.next_request_id++; - if (g_state.next_request_id == 0) { - g_state.next_request_id = 1; + auto& state = NpHandler::GetInstance().GetMatching2State(); + std::lock_guard lock(state.mutex); + const OrbisNpMatching2RequestId id = state.next_request_id++; + if (state.next_request_id == 0) { + state.next_request_id = 1; } return id; } -static bool g_initialized = false; - bool IsInitialized() { - return g_initialized; + return NpHandler::GetInstance().GetMatching2State().initialized.load(); } void SetInitialized(bool initialized) { - g_initialized = initialized; + NpHandler::GetInstance().GetMatching2State().initialized.store(initialized); } -void StoreRequestCallback(ContextObject* ctx, const OrbisNpMatching2RequestOptParam* requestOpt) { +void StoreRequestCallback(const std::shared_ptr& ctx, + const OrbisNpMatching2RequestOptParam* requestOpt) { ctx->per_request_callback = nullptr; ctx->per_request_callback_arg = nullptr; if (requestOpt && requestOpt->callback) { @@ -1281,7 +1505,7 @@ void StoreRequestCallback(ContextObject* ctx, const OrbisNpMatching2RequestOptPa } } -RequestCallbackInfo ConsumeRequestCallback(ContextObject* ctx) { +RequestCallbackInfo ConsumeRequestCallback(const std::shared_ptr& ctx) { RequestCallbackInfo cb{}; if (ctx->per_request_callback) { cb.callback = ctx->per_request_callback; @@ -1298,19 +1522,100 @@ RequestCallbackInfo ConsumeRequestCallback(ContextObject* ctx) { namespace { +std::string CallbackOnlineId(const Libraries::Np::OrbisNpId& npid) { + return std::string(npid.handle.data, strnlen(npid.handle.data, ORBIS_NP_ONLINEID_MAX_LENGTH)); +} + +template +void LogCallbackObject(std::string_view label, const T* object) { + if (!object) { + return; + } + LOG_INFO(Lib_NpMatching2, "Callback payload {}: ptr={} size={:#x} raw={}", label, + fmt::ptr(object), sizeof(T), HexPreview(object, sizeof(T), sizeof(T))); +} + +bool IsCreateJoinRequestEvent(OrbisNpMatching2Event event) { + return event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM || + event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM || + event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A || + event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM_A; +} + +bool IsMemberJoinedEvent(OrbisNpMatching2Event event) { + return event == ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED || + event == ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED_A; +} + +void LogCreateJoinCallbackPayload(const PendingEvent& ev) { + if (!IsCreateJoinRequestEvent(ev.req_event) || !ev.payload_owner) { + return; + } + + const CallbackPayload& payload = *ev.payload_owner; + LOG_DEBUG(Lib_NpMatching2, + "Callback create/join payload begin: ctx={} reqId={} event={:#x} data={} " + "normal_members={} a_members={}", + ev.ctx_id, ev.req_id, static_cast(ev.req_event), fmt::ptr(ev.request_data), + payload.member_data.size(), payload.member_data_a.size()); + LogCallbackObject("create_join_response", payload.create_join_response.get()); + LogCallbackObject("create_join_response_a", payload.create_join_response_a.get()); + LogCallbackObject("room_data", payload.room_data.get()); + for (size_t i = 0; i < payload.member_data.size(); ++i) { + const auto& member = payload.member_data[i]; + LOG_DEBUG(Lib_NpMatching2, + "Callback payload member[{}]: ptr={} size={:#x} npid='{}' npid_raw={} raw={}", i, + fmt::ptr(&member), sizeof(member), CallbackOnlineId(member.npId), + HexPreview(&member.npId, sizeof(member.npId), sizeof(member.npId)), + HexPreview(&member, sizeof(member), sizeof(member))); + } + for (size_t i = 0; i < payload.member_data_a.size(); ++i) { + const auto& member = payload.member_data_a[i]; + LOG_DEBUG(Lib_NpMatching2, "Callback payload member_a[{}]: ptr={} size={:#x} raw={}", i, + fmt::ptr(&member), sizeof(member), + HexPreview(&member, sizeof(member), sizeof(member))); + } + LOG_DEBUG(Lib_NpMatching2, "Callback create/join payload end: ctx={} reqId={} event={:#x}", + ev.ctx_id, ev.req_id, static_cast(ev.req_event)); +} + +void LogMemberJoinedCallbackPayload(const PendingEvent& ev) { + if (!IsMemberJoinedEvent(ev.room_event) || !ev.payload_owner) { + return; + } + + const CallbackPayload& payload = *ev.payload_owner; + LOG_DEBUG(Lib_NpMatching2, + "Callback member-joined payload begin: ctx={} room={} event={:#x} data={}", ev.ctx_id, + ev.room_id, static_cast(ev.room_event), fmt::ptr(ev.room_event_data)); + LogCallbackObject("room_member_update", payload.room_member_update.get()); + LogCallbackObject("room_member_update_a", payload.room_member_update_a.get()); + if (payload.event_member) { + const auto& member = *payload.event_member; + LOG_DEBUG(Lib_NpMatching2, + "Callback payload joined_member: ptr={} size={:#x} npid='{}' npid_raw={} raw={}", + fmt::ptr(&member), sizeof(member), CallbackOnlineId(member.npId), + HexPreview(&member.npId, sizeof(member.npId), sizeof(member.npId)), + HexPreview(&member, sizeof(member), sizeof(member))); + } + LogCallbackObject("joined_member_a", payload.event_member_a.get()); + LOG_DEBUG(Lib_NpMatching2, "Callback member-joined payload end: ctx={} room={} event={:#x}", + ev.ctx_id, ev.room_id, static_cast(ev.room_event)); +} + void FireEvent(const PendingEvent& ev) { - ContextObject* ctx = ContextManager::Instance().Get(ev.ctx_id); + const auto& ctx = ev.context_owner; if (!ctx) { - LOG_WARNING(Lib_NpMatching2, "FireEvent type={} dropped: ctx={} not found", - static_cast(ev.type), ev.ctx_id); + LOG_WARNING(Lib_NpMatching2, "type={} dropped: ctx={} not found", static_cast(ev.type), + ev.ctx_id); return; } switch (ev.type) { case PendingEvent::CONTEXT_CB: if (ctx->context_callback) { - LOG_DEBUG(Lib_NpMatching2, "callback CONTEXT ctx={} event={:#x} cause={} err={:#x}", - ev.ctx_id, static_cast(ev.ctx_event), - static_cast(ev.ctx_event_cause), ev.error_code); + LOG_INFO(Lib_NpMatching2, "callback CONTEXT ctx={} event={:#x} cause={} err={:#x}", + ev.ctx_id, static_cast(ev.ctx_event), static_cast(ev.ctx_event_cause), + ev.error_code); ctx->context_callback(ev.ctx_id, ev.ctx_event, ev.ctx_event_cause, ev.error_code, ctx->context_callback_arg); } else { @@ -1318,15 +1623,16 @@ void FireEvent(const PendingEvent& ev) { ev.ctx_id, static_cast(ev.ctx_event)); } if (ev.ctx_event == ORBIS_NP_MATCHING2_CONTEXT_EVENT_STOPPED) { - ContextManager::Instance().CompleteStop(ev.ctx_id); + NpHandler::GetInstance().GetMatching2ContextManager().CompleteStop(ev.ctx_id, ctx); } break; case PendingEvent::REQUEST_CB: if (ev.request_cb) { - LOG_DEBUG(Lib_NpMatching2, - "callback REQUEST ctx={} reqId={} event={:#x} err={:#x} data={}", ev.ctx_id, - ev.req_id, static_cast(ev.req_event), ev.error_code, - fmt::ptr(ev.request_data)); + LOG_INFO(Lib_NpMatching2, + "callback REQUEST ctx={} reqId={} event={:#x} err={:#x} data={}", ev.ctx_id, + ev.req_id, static_cast(ev.req_event), ev.error_code, + fmt::ptr(ev.request_data)); + LogCreateJoinCallbackPayload(ev); ev.request_cb(ev.ctx_id, ev.req_id, ev.req_event, ev.error_code, ev.request_data, ev.request_cb_arg); } else { @@ -1337,10 +1643,9 @@ void FireEvent(const PendingEvent& ev) { break; case PendingEvent::SIGNALING_CB: if (ctx->signaling_callback) { - LOG_DEBUG(Lib_NpMatching2, - "callback SIGNALING ctx={} room={} member={} event={:#x} err={:#x}", - ev.ctx_id, ev.room_id, ev.member_id, static_cast(ev.sig_event), - ev.error_code); + LOG_INFO(Lib_NpMatching2, + "callback SIGNALING ctx={} room={} member={} event={:#x} err={:#x}", ev.ctx_id, + ev.room_id, ev.member_id, static_cast(ev.sig_event), ev.error_code); ctx->signaling_callback(ev.ctx_id, ev.room_id, ev.member_id, ev.sig_event, ev.error_code, ctx->signaling_callback_arg); } else { @@ -1351,9 +1656,10 @@ void FireEvent(const PendingEvent& ev) { break; case PendingEvent::ROOM_EVENT_CB: if (ctx->room_event_callback) { - LOG_DEBUG(Lib_NpMatching2, "callback ROOM_EVENT ctx={} room={} event={:#x} data={}", - ev.ctx_id, ev.room_id, static_cast(ev.room_event), - fmt::ptr(ev.room_event_data)); + LOG_INFO(Lib_NpMatching2, "callback ROOM_EVENT ctx={} room={} event={:#x} data={}", + ev.ctx_id, ev.room_id, static_cast(ev.room_event), + fmt::ptr(ev.room_event_data)); + LogMemberJoinedCallbackPayload(ev); ctx->room_event_callback(ev.ctx_id, ev.room_id, ev.room_event, ev.room_event_data, ctx->room_event_callback_arg); } else { @@ -1364,9 +1670,9 @@ void FireEvent(const PendingEvent& ev) { break; case PendingEvent::LOBBY_EVENT_CB: if (ctx->lobby_event_callback) { - LOG_DEBUG(Lib_NpMatching2, "callback LOBBY_EVENT ctx={} lobby={} event={:#x} data={}", - ev.ctx_id, ev.lobby_id, static_cast(ev.lobby_event), - fmt::ptr(ev.lobby_event_data)); + LOG_INFO(Lib_NpMatching2, "callback LOBBY_EVENT ctx={} lobby={} event={:#x} data={}", + ev.ctx_id, ev.lobby_id, static_cast(ev.lobby_event), + fmt::ptr(ev.lobby_event_data)); ctx->lobby_event_callback(ev.ctx_id, ev.lobby_id, ev.lobby_event, ev.lobby_event_data, ctx->lobby_event_callback_arg); } else { @@ -1377,8 +1683,8 @@ void FireEvent(const PendingEvent& ev) { break; case PendingEvent::LOBBY_MESSAGE_CB: if (ctx->lobby_message_callback) { - LOG_DEBUG(Lib_NpMatching2, "callback LOBBY_MESSAGE ctx={} lobby={} src={} event={:#x}", - ev.ctx_id, ev.lobby_id, ev.src_member_id, static_cast(ev.msg_event)); + LOG_INFO(Lib_NpMatching2, "callback LOBBY_MESSAGE ctx={} lobby={} src={} event={:#x}", + ev.ctx_id, ev.lobby_id, ev.src_member_id, static_cast(ev.msg_event)); ctx->lobby_message_callback(ev.ctx_id, ev.lobby_id, ev.src_member_id, ev.msg_event, ev.message_data, ctx->lobby_message_callback_arg); } else { @@ -1389,8 +1695,29 @@ void FireEvent(const PendingEvent& ev) { break; case PendingEvent::ROOM_MESSAGE_CB: if (ctx->room_message_callback) { - LOG_DEBUG(Lib_NpMatching2, "callback ROOM_MESSAGE ctx={} room={} src={} event={:#x}", - ev.ctx_id, ev.room_id, ev.src_member_id, static_cast(ev.msg_event)); + LOG_INFO(Lib_NpMatching2, + "callback ROOM_MESSAGE ctx={} room={} src={} event={:#x} cb={} data={} " + "userdata={} aVariant={}", + ev.ctx_id, ev.room_id, ev.src_member_id, static_cast(ev.msg_event), + fmt::ptr(ctx->room_message_callback), fmt::ptr(ev.message_data), + fmt::ptr(ctx->room_message_callback_arg), ctx->a_variant); + if (ctx->a_variant && ev.message_data) { + const auto* info = + static_cast(ev.message_data); + LOG_DEBUG(Lib_NpMatching2, + "callback ROOM_MESSAGE_A data: filtered={} cast={} dst={} srcMember={} " + "msg={} msgLen={}", + info->filtered, info->castType, fmt::ptr(info->dst), + fmt::ptr(&info->srcMember), fmt::ptr(info->msg), info->msgLen); + } else if (ev.message_data) { + const auto* info = + static_cast(ev.message_data); + LOG_DEBUG(Lib_NpMatching2, + "callback ROOM_MESSAGE data: filtered={} cast={} dst={} srcNpId={} " + "msg={} msgLen={}", + info->filtered, info->castType, fmt::ptr(info->dst), + fmt::ptr(info->srcMember), fmt::ptr(info->msg), info->msgLen); + } ctx->room_message_callback(ev.ctx_id, ev.room_id, ev.src_member_id, ev.msg_event, ev.message_data, ctx->room_message_callback_arg); } else { @@ -1406,16 +1733,17 @@ void FireEvent(const PendingEvent& ev) { PS4_SYSV_ABI void* EventDispatcherThreadMain(void*) { Common::SetCurrentThreadName("Matching2:Dispatch"); - std::unique_lock lock(g_state.queue_mutex); - while (g_state.dispatch_running.load()) { + auto& state = NpHandler::GetInstance().GetMatching2State(); + std::unique_lock lock(state.queue_mutex); + while (state.dispatch_running.load()) { const auto now = std::chrono::steady_clock::now(); std::vector ready; auto nearest = std::chrono::steady_clock::time_point::max(); - for (auto it = g_state.pending_events.begin(); it != g_state.pending_events.end();) { + for (auto it = state.pending_events.begin(); it != state.pending_events.end();) { if (it->fire_at <= now) { ready.push_back(std::move(*it)); - it = g_state.pending_events.erase(it); + it = state.pending_events.erase(it); } else { nearest = std::min(nearest, it->fire_at); ++it; @@ -1432,50 +1760,58 @@ PS4_SYSV_ABI void* EventDispatcherThreadMain(void*) { } if (nearest == std::chrono::steady_clock::time_point::max()) { - g_state.queue_cv.wait(lock); + state.queue_cv.wait(lock); } else { - g_state.queue_cv.wait_until(lock, nearest); + state.queue_cv.wait_until(lock, nearest); } } return nullptr; } void InitEventDispatcher() { - if (g_state.dispatch_running.exchange(true)) { + auto& state = NpHandler::GetInstance().GetMatching2State(); + if (state.dispatch_running.exchange(true)) { return; } const s32 rc = NpCommon::sceNpCreateThread( - &g_state.dispatch_thread, EventDispatcherThreadMain, nullptr, + &state.dispatch_thread, EventDispatcherThreadMain, nullptr, Libraries::Kernel::ORBIS_KERNEL_PRIO_FIFO_DEFAULT, ORBIS_NP_MATCHING2_THREAD_STACK_SIZE_DEFAULT, 0, "SceNpMatching2Ex"); if (rc < 0) { LOG_ERROR(Lib_NpMatching2, "failed to create event dispatcher thread: {:#x}", rc); - g_state.dispatch_running.store(false); - g_state.dispatch_thread = nullptr; + state.dispatch_running.store(false); + state.dispatch_thread = nullptr; return; } - LOG_DEBUG(Lib_NpMatching2, "event dispatcher thread created"); + LOG_INFO(Lib_NpMatching2, "event dispatcher thread created"); } void TermEventDispatcher() { - if (!g_state.dispatch_running.exchange(false)) { + auto& state = NpHandler::GetInstance().GetMatching2State(); + if (!state.dispatch_running.exchange(false)) { return; } - g_state.queue_cv.notify_all(); - if (g_state.dispatch_thread) { - NpCommon::sceNpJoinThread(g_state.dispatch_thread, nullptr); - g_state.dispatch_thread = nullptr; + state.queue_cv.notify_all(); + if (state.dispatch_thread) { + NpCommon::sceNpJoinThread(state.dispatch_thread, nullptr); + state.dispatch_thread = nullptr; } - std::lock_guard lock(g_state.queue_mutex); - g_state.pending_events.clear(); + std::lock_guard lock(state.queue_mutex); + state.pending_events.clear(); } void ScheduleEvent(PendingEvent ev) { - { - std::lock_guard lock(g_state.queue_mutex); - g_state.pending_events.push_back(std::move(ev)); + if (!ev.context_owner) { + LOG_WARNING(Lib_NpMatching2, "type={} dropped: ctx={} not found", static_cast(ev.type), + ev.ctx_id); + return; } - g_state.queue_cv.notify_all(); + auto& state = NpHandler::GetInstance().GetMatching2State(); + { + std::lock_guard lock(state.queue_mutex); + state.pending_events.push_back(std::move(ev)); + } + state.queue_cv.notify_all(); } } // namespace Libraries::Np::NpMatching2 diff --git a/src/core/libraries/np/np_matching2/np_matching2_internal.h b/src/core/libraries/np/np_matching2/np_matching2_internal.h index 3e25b9437..35834d2c5 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_internal.h +++ b/src/core/libraries/np/np_matching2/np_matching2_internal.h @@ -16,6 +16,7 @@ #include "common/types.h" #include "core/libraries/kernel/threads.h" #include "core/libraries/np/np_matching2/np_matching2.h" +#include "core/libraries/np/np_matching2/np_matching2_cache.h" #include "core/libraries/np/np_types.h" namespace shadnet { @@ -28,69 +29,12 @@ class LeaveRoomReply; class SearchRoomReply; } // namespace shadnet +namespace Libraries::Np { +class NpHandler; +} + namespace Libraries::Np::NpMatching2 { -struct PeerInfo { - u32 addr = 0; - u16 port = 0; - OrbisNpMatching2RoomMemberId member_id = 0; - s32 conn_id = 0; - s32 status = 0; - Libraries::Np::OrbisNpOnlineId online_id{}; - bool handshake_started = false; - bool sent_check = false; - bool sent_established = false; - u64 nonce = 0; - u32 ping_us = 0; - std::chrono::steady_clock::time_point last_send{}; - std::chrono::steady_clock::time_point last_check_send{}; -}; - -struct MemberBinCache { - OrbisNpMatching2AttributeId id = 0; - u64 update_date = 0; - std::vector data; -}; - -struct MemberCache { - Libraries::Np::OrbisNpId np_id{}; - Libraries::Np::OrbisNpAccountId account_id = 0; - Libraries::Np::OrbisNpPlatformType platform = Libraries::Np::OrbisNpPlatformType::None; - u64 join_date = 0; - OrbisNpMatching2RoomMemberId member_id = 0; - OrbisNpMatching2TeamId team_id = 0; - OrbisNpMatching2RoomGroupId group_id = 0; - OrbisNpMatching2NatType nat_type = 0; - OrbisNpMatching2Flags flag_attr = 0; - u32 addr = 0; - u16 port = 0; - std::map bins; -}; - -struct RoomCache { - u32 num_slots = 0; - u64 mask_password = 0; - OrbisNpMatching2SignalingType signaling_type = ORBIS_NP_MATCHING2_SIGNALING_TYPE_MESH; - OrbisNpMatching2RoomMemberId signaling_main_member = 0; - OrbisNpMatching2ServerId server_id = 0; - OrbisNpMatching2WorldId world_id = 0; - OrbisNpMatching2LobbyId lobby_id = 0; - OrbisNpMatching2RoomId room_id = 0; - u16 max_slot = 0; - u16 public_slots = 0; - u16 private_slots = 0; - u16 open_public_slots = 0; - u16 open_private_slots = 0; - u64 passwd_slot_mask = 0; - u64 joined_slot_mask = 0; - OrbisNpMatching2Flags flags = 0; - std::vector bin_attrs_internal; - std::vector> bin_buffers; - std::map groups; - std::map members; - bool owner = false; -}; - struct CallbackPayload { std::unique_ptr room_data; std::unique_ptr create_join_response; @@ -106,6 +50,10 @@ struct CallbackPayload { room_member_data_external_list_response; std::unique_ptr room_member_data_external_list_response_a; + std::unique_ptr + room_member_data_internal_response; + std::unique_ptr + room_member_data_internal_response_a; std::unique_ptr user_info_list_response; std::unique_ptr user_info_list_response_a; std::unique_ptr world_info_response; @@ -120,6 +68,7 @@ struct CallbackPayload { std::vector room_bin_attrs; std::vector room_bin_attr_ptrs; std::vector member_bin_attrs; + std::vector member_bin_attr_ptrs; std::vector room_data_external; std::vector room_data_external_a; std::vector user_info; @@ -136,18 +85,20 @@ struct CallbackPayload { std::unique_ptr room_message_info_a; std::unique_ptr room_message_dst; std::unique_ptr room_message_src_npid; - std::unique_ptr room_message_src_addr; - std::unique_ptr room_message_src_online_id; std::vector room_message_multicast_members; std::vector room_message_data; void* room_message_callback_data = nullptr; std::unique_ptr room_member_update; std::unique_ptr room_member_update_a; + std::unique_ptr room_member_data_internal_update; + std::unique_ptr + room_member_data_internal_update_a; std::unique_ptr room_update; std::unique_ptr room_data_internal_update; std::unique_ptr event_chg_flag_attr; std::unique_ptr event_prev_flag_attr; + std::unique_ptr event_chg_team_id; std::unique_ptr event_chg_passwd_slot_mask; std::unique_ptr event_prev_passwd_slot_mask; std::unique_ptr event_member; @@ -171,6 +122,8 @@ struct CallbackPayload { room_data_external_list_response_a.reset(); room_member_data_external_list_response.reset(); room_member_data_external_list_response_a.reset(); + room_member_data_internal_response.reset(); + room_member_data_internal_response_a.reset(); user_info_list_response.reset(); user_info_list_response_a.reset(); world_info_response.reset(); @@ -185,6 +138,7 @@ struct CallbackPayload { room_bin_attrs.clear(); room_bin_attr_ptrs.clear(); member_bin_attrs.clear(); + member_bin_attr_ptrs.clear(); room_data_external.clear(); room_data_external_a.clear(); user_info.clear(); @@ -200,17 +154,18 @@ struct CallbackPayload { room_message_info_a.reset(); room_message_dst.reset(); room_message_src_npid.reset(); - room_message_src_addr.reset(); - room_message_src_online_id.reset(); room_message_multicast_members.clear(); room_message_data.clear(); room_message_callback_data = nullptr; room_member_update.reset(); room_member_update_a.reset(); + room_member_data_internal_update.reset(); + room_member_data_internal_update_a.reset(); room_update.reset(); room_data_internal_update.reset(); event_chg_flag_attr.reset(); event_prev_flag_attr.reset(); + event_chg_team_id.reset(); event_chg_passwd_slot_mask.reset(); event_prev_passwd_slot_mask.reset(); event_member.reset(); @@ -219,7 +174,7 @@ struct CallbackPayload { } }; -struct ContextObject { +struct ContextObject : std::enable_shared_from_this { OrbisNpMatching2ContextId ctx_id = 0; bool started = false; bool stop_pending = false; @@ -231,8 +186,6 @@ struct ContextObject { Libraries::Np::OrbisNpId owner_np_id{}; Libraries::Np::OrbisNpOnlineId online_id{}; - u64 handler_registration_generation = 1; - OrbisNpMatching2WorldId world_id = 0; OrbisNpMatching2LobbyId lobby_id = 0; OrbisNpMatching2RoomId room_id = 0; @@ -241,14 +194,6 @@ struct ContextObject { u32 max_slot = 5; OrbisNpMatching2Flags flag_attr = 0; - std::map peers; - - std::map room_cache; - - CallbackPayload request_payload; - CallbackPayload* request_payload_override = nullptr; - CallbackPayload room_event_payload; - OrbisNpMatching2ContextCallback context_callback = nullptr; void* context_callback_arg = nullptr; OrbisNpMatching2RequestCallback default_request_callback = nullptr; @@ -265,47 +210,6 @@ struct ContextObject { void* lobby_message_callback_arg = nullptr; OrbisNpMatching2SignalingCallback signaling_callback = nullptr; void* signaling_callback_arg = nullptr; - - void Reset() { - ctx_id = 0; - started = false; - stop_pending = false; - destroy_pending = false; - a_variant = false; - server_id = 1; - service_label = 0; - owner_np_id = {}; - online_id = {}; - handler_registration_generation = 1; - world_id = 0; - lobby_id = 0; - room_id = 0; - my_member_id = 0; - is_room_owner = false; - max_slot = 5; - flag_attr = 0; - peers.clear(); - room_cache.clear(); - request_payload.Reset(); - request_payload_override = nullptr; - room_event_payload.Reset(); - context_callback = nullptr; - context_callback_arg = nullptr; - default_request_callback = nullptr; - default_request_callback_arg = nullptr; - per_request_callback = nullptr; - per_request_callback_arg = nullptr; - room_event_callback = nullptr; - room_event_callback_arg = nullptr; - room_message_callback = nullptr; - room_message_callback_arg = nullptr; - lobby_event_callback = nullptr; - lobby_event_callback_arg = nullptr; - lobby_message_callback = nullptr; - lobby_message_callback_arg = nullptr; - signaling_callback = nullptr; - signaling_callback_arg = nullptr; - } }; struct RequestCallbackInfo { @@ -317,15 +221,13 @@ class ContextManager { public: static constexpr u32 kMaxContexts = 10; - static ContextManager& Instance(); - s32 CreateContext(const OrbisNpId* owner_np_id, OrbisNpServiceLabel service_label, OrbisNpMatching2ContextId* out_ctx_id, bool a_variant = false); - bool Check(OrbisNpMatching2ContextId ctx_id); - ContextObject* Get(OrbisNpMatching2ContextId ctx_id); + std::shared_ptr Get(OrbisNpMatching2ContextId ctx_id); bool Destroy(OrbisNpMatching2ContextId ctx_id); - void CompleteStop(OrbisNpMatching2ContextId ctx_id); + void CompleteStop(OrbisNpMatching2ContextId ctx_id, + const std::shared_ptr& expected_context); s32 Start(OrbisNpMatching2ContextId ctx_id); s32 Stop(OrbisNpMatching2ContextId ctx_id); @@ -335,12 +237,13 @@ public: void Reset(); private: + friend class Libraries::Np::NpHandler; + ContextManager() = default; - ContextObject* GetLocked(OrbisNpMatching2ContextId ctx_id); + std::shared_ptr GetLocked(OrbisNpMatching2ContextId ctx_id); std::mutex m_mutex; - std::array m_contexts{}; - std::array m_used{}; + std::array, kMaxContexts + 1> m_contexts{}; OrbisNpMatching2ContextId m_next_id = 1; OrbisNpMatching2ContextCallback m_pending_context_callback = nullptr; @@ -359,6 +262,7 @@ struct PendingEvent { }; Type type = CONTEXT_CB; OrbisNpMatching2ContextId ctx_id = 0; + std::shared_ptr context_owner; std::chrono::steady_clock::time_point fire_at; OrbisNpMatching2Event ctx_event{}; @@ -370,7 +274,7 @@ struct PendingEvent { OrbisNpMatching2RequestCallback request_cb = nullptr; void* request_cb_arg = nullptr; void* request_data = nullptr; - std::shared_ptr request_payload_owner; + std::shared_ptr payload_owner; OrbisNpMatching2RoomId room_id = 0; OrbisNpMatching2RoomMemberId member_id = 0; @@ -402,32 +306,45 @@ struct NpMatching2State { std::atomic dispatch_running{false}; }; -extern NpMatching2State g_state; - OrbisNpMatching2RequestId AllocRequestId(); bool IsInitialized(); void SetInitialized(bool initialized); -void StoreRequestCallback(ContextObject* ctx, const OrbisNpMatching2RequestOptParam* requestOpt); -RequestCallbackInfo ConsumeRequestCallback(ContextObject* ctx); +void StoreRequestCallback(const std::shared_ptr& ctx, + const OrbisNpMatching2RequestOptParam* requestOpt); +RequestCallbackInfo ConsumeRequestCallback(const std::shared_ptr& ctx); -void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp); -void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp); -void* BuildLeaveRoomPayload(ContextObject& ctx, const shadnet::LeaveRoomReply& resp); -void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldInfoListReply& resp); -void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply& resp); -void* BuildSearchRoomPayloadA(ContextObject& ctx, const shadnet::SearchRoomReply& resp); -void* BuildGetRoomDataExternalListPayload(ContextObject& ctx, +void* BuildCreateJoinRoomPayload(ContextObject& ctx, CallbackPayload& payload, + const shadnet::CreateJoinRoomResponse& resp); +void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, CallbackPayload& payload, + const shadnet::CreateJoinRoomResponse& resp); +void* BuildLeaveRoomPayload(ContextObject& ctx, CallbackPayload& payload, + const shadnet::LeaveRoomReply& resp); +void* BuildGetWorldInfoListPayload(ContextObject& ctx, CallbackPayload& payload, + const shadnet::GetWorldInfoListReply& resp); +void* BuildSearchRoomPayload(ContextObject& ctx, CallbackPayload& payload, + const shadnet::SearchRoomReply& resp); +void* BuildSearchRoomPayloadA(ContextObject& ctx, CallbackPayload& payload, + const shadnet::SearchRoomReply& resp); +void* BuildGetRoomDataExternalListPayload(ContextObject& ctx, CallbackPayload& payload, const shadnet::GetRoomDataExternalListReply& resp); -void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx, +void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx, CallbackPayload& payload, const shadnet::GetRoomDataExternalListReply& resp); void* BuildGetRoomMemberDataExternalListPayload( - ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp); + ContextObject& ctx, CallbackPayload& payload, + const shadnet::GetRoomMemberDataExternalListReply& resp); void* BuildGetRoomMemberDataExternalListPayloadA( - ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp); -void* BuildGetUserInfoListPayload(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp); -void* BuildGetUserInfoListPayloadA(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp); -void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId room_id); + ContextObject& ctx, CallbackPayload& payload, + const shadnet::GetRoomMemberDataExternalListReply& resp); +void* BuildGetUserInfoListPayload(ContextObject& ctx, CallbackPayload& payload, + const shadnet::GetUserInfoListReply& resp); +void* BuildGetUserInfoListPayloadA(ContextObject& ctx, CallbackPayload& payload, + const shadnet::GetUserInfoListReply& resp); +void* BuildGetRoomDataInternalPayload(ContextObject& ctx, CallbackPayload& payload, + OrbisNpMatching2RoomId room_id); +void* BuildGetRoomMemberDataInternalPayload( + ContextObject& ctx, CallbackPayload& payload, + const OrbisNpMatching2GetRoomMemberDataInternalRequest& request); void* BuildRoomMessagePayload(CallbackPayload& p, bool a_variant, OrbisNpMatching2CastType castType, const std::vector& dstMembers, const MemberCache* srcMember, const std::vector& msg); diff --git a/src/core/libraries/np/np_matching2/np_matching2_mm.cpp b/src/core/libraries/np/np_matching2/np_matching2_mm.cpp index bc5bf3b77..58be9c725 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_mm.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2_mm.cpp @@ -1,9 +1,11 @@ // SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include +#include #include #include #include @@ -13,14 +15,16 @@ #include #include "common/logging/log.h" -#include "core/libraries/network/net.h" -#include "core/libraries/network/net_upnp.h" -#include "core/libraries/network/sockets.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_p2p.h" +#include "core/libraries/net/net_upnp.h" #include "core/libraries/np/np_error.h" +#include "core/libraries/np/np_handler.h" #include "core/libraries/np/np_matching2/np_matching2_internal.h" #include "core/libraries/np/np_matching2/np_matching2_mm.h" #include "core/libraries/np/np_matching2/np_matching2_signaling.h" -#include "core/libraries/np/np_signaling/np_signaling_stubs.h" +#include "core/libraries/np/np_signaling/np_signaling_transport.h" +#include "core/libraries/np/signaling_handler.h" #include "shadnet.pb.h" #include "shadnet/client.h" @@ -93,6 +97,38 @@ std::string OnlineIdToString(const Libraries::Np::OrbisNpOnlineId& online_id) { return std::string(buf); } +std::string HexPreview(const void* data, u32 size, u32 max_bytes = 96) { + if (!data || size == 0) { + return {}; + } + + const auto* bytes = static_cast(data); + const u32 count = std::min(size, max_bytes); + std::string out; + out.reserve(count * 3 + 16); + char byte_text[4]{}; + for (u32 i = 0; i < count; ++i) { + std::snprintf(byte_text, sizeof(byte_text), "%02x", bytes[i]); + if (!out.empty()) { + out.push_back(' '); + } + out += byte_text; + } + if (count < size) { + out += " ..."; + } + return out; +} + +void LogBinAttr(std::string_view prefix, u64 index, const OrbisNpMatching2BinAttr& attr) { + LOG_INFO(Lib_NpMatching2, "{}[{}]: id={:#x} size={} data={}", prefix, index, attr.id, + attr.dataSize, HexPreview(attr.data, attr.dataSize)); +} + +void LogIntAttr(std::string_view prefix, u64 index, const OrbisNpMatching2IntAttr& attr) { + LOG_INFO(Lib_NpMatching2, "{}[{}]: id={:#x} value={}", prefix, index, attr.id, attr.num); +} + void AppendBinAttr(shadnet::MatchingBinAttr* dst, const OrbisNpMatching2BinAttr& src) { dst->set_attr_id(src.id); if (src.data && src.dataSize > 0) { @@ -100,6 +136,36 @@ void AppendBinAttr(shadnet::MatchingBinAttr* dst, const OrbisNpMatching2BinAttr& } } +void AppendCreateRoomBinAttrById(shadnet::CreateRoomRequest& req, + const OrbisNpMatching2BinAttr& src, std::string_view source) { + switch (src.id) { + case ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_INTERNAL_1_ID: + case ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_INTERNAL_2_ID: + AppendBinAttr(req.add_internal_bin_attrs(), src); + LOG_INFO(Lib_NpMatching2, + "CreateJoinRoom: routed bin attr id={:#x} from {} to internal size={}", src.id, + source, src.dataSize); + break; + case ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_EXTERNAL_1_ID: + case ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_EXTERNAL_2_ID: + AppendBinAttr(req.add_external_bin_attrs(), src); + LOG_INFO(Lib_NpMatching2, + "CreateJoinRoom: routed bin attr id={:#x} from {} to external size={}", src.id, + source, src.dataSize); + break; + case ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_BIN_ATTR_EXTERNAL_1_ID: + AppendBinAttr(req.add_external_search_bin_attrs(), src); + LOG_INFO(Lib_NpMatching2, + "CreateJoinRoom: routed bin attr id={:#x} from {} to search external size={}", + src.id, source, src.dataSize); + break; + default: + LOG_WARNING(Lib_NpMatching2, "CreateJoinRoom: unexpected bin attr id={:#x} from {} size={}", + src.id, source, src.dataSize); + break; + } +} + void AppendIntAttr(shadnet::MatchingIntAttr* dst, const OrbisNpMatching2IntAttr& src) { dst->set_attr_id(src.id); dst->set_attr_value(src.num); @@ -155,7 +221,7 @@ std::string ExtractProtoBytes(const std::vector& payload, size_t offset = 0) void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error, const std::vector& body) { - ContextObject* ctx = ContextManager::Instance().Get(pr.ctx_id); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(pr.ctx_id); if (!ctx) { return; } @@ -167,6 +233,7 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error, PendingEvent ev{}; ev.type = PendingEvent::CONTEXT_CB; ev.ctx_id = pr.ctx_id; + ev.context_owner = ctx; ev.fire_at = std::chrono::steady_clock::now(); ev.ctx_event = pr.req_event; ev.ctx_event_cause = ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION; @@ -180,21 +247,23 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error, if (error_code == 0) { request_payload_owner = std::make_shared(); - ctx->request_payload_override = request_payload_owner.get(); + auto& payload = *request_payload_owner; const std::string proto = ExtractProtoBytes(body); if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM || pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A) { shadnet::CreateRoomReply reply; if (reply.ParseFromString(proto) && reply.has_details()) { - request_data = pr.a_variant ? BuildCreateJoinRoomPayloadA(*ctx, reply.details()) - : BuildCreateJoinRoomPayload(*ctx, reply.details()); + request_data = pr.a_variant + ? BuildCreateJoinRoomPayloadA(*ctx, payload, reply.details()) + : BuildCreateJoinRoomPayload(*ctx, payload, reply.details()); } } else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM || pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM_A) { shadnet::JoinRoomReply reply; if (reply.ParseFromString(proto) && reply.has_details()) { - request_data = pr.a_variant ? BuildCreateJoinRoomPayloadA(*ctx, reply.details()) - : BuildCreateJoinRoomPayload(*ctx, reply.details()); + request_data = pr.a_variant + ? BuildCreateJoinRoomPayloadA(*ctx, payload, reply.details()) + : BuildCreateJoinRoomPayload(*ctx, payload, reply.details()); } } else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_LEAVE_ROOM) { shadnet::LeaveRoomReply reply; @@ -202,26 +271,27 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error, QueueMatching2DeadForRoomPeers( *ctx, static_cast(reply.room_id()), ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_MYSELF); - request_data = BuildLeaveRoomPayload(*ctx, reply); + request_data = BuildLeaveRoomPayload(*ctx, payload, reply); } } else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST) { shadnet::GetWorldInfoListReply reply; if (reply.ParseFromString(proto)) { - request_data = BuildGetWorldInfoListPayload(*ctx, reply); + request_data = BuildGetWorldInfoListPayload(*ctx, payload, reply); } } else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM || pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A) { shadnet::SearchRoomReply reply; if (reply.ParseFromString(proto)) { - request_data = pr.a_variant ? BuildSearchRoomPayloadA(*ctx, reply) - : BuildSearchRoomPayload(*ctx, reply); + request_data = pr.a_variant ? BuildSearchRoomPayloadA(*ctx, payload, reply) + : BuildSearchRoomPayload(*ctx, payload, reply); } } else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_EXTERNAL_LIST || pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_EXTERNAL_LIST_A) { shadnet::GetRoomDataExternalListReply reply; if (reply.ParseFromString(proto)) { - request_data = pr.a_variant ? BuildGetRoomDataExternalListPayloadA(*ctx, reply) - : BuildGetRoomDataExternalListPayload(*ctx, reply); + request_data = pr.a_variant + ? BuildGetRoomDataExternalListPayloadA(*ctx, payload, reply) + : BuildGetRoomDataExternalListPayload(*ctx, payload, reply); } } else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_EXTERNAL_LIST || @@ -229,23 +299,23 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error, ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_EXTERNAL_LIST_A) { shadnet::GetRoomMemberDataExternalListReply reply; if (reply.ParseFromString(proto)) { - request_data = pr.a_variant - ? BuildGetRoomMemberDataExternalListPayloadA(*ctx, reply) - : BuildGetRoomMemberDataExternalListPayload(*ctx, reply); + request_data = + pr.a_variant ? BuildGetRoomMemberDataExternalListPayloadA(*ctx, payload, reply) + : BuildGetRoomMemberDataExternalListPayload(*ctx, payload, reply); } } else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_USER_INFO_LIST) { shadnet::GetUserInfoListReply reply; if (reply.ParseFromString(proto)) { - request_data = pr.a_variant ? BuildGetUserInfoListPayloadA(*ctx, reply) - : BuildGetUserInfoListPayload(*ctx, reply); + request_data = pr.a_variant ? BuildGetUserInfoListPayloadA(*ctx, payload, reply) + : BuildGetUserInfoListPayload(*ctx, payload, reply); } } - ctx->request_payload_override = nullptr; } PendingEvent ev{}; ev.type = PendingEvent::REQUEST_CB; ev.ctx_id = pr.ctx_id; + ev.context_owner = ctx; ev.fire_at = std::chrono::steady_clock::now(); ev.req_id = pr.req_id; ev.req_event = pr.req_event; @@ -253,16 +323,16 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error, ev.request_cb = pr.request_cb; ev.request_cb_arg = pr.request_cb_arg; ev.request_data = request_data; - ev.request_payload_owner = std::move(request_payload_owner); + ev.payload_owner = std::move(request_payload_owner); ScheduleEvent(std::move(ev)); if (error_code == 0 && (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM || pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM_A)) { - StartMatching2SignalingForRoomPeers(*ctx, ctx->room_id); + SignalingHandler::StartMatching2(*ctx); } else if (error_code == 0 && (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM || pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A)) { - StartMatching2SignalingForRoomPeers(*ctx, ctx->room_id); + SignalingHandler::StartMatching2(*ctx); } } @@ -282,8 +352,8 @@ void AppendEventMemberBinAttrs(CallbackPayload& p, const MemberCache& mc) { } } -void BuildMemberUpdate(CallbackPayload& p, const RoomCache& rc, const MemberCache& mc, - OrbisNpMatching2EventCause cause, bool a_variant) { +void BuildMemberData(CallbackPayload& p, const RoomCache& rc, const MemberCache& mc, + bool a_variant) { if (mc.group_id != 0) { auto grp_it = rc.groups.find(mc.group_id); if (grp_it != rc.groups.end()) { @@ -311,12 +381,6 @@ void BuildMemberUpdate(CallbackPayload& p, const RoomCache& rc, const MemberCach p.member_bin_attrs.empty() ? nullptr : p.member_bin_attrs.data(); m.roomMemberInternalBinAttrs = p.member_bin_attrs.size(); - p.room_member_update_a = std::make_unique(); - OrbisNpMatching2RoomMemberUpdateA& u = *p.room_member_update_a; - u = OrbisNpMatching2RoomMemberUpdateA{}; - u.roomMemberDataInternal = p.event_member_a.get(); - u.eventCause = cause; - p.room_event_data = p.room_member_update_a.get(); return; } @@ -332,18 +396,85 @@ void BuildMemberUpdate(CallbackPayload& p, const RoomCache& rc, const MemberCach m.roomGroup = p.room_groups.empty() ? nullptr : p.room_groups.data(); m.roomMemberBinAttrInternal = p.member_bin_attrs.empty() ? nullptr : p.member_bin_attrs.data(); m.roomMemberBinAttrInternalNum = p.member_bin_attrs.size(); +} + +void BuildMemberUpdate(CallbackPayload& p, const RoomCache& rc, const MemberCache& mc, + OrbisNpMatching2EventCause cause, bool a_variant) { + BuildMemberData(p, rc, mc, a_variant); + + if (a_variant) { + p.room_member_update_a = std::make_unique(); + auto& u = *p.room_member_update_a; + u = {}; + u.roomMemberDataInternal = p.event_member_a.get(); + u.eventCause = cause; + p.room_event_data = p.room_member_update_a.get(); + return; + } p.room_member_update = std::make_unique(); - OrbisNpMatching2RoomMemberUpdate& u = *p.room_member_update; - u = OrbisNpMatching2RoomMemberUpdate{}; + auto& u = *p.room_member_update; + u = {}; u.roomMemberDataInternal = p.event_member.get(); u.eventCause = cause; p.room_event_data = p.room_member_update.get(); } +void BuildMemberDataInternalUpdate(CallbackPayload& p, const RoomCache& rc, const MemberCache& mc, + OrbisNpMatching2FlagAttr prev_flag_attr, + const std::vector& attr_ids, + bool a_variant) { + BuildMemberData(p, rc, mc, a_variant); + + p.event_chg_flag_attr = std::make_unique( + static_cast(mc.flag_attr)); + p.event_prev_flag_attr = std::make_unique(prev_flag_attr); + p.event_chg_team_id = std::make_unique(mc.team_id); + + p.member_bin_attr_ptrs.reserve(attr_ids.size()); + for (const auto attr_id : attr_ids) { + const auto attr = + std::ranges::find_if(p.member_bin_attrs, [attr_id](const auto& candidate) { + return candidate.binAttr.id == attr_id; + }); + if (attr != p.member_bin_attrs.end()) { + p.member_bin_attr_ptrs.push_back(&*attr); + } + } + + if (a_variant) { + p.room_member_data_internal_update_a = + std::make_unique(); + auto& u = *p.room_member_data_internal_update_a; + u = {}; + u.chgRoomMemberDataInternal = p.event_member_a.get(); + u.chgFlagAttr = p.event_chg_flag_attr.get(); + u.prevFlagAttr = p.event_prev_flag_attr.get(); + u.chgTeamId = p.event_chg_team_id.get(); + u.chgRoomMemberBinAttrInternal = + p.member_bin_attr_ptrs.empty() ? nullptr : p.member_bin_attr_ptrs.data(); + u.chgRoomMemberBinAttrInternalNum = p.member_bin_attr_ptrs.size(); + p.room_event_data = p.room_member_data_internal_update_a.get(); + return; + } + + p.room_member_data_internal_update = + std::make_unique(); + auto& u = *p.room_member_data_internal_update; + u = {}; + u.chgRoomMemberDataInternal = p.event_member.get(); + u.chgFlagAttr = p.event_chg_flag_attr.get(); + u.prevFlagAttr = p.event_prev_flag_attr.get(); + u.chgTeamId = p.event_chg_team_id.get(); + u.chgRoomMemberBinAttrInternal = + p.member_bin_attr_ptrs.empty() ? nullptr : p.member_bin_attr_ptrs.data(); + u.chgRoomMemberBinAttrInternalNum = p.member_bin_attr_ptrs.size(); + p.room_event_data = p.room_member_data_internal_update.get(); +} + void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) { - ContextObject* ctx = - ContextManager::Instance().Get(static_cast(n.ctx_id)); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get( + static_cast(n.ctx_id)); if (!ctx) { return; } @@ -355,188 +486,219 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) { auto payload_owner = std::make_shared(); CallbackPayload& p = *payload_owner; - switch (event) { - case ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED: { - auto room_it = ctx->room_cache.find(room_id); - if (room_it == ctx->room_cache.end()) { - return; - } - MemberCache& mc = room_it->second.members[member_id]; - mc = MemberCache{}; - mc.member_id = member_id; - mc.team_id = static_cast(n.member_team_id); - mc.nat_type = static_cast(n.member_nat_type); - mc.flag_attr = n.member_flag_attr; - mc.group_id = static_cast(n.member_group_id); - mc.join_date = n.member_join_date; - mc.addr = IpStringToAddr(n.member_addr); - mc.port = Libraries::Net::sceNetHtons(static_cast(n.member_port)); - SetNpId(mc.np_id, n.member_npid); - mc.account_id = static_cast(n.member_account_id); - mc.platform = static_cast(n.member_platform); - for (const auto& a : n.member_bin_attrs) { - MemberBinCache& b = mc.bins[a.attr_id]; - b.id = static_cast(a.attr_id); - b.data = a.data; - } + { + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx->ctx_id); + switch (event) { + case ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED: { + auto room_it = cache->rooms.find(room_id); + if (room_it == cache->rooms.end()) { + return; + } + MemberCache& mc = room_it->second.members[member_id]; + mc = MemberCache{}; + mc.member_id = member_id; + mc.team_id = static_cast(n.member_team_id); + mc.nat_type = static_cast(n.member_nat_type); + mc.flag_attr = n.member_flag_attr; + mc.group_id = static_cast(n.member_group_id); + mc.join_date = n.member_join_date; + mc.addr = IpStringToAddr(n.member_addr); + mc.port = Libraries::Net::sceNetHtons(static_cast(n.member_port)); + SetNpId(mc.np_id, n.member_npid); + mc.account_id = static_cast(n.member_account_id); + mc.platform = static_cast(n.member_platform); + for (const auto& a : n.member_bin_attrs) { + MemberBinCache& b = mc.bins[a.attr_id]; + b.id = static_cast(a.attr_id); + b.data = a.data; + } - PeerInfo pi{}; - pi.member_id = member_id; - pi.addr = mc.addr; - pi.port = mc.port; - SetNpOnlineId(pi.online_id, n.member_npid); - ctx->peers[member_id] = pi; + PeerInfo pi{}; + pi.member_id = member_id; + pi.addr = mc.addr; + pi.port = mc.port; + SetNpOnlineId(pi.online_id, n.member_npid); + cache->peers[member_id] = pi; - BuildMemberUpdate(p, room_it->second, mc, cause, ctx->a_variant); - break; - } - case ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_LEFT: { - auto room_it = ctx->room_cache.find(room_id); - if (room_it == ctx->room_cache.end()) { - return; + BuildMemberUpdate(p, room_it->second, mc, cause, ctx->a_variant); + break; } - auto mem_it = room_it->second.members.find(member_id); - if (mem_it == room_it->second.members.end()) { - return; + case ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_LEFT: { + auto room_it = cache->rooms.find(room_id); + if (room_it == cache->rooms.end()) { + return; + } + auto mem_it = room_it->second.members.find(member_id); + if (mem_it == room_it->second.members.end()) { + return; + } + BuildMemberUpdate(p, room_it->second, mem_it->second, cause, ctx->a_variant); + QueueMatching2SignalingEvent(*ctx, room_id, member_id, + ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD, + ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_PEER); + room_it->second.members.erase(mem_it); + cache->peers.erase(member_id); + break; } - BuildMemberUpdate(p, room_it->second, mem_it->second, cause, ctx->a_variant); - QueueMatching2SignalingEvent(*ctx, room_id, member_id, - ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD, - ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_PEER); - room_it->second.members.erase(mem_it); - ctx->peers.erase(member_id); - break; - } - case ORBIS_NP_MATCHING2_ROOM_EVENT_KICKEDOUT: { - p.room_update = std::make_unique(); - OrbisNpMatching2RoomUpdate& u = *p.room_update; - u = OrbisNpMatching2RoomUpdate{}; - u.eventCause = cause; - u.errorCode = n.error_code; - p.room_event_data = p.room_update.get(); - break; - } - case ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_ROOM_DATA_INTERNAL: { - auto room_it = ctx->room_cache.find(room_id); - if (room_it == ctx->room_cache.end()) { - return; - } - RoomCache& rc = room_it->second; - const auto prev_flags = static_cast(rc.flags); - const auto prev_passwd_slot_mask = rc.passwd_slot_mask; - rc.flags = n.flags; - if (n.has_passwd_mask) { - rc.passwd_slot_mask = n.passwd_slot_mask; + case ORBIS_NP_MATCHING2_ROOM_EVENT_KICKEDOUT: { + p.room_update = std::make_unique(); + OrbisNpMatching2RoomUpdate& u = *p.room_update; + u = OrbisNpMatching2RoomUpdate{}; + u.eventCause = cause; + u.errorCode = n.error_code; + p.room_event_data = p.room_update.get(); + break; } + case ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_ROOM_DATA_INTERNAL: { + auto room_it = cache->rooms.find(room_id); + if (room_it == cache->rooms.end()) { + return; + } + RoomCache& rc = room_it->second; + const auto prev_flags = static_cast(rc.flags); + const auto prev_passwd_slot_mask = rc.passwd_slot_mask; + rc.flags = n.flags; + if (n.has_passwd_mask) { + rc.passwd_slot_mask = n.passwd_slot_mask; + } - std::vector changed_attrs; - changed_attrs.reserve(n.bin_attrs.size()); - for (const auto& a : n.bin_attrs) { - rc.bin_buffers.emplace_back(a.data.begin(), a.data.end()); - auto& buf = rc.bin_buffers.back(); + std::vector changed_attrs; + changed_attrs.reserve(n.bin_attrs.size()); + for (const auto& a : n.bin_attrs) { + rc.bin_buffers.emplace_back(a.data.begin(), a.data.end()); + auto& buf = rc.bin_buffers.back(); - OrbisNpMatching2RoomBinAttrInternal updated{}; - updated.binAttr.id = static_cast(a.attr_id); - updated.binAttr.data = buf.empty() ? nullptr : buf.data(); - updated.binAttr.dataSize = buf.size(); - updated.lastUpdate.tick = a.update_date; - updated.memberId = static_cast(a.update_member_id); + OrbisNpMatching2RoomBinAttrInternal updated{}; + updated.binAttr.id = static_cast(a.attr_id); + updated.binAttr.data = buf.empty() ? nullptr : buf.data(); + updated.binAttr.dataSize = buf.size(); + updated.lastUpdate.tick = a.update_date; + updated.memberId = static_cast(a.update_member_id); - bool replaced = false; - for (auto& existing : rc.bin_attrs_internal) { - if (existing.binAttr.id == updated.binAttr.id) { - existing = updated; - replaced = true; - break; + bool replaced = false; + for (auto& existing : rc.bin_attrs_internal) { + if (existing.binAttr.id == updated.binAttr.id) { + existing = updated; + replaced = true; + break; + } } + if (!replaced) { + rc.bin_attrs_internal.push_back(updated); + } + changed_attrs.push_back(updated); } - if (!replaced) { - rc.bin_attrs_internal.push_back(updated); + + p.room_groups.reserve(rc.groups.size()); + for (const auto& [gid, group] : rc.groups) { + p.room_groups.push_back(group); } - changed_attrs.push_back(updated); - } - - p.room_groups.reserve(rc.groups.size()); - for (const auto& [gid, group] : rc.groups) { - p.room_groups.push_back(group); - } - p.room_group_ptrs.reserve(p.room_groups.size()); - for (auto& group : p.room_groups) { - p.room_group_ptrs.push_back(&group); - } - - p.room_bin_attrs.resize(changed_attrs.size()); - p.bin_buffers.clear(); - p.bin_buffers.reserve(changed_attrs.size()); - for (size_t i = 0; i < changed_attrs.size(); ++i) { - const auto& src = changed_attrs[i]; - const auto* data = static_cast(src.binAttr.data); - if (data && src.binAttr.dataSize > 0) { - p.bin_buffers.emplace_back(data, data + src.binAttr.dataSize); - } else { - p.bin_buffers.emplace_back(); + p.room_group_ptrs.reserve(p.room_groups.size()); + for (auto& group : p.room_groups) { + p.room_group_ptrs.push_back(&group); } - auto& buf = p.bin_buffers.back(); - auto& dst = p.room_bin_attrs[i]; - dst = OrbisNpMatching2RoomBinAttrInternal{}; - dst.lastUpdate = src.lastUpdate; - dst.memberId = src.memberId; - dst.binAttr.id = src.binAttr.id; - dst.binAttr.data = buf.empty() ? nullptr : buf.data(); - dst.binAttr.dataSize = buf.size(); + + p.room_bin_attrs.resize(changed_attrs.size()); + p.bin_buffers.clear(); + p.bin_buffers.reserve(changed_attrs.size()); + for (size_t i = 0; i < changed_attrs.size(); ++i) { + const auto& src = changed_attrs[i]; + const auto* data = static_cast(src.binAttr.data); + if (data && src.binAttr.dataSize > 0) { + p.bin_buffers.emplace_back(data, data + src.binAttr.dataSize); + } else { + p.bin_buffers.emplace_back(); + } + auto& buf = p.bin_buffers.back(); + auto& dst = p.room_bin_attrs[i]; + dst = OrbisNpMatching2RoomBinAttrInternal{}; + dst.lastUpdate = src.lastUpdate; + dst.memberId = src.memberId; + dst.binAttr.id = src.binAttr.id; + dst.binAttr.data = buf.empty() ? nullptr : buf.data(); + dst.binAttr.dataSize = buf.size(); + } + p.room_bin_attr_ptrs.reserve(p.room_bin_attrs.size()); + for (auto& attr : p.room_bin_attrs) { + p.room_bin_attr_ptrs.push_back(&attr); + } + + p.room_data = std::make_unique(); + auto& room = *p.room_data; + room = {}; + room.publicSlots = rc.public_slots; + room.privateSlots = rc.private_slots; + room.openPublicSlots = rc.open_public_slots; + room.openPrivateSlots = rc.open_private_slots; + room.maxSlot = rc.max_slot; + room.serverId = rc.server_id; + room.worldId = rc.world_id; + room.lobbyId = rc.lobby_id; + room.roomId = rc.room_id; + room.passwdSlotMask = rc.passwd_slot_mask; + room.joinedSlotMask = rc.joined_slot_mask; + room.roomGroup = p.room_groups.empty() ? nullptr : p.room_groups.data(); + room.roomGroups = p.room_groups.size(); + room.flags = rc.flags; + room.internalBinAttr = p.room_bin_attrs.empty() ? nullptr : p.room_bin_attrs.data(); + room.internalBinAttrs = p.room_bin_attrs.size(); + + p.event_chg_flag_attr = std::make_unique( + static_cast(rc.flags)); + p.event_prev_flag_attr = std::make_unique(prev_flags); + p.event_chg_passwd_slot_mask = + std::make_unique(rc.passwd_slot_mask); + p.event_prev_passwd_slot_mask = + std::make_unique(prev_passwd_slot_mask); + + p.room_data_internal_update = + std::make_unique(); + auto& u = *p.room_data_internal_update; + u = {}; + u.chgRoomDataInternal = p.room_data.get(); + u.chgFlagAttr = p.event_chg_flag_attr.get(); + u.prevFlagAttr = p.event_prev_flag_attr.get(); + u.chgRoomPasswordSlotMask = p.event_chg_passwd_slot_mask.get(); + u.prevRoomPasswordSlotMask = p.event_prev_passwd_slot_mask.get(); + u.chgRoomGroup = p.room_group_ptrs.empty() ? nullptr : p.room_group_ptrs.data(); + u.chgRoomGroupNum = p.room_group_ptrs.size(); + u.chgRoomBinAttrInternal = + p.room_bin_attr_ptrs.empty() ? nullptr : p.room_bin_attr_ptrs.data(); + u.chgRoomBinAttrInternalNum = p.room_bin_attr_ptrs.size(); + p.room_event_data = p.room_data_internal_update.get(); + break; } - p.room_bin_attr_ptrs.reserve(p.room_bin_attrs.size()); - for (auto& attr : p.room_bin_attrs) { - p.room_bin_attr_ptrs.push_back(&attr); + case ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_ROOM_MEMBER_DATA_INTERNAL: { + auto room_it = cache->rooms.find(room_id); + if (room_it == cache->rooms.end()) { + return; + } + auto member_it = room_it->second.members.find(member_id); + if (member_it == room_it->second.members.end()) { + return; + } + MemberCache& member = member_it->second; + const auto prev_flag_attr = static_cast(member.flag_attr); + member.team_id = static_cast(n.member_team_id); + member.flag_attr = n.member_flag_attr; + member.group_id = static_cast(n.member_group_id); + std::vector attr_ids; + attr_ids.reserve(n.member_bin_attrs.size()); + for (const auto& attr : n.member_bin_attrs) { + MemberBinCache& bin = member.bins[attr.attr_id]; + bin.id = static_cast(attr.attr_id); + bin.update_date = attr.update_date; + bin.data = attr.data; + attr_ids.push_back(bin.id); + } + BuildMemberDataInternalUpdate(p, room_it->second, member, prev_flag_attr, attr_ids, + ctx->a_variant); + break; + } + default: + LOG_WARNING(Lib_NpMatching2, "unhandled room event {:#x}", n.event); + return; } - - p.room_data = std::make_unique(); - auto& room = *p.room_data; - room = {}; - room.publicSlots = rc.public_slots; - room.privateSlots = rc.private_slots; - room.openPublicSlots = rc.open_public_slots; - room.openPrivateSlots = rc.open_private_slots; - room.maxSlot = rc.max_slot; - room.serverId = rc.server_id; - room.worldId = rc.world_id; - room.lobbyId = rc.lobby_id; - room.roomId = rc.room_id; - room.passwdSlotMask = rc.passwd_slot_mask; - room.joinedSlotMask = rc.joined_slot_mask; - room.roomGroup = p.room_groups.empty() ? nullptr : p.room_groups.data(); - room.roomGroups = p.room_groups.size(); - room.flags = rc.flags; - room.internalBinAttr = p.room_bin_attrs.empty() ? nullptr : p.room_bin_attrs.data(); - room.internalBinAttrs = p.room_bin_attrs.size(); - - p.event_chg_flag_attr = std::make_unique( - static_cast(rc.flags)); - p.event_prev_flag_attr = std::make_unique(prev_flags); - p.event_chg_passwd_slot_mask = - std::make_unique(rc.passwd_slot_mask); - p.event_prev_passwd_slot_mask = - std::make_unique(prev_passwd_slot_mask); - - p.room_data_internal_update = std::make_unique(); - auto& u = *p.room_data_internal_update; - u = {}; - u.chgRoomDataInternal = p.room_data.get(); - u.chgFlagAttr = p.event_chg_flag_attr.get(); - u.prevFlagAttr = p.event_prev_flag_attr.get(); - u.chgRoomPasswordSlotMask = p.event_chg_passwd_slot_mask.get(); - u.prevRoomPasswordSlotMask = p.event_prev_passwd_slot_mask.get(); - u.chgRoomGroup = p.room_group_ptrs.empty() ? nullptr : p.room_group_ptrs.data(); - u.chgRoomGroupNum = p.room_group_ptrs.size(); - u.chgRoomBinAttrInternal = - p.room_bin_attr_ptrs.empty() ? nullptr : p.room_bin_attr_ptrs.data(); - u.chgRoomBinAttrInternalNum = p.room_bin_attr_ptrs.size(); - p.room_event_data = p.room_data_internal_update.get(); - break; - } - default: - LOG_WARNING(Lib_NpMatching2, "unhandled room event {:#x}", n.event); - return; } LOG_DEBUG(Lib_NpMatching2, "RoomEvent ctx={} room={} event={:#x} cause={}", n.ctx_id, n.room_id, @@ -562,43 +724,51 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) { PendingEvent ev{}; ev.type = PendingEvent::ROOM_EVENT_CB; ev.ctx_id = ctx->ctx_id; + ev.context_owner = ctx; ev.fire_at = std::chrono::steady_clock::now(); ev.room_id = room_id; ev.room_event = fired_event; ev.room_event_data = p.room_event_data; - ev.request_payload_owner = std::move(payload_owner); + ev.payload_owner = std::move(payload_owner); ScheduleEvent(std::move(ev)); if (event == ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED) { - StartMatching2PeerHandshake(*ctx, room_id, member_id); + SignalingHandler::StartMatching2(*ctx); } } void HandleRoomMessage(const ShadNet::NotifyRoomMessage& n) { - ContextObject* ctx = - ContextManager::Instance().Get(static_cast(n.ctx_id)); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get( + static_cast(n.ctx_id)); if (!ctx) { return; } const auto room_id = static_cast(n.room_id); const auto src_member_id = static_cast(n.src_member_id); - const MemberCache* src_member = nullptr; - const auto room_it = ctx->room_cache.find(room_id); - if (room_it != ctx->room_cache.end()) { - const auto member_it = room_it->second.members.find(src_member_id); - if (member_it != room_it->second.members.end()) { - src_member = &member_it->second; + MemberCache src_member_copy{}; + bool have_src_member = false; + { + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx->ctx_id); + const auto room_it = cache->rooms.find(room_id); + if (room_it != cache->rooms.end()) { + const auto member_it = room_it->second.members.find(src_member_id); + if (member_it != room_it->second.members.end()) { + src_member_copy = member_it->second; + have_src_member = true; + } } } - MemberCache fallback_src{}; - if (!src_member) { - fallback_src.member_id = src_member_id; - SetNpId(fallback_src.np_id, n.src_npid); - fallback_src.account_id = static_cast(n.src_account_id); - fallback_src.platform = static_cast(n.src_platform); - src_member = &fallback_src; + MemberCache notification_src{}; + const MemberCache* src_member = &src_member_copy; + if (!have_src_member) { + notification_src.member_id = src_member_id; + SetNpId(notification_src.np_id, n.src_npid); + notification_src.account_id = + static_cast(n.src_account_id); + notification_src.platform = static_cast(n.src_platform); + src_member = ¬ification_src; } std::vector dst_members; @@ -615,13 +785,14 @@ void HandleRoomMessage(const ShadNet::NotifyRoomMessage& n) { PendingEvent ev{}; ev.type = PendingEvent::ROOM_MESSAGE_CB; ev.ctx_id = ctx->ctx_id; + ev.context_owner = ctx; ev.fire_at = std::chrono::steady_clock::now(); ev.room_id = room_id; ev.src_member_id = src_member_id; ev.msg_event = ctx->a_variant ? ORBIS_NP_MATCHING2_ROOM_MSG_EVENT_MESSAGE_A : ORBIS_NP_MATCHING2_ROOM_MSG_EVENT_MESSAGE; ev.message_data = data; - ev.request_payload_owner = std::move(payload_owner); + ev.payload_owner = std::move(payload_owner); ScheduleEvent(std::move(ev)); } @@ -676,31 +847,29 @@ void SetMmShadNetClient(std::shared_ptr client, Net::UPnPClient::Instance().SetP2PFeaturesEnabled(matching2_enabled); if (!client) { - NpSignaling::Stubs::SetTransportHooks({}); - NpSignaling::Stubs::SetPeerResolver(nullptr); - NpSignaling::Stubs::SetMatching2Enabled(false); - NpSignaling::Stubs::SetMmServerEndpoint(0, 0); - StopMatching2HandshakeThread(); + NpSignaling::Transport::SetTransportHooks({}); + NpSignaling::Transport::SetPeerResolver(nullptr); + NpSignaling::Transport::SetMatching2Enabled(false); + NpSignaling::Transport::SetMmServerEndpoint(0, 0); + StopMatching2SignalingRuntime(); return; } - NpSignaling::Stubs::SetTransportHooks({ + NpSignaling::Transport::SetTransportHooks({ .signaling_send = Net::P2PSignalingSendTo, .signaling_recv = Net::P2PSignalingRecvFrom, .control_send = Net::P2PControlSendTo, .control_recv = Net::P2PControlRecvFrom, .transport_ready = Net::P2PTransportIsReady, - .configured_port = Net::GetP2PConfiguredPort, + .configured_port = Net::GetP2PAdvertisedPort, // the bound port once running .advertised_addr = Net::GetP2PAdvertisedAddr, .ensure_transport = Net::EnsureP2PTransport, }); - NpSignaling::Stubs::SetPeerResolver(matching2_enabled ? RequestSignalingInfos : nullptr); - NpSignaling::Stubs::SetMatching2Enabled(matching2_enabled); - NpSignaling::Stubs::SetMmServerEndpoint(server_addr, server_udp_port); - if (matching2_enabled) { - StartMatching2HandshakeThread(); - } else { - StopMatching2HandshakeThread(); + NpSignaling::Transport::SetPeerResolver(matching2_enabled ? RequestSignalingInfos : nullptr); + NpSignaling::Transport::SetMatching2Enabled(matching2_enabled); + NpSignaling::Transport::SetMmServerEndpoint(server_addr, server_udp_port); + if (!matching2_enabled) { + StopMatching2SignalingRuntime(); } client->onRoomEvent = [](const ShadNet::NotifyRoomEvent& n) { HandleRoomEvent(n); }; client->onRoomMessage = [](const ShadNet::NotifyRoomMessage& n) { HandleRoomMessage(n); }; @@ -720,11 +889,11 @@ void ClearMmShadNetClient() { old_client->onRoomEvent = nullptr; old_client->onRoomMessage = nullptr; } - NpSignaling::Stubs::SetTransportHooks({}); - NpSignaling::Stubs::SetPeerResolver(nullptr); - NpSignaling::Stubs::SetMatching2Enabled(false); - NpSignaling::Stubs::SetMmServerEndpoint(0, 0); - StopMatching2HandshakeThread(); + NpSignaling::Transport::SetTransportHooks({}); + NpSignaling::Transport::SetPeerResolver(nullptr); + NpSignaling::Transport::SetMatching2Enabled(false); + NpSignaling::Transport::SetMmServerEndpoint(0, 0); + StopMatching2SignalingRuntime(); { std::lock_guard lock(g_mm.pending_mutex); g_mm.pending.clear(); @@ -742,7 +911,7 @@ void MmContextStart(OrbisNpMatching2ContextId ctx_id) { req.set_ctx_id(ctx_id); MmSubmitRequest(ctx_id, 0, ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED, MmCommand::ContextStart, MakeProtoPayload(req)); - if (ContextObject* ctx = ContextManager::Instance().Get(ctx_id)) { + if (auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctx_id)) { SendMatching2StunPing(*ctx); } } @@ -763,20 +932,19 @@ s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId client = g_mm.client; } if (!client || !client->IsAuthenticated()) { - LOG_ERROR(Lib_NpMatching2, "MmSubmitRequest({}): not connected", static_cast(cmd)); + LOG_ERROR(Lib_NpMatching2, "cmd={} not connected", static_cast(cmd)); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; } - ContextObject* ctx = ContextManager::Instance().Get(ctx_id); + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctx_id); if (!ctx) { - LOG_ERROR(Lib_NpMatching2, "MmSubmitRequest({}): invalid ctx={}", static_cast(cmd), - ctx_id); + LOG_ERROR(Lib_NpMatching2, "cmd={} invalid ctx={}", static_cast(cmd), ctx_id); return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; } const RequestCallbackInfo request_cb = ConsumeRequestCallback(ctx); if (IsMatching2BackendDisabled() && !IsContextLifecycleEvent(req_event)) { LOG_INFO(Lib_NpMatching2, - "MmSubmitRequest: matching2 backend disabled; failing ctx={} reqId={} " + "matching2 backend disabled; failing ctx={} reqId={} " "event={:#x} cmd={}", ctx_id, req_id, static_cast(req_event), static_cast(cmd)); @@ -784,8 +952,7 @@ s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId void* request_data = nullptr; std::shared_ptr request_payload_owner; if (req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST) { - LOG_INFO(Lib_NpMatching2, - "MmSubmitRequest: matching2 backend disabled; returning empty world"); + LOG_INFO(Lib_NpMatching2, "matching2 backend disabled; returning empty world"); shadnet::GetWorldInfoListReply reply; auto* world = reply.add_worlds(); world->set_world_id(1); @@ -795,15 +962,14 @@ s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId world->set_rooms_num(0); world->set_room_members_num(0); request_payload_owner = std::make_shared(); - ctx->request_payload_override = request_payload_owner.get(); - request_data = BuildGetWorldInfoListPayload(*ctx, reply); - ctx->request_payload_override = nullptr; + request_data = BuildGetWorldInfoListPayload(*ctx, *request_payload_owner, reply); error_code = ORBIS_OK; } PendingEvent ev{}; ev.type = PendingEvent::REQUEST_CB; ev.ctx_id = ctx_id; + ev.context_owner = ctx; ev.fire_at = std::chrono::steady_clock::now(); ev.req_id = req_id; ev.req_event = req_event; @@ -811,13 +977,13 @@ s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId ev.request_cb = request_cb.callback; ev.request_cb_arg = request_cb.arg; ev.request_data = request_data; - ev.request_payload_owner = std::move(request_payload_owner); + ev.payload_owner = std::move(request_payload_owner); ScheduleEvent(std::move(ev)); return ORBIS_OK; } LOG_INFO(Lib_NpMatching2, - "MmSubmitRequest: ctx={} reqId={} event={:#x} cmd={} aVariant={} callback={:#x} " + "ctx={} reqId={} event={:#x} cmd={} aVariant={} callback={:#x} " "arg={}", ctx_id, req_id, static_cast(req_event), static_cast(cmd), a_variant, reinterpret_cast(request_cb.callback), fmt::ptr(request_cb.arg)); @@ -846,6 +1012,13 @@ s32 MmCreateJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req.set_allowed_user_count(static_cast(request.allowedUsers)); req.set_blocked_user_count(static_cast(request.blockedUsers)); req.set_internal_bin_attr_count(static_cast(request.internalBinAttrs)); + LOG_INFO(Lib_NpMatching2, + "ctx={} reqId={} maxSlot={} world={} lobby={} flags={:#x} " + "internalBin={} searchInt={} searchBin={} extBin={} memberBin={}", + ctx_id, req_id, request.maxSlot, request.worldId, request.lobbyId, request.flags, + request.internalBinAttrs, request.externalSearchIntAttrs, + request.externalSearchBinAttrs, request.externalBinAttrs, + request.memberInternalBinAttrs); AppendCreateJoinRoomCommon(req, request.roomPasswd, request.passwdSlotMask, request.groupConfig, request.groupConfigs, request.joinGroupLabel); req.set_user_id_kind(shadnet::MATCHING_USER_ID_ONLINE_ID); @@ -860,18 +1033,23 @@ s32 MmCreateJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId } } for (u64 i = 0; i < request.internalBinAttrs; ++i) { - AppendBinAttr(req.add_internal_bin_attrs(), request.internalBinAttr[i]); + LogBinAttr(" createInternalBin", i, request.internalBinAttr[i]); + AppendCreateRoomBinAttrById(req, request.internalBinAttr[i], "internalBinAttr"); } for (u64 i = 0; i < request.externalSearchIntAttrs; ++i) { + LogIntAttr(" createExternalSearchInt", i, request.externalSearchIntAttr[i]); AppendIntAttr(req.add_external_search_int_attrs(), request.externalSearchIntAttr[i]); } for (u64 i = 0; i < request.externalSearchBinAttrs; ++i) { - AppendBinAttr(req.add_external_search_bin_attrs(), request.externalSearchBinAttr[i]); + LogBinAttr(" createExternalSearchBin", i, request.externalSearchBinAttr[i]); + AppendCreateRoomBinAttrById(req, request.externalSearchBinAttr[i], "externalSearchBinAttr"); } for (u64 i = 0; i < request.externalBinAttrs; ++i) { - AppendBinAttr(req.add_external_bin_attrs(), request.externalBinAttr[i]); + LogBinAttr(" createExternalBin", i, request.externalBinAttr[i]); + AppendCreateRoomBinAttrById(req, request.externalBinAttr[i], "externalBinAttr"); } for (u64 i = 0; i < request.memberInternalBinAttrs; ++i) { + LogBinAttr(" createMemberInternalBin", i, request.memberInternalBinAttr[i]); AppendBinAttr(req.add_member_bin_attrs(), request.memberInternalBinAttr[i]); } req.set_join_group_label_present(request.joinGroupLabel != nullptr); @@ -898,6 +1076,13 @@ s32 MmCreateJoinRoomA(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestI req.set_allowed_user_count(static_cast(request.allowedUsers)); req.set_blocked_user_count(static_cast(request.blockedUsers)); req.set_internal_bin_attr_count(static_cast(request.internalBinAttrs)); + LOG_INFO(Lib_NpMatching2, + "ctx={} reqId={} maxSlot={} world={} lobby={} flags={:#x} " + "internalBin={} searchInt={} searchBin={} extBin={} memberBin={}", + ctx_id, req_id, request.maxSlot, request.worldId, request.lobbyId, request.flags, + request.internalBinAttrs, request.externalSearchIntAttrs, + request.externalSearchBinAttrs, request.externalBinAttrs, + request.memberInternalBinAttrs); AppendCreateJoinRoomCommon(req, request.roomPasswd, request.passwdSlotMask, request.groupConfig, request.groupConfigs, request.joinGroupLabel); req.set_user_id_kind(shadnet::MATCHING_USER_ID_ACCOUNT_ID); @@ -912,18 +1097,24 @@ s32 MmCreateJoinRoomA(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestI } } for (u64 i = 0; i < request.internalBinAttrs; ++i) { - AppendBinAttr(req.add_internal_bin_attrs(), request.internalBinAttr[i]); + LogBinAttr(" createInternalBinA", i, request.internalBinAttr[i]); + AppendCreateRoomBinAttrById(req, request.internalBinAttr[i], "internalBinAttrA"); } for (u64 i = 0; i < request.externalSearchIntAttrs; ++i) { + LogIntAttr(" createExternalSearchIntA", i, request.externalSearchIntAttr[i]); AppendIntAttr(req.add_external_search_int_attrs(), request.externalSearchIntAttr[i]); } for (u64 i = 0; i < request.externalSearchBinAttrs; ++i) { - AppendBinAttr(req.add_external_search_bin_attrs(), request.externalSearchBinAttr[i]); + LogBinAttr(" createExternalSearchBinA", i, request.externalSearchBinAttr[i]); + AppendCreateRoomBinAttrById(req, request.externalSearchBinAttr[i], + "externalSearchBinAttrA"); } for (u64 i = 0; i < request.externalBinAttrs; ++i) { - AppendBinAttr(req.add_external_bin_attrs(), request.externalBinAttr[i]); + LogBinAttr(" createExternalBinA", i, request.externalBinAttr[i]); + AppendCreateRoomBinAttrById(req, request.externalBinAttr[i], "externalBinAttrA"); } for (u64 i = 0; i < request.memberInternalBinAttrs; ++i) { + LogBinAttr(" createMemberInternalBinA", i, request.memberInternalBinAttr[i]); AppendBinAttr(req.add_member_bin_attrs(), request.memberInternalBinAttr[i]); } req.set_join_group_label_present(request.joinGroupLabel != nullptr); @@ -1051,8 +1242,15 @@ s32 MmGetRoomDataExternalList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2 for (u64 i = 0; i < request.attrIdNum; ++i) { req.add_attr_ids(request.attrId[i]); } - LOG_DEBUG(Lib_NpMatching2, "getRoomDataExternalList roomN={} attrN={}", request.roomIdNum, - request.attrIdNum); + LOG_INFO(Lib_NpMatching2, "getRoomDataExternalList roomN={} attrN={}", request.roomIdNum, + request.attrIdNum); + for (u64 i = 0; i < request.roomIdNum; ++i) { + LOG_INFO(Lib_NpMatching2, " getRoomDataExternalList roomId[{}]={}", i, request.roomId[i]); + } + for (u64 i = 0; i < request.attrIdNum; ++i) { + LOG_INFO(Lib_NpMatching2, " getRoomDataExternalList attrId[{}]={:#x}", i, + request.attrId[i]); + } return MmSubmitRequest(ctx_id, req_id, a_variant ? ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_EXTERNAL_LIST_A @@ -1122,6 +1320,8 @@ s32 MmSendRoomMessage(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestI req.set_msg(request.msg, request.msgLen); } + LOG_DEBUG(Lib_NpMatching2, "sendRoomMessage room={} cast={} dstN={} bytes={}", request.roomId, + request.castType, req.dst_member_ids_size(), request.msgLen); return MmSubmitRequest(ctx_id, req_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEND_ROOM_MESSAGE, MmCommand::SendRoomMessage, MakeProtoPayload(req)); } @@ -1133,7 +1333,13 @@ s32 MmSetRoomDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2Requ req.set_room_id(request.roomId); req.set_flag_filter(request.flagFilter); req.set_flag_attr(request.flagAttr); + LOG_INFO(Lib_NpMatching2, + "ctx={} reqId={} room={} flags={:#x}/{:#x} binAttrs={} " + "passwdMask={}", + ctx_id, req_id, request.roomId, request.flagFilter, request.flagAttr, + request.roomBinAttrInternalNum, fmt::ptr(request.passwordSlotMask)); for (u64 i = 0; i < request.roomBinAttrInternalNum; ++i) { + LogBinAttr(" internalBin", i, request.roomBinAttrInternal[i]); AppendBinAttr(req.add_bin_attrs(), request.roomBinAttrInternal[i]); } if (request.passwordSlotMask) { @@ -1144,18 +1350,49 @@ s32 MmSetRoomDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2Requ MmCommand::SetRoomDataInternal, MakeProtoPayload(req)); } +s32 MmSetRoomMemberDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2SetRoomMemberDataInternalRequest& request) { + shadnet::SetRoomMemberDataInternalRequest req; + req.set_req_id(req_id); + req.set_room_id(request.roomId); + req.set_member_id(request.memberId); + req.set_team_id(request.teamId); + req.set_flag_filter(request.flagFilter); + req.set_flag_attr(request.flagAttr); + LOG_INFO(Lib_NpMatching2, + "ctx={} reqId={} room={} member={} team={} " + "flags={:#x}/{:#x} binAttrs={}", + ctx_id, req_id, request.roomId, request.memberId, request.teamId, request.flagFilter, + request.flagAttr, request.roomMemberBinAttrInternalNum); + for (u64 i = 0; i < request.roomMemberBinAttrInternalNum; ++i) { + LogBinAttr(" memberInternalBin", i, request.roomMemberBinAttrInternal[i]); + AppendBinAttr(req.add_bin_attrs(), request.roomMemberBinAttrInternal[i]); + } + return MmSubmitRequest(ctx_id, req_id, + ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_MEMBER_DATA_INTERNAL, + MmCommand::SetRoomMemberDataInternal, MakeProtoPayload(req)); +} + s32 MmSetRoomDataExternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, const OrbisNpMatching2SetRoomDataExternalRequest& request) { shadnet::SetRoomDataExternalRequest req; req.set_req_id(req_id); req.set_room_id(request.roomId); + LOG_INFO(Lib_NpMatching2, + "ctx={} reqId={} room={} searchInt={} searchBin={} " + "extBin={}", + ctx_id, req_id, request.roomId, request.roomSearchableIntAttrExternalNum, + request.roomSearchableBinAttrExternalNum, request.roomBinAttrExternalNum); for (u64 i = 0; i < request.roomSearchableIntAttrExternalNum; ++i) { + LogIntAttr(" externalSearchInt", i, request.roomSearchableIntAttrExternal[i]); AppendIntAttr(req.add_search_int_attrs(), request.roomSearchableIntAttrExternal[i]); } for (u64 i = 0; i < request.roomSearchableBinAttrExternalNum; ++i) { + LogBinAttr(" externalSearchBin", i, request.roomSearchableBinAttrExternal[i]); AppendBinAttr(req.add_search_bin_attrs(), request.roomSearchableBinAttrExternal[i]); } for (u64 i = 0; i < request.roomBinAttrExternalNum; ++i) { + LogBinAttr(" externalBin", i, request.roomBinAttrExternal[i]); AppendBinAttr(req.add_ext_bin_attrs(), request.roomBinAttrExternal[i]); } return MmSubmitRequest(ctx_id, req_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_EXTERNAL, @@ -1198,6 +1435,12 @@ bool RequestSignalingInfos(std::string_view target_online_id, u32* out_addr, u16 if (!client || !client->IsAuthenticated()) { return false; } + // Room events and replies are handled on the reader thread, which is the one that would + // read this reply. Waiting there always times out, so leave it to the handshake thread. + if (ShadNet::ShadNetClient::OnReaderThread()) { + LOG_DEBUG(Lib_NpMatching2, "'{}': deferred to the handshake thread", target_online_id); + return false; + } shadnet::RequestSignalingInfosRequest req; req.set_target_npid(std::string(target_online_id)); @@ -1236,7 +1479,8 @@ bool RequestSignalingInfos(std::string_view target_online_id, u32* out_addr, u16 } *out_addr = addr_nbo; *out_port = Libraries::Net::sceNetHtons(port_host); - LOG_DEBUG(Lib_NpMatching2, "'{}' -> {}:{}", target_online_id, addr_str, port_host); + LOG_INFO(Lib_NpMatching2, "received peer='{}' ip={} port={} addr_nbo={:#x} port_nbo={:#x}", + target_online_id, addr_str, port_host, addr_nbo, *out_port); return true; } diff --git a/src/core/libraries/np/np_matching2/np_matching2_mm.h b/src/core/libraries/np/np_matching2/np_matching2_mm.h index b74707102..09fe6d40e 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_mm.h +++ b/src/core/libraries/np/np_matching2/np_matching2_mm.h @@ -35,6 +35,7 @@ enum class MmCommand : u16 { GetUserInfoList = 113, GetRoomMemberDataExternalList = 114, SendRoomMessage = 115, + SetRoomMemberDataInternal = 116, }; void SetMmShadNetClient(std::shared_ptr client, @@ -81,6 +82,8 @@ s32 MmSendRoomMessage(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestI const OrbisNpMatching2SendRoomMessageRequest& request); s32 MmSetRoomDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, const OrbisNpMatching2SetRoomDataInternalRequest& request); +s32 MmSetRoomMemberDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2SetRoomMemberDataInternalRequest& request); s32 MmSetRoomDataExternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, const OrbisNpMatching2SetRoomDataExternalRequest& request); s32 MmKickoutRoomMember(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, diff --git a/src/core/libraries/np/np_matching2/np_matching2_signaling.cpp b/src/core/libraries/np/np_matching2/np_matching2_signaling.cpp index ee8f33886..9adc94245 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_signaling.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2_signaling.cpp @@ -2,63 +2,31 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include #include #include -#include #include -#include #include -#include -#include #include "common/logging/log.h" -#include "core/libraries/network/net.h" -#include "core/libraries/network/sockets.h" +#include "common/singleton.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_p2p.h" +#include "core/libraries/net/net_util.h" +#include "core/libraries/network/netctl.h" #include "core/libraries/np/np_error.h" +#include "core/libraries/np/np_handler.h" #include "core/libraries/np/np_matching2/np_matching2_internal.h" #include "core/libraries/np/np_matching2/np_matching2_mm.h" #include "core/libraries/np/np_matching2/np_matching2_signaling.h" -#include "core/libraries/np/np_signaling/np_signaling_stubs.h" +#include "core/libraries/np/np_signaling/np_signaling_state.h" +#include "core/libraries/np/np_signaling/np_signaling_transport.h" +#include "core/libraries/np/signaling_handler.h" namespace Libraries::Np::NpMatching2 { namespace { constexpr s32 kMatching2ConnInactive = 0; -constexpr s32 kMatching2ConnPending = 1; -constexpr s32 kMatching2ConnActive = 2; -constexpr auto kMatching2HandshakeRetry = std::chrono::milliseconds(350); -constexpr auto kMatching2HandshakeTimeout = std::chrono::seconds(10); -constexpr auto kMatching2StunPingInterval = std::chrono::seconds(5); - -std::atomic g_matching2_stop{false}; -std::mutex g_matching2_thread_mutex; -std::thread g_matching2_thread; - -enum class Matching2HandshakeKind : u8 { - Offer = 1, - Accept = 2, - Check = 3, - CheckAck = 4, -}; - -#pragma pack(push, 1) -struct Matching2HandshakePacket { - u8 magic[4] = {'S', 'H', 'A', 'D'}; - u8 type = 0x21; - u8 kind = 0; - u64 room_id = 0; - u16 from_member_id = 0; - u16 to_member_id = 0; - u8 online_id_from[16]{}; - u32 mapped_addr = 0; - u16 mapped_port = 0; - u16 reserved = 0; - u64 nonce = 0; -}; -#pragma pack(pop) -static_assert(sizeof(Matching2HandshakePacket) == 0x32); #pragma pack(push, 1) struct Matching2StunPing { @@ -69,284 +37,27 @@ struct Matching2StunPing { #pragma pack(pop) static_assert(sizeof(Matching2StunPing) == 21); -bool HasMatching2Magic(const Matching2HandshakePacket& pkt) { - return pkt.magic[0] == 'S' && pkt.magic[1] == 'H' && pkt.magic[2] == 'A' && - pkt.magic[3] == 'D' && pkt.type == 0x21; -} - std::string OnlineIdToString(const Libraries::Np::OrbisNpOnlineId& online_id) { char buf[ORBIS_NP_ONLINEID_MAX_LENGTH + 1]{}; std::memcpy(buf, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); return std::string(buf); } -bool ShouldConnectToPeer(const RoomCache& room, OrbisNpMatching2RoomMemberId self, - OrbisNpMatching2RoomMemberId peer) { - if (peer == 0 || peer == self) { - return false; - } - if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_NONE) { - return false; - } - if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_STAR && - room.signaling_main_member != 0) { - return self == room.signaling_main_member || peer == room.signaling_main_member; - } - return true; -} - -bool ResolvePeerEndpoint(const MemberCache& member, PeerInfo& peer) { - if (peer.addr != 0 && peer.port != 0) { - return true; - } - if (member.addr != 0 && member.port != 0) { - peer.addr = member.addr; - peer.port = member.port; - if (peer.online_id.data[0] == 0) { - peer.online_id = member.np_id.handle; - } - return true; - } - - const std::string online_id(member.np_id.handle.data); - u32 resolved_addr = 0; - u16 resolved_port = 0; - if (!online_id.empty() && RequestSignalingInfos(online_id, &resolved_addr, &resolved_port)) { - peer.addr = resolved_addr; - peer.port = resolved_port; - } - if (peer.online_id.data[0] == 0) { - peer.online_id = member.np_id.handle; - } - return peer.addr != 0 && peer.port != 0; -} - -void MarkMatching2PeerActive(ContextObject& ctx, OrbisNpMatching2RoomId room_id, - OrbisNpMatching2RoomMemberId member_id, u32 addr, u16 port) { - if (member_id == 0 || member_id == ctx.my_member_id) { - return; - } - - PeerInfo& peer = ctx.peers[member_id]; - peer.member_id = member_id; - if (addr != 0) { - peer.addr = addr; - } - if (port != 0) { - peer.port = port; - } - const bool first_active = peer.status != kMatching2ConnActive; - peer.status = kMatching2ConnActive; - peer.handshake_started = true; - - if (first_active && !peer.sent_established) { - peer.sent_established = true; - QueueMatching2SignalingEvent(ctx, room_id, member_id, - ORBIS_NP_MATCHING2_SIGNALING_EVENT_ESTABLISHED, ORBIS_OK); - LOG_INFO(Lib_NpMatching2, - "Matching2 signaling established: ctx={} room={} member={} addr={:#x}:{}", - ctx.ctx_id, room_id, member_id, peer.addr, Libraries::Net::sceNetNtohs(peer.port)); - } -} - -bool SendMatching2Handshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id, - OrbisNpMatching2RoomMemberId member_id, Matching2HandshakeKind kind, - u64 nonce) { - auto room_it = ctx.room_cache.find(room_id); - if (room_it == ctx.room_cache.end()) { - return false; - } - auto member_it = room_it->second.members.find(member_id); - if (member_it == room_it->second.members.end()) { - return false; - } - - PeerInfo& peer = ctx.peers[member_id]; - peer.member_id = member_id; - if (!ResolvePeerEndpoint(member_it->second, peer)) { - LOG_WARNING(Lib_NpMatching2, "Matching2 signaling: unresolved endpoint room={} member={}", - room_id, member_id); - return false; - } - - Matching2HandshakePacket pkt{}; - pkt.kind = static_cast(kind); - pkt.room_id = room_id; - pkt.from_member_id = ctx.my_member_id; - pkt.to_member_id = member_id; - std::memcpy(pkt.online_id_from, ctx.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); - pkt.mapped_addr = Net::GetP2PAdvertisedAddr(); - pkt.mapped_port = Net::GetP2PConfiguredPort() != 0 - ? Libraries::Net::sceNetHtons(Net::GetP2PConfiguredPort()) - : 0; - pkt.nonce = nonce; - - const int rc = Net::P2PMatching2SendTo(&pkt, sizeof(pkt), peer.addr, peer.port); - const auto now = std::chrono::steady_clock::now(); - peer.last_send = now; - if (kind == Matching2HandshakeKind::Check) { - peer.last_check_send = now; - } - LOG_DEBUG(Lib_NpMatching2, - "Matching2 handshake send kind={} ctx={} room={} {}->{} dst={:#x}:{} rc={}", - static_cast(kind), ctx.ctx_id, room_id, ctx.my_member_id, member_id, peer.addr, - Libraries::Net::sceNetNtohs(peer.port), rc); - return rc >= 0; -} - -ContextObject* FindContextForMatching2Packet(const Matching2HandshakePacket& pkt) { - for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) { - ContextObject* ctx = - ContextManager::Instance().Get(static_cast(id)); - if (!ctx || ctx->room_id != pkt.room_id || ctx->my_member_id != pkt.to_member_id) { - continue; - } - if (ctx->room_cache.find(static_cast(pkt.room_id)) != - ctx->room_cache.end()) { - return ctx; - } - } - return nullptr; -} - -void HandleMatching2HandshakePacket(u32 from_addr, u16 from_port, - const Matching2HandshakePacket& pkt) { - if (!HasMatching2Magic(pkt)) { - return; - } - - ContextObject* ctx = FindContextForMatching2Packet(pkt); - if (!ctx) { - LOG_DEBUG(Lib_NpMatching2, "Matching2 handshake: no ctx for room={} to_member={}", - pkt.room_id, pkt.to_member_id); - return; - } - - const auto room_id = static_cast(pkt.room_id); - const auto member_id = static_cast(pkt.from_member_id); - auto room_it = ctx->room_cache.find(room_id); - if (room_it == ctx->room_cache.end() || - !ShouldConnectToPeer(room_it->second, ctx->my_member_id, member_id)) { - return; - } - - PeerInfo& peer = ctx->peers[member_id]; - peer.member_id = member_id; - peer.addr = pkt.mapped_addr != 0 ? pkt.mapped_addr : from_addr; - peer.port = pkt.mapped_port != 0 ? pkt.mapped_port : from_port; - peer.status = - peer.status == kMatching2ConnActive ? kMatching2ConnActive : kMatching2ConnPending; - peer.handshake_started = true; - SetNpOnlineId(peer.online_id, - std::string_view(reinterpret_cast(pkt.online_id_from), - ORBIS_NP_ONLINEID_MAX_LENGTH)); - - const auto kind = static_cast(pkt.kind); - switch (kind) { - case Matching2HandshakeKind::Offer: - SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Accept, 0); - break; - case Matching2HandshakeKind::Accept: - peer.sent_check = true; - SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Check, peer.nonce); - break; - case Matching2HandshakeKind::Check: - SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::CheckAck, - pkt.nonce); - MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port); - break; - case Matching2HandshakeKind::CheckAck: - if (pkt.nonce == 0 || pkt.nonce == peer.nonce) { - if (peer.last_check_send.time_since_epoch().count() != 0) { - const auto rtt = std::chrono::duration_cast( - std::chrono::steady_clock::now() - peer.last_check_send); - peer.ping_us = - static_cast(std::min(static_cast(std::max(0, rtt.count())), - std::numeric_limits::max())); - peer.last_check_send = {}; - } - MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port); - } - break; - default: - break; - } -} - -void Matching2HandshakeThreadMain() { - auto last_stun_ping = std::chrono::steady_clock::time_point{}; - while (!g_matching2_stop.load(std::memory_order_relaxed)) { - Matching2HandshakePacket pkt{}; - u32 from_addr = 0; - u16 from_port = 0; - const int rc = Net::P2PMatching2RecvFrom(&pkt, sizeof(pkt), &from_addr, &from_port); - if (rc == sizeof(pkt)) { - HandleMatching2HandshakePacket(from_addr, from_port, pkt); - } - - const auto now = std::chrono::steady_clock::now(); - const bool should_stun_ping = last_stun_ping.time_since_epoch().count() == 0 || - now - last_stun_ping >= kMatching2StunPingInterval; - for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) { - ContextObject* ctx = - ContextManager::Instance().Get(static_cast(id)); - if (!ctx) { - continue; - } - if (should_stun_ping) { - SendMatching2StunPing(*ctx); - } - if (ctx->room_id == 0) { - continue; - } - auto room_it = ctx->room_cache.find(ctx->room_id); - if (room_it == ctx->room_cache.end()) { - continue; - } - for (auto& [member_id, peer] : ctx->peers) { - if (peer.status != kMatching2ConnPending || !peer.handshake_started) { - continue; - } - if (peer.last_send.time_since_epoch().count() != 0 && - now - peer.last_send > kMatching2HandshakeTimeout) { - peer.status = kMatching2ConnInactive; - QueueMatching2SignalingEvent(*ctx, ctx->room_id, member_id, - ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD, - ORBIS_NP_MATCHING2_SIGNALING_ERROR_TIMEOUT); - continue; - } - if (peer.last_send.time_since_epoch().count() == 0 || - now - peer.last_send >= kMatching2HandshakeRetry) { - SendMatching2Handshake(*ctx, ctx->room_id, member_id, - peer.sent_check ? Matching2HandshakeKind::Check - : Matching2HandshakeKind::Offer, - peer.sent_check ? peer.nonce : 0); - } - } - } - if (should_stun_ping) { - last_stun_ping = now; - } - - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - } -} - } // namespace bool SendMatching2StunPing(const ContextObject& ctx) { - if (!NpSignaling::Stubs::Matching2Enabled()) { + if (!NpSignaling::Transport::Matching2Enabled()) { return false; } if (!ctx.started || ctx.online_id.data[0] == '\0') { return false; } - if (!NpSignaling::Stubs::EnsureTransport()) { + if (!NpSignaling::Transport::EnsureTransport()) { return false; } - const u32 server_addr = NpSignaling::Stubs::MmServerAddr(); - const u16 server_udp = NpSignaling::Stubs::MmServerUdpPort(); + const u32 server_addr = NpSignaling::Transport::MmServerAddr(); + const u16 server_udp = NpSignaling::Transport::MmServerUdpPort(); if (server_addr == 0 || server_udp == 0) { return false; } @@ -354,10 +65,10 @@ bool SendMatching2StunPing(const ContextObject& ctx) { Matching2StunPing ping{}; ping.cmd = 0x01; std::memcpy(ping.online_id, ctx.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); - ping.local_ip = NpSignaling::Stubs::AdvertisedAddr(); + ping.local_ip = NpSignaling::Transport::AdvertisedAddr(); const int rc = - NpSignaling::Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_udp); + NpSignaling::Transport::SignalingSendTo(&ping, sizeof(ping), server_addr, server_udp); LOG_DEBUG(Lib_NpMatching2, "Matching2 STUN ping: ctx={} online_id='{}' server={:#x}:{} local_ip={:#x} rc={}", ctx.ctx_id, OnlineIdToString(ctx.online_id), server_addr, @@ -368,9 +79,17 @@ bool SendMatching2StunPing(const ContextObject& ctx) { void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId room_id, OrbisNpMatching2RoomMemberId member_id, OrbisNpMatching2Event event, s32 error_code) { + if (!ctx.signaling_callback) { + LOG_ERROR(Lib_NpMatching2, + "Event skipped: ctx={} room={} member={} event={:#x} no callback", ctx.ctx_id, + room_id, member_id, static_cast(event)); + return; + } + PendingEvent ev{}; ev.type = PendingEvent::SIGNALING_CB; ev.ctx_id = ctx.ctx_id; + ev.context_owner = ctx.shared_from_this(); ev.fire_at = std::chrono::steady_clock::now(); ev.room_id = room_id; ev.member_id = member_id; @@ -379,95 +98,50 @@ void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId roo ScheduleEvent(std::move(ev)); } -void StartMatching2PeerHandshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id, - OrbisNpMatching2RoomMemberId member_id) { - auto room_it = ctx.room_cache.find(room_id); - if (room_it == ctx.room_cache.end() || - !ShouldConnectToPeer(room_it->second, ctx.my_member_id, member_id)) { - return; - } - auto member_it = room_it->second.members.find(member_id); - if (member_it == room_it->second.members.end()) { - return; - } - - Net::EnsureP2PTransport(); - - PeerInfo& peer = ctx.peers[member_id]; - peer.member_id = member_id; - if (peer.status == kMatching2ConnActive) { - return; - } - ResolvePeerEndpoint(member_it->second, peer); - peer.status = kMatching2ConnPending; - peer.handshake_started = true; - peer.sent_check = false; - if (peer.nonce == 0) { - peer.nonce = static_cast(std::chrono::steady_clock::now().time_since_epoch().count() ^ - (static_cast(ctx.ctx_id) << 48) ^ - (static_cast(member_id) << 16)); - } - SendMatching2Handshake(ctx, room_id, member_id, Matching2HandshakeKind::Offer, 0); -} - -void StartMatching2SignalingForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id) { - const auto room_it = ctx.room_cache.find(room_id); - if (room_it == ctx.room_cache.end()) { - return; - } - for (const auto& [member_id, member] : room_it->second.members) { - StartMatching2PeerHandshake(ctx, room_id, member_id); - } -} - void QueueMatching2DeadForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id, s32 error_code) { - auto room_it = ctx.room_cache.find(room_id); - if (room_it == ctx.room_cache.end()) { - return; + std::vector dead_members; + { + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + const auto room_it = cache->rooms.find(room_id); + if (room_it == cache->rooms.end()) { + return; + } + + for (const auto& [member_id, member] : room_it->second.members) { + if (member_id == 0 || member_id == ctx.my_member_id) { + continue; + } + + const auto peer_it = cache->peers.find(member_id); + if (peer_it != cache->peers.end()) { + peer_it->second.status = kMatching2ConnInactive; + } + dead_members.push_back(member_id); + } } - for (const auto& [member_id, member] : room_it->second.members) { - if (member_id == 0 || member_id == ctx.my_member_id) { - continue; - } - - auto peer_it = ctx.peers.find(member_id); - if (peer_it != ctx.peers.end()) { - peer_it->second.status = kMatching2ConnInactive; - peer_it->second.handshake_started = false; - peer_it->second.sent_check = false; - } - + for (const auto member_id : dead_members) { QueueMatching2SignalingEvent(ctx, room_id, member_id, ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD, error_code); LOG_INFO(Lib_NpMatching2, "Matching2 signaling dead: ctx={} room={} member={} reason={:#x}", ctx.ctx_id, room_id, member_id, static_cast(error_code)); } + SignalingHandler::StopMatching2(ctx, room_id, error_code); } -void StartMatching2HandshakeThread() { - std::lock_guard lock(g_matching2_thread_mutex); - if (g_matching2_thread.joinable()) { - return; - } - g_matching2_stop.store(false, std::memory_order_relaxed); - g_matching2_thread = std::thread(Matching2HandshakeThreadMain); +void StartMatching2SignalingRuntime() { + SignalingHandler::Start(); } -void StopMatching2HandshakeThread() { - { - std::lock_guard lock(g_matching2_thread_mutex); - g_matching2_stop.store(true, std::memory_order_relaxed); - } - if (g_matching2_thread.joinable()) { - g_matching2_thread.join(); - } +void StopMatching2SignalingRuntime() { + SignalingHandler::Stop(); } u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId) { - const auto room_it = ctx.room_cache.find(roomId); - if (room_it == ctx.room_cache.end()) { + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + const auto room_it = cache->rooms.find(roomId); + if (room_it == cache->rooms.end()) { return 0; } @@ -477,8 +151,8 @@ u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId) { if (member_id == ctx.my_member_id) { continue; } - const auto peer_it = ctx.peers.find(member_id); - if (peer_it == ctx.peers.end() || peer_it->second.ping_us == 0) { + const auto peer_it = cache->peers.find(member_id); + if (peer_it == cache->peers.end() || peer_it->second.ping_us == 0) { continue; } total_ping += peer_it->second.ping_us; @@ -488,16 +162,16 @@ u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId) { return ping_count == 0 ? 0 : static_cast(total_ping / ping_count); } -void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomId roomId) { - CallbackPayload& p = - ctx.request_payload_override ? *ctx.request_payload_override : ctx.request_payload; +void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, CallbackPayload& p, + OrbisNpMatching2RoomId roomId) { p.Reset(); - const auto room_it = ctx.room_cache.find(roomId); + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + const auto room_it = cache->rooms.find(roomId); p.ping_info_response = std::make_unique(); auto& out = *p.ping_info_response; out = {}; - if (room_it != ctx.room_cache.end()) { + if (room_it != cache->rooms.end()) { out.serverId = room_it->second.server_id; out.worldId = room_it->second.world_id; out.roomId = room_it->second.room_id; @@ -512,6 +186,16 @@ void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomI return p.request_data; } +// Our own address combined with the external IP from the STUN echo plus our P2P port +static void FillMappedAddr(OrbisNpMatching2SignalingConnectionInfoAddr& out) { + u32 addr = Common::Singleton::Instance()->GetExternalIp(); + if (addr == 0) { + addr = Net::GetP2PAdvertisedAddr(); + } + out.addr = addr; + out.port = Net::sceNetHtons(Net::GetP2PAdvertisedPort()); +} + s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId roomId, OrbisNpMatching2RoomMemberId memberId, u32 infoType, void* connInfo, bool a_variant) { @@ -520,8 +204,9 @@ s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - const auto room_it = ctx.room_cache.find(roomId); - if (room_it == ctx.room_cache.end()) { + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + const auto room_it = cache->rooms.find(roomId); + if (room_it == cache->rooms.end()) { LOG_INFO(Lib_NpMatching2, "room={} not cached for connection info", roomId); if (a_variant) { *static_cast(connInfo) = {}; @@ -534,8 +219,8 @@ s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId const auto member_it = room_it->second.members.find(memberId); const MemberCache* member = member_it != room_it->second.members.end() ? &member_it->second : nullptr; - const auto peer_it = ctx.peers.find(memberId); - const PeerInfo* peer = peer_it != ctx.peers.end() ? &peer_it->second : nullptr; + const auto peer_it = cache->peers.find(memberId); + const PeerInfo* peer = peer_it != cache->peers.end() ? &peer_it->second : nullptr; if (a_variant) { auto* out = static_cast(connInfo); @@ -547,8 +232,10 @@ s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_BANDWIDTH: out->bandwidth = 0; break; - case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR: case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR: + FillMappedAddr(out->address); + break; + case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR: if (peer) { out->address.addr = peer->addr; out->address.port = peer->port; @@ -586,8 +273,10 @@ s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId out->npId = member->np_id; } break; - case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR: case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR: + FillMappedAddr(out->address); + break; + case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR: if (peer) { out->address.addr = peer->addr; out->address.port = peer->port; @@ -611,4 +300,39 @@ s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId return ORBIS_OK; } +s32 FillMatching2LocalNetInfo(void* info) { + auto* out = static_cast(info); + if (!out || out->size != sizeof(NpSignaling::OrbisNpSignalingNetInfo)) { + LOG_ERROR(Lib_NpMatching2, "bad net info (size {})", out ? out->size : 0); + return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + } + + auto* netinfo = Common::Singleton::Instance(); + // GetIp() is empty until something asks for it. RetrieveIp() connects out, so only once. + if (netinfo->GetIp().empty()) { + netinfo->RetrieveIp(); + } + out->localAddr = NpSignaling::ParseIpv4Nbo(netinfo->GetIp()); + if (out->localAddr == 0) { + out->localAddr = NpSignaling::ParseIpv4Nbo("127.0.0.1"); + } + out->mappedAddr = 0; + out->natStatus = 0; + NetCtl::OrbisNetCtlNatInfo nat_info{}; + nat_info.size = sizeof(nat_info); + if (NetCtl::sceNetCtlGetNatInfo(&nat_info) >= 0) { + out->mappedAddr = nat_info.mapped_addr; + out->natStatus = nat_info.nat_type; + } + if (out->mappedAddr == 0) { + const u32 external = netinfo->GetExternalIp(); + out->mappedAddr = external != 0 ? external : out->localAddr; + } + out->_pad_14 = 0; + + LOG_INFO(Lib_NpMatching2, "localAddr={:#x} mappedAddr={:#x} natStatus={}", out->localAddr, + out->mappedAddr, out->natStatus); + return ORBIS_OK; +} + } // namespace Libraries::Np::NpMatching2 diff --git a/src/core/libraries/np/np_matching2/np_matching2_signaling.h b/src/core/libraries/np/np_matching2/np_matching2_signaling.h index a6a632e65..33877eb9d 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_signaling.h +++ b/src/core/libraries/np/np_matching2/np_matching2_signaling.h @@ -9,15 +9,13 @@ namespace Libraries::Np::NpMatching2 { struct ContextObject; +struct CallbackPayload; -void StartMatching2HandshakeThread(); -void StopMatching2HandshakeThread(); +void StartMatching2SignalingRuntime(); +void StopMatching2SignalingRuntime(); bool SendMatching2StunPing(const ContextObject& ctx); -void StartMatching2PeerHandshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id, - OrbisNpMatching2RoomMemberId member_id); -void StartMatching2SignalingForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id); void QueueMatching2DeadForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id, s32 error_code); void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId room_id, @@ -25,9 +23,11 @@ void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId roo OrbisNpMatching2Event event, s32 error_code); u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId); -void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomId roomId); +void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, CallbackPayload& payload, + OrbisNpMatching2RoomId roomId); s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId roomId, OrbisNpMatching2RoomMemberId memberId, u32 infoType, void* connInfo, bool a_variant); +s32 FillMatching2LocalNetInfo(void* info); } // namespace Libraries::Np::NpMatching2 diff --git a/src/core/libraries/np/np_signaling/np_signaling.cpp b/src/core/libraries/np/np_signaling/np_signaling.cpp index 2de1858b5..ce2127026 100644 --- a/src/core/libraries/np/np_signaling/np_signaling.cpp +++ b/src/core/libraries/np/np_signaling/np_signaling.cpp @@ -2,14 +2,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include "common/logging/log.h" #include "common/singleton.h" #include "core/libraries/error_codes.h" -#include "core/libraries/libs.h" -#include "core/libraries/network/net.h" -#include "core/libraries/network/net_util.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_util.h" #include "core/libraries/network/netctl.h" #include "core/libraries/np/np_common.h" #include "core/libraries/np/np_handler.h" @@ -17,7 +17,8 @@ #include "core/libraries/np/np_signaling/np_signaling.h" #include "core/libraries/np/np_signaling/np_signaling_helpers.h" #include "core/libraries/np/np_signaling/np_signaling_state.h" -#include "core/libraries/np/np_signaling/np_signaling_stubs.h" +#include "core/libraries/np/np_signaling/np_signaling_transport.h" +#include "core/libraries/np/signaling_handler.h" namespace Libraries::Np::NpSignaling { @@ -25,10 +26,9 @@ using Libraries::Net::sceNetNtohs; s32 PS4_SYSV_ABI sceNpSignalingInitialize(s64 memorySize, s32 threadPriority, s32 cpuAffinityMask, s64 threadStackSize) { - if (g_initialized) { + if (NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_ALREADY_INITIALIZED; } - InitSignalingMutex(); SignalingMutexGuard lock; u32 app_type_4 = 0; @@ -67,63 +67,49 @@ s32 PS4_SYSV_ABI sceNpSignalingInitialize(s64 memorySize, s32 threadPriority, s3 return check_app_type_rc; } - g_initialized = true; + NpHandler::GetInstance().GetSignalingState().initialized = true; const s32 rc = Helpers::StartMainRuntime(threadPriority, cpuAffinityMask, threadStackSize); if (rc < 0) { - g_initialized = false; + NpHandler::GetInstance().GetSignalingState().initialized = false; Helpers::ShutdownSignalingHeap(); return rc; } const s32 echo_rc = Helpers::StartEchoRuntime(threadPriority, cpuAffinityMask); if (echo_rc < 0) { - g_initialized = false; + NpHandler::GetInstance().GetSignalingState().initialized = false; Helpers::ShutdownRuntime(); Helpers::ShutdownSignalingHeap(); return echo_rc; } - const s32 callout_rc = NpCommon::sceNpCalloutInitCtx( - &g_callout_ctx, "SceNpSignalingCallout", static_cast(threadStackSize), threadPriority, - static_cast(cpuAffinityMask)); - if (callout_rc < 0) { - g_initialized = false; - Helpers::ShutdownRuntime(); - Helpers::ShutdownSignalingHeap(); - return callout_rc; - } - g_callout_ctx_active = true; return ORBIS_OK; } s32 PS4_SYSV_ABI sceNpSignalingTerminate() { - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } { SignalingMutexGuard lock; - g_initialized = false; + NpHandler::GetInstance().GetSignalingState().initialized = false; } Helpers::ShutdownRuntime(); - if (g_callout_ctx_active) { - NpCommon::sceNpCalloutTermCtx(&g_callout_ctx); - g_callout_ctx_active = false; - } - Helpers::ShutdownSignalingHeap(); + size_t context_count = 0; { SignalingMutexGuard lock; - g_contexts.clear(); - g_connections.clear(); - g_npid_to_conn.clear(); - g_peer_netinfo_results.clear(); + auto& state = NpHandler::GetInstance().GetSignalingState(); + context_count = state.contexts.size(); + state.ResetSig1Data(); + } + for (size_t i = 0; i < context_count; ++i) { + SignalingHandler::ReleaseContextRef(); } - - DestroySignalingMutex(); LOG_INFO(Lib_NpSignaling, "cleared all state"); return ORBIS_OK; @@ -133,7 +119,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v OrbisNpSignalingContextId* outContextId) { { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } } @@ -147,12 +133,12 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT; } - const bool transport_ready = Stubs::EnsureTransport(); + const bool transport_ready = Transport::EnsureTransport(); s32 ctx_id = 0; { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } @@ -161,7 +147,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v return ORBIS_NP_SIGNALING_ERROR_CTXID_NOT_AVAILABLE; } - NpSignalingContext& ctx = g_contexts[ctx_id]; + NpSignalingContext& ctx = NpHandler::GetInstance().GetSignalingState().contexts[ctx_id]; ctx.callback = reinterpret_cast(callback); ctx.callback_arg = callbackArg; ctx.active = true; @@ -171,7 +157,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v ctx.compiled_sdk_version = CaptureCompiledSdkVersion(); ctx.activate_budget_us = kActivateBudgetMaxUs; ctx.activate_last_update_us = NowUs(); - ctx.bound_port = Stubs::ConfiguredPort(); + ctx.bound_port = Transport::ConfiguredPort(); *outContextId = ctx_id; LOG_INFO(Lib_NpSignaling, "ctxId={} owner='{}' callback={} arg={} sdk={:#x} p2p_port={}", @@ -179,9 +165,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v fmt::ptr(callbackArg), ctx.compiled_sdk_version, ctx.bound_port); } - if (transport_ready) { - SendStunPing(ctx_id); - } + SignalingHandler::AddContextRef(); return ORBIS_OK; } @@ -189,7 +173,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void* OrbisNpSignalingContextId* outContextId) { { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } } @@ -202,15 +186,21 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void* if (npid_rc < 0) { return npid_rc; } - // Account id isn't needed without matchmaking wired up; leave it 0 for now. - const OrbisNpAccountId account_id = 0; + OrbisNpAccountId account_id = 0; + const s32 account_rc = NpManager::sceNpGetAccountIdA(userId, &account_id); + if (account_rc < 0) { + return account_rc; + } + if (account_id == 0) { + return ORBIS_NP_ERROR_USER_NOT_FOUND; + } - const bool transport_ready = Stubs::EnsureTransport(); + const bool transport_ready = Transport::EnsureTransport(); s32 ctx_id = 0; { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } @@ -219,7 +209,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void* return ORBIS_NP_SIGNALING_ERROR_CTXID_NOT_AVAILABLE; } - NpSignalingContext& ctx = g_contexts[ctx_id]; + NpSignalingContext& ctx = NpHandler::GetInstance().GetSignalingState().contexts[ctx_id]; ctx.callback = reinterpret_cast(callback); ctx.callback_arg = callbackArg; ctx.active = true; @@ -231,16 +221,14 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void* ctx.compiled_sdk_version = CaptureCompiledSdkVersion(); ctx.activate_budget_us = kActivateBudgetMaxUs; ctx.activate_last_update_us = NowUs(); - ctx.bound_port = Stubs::ConfiguredPort(); + ctx.bound_port = Transport::ConfiguredPort(); *outContextId = ctx_id; LOG_INFO(Lib_NpSignaling, "ctxId={} userId={} owner='{}' accountId={:#x}", ctx_id, userId, OnlineIdToString(ctx.owner_online_id), account_id); } - if (transport_ready) { - SendStunPing(ctx_id); - } + SignalingHandler::AddContextRef(); return ORBIS_OK; } @@ -248,13 +236,13 @@ s32 PS4_SYSV_ABI sceNpSignalingDeleteContext(OrbisNpSignalingContextId ctxId) { std::vector active_conns; { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } LOG_INFO(Lib_NpSignaling, "ctxId={}", ctxId); - for (const auto& [cid, ci] : g_connections) { + for (const auto& [cid, ci] : NpHandler::GetInstance().GetSignalingState().connections) { if (ci.ctx_id == ctxId && ci.status != ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE) { active_conns.push_back(cid); } @@ -262,148 +250,91 @@ s32 PS4_SYSV_ABI sceNpSignalingDeleteContext(OrbisNpSignalingContextId ctxId) { } for (const s32 cid : active_conns) { - CloseConnectionAndDispatchDead(cid, ORBIS_NP_SIGNALING_ERROR_TERMINATED_BY_PEER); + SignalingHandler::TerminateSig1(cid); } { SignalingMutexGuard lock; RemoveContextConnectionsLocked(ctxId); - for (auto it = g_peer_netinfo_results.begin(); it != g_peer_netinfo_results.end();) { - it = (it->second.ctx_id == ctxId) ? g_peer_netinfo_results.erase(it) : std::next(it); + for (auto it = NpHandler::GetInstance().GetSignalingState().peer_netinfo_results.begin(); + it != NpHandler::GetInstance().GetSignalingState().peer_netinfo_results.end();) { + it = (it->second.ctx_id == ctxId) + ? NpHandler::GetInstance().GetSignalingState().peer_netinfo_results.erase(it) + : std::next(it); } - g_contexts.erase(ctxId); + NpHandler::GetInstance().GetSignalingState().contexts.erase(ctxId); } + SignalingHandler::ReleaseContextRef(); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNpSignalingActivateConnection(OrbisNpSignalingContextId ctxId, const void* peerNpId, OrbisNpSignalingConnectionId* outConnId) { - LOG_INFO(Lib_NpSignaling, "t={} ctxId={} peerNpId={:p} connId={:p}", NowMs(), ctxId, peerNpId, - fmt::ptr(outConnId)); + const s64 call_time = NowMs(); + const void* caller = __builtin_return_address(0); + const void* frame = __builtin_frame_address(0); + LOG_INFO(Lib_NpSignaling, "t={} ctxId={} peerNpId={:p} outConnId={:p}", call_time, ctxId, + peerNpId, fmt::ptr(outConnId)); { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { + LOG_INFO(Lib_NpSignaling, + "result: t={} ctxId={} peer='' " + "rc={:#x}", + call_time, ctxId, ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED); return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } } if (!peerNpId || !outConnId) { + LOG_INFO(Lib_NpSignaling, + "result: t={} ctxId={} peer='' " + "rc={:#x}", + call_time, ctxId, ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT); return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT; } OrbisNpId peer_npid{}; OrbisNpOnlineId peer_online_id{}; - if (NormalizeNpId(peerNpId, &peer_npid, &peer_online_id) != ORBIS_OK) { - return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT; + const s32 normalize_rc = NormalizeNpId(peerNpId, &peer_npid, &peer_online_id); + if (normalize_rc != ORBIS_OK) { + Helpers::LogInvalidActivationContext(call_time, peerNpId, outConnId, caller, frame); + LOG_INFO(Lib_NpSignaling, + "result: t={} ctxId={} peer='' " + "rc={:#x}", + call_time, ctxId, normalize_rc); + return normalize_rc; } + const std::string peer_online_id_str = OnlineIdToString(peer_online_id); - - s32 cid = 0; - bool reused_established = false; - bool queue_activation = false; - bool start_handshake = true; - { - SignalingMutexGuard lock; - if (!g_initialized) { - return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; - } - const auto ctx_it = g_contexts.find(ctxId); - if (ctx_it == g_contexts.end() || !ctx_it->second.active) { - return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; - } - if (std::memcmp(&ctx_it->second.owner_npid, &peer_npid, sizeof(peer_npid)) == 0) { - return ORBIS_NP_SIGNALING_ERROR_OWN_NP_ID; - } - if (!ConsumeActivationBudgetGatedLocked(ctx_it->second)) { - return ORBIS_NP_SIGNALING_ERROR_EXCEED_RATE_LIMIT; - } - - const CtxNpIdKey lookup_key = MakeCtxNpIdKey(ctxId, peer_npid); - const auto existing_it = g_npid_to_conn.find(lookup_key); - if (existing_it != g_npid_to_conn.end()) { - const auto conn_it = g_connections.find(existing_it->second); - if (conn_it != g_connections.end() && conn_it->second.state == ConnState::Inactive) { - RemoveConnectionLocked(existing_it->second); - } else if (conn_it != g_connections.end()) { - cid = existing_it->second; - ConnectionInfo& ci = conn_it->second; - const bool was_locally_activated = ci.locally_activated; - ci.locally_activated = true; - if (ci.state == ConnState::Established) { - ClearLingerAndTimeoutLocked(cid); - reused_established = true; - start_handshake = false; - } else { - ClearLingerAndTimeoutLocked(cid); - start_handshake = true; - } - if (!was_locally_activated) { - queue_activation = true; - } - } - } - bool created_new = false; - if (cid == 0) { - cid = AllocateConnectionIdLocked(); - if (cid < 0) { - return ORBIS_NP_SIGNALING_ERROR_OUT_OF_MEMORY; - } - ConnectionInfo ci{}; - ci.conn_id = cid; - ci.ctx_id = ctxId; - ci.state = ConnState::SendingOffer; - ci.status = ORBIS_NP_SIGNALING_CONN_STATUS_PENDING; - ci.is_initiator = true; - ci.locally_activated = true; - ci.npid = peer_npid; - ci.online_id = peer_online_id; - g_connections[cid] = std::move(ci); - g_npid_to_conn[lookup_key] = cid; - created_new = true; - } - if (created_new) { - ArmConnectTimeoutLocked(cid); - queue_activation = true; - start_handshake = true; - } - if (queue_activation) { - QueueActivationLocked(cid, peer_online_id_str, start_handshake); - } - LOG_INFO(Lib_NpSignaling, "ctxId={} peer='{}' connId={} Status: {}", ctxId, - peer_online_id_str, cid, - created_new ? "queued, async" - : reused_established ? "queued local activation on established connection" - : queue_activation ? "queued local activation on existing connection" - : "reused transient"); + const s32 rc = SignalingHandler::ActivateSig1(ctxId, peer_npid, peer_online_id, outConnId); + if (rc == ORBIS_OK) { + LOG_INFO(Lib_NpSignaling, + "result: t={} ctxId={} peer='{}' rc={:#x} " + "connId={}", + call_time, ctxId, peer_online_id_str, rc, *outConnId); + } else { + LOG_INFO(Lib_NpSignaling, "result: t={} ctxId={} peer='{}' rc={:#x}", call_time, ctxId, + peer_online_id_str, rc); } - - *outConnId = cid; - - if (queue_activation) { - g_dispatch_cv.notify_all(); - return ORBIS_OK; - } - - if (reused_established) { - EstablishConnection(cid, false); - } - return ORBIS_OK; + return rc; } s32 PS4_SYSV_ABI sceNpSignalingActivateConnectionA( OrbisNpSignalingContextId ctxId, const OrbisNpSignalingAccountPlatformPair* peerAddr, OrbisNpSignalingConnectionId* outConnId) { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!peerAddr || peerAddr->accountId == 0 || !outConnId || peerAddr->platformType == 0) { return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT; } - const auto ctx_it = g_contexts.find(ctxId); - if (ctx_it == g_contexts.end() || !ctx_it->second.active) { + const auto ctx_it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctxId); + if (ctx_it == NpHandler::GetInstance().GetSignalingState().contexts.end() || + !ctx_it->second.active) { return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; } if ((ctx_it->second.flags & 0x4) != 0 && ctx_it->second.account_id == peerAddr->accountId && @@ -422,7 +353,7 @@ s32 PS4_SYSV_ABI sceNpSignalingActivateConnectionA( ci.conn_id = cid; ci.ctx_id = ctxId; ci.status = ORBIS_NP_SIGNALING_CONN_STATUS_PENDING; - g_connections[cid] = std::move(ci); + NpHandler::GetInstance().GetSignalingState().connections[cid] = std::move(ci); *outConnId = cid; LOG_INFO(Lib_NpSignaling, "ctxId={} accountId={:#x} platform={} connId={}", ctxId, @@ -434,22 +365,23 @@ s32 PS4_SYSV_ABI sceNpSignalingDeactivateConnection(OrbisNpSignalingContextId ct OrbisNpSignalingConnectionId connId) { { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!IsContextValidLocked(ctxId)) { return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; } - const auto it = g_connections.find(connId); - if (it == g_connections.end() || it->second.ctx_id != ctxId) { + const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(connId); + if (it == NpHandler::GetInstance().GetSignalingState().connections.end() || + it->second.ctx_id != ctxId) { return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND; } LOG_INFO(Lib_NpSignaling, "t={} ctxId={} connId={} peer='{}' status={}", NowMs(), ctxId, connId, OnlineIdToString(it->second.online_id), it->second.status); } - DeactivateConnectionFaithful(connId); + SignalingHandler::DeactivateSig1(connId); return ORBIS_OK; } @@ -457,19 +389,20 @@ s32 PS4_SYSV_ABI sceNpSignalingTerminateConnection(OrbisNpSignalingContextId ctx OrbisNpSignalingConnectionId connId) { { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!IsContextValidLocked(ctxId)) { return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; } - const auto it = g_connections.find(connId); - if (it == g_connections.end() || it->second.ctx_id != ctxId) { + const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(connId); + if (it == NpHandler::GetInstance().GetSignalingState().connections.end() || + it->second.ctx_id != ctxId) { return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND; } LOG_INFO(Lib_NpSignaling, "ctxId={} connId={}", ctxId, connId); } - TerminateConnectionFaithful(connId); + SignalingHandler::TerminateSig1(connId); { SignalingMutexGuard lock; RemoveConnectionLocked(connId); @@ -482,7 +415,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionStatus(OrbisNpSignalingContextId ctx u32* outStatus, u32* outPeerAddr, u16* outPeerPort) { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!outStatus) { @@ -492,20 +425,27 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionStatus(OrbisNpSignalingContextId ctx return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; } - const auto it = g_connections.find(connId); - if (it == g_connections.end() || it->second.ctx_id != ctxId) { + const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(connId); + if (it == NpHandler::GetInstance().GetSignalingState().connections.end() || + it->second.ctx_id != ctxId) { *outStatus = ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE; return ORBIS_OK; } + const auto transport_it = + NpHandler::GetInstance().GetSignalingState().peer_transports.find(it->second.transport_id); + const auto* transport = + transport_it != NpHandler::GetInstance().GetSignalingState().peer_transports.end() + ? &transport_it->second + : nullptr; const s32 state = ConnectionStateFromStatus(it->second.status); if (state == 10) { *outStatus = ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE; - if (outPeerAddr) { - *outPeerAddr = it->second.addr; + if (outPeerAddr && transport) { + *outPeerAddr = transport->addr; } - if (outPeerPort) { - *outPeerPort = it->second.port; + if (outPeerPort && transport) { + *outPeerPort = transport->port; } } else if (state != 0) { *outStatus = ORBIS_NP_SIGNALING_CONN_STATUS_PENDING; @@ -522,7 +462,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionInfo(OrbisNpSignalingContextId ctxId OrbisNpSignalingConnectionId connId, s32 infoCode, void* outInfo) { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!outInfo) { @@ -538,7 +478,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionInfoA(OrbisNpSignalingContextId ctxI OrbisNpSignalingConnectionId connId, s32 infoCode, void* outInfo) { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!outInfo) { @@ -555,7 +495,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromNpId(OrbisNpSignalingContextId c OrbisNpSignalingConnectionId* outConnId) { { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } } @@ -570,19 +510,20 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromNpId(OrbisNpSignalingContextId c } SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!IsContextValidLocked(ctxId)) { return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; } - const auto it = g_npid_to_conn.find(MakeCtxNpIdKey(ctxId, peer_npid)); - if (it == g_npid_to_conn.end()) { + const auto it = NpHandler::GetInstance().GetSignalingState().npid_to_conn.find( + MakeCtxNpIdKey(ctxId, peer_npid)); + if (it == NpHandler::GetInstance().GetSignalingState().npid_to_conn.end()) { return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND; } - const auto conn_it = g_connections.find(it->second); - if (conn_it == g_connections.end()) { + const auto conn_it = NpHandler::GetInstance().GetSignalingState().connections.find(it->second); + if (conn_it == NpHandler::GetInstance().GetSignalingState().connections.end()) { return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND; } @@ -596,7 +537,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromPeerAddress(OrbisNpSignalingContextId ctxId, u32 peerAddr, u16 peerPort, OrbisNpSignalingConnectionId* outConnId) { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!outConnId) { @@ -606,9 +547,12 @@ sceNpSignalingGetConnectionFromPeerAddress(OrbisNpSignalingContextId ctxId, u32 return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; } - for (const auto& [id, ci] : g_connections) { - if (ci.ctx_id == ctxId && ci.addr == peerAddr && ci.port == peerPort && - ConnectionStateFromStatus(ci.status) == 10) { + for (const auto& [id, ci] : NpHandler::GetInstance().GetSignalingState().connections) { + const auto transport_it = + NpHandler::GetInstance().GetSignalingState().peer_transports.find(ci.transport_id); + if (transport_it != NpHandler::GetInstance().GetSignalingState().peer_transports.end() && + ci.ctx_id == ctxId && transport_it->second.addr == peerAddr && + transport_it->second.port == peerPort && ConnectionStateFromStatus(ci.status) == 10) { *outConnId = id; LOG_INFO(Lib_NpSignaling, "ctxId={} addr={:#x} port={} connId={}", ctxId, peerAddr, sceNetNtohs(peerPort), id); @@ -622,14 +566,15 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromPeerAddressA( OrbisNpSignalingContextId ctxId, const OrbisNpSignalingAccountPlatformPair* peerAddr, OrbisNpSignalingConnectionId* outConnId) { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!peerAddr || !outConnId) { return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT; } - const auto ctx_it = g_contexts.find(ctxId); - if (ctx_it == g_contexts.end() || !ctx_it->second.active) { + const auto ctx_it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctxId); + if (ctx_it == NpHandler::GetInstance().GetSignalingState().contexts.end() || + !ctx_it->second.active) { return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; } if ((ctx_it->second.flags & 0x4) == 0) { @@ -642,11 +587,11 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromPeerAddressA( s32 PS4_SYSV_ABI sceNpSignalingSetContextOption(OrbisNpSignalingContextId ctxId, s32 optionId, s32 optionValue) { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } - const auto it = g_contexts.find(ctxId); - if (it == g_contexts.end() || !it->second.active) { + const auto it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctxId); + if (it == NpHandler::GetInstance().GetSignalingState().contexts.end() || !it->second.active) { return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; } if (optionId != ORBIS_NP_SIGNALING_CONTEXT_OPTION_FLAG) { @@ -665,14 +610,14 @@ s32 PS4_SYSV_ABI sceNpSignalingSetContextOption(OrbisNpSignalingContextId ctxId, s32 PS4_SYSV_ABI sceNpSignalingGetContextOption(OrbisNpSignalingContextId ctxId, s32 optionId, s32* outOptionValue) { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!outOptionValue) { return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT; } - const auto it = g_contexts.find(ctxId); - if (it == g_contexts.end() || !it->second.active) { + const auto it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctxId); + if (it == NpHandler::GetInstance().GetSignalingState().contexts.end() || !it->second.active) { return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; } if (optionId != ORBIS_NP_SIGNALING_CONTEXT_OPTION_FLAG) { @@ -686,7 +631,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetLocalNetInfo(OrbisNpSignalingContextId ctxId, OrbisNpSignalingNetInfo* info) { { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } } @@ -698,7 +643,14 @@ s32 PS4_SYSV_ABI sceNpSignalingGetLocalNetInfo(OrbisNpSignalingContextId ctxId, } auto* netinfo = Common::Singleton::Instance(); + // GetIp() is empty until something asks for it. RetrieveIp() connects out, so only once. + if (netinfo->GetIp().empty()) { + netinfo->RetrieveIp(); + } info->localAddr = ParseIpv4Nbo(netinfo->GetIp()); + if (info->localAddr == 0) { + info->localAddr = ParseIpv4Nbo("127.0.0.1"); + } NetCtl::OrbisNetCtlNatInfo nat_info{}; nat_info.size = sizeof(nat_info); @@ -723,7 +675,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfo(OrbisNpSignalingContextId ctxId, c OrbisNpSignalingRequestId* outReqId) { { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } } @@ -756,14 +708,15 @@ s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoA(OrbisNpSignalingContextId ctxId, const void* peerAccountPayload, OrbisNpSignalingRequestId* outReqId) { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!peerAccountPayload || !outReqId) { return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT; } - const auto ctx_it = g_contexts.find(ctxId); - if (ctx_it == g_contexts.end() || !ctx_it->second.active) { + const auto ctx_it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctxId); + if (ctx_it == NpHandler::GetInstance().GetSignalingState().contexts.end() || + !ctx_it->second.active) { return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; } if ((ctx_it->second.flags & 0x4) == 0) { @@ -780,7 +733,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoA(OrbisNpSignalingContextId ctxId, s32 PS4_SYSV_ABI sceNpSignalingCancelPeerNetInfo(OrbisNpSignalingContextId ctxId, s32 reqOrConnId) { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!IsContextValidLocked(ctxId)) { @@ -797,7 +750,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoResult(OrbisNpSignalingContextId ct OrbisNpSignalingConnectionId connId, OrbisNpSignalingNetInfo* peerNetInfo) { SignalingMutexGuard lock; - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!peerNetInfo) { @@ -824,7 +777,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoResult(OrbisNpSignalingContextId ct } s32 PS4_SYSV_ABI sceNpSignalingGetMemoryInfo(OrbisNpSignalingMemoryInfo* info) { - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!info) { @@ -838,7 +791,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetMemoryInfo(OrbisNpSignalingMemoryInfo* info) { s32 PS4_SYSV_ABI sceNpSignalingGetConnectionStatistics(OrbisNpSignalingConnectionStatistics* stats) { - if (!g_initialized) { + if (!NpHandler::GetInstance().GetSignalingState().initialized) { return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; } if (!stats) { @@ -851,55 +804,4 @@ sceNpSignalingGetConnectionStatistics(OrbisNpSignalingConnectionStatistics* stat return ORBIS_OK; } -void RegisterLib(Core::Loader::SymbolsResolver* sym) { - LIB_FUNCTION("0UvTFeomAUM", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingActivateConnection); - LIB_FUNCTION("ZPLavCKqAB0", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingActivateConnectionA); - LIB_FUNCTION("X1G4kkN2R-8", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingCancelPeerNetInfo); - LIB_FUNCTION("5yYjEdd4t8Y", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingCreateContext); - LIB_FUNCTION("dDLNFdY8dws", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingCreateContextA); - LIB_FUNCTION("6UEembipgrM", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingDeactivateConnection); - LIB_FUNCTION("hx+LIg-1koI", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingDeleteContext); - LIB_FUNCTION("GQ0hqmzj0F4", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetConnectionFromNpId); - LIB_FUNCTION("CkPxQjSm018", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetConnectionFromPeerAddress); - LIB_FUNCTION("B7cT9aVby7A", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetConnectionFromPeerAddressA); - LIB_FUNCTION("AN3h0EBSX7A", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetConnectionInfo); - LIB_FUNCTION("rcylknsUDwg", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetConnectionInfoA); - LIB_FUNCTION("C6ZNCDTj00Y", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetConnectionStatistics); - LIB_FUNCTION("bD-JizUb3JM", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetConnectionStatus); - LIB_FUNCTION("npU5V56id34", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetContextOption); - LIB_FUNCTION("U8AQMlOFBc8", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetLocalNetInfo); - LIB_FUNCTION("tOpqyDyMje4", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetMemoryInfo); - LIB_FUNCTION("zFgFHId7vAE", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetPeerNetInfo); - LIB_FUNCTION("Shr7bZq8QHY", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetPeerNetInfoA); - LIB_FUNCTION("2HajCEGgG4s", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingGetPeerNetInfoResult); - LIB_FUNCTION("3KOuC4RmZZU", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingInitialize); - LIB_FUNCTION("IHRDvZodPYY", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingSetContextOption); - LIB_FUNCTION("NPhw0UXaNrk", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingTerminate); - LIB_FUNCTION("b4qaXPzMJxo", "libSceNpSignaling", 1, "libSceNpSignaling", - sceNpSignalingTerminateConnection); -} - } // namespace Libraries::Np::NpSignaling diff --git a/src/core/libraries/np/np_signaling/np_signaling_helpers.cpp b/src/core/libraries/np/np_signaling/np_signaling_helpers.cpp index 6969c0bf5..bb311353e 100644 --- a/src/core/libraries/np/np_signaling/np_signaling_helpers.cpp +++ b/src/core/libraries/np/np_signaling/np_signaling_helpers.cpp @@ -1,8 +1,13 @@ // SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include #include #include +#include +#include #include #include "common/alignment.h" #include "common/logging/log.h" @@ -10,19 +15,26 @@ #include "core/libraries/error_codes.h" #include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/memory.h" -#include "core/libraries/network/net.h" -#include "core/libraries/network/net_util.h" +#include "core/libraries/libs.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_util.h" #include "core/libraries/np/np_common.h" #include "core/libraries/np/np_error.h" +#include "core/libraries/np/np_handler.h" +#include "core/libraries/np/np_signaling/np_signaling.h" #include "core/libraries/np/np_signaling/np_signaling_helpers.h" #include "core/libraries/np/np_signaling/np_signaling_state.h" -#include "core/libraries/np/np_signaling/np_signaling_stubs.h" +#include "core/libraries/np/np_signaling/np_signaling_transport.h" +#include "core/libraries/np/signaling_handler.h" +#include "core/memory.h" namespace Libraries::Np::NpSignaling::Helpers { namespace { constexpr s32 InferredNpAppType = 0; +constexpr uintptr_t kHostPageSize = 0x1000; +constexpr size_t kDumpRowSize = 32; bool g_signaling_heap_initialized = false; void* g_signaling_heap_base = nullptr; @@ -50,8 +62,84 @@ s16 GetAppTypeStateGate() { void SetAppTypeMarker(u16 marker) { LOG_DEBUG(Lib_NpSignaling, "marker={:#x}", marker); } + +std::string HexDumpRow(const u8* bytes, size_t size) { + static constexpr char kHex[] = "0123456789abcdef"; + std::string result; + result.reserve(size * 3); + for (size_t i = 0; i < size; ++i) { + if (i != 0) { + result.push_back(' '); + } + result.push_back(kHex[bytes[i] >> 4]); + result.push_back(kHex[bytes[i] & 0xf]); + } + return result; +} + +std::string AsciiDumpRow(const u8* bytes, size_t size) { + std::string result; + result.reserve(size); + for (size_t i = 0; i < size; ++i) { + result.push_back(std::isprint(bytes[i]) ? static_cast(bytes[i]) : '.'); + } + return result; +} + +void LogMemoryWindow(std::string_view label, const void* focus, size_t focus_size, size_t before, + size_t after) { + if (!focus) { + LOG_INFO(Lib_NpSignaling, "Invalid activation dump {}: unavailable (null focus)", label); + return; + } + + const uintptr_t focus_addr = reinterpret_cast(focus); + const uintptr_t page_start = focus_addr & ~(kHostPageSize - 1); + const uintptr_t page_end = page_start + kHostPageSize; + const uintptr_t start = std::max(page_start, focus_addr - std::min(before, focus_addr)); + const uintptr_t requested_end = focus_addr + focus_size + after; + const uintptr_t end = std::min(page_end, requested_end); + const size_t size = end > start ? end - start : 0; + + auto* memory = Core::Memory::Instance(); + if (size == 0 || !memory || !memory->IsValidMapping(start, size)) { + LOG_INFO(Lib_NpSignaling, + "Invalid activation dump {}: unavailable start={:#x} size={:#x} focus={:#x}", + label, start, size, focus_addr); + return; + } + + LOG_INFO(Lib_NpSignaling, + "Invalid activation dump {}: start={:#x} size={:#x} focus={:#x} focus_offset={:#x} " + "focus_size={:#x}", + label, start, size, focus_addr, focus_addr - start, focus_size); + const auto* bytes = reinterpret_cast(start); + for (size_t offset = 0; offset < size; offset += kDumpRowSize) { + const size_t row_size = std::min(kDumpRowSize, size - offset); + LOG_INFO(Lib_NpSignaling, "Invalid activation dump {} {:#x} +{:#05x}: {} |{}|", label, + start + offset, offset, HexDumpRow(bytes + offset, row_size), + AsciiDumpRow(bytes + offset, row_size)); + } +} + } // namespace +void LogInvalidActivationContext(s64 call_time, const void* peer_npid, const void* out_conn_id, + const void* caller, const void* frame) { + LOG_INFO(Lib_NpSignaling, + "Invalid activation context: t={} caller={:p} frame={:p} peerNpId={:p} " + "outConnId={:p} peer_to_out_delta={}", + call_time, caller, frame, peer_npid, out_conn_id, + reinterpret_cast(out_conn_id) - reinterpret_cast(peer_npid)); + + // Keep each read within the focus address's host page so diagnostics cannot cross into an + // unmapped guard page. The peer window is intentionally large enough to include adjacent room + // member/scratch records and, for the observed title, the nearby output connection ID. + LogMemoryWindow("peer", peer_npid, sizeof(OrbisNpId), 0x100, 0x200); + LogMemoryWindow("stack", frame, sizeof(uintptr_t) * 2, 0x100, 0x100); + LogMemoryWindow("caller_code", caller, 1, 0x40, 0x40); +} + void SetRuntimeHooks(const SignalingRuntimeHooks& hooks) { g_runtime_hooks = hooks; g_runtime_hooks_registered = true; @@ -142,13 +230,6 @@ s32 StartMainRuntime(s32 thread_priority, s32 cpu_affinity_mask, s64 thread_stac if (g_runtime_hooks.start_dispatch != nullptr) { g_runtime_hooks.start_dispatch(priority, affinity, stack_size); } - if (g_runtime_hooks.start_receive != nullptr) { - g_runtime_hooks.start_receive(priority, affinity, stack_size); - } - if (g_runtime_hooks.start_ping != nullptr) { - g_runtime_hooks.start_ping(priority, affinity, stack_size); - } - return ORBIS_OK; } @@ -156,16 +237,28 @@ s32 StartEchoRuntime(s32 thread_priority, s32 cpu_affinity_mask) { LOG_DEBUG(Lib_NpSignaling, "thread_priority={} cpu_affinity_mask={}", thread_priority, cpu_affinity_mask); - auto sock = Libraries::Net::sceNetSocket("SceNpSignalingIoctl", 2, 6, 0); + // A DGRAM_P2P socket, which also starts the P2P transport. + auto sock = + Libraries::Net::sceNetSocket("SceNpSignalingIoctl", Libraries::Net::ORBIS_NET_AF_INET, + Libraries::Net::ORBIS_NET_SOCK_DGRAM_P2P, 0); if (sock < 0) { - return sock; + // Only an emulator-side problem (the P2P UDP port is taken): signaling itself still + // works without the echo probe, so carry on as the console would. + LOG_WARNING(Lib_NpSignaling, "P2P socket for the echo probe failed: {:#x}", + static_cast(sock)); + return ORBIS_OK; } - const s32 ioctl_rc = Libraries::Net::sceNetIoctl(); + std::array ioctl_data{}; + const s32 ioctl_rc = Libraries::Net::sceNetIoctl(sock, 0xc02450ca, ioctl_data.data()); Libraries::Net::sceNetSocketClose(sock); g_echo_probe_word2 = 0; g_echo_probe_word3 = 0; + if (ioctl_rc >= 0) { + std::memcpy(&g_echo_probe_word2, ioctl_data.data() + 16, sizeof(g_echo_probe_word2)); + std::memcpy(&g_echo_probe_word3, ioctl_data.data() + 24, sizeof(g_echo_probe_word3)); + } g_echo_thread_handle = static_cast(-1); g_echo_thread_status = -1; return ioctl_rc; @@ -180,12 +273,6 @@ void ShutdownRuntime() { return; } - if (g_runtime_hooks.stop_ping != nullptr) { - g_runtime_hooks.stop_ping(); - } - if (g_runtime_hooks.stop_receive != nullptr) { - g_runtime_hooks.stop_receive(); - } if (g_runtime_hooks.stop_dispatch != nullptr) { g_runtime_hooks.stop_dispatch(); } @@ -200,268 +287,30 @@ void ShutdownRuntime() { namespace Libraries::Np::NpSignaling { -using Libraries::Net::sceNetNtohs; - -static Kernel::PthreadT g_dispatch_thread{}; -static Kernel::PthreadT g_receive_thread{}; -static bool g_receive_stop = false; -static Kernel::PthreadT g_ping_thread{}; -static bool g_ping_stop = false; - -static void HandleStunEcho(s32 ctx_id, const StunEcho& echo) { - SignalingMutexGuard lock; - const auto it = g_contexts.find(ctx_id); - if (it == g_contexts.end() || !it->second.active) { - return; - } - NpSignalingContext& ctx = it->second; - ctx.ext_addr.store(echo.ext_ip); - ctx.ext_port.store(echo.ext_port); - - LOG_DEBUG(Lib_NpSignaling, "STUN echo: ctxId={} ext_addr={:#x} ext_port={}", ctx_id, - echo.ext_ip, sceNetNtohs(echo.ext_port)); - - auto* netinfo = Common::Singleton::Instance(); - netinfo->SetExternalIp(echo.ext_ip); - - ctx.stun_cv.notify_all(); -} - -static bool HasSignalingMagic(const u8* buf, size_t nbytes) { - return nbytes >= 5 && std::memcmp(buf, kSignalingMagic, sizeof(kSignalingMagic)) == 0; -} - -static void DrainControlPackets() { - static constexpr u32 kBufSize = 256; - u8 buf[kBufSize]; - for (;;) { - u32 from_addr = 0; - u16 from_port = 0; - const int rc = Stubs::ControlRecvFrom(buf, kBufSize, &from_addr, &from_port); - if (rc <= 0) { - break; - } - const auto nbytes = static_cast(rc); - if (nbytes == sizeof(SignalingControl) && HasSignalingMagic(buf, nbytes) && - buf[4] == static_cast(SignalingPacketType::Control)) { - SignalingControl ctrl{}; - std::memcpy(&ctrl, buf, sizeof(ctrl)); - HandleControlPacket(from_addr, from_port, ctrl); - } else { - LOG_WARNING(Lib_NpSignaling, "ReceiveThread: bad control packet (size={})", nbytes); - } - } -} - -static void ReceiveThreadMain() { - static constexpr u32 kBufSize = 256; - u8 buf[kBufSize]; - - while (!g_receive_stop) { - DrainControlPackets(); - - u32 from_addr = 0; - u16 from_port = 0; - - const int rc = Stubs::SignalingRecvFrom(buf, kBufSize, &from_addr, &from_port); - if (rc <= 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - continue; - } - - const auto nbytes = static_cast(rc); - - if (nbytes != sizeof(StunEcho)) { - LOG_DEBUG( - Lib_NpSignaling, - "ReceiveThread DATA from {:#x}:{} size={} b0-4={:02x},{:02x},{:02x},{:02x},{:02x}", - from_addr, sceNetNtohs(from_port), nbytes, buf[0], buf[1], buf[2], buf[3], - nbytes >= 5 ? buf[4] : 0); - } - - if (nbytes == sizeof(StunEcho)) { - s32 ctx_id = 0; - { - SignalingMutexGuard lock; - for (const auto& [cid, ctx] : g_contexts) { - if (ctx.active) { - ctx_id = cid; - break; - } - } - } - if (ctx_id == 0) { - continue; - } - StunEcho echo{}; - std::memcpy(&echo, buf, sizeof(echo)); - HandleStunEcho(ctx_id, echo); - continue; - } - - if (!HasSignalingMagic(buf, nbytes)) { - LOG_WARNING(Lib_NpSignaling, "ReceiveThread: dropping non-SHAD packet (size={})", - nbytes); - continue; - } - const u8 type = buf[4]; - - if (nbytes == sizeof(SignalingEchoPing) && - type == static_cast(SignalingPacketType::EchoPing)) { - SignalingEchoPing ping{}; - std::memcpy(&ping, buf, sizeof(ping)); - SignalingEchoPong pong{}; - pong.conn_id = ping.conn_id; - pong.orig_ts_us = ping.send_ts_us; - Stubs::SignalingSendTo(&pong, sizeof(pong), from_addr, from_port); - continue; - } - if (nbytes == sizeof(SignalingEchoPong) && - type == static_cast(SignalingPacketType::EchoPong)) { - SignalingEchoPong pong{}; - std::memcpy(&pong, buf, sizeof(pong)); - const s64 now = NowUs(); - s32 rtt_us = static_cast(now - static_cast(pong.orig_ts_us)); - if (rtt_us < 0) { - rtt_us = 0; - } - { - SignalingMutexGuard lock; - RecordRttSampleLocked(static_cast(pong.conn_id), rtt_us); - } - continue; - } - if (nbytes == sizeof(SignalingHandshake) && - type == static_cast(SignalingPacketType::Handshake)) { - SignalingHandshake hs{}; - std::memcpy(&hs, buf, sizeof(hs)); - HandleHandshakePacket(from_addr, from_port, hs); - continue; - } - - LOG_WARNING(Lib_NpSignaling, "ReceiveThread: unexpected SHAD packet type={} size={}", type, - nbytes); - } -} - -static PS4_SYSV_ABI void* ReceiveThreadFunc(void*) { - ReceiveThreadMain(); - return nullptr; -} - -static void StartReceiveThread(s32 priority, u64 affinity_mask, u64 stack_size) { - g_receive_stop = false; - if (!g_receive_thread) { - NpCommon::sceNpCreateThread(&g_receive_thread, ReceiveThreadFunc, nullptr, priority, - stack_size, affinity_mask, "SceNpSignalingRecv"); - } -} - -static void StopReceiveThread() { - { - SignalingMutexGuard lock; - g_receive_stop = true; - } - if (g_receive_thread) { - NpCommon::sceNpJoinThread(g_receive_thread, nullptr); - g_receive_thread = {}; - } -} - -static void PingThreadMain() { - while (!g_ping_stop) { - if (!Stubs::EnsureTransport()) { - std::this_thread::sleep_for(std::chrono::milliseconds(kSigRetryMs)); - continue; - } - - struct CtxSnapshot { - s32 ctx_id; - OrbisNpOnlineId online_id{}; - bool resolved; - }; - std::vector contexts; - { - SignalingMutexGuard lock; - for (const auto& [ctx_id, ctx] : g_contexts) { - if (ctx.active) { - contexts.push_back({ctx_id, ctx.owner_online_id, ctx.ext_addr.load() != 0}); - } - } - } - - if (Stubs::Matching2Enabled()) { - const u32 server_addr = Stubs::MmServerAddr(); - const u16 server_port = Stubs::MmServerUdpPort(); - - for (const auto& cs : contexts) { - if (server_addr == 0 || server_port == 0) { - break; - } - StunPing ping{}; - ping.cmd = 0x01; - std::memcpy(ping.online_id, cs.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); - ping.local_ip = Stubs::AdvertisedAddr(); - - Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_port); - } - } - - SendEchoPings(); - - const bool any_unresolved = std::any_of(contexts.begin(), contexts.end(), - [](const auto& cs) { return !cs.resolved; }); - const u32 sleep_ms = any_unresolved ? kSigRetryMs : kSigPingMs; - std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); - } -} - -static PS4_SYSV_ABI void* PingThreadFunc(void*) { - PingThreadMain(); - return nullptr; -} - -static void StartPingThread(s32 priority, u64 affinity_mask, u64 stack_size) { - g_ping_stop = false; - if (!g_ping_thread) { - NpCommon::sceNpCreateThread(&g_ping_thread, PingThreadFunc, nullptr, priority, stack_size, - affinity_mask, "SceNpSignalingPing"); - } -} - -static void StopPingThread() { - g_ping_stop = true; - if (g_ping_thread) { - NpCommon::sceNpJoinThread(g_ping_thread, nullptr); - g_ping_thread = {}; - } -} - static void DispatchThreadMain() { + auto& state = NpHandler::GetInstance().GetSignalingState(); for (;;) { - ProcessPendingActivations(); - QueuedDispatch dispatch; { - std::unique_lock lock(g_dispatch_mutex); + std::unique_lock lock(state.dispatch_mutex); for (;;) { - if (g_dispatch_stop) { + if (state.dispatch_stop) { return; } - if (g_dispatch_queue.empty()) { - g_dispatch_cv.wait_for(lock, std::chrono::seconds(1), [] { - return g_dispatch_stop || !g_dispatch_queue.empty(); + if (state.dispatch_queue.empty()) { + state.dispatch_cv.wait_for(lock, std::chrono::seconds(1), [&state] { + return state.dispatch_stop || !state.dispatch_queue.empty(); }); break; } - const auto it = g_dispatch_queue.begin(); + const auto it = state.dispatch_queue.begin(); const auto now = std::chrono::steady_clock::now(); if (it->first > now) { - g_dispatch_cv.wait_until(lock, it->first); + state.dispatch_cv.wait_until(lock, it->first); break; } dispatch = std::move(it->second); - g_dispatch_queue.erase(it); + state.dispatch_queue.erase(it); break; } } @@ -488,36 +337,85 @@ static PS4_SYSV_ABI void* DispatchThreadFunc(void*) { } static void StartDispatchThread(s32 priority, u64 affinity_mask, u64 stack_size) { - std::lock_guard lock(g_dispatch_mutex); - g_dispatch_stop = false; - if (!g_dispatch_thread) { - NpCommon::sceNpCreateThread(&g_dispatch_thread, DispatchThreadFunc, nullptr, priority, + auto& state = NpHandler::GetInstance().GetSignalingState(); + std::lock_guard lock(state.dispatch_mutex); + state.dispatch_stop = false; + if (!state.dispatch_thread) { + NpCommon::sceNpCreateThread(&state.dispatch_thread, DispatchThreadFunc, nullptr, priority, stack_size, affinity_mask, "SceNpSignalingMain"); } } static void StopDispatchThread() { + auto& state = NpHandler::GetInstance().GetSignalingState(); { - std::lock_guard lock(g_dispatch_mutex); - g_dispatch_stop = true; - g_dispatch_queue.clear(); + std::lock_guard lock(state.dispatch_mutex); + state.dispatch_stop = true; + state.dispatch_queue.clear(); } - g_dispatch_cv.notify_all(); - if (g_dispatch_thread) { - NpCommon::sceNpJoinThread(g_dispatch_thread, nullptr); - g_dispatch_thread = {}; + state.dispatch_cv.notify_all(); + if (state.dispatch_thread) { + NpCommon::sceNpJoinThread(state.dispatch_thread, nullptr); + state.dispatch_thread = {}; } } void RegisterRuntimeHooks() { Helpers::SetRuntimeHooks({ .start_dispatch = StartDispatchThread, - .start_receive = StartReceiveThread, - .start_ping = StartPingThread, - .stop_ping = StopPingThread, - .stop_receive = StopReceiveThread, .stop_dispatch = StopDispatchThread, }); } +void RegisterLib(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("0UvTFeomAUM", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingActivateConnection); + LIB_FUNCTION("ZPLavCKqAB0", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingActivateConnectionA); + LIB_FUNCTION("X1G4kkN2R-8", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingCancelPeerNetInfo); + LIB_FUNCTION("5yYjEdd4t8Y", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingCreateContext); + LIB_FUNCTION("dDLNFdY8dws", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingCreateContextA); + LIB_FUNCTION("6UEembipgrM", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingDeactivateConnection); + LIB_FUNCTION("hx+LIg-1koI", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingDeleteContext); + LIB_FUNCTION("GQ0hqmzj0F4", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetConnectionFromNpId); + LIB_FUNCTION("CkPxQjSm018", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetConnectionFromPeerAddress); + LIB_FUNCTION("B7cT9aVby7A", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetConnectionFromPeerAddressA); + LIB_FUNCTION("AN3h0EBSX7A", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetConnectionInfo); + LIB_FUNCTION("rcylknsUDwg", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetConnectionInfoA); + LIB_FUNCTION("C6ZNCDTj00Y", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetConnectionStatistics); + LIB_FUNCTION("bD-JizUb3JM", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetConnectionStatus); + LIB_FUNCTION("npU5V56id34", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetContextOption); + LIB_FUNCTION("U8AQMlOFBc8", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetLocalNetInfo); + LIB_FUNCTION("tOpqyDyMje4", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetMemoryInfo); + LIB_FUNCTION("zFgFHId7vAE", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetPeerNetInfo); + LIB_FUNCTION("Shr7bZq8QHY", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetPeerNetInfoA); + LIB_FUNCTION("2HajCEGgG4s", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingGetPeerNetInfoResult); + LIB_FUNCTION("3KOuC4RmZZU", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingInitialize); + LIB_FUNCTION("IHRDvZodPYY", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingSetContextOption); + LIB_FUNCTION("NPhw0UXaNrk", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingTerminate); + LIB_FUNCTION("b4qaXPzMJxo", "libSceNpSignaling", 1, "libSceNpSignaling", + sceNpSignalingTerminateConnection); +} + } // namespace Libraries::Np::NpSignaling diff --git a/src/core/libraries/np/np_signaling/np_signaling_helpers.h b/src/core/libraries/np/np_signaling/np_signaling_helpers.h index 2e54afbe4..4ccf33a40 100644 --- a/src/core/libraries/np/np_signaling/np_signaling_helpers.h +++ b/src/core/libraries/np/np_signaling/np_signaling_helpers.h @@ -9,14 +9,12 @@ namespace Libraries::Np::NpSignaling::Helpers { struct SignalingRuntimeHooks { void (*start_dispatch)(s32 priority, u64 affinity_mask, u64 stack_size); - void (*start_receive)(s32 priority, u64 affinity_mask, u64 stack_size); - void (*start_ping)(s32 priority, u64 affinity_mask, u64 stack_size); - void (*stop_ping)(); - void (*stop_receive)(); void (*stop_dispatch)(); }; void SetRuntimeHooks(const SignalingRuntimeHooks& hooks); +void LogInvalidActivationContext(s64 call_time, const void* peer_npid, const void* out_conn_id, + const void* caller, const void* frame); s32 CheckInitializeAppType(u32* is_app_type_4); s32 InitSignalingHeap(s64 pool_size); void ShutdownSignalingHeap(); diff --git a/src/core/libraries/np/np_signaling/np_signaling_state.cpp b/src/core/libraries/np/np_signaling/np_signaling_state.cpp index 948e602c4..348da9f74 100644 --- a/src/core/libraries/np/np_signaling/np_signaling_state.cpp +++ b/src/core/libraries/np/np_signaling/np_signaling_state.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "common/logging/log.h" @@ -13,125 +14,69 @@ #include "core/libraries/error_codes.h" #include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/process.h" -#include "core/libraries/network/net.h" -#include "core/libraries/network/net_util.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_util.h" #include "core/libraries/np/np_error.h" +#include "core/libraries/np/np_handler.h" #include "core/libraries/np/np_signaling/np_signaling_state.h" -#include "core/libraries/np/np_signaling/np_signaling_stubs.h" +#include "core/libraries/np/np_signaling/np_signaling_transport.h" namespace Libraries::Np::NpSignaling { using Libraries::Net::sceNetNtohs; -std::unordered_map g_contexts; -std::unordered_map g_connections; -std::unordered_map g_npid_to_conn; -std::unordered_map g_peer_netinfo_results; -std::vector g_pending_activations; -u32 g_peer_netinfo_next_id = 1; -u32 g_peak_connection_count = 0; -u32 g_last_assigned_context_id = 0; -u32 g_connection_id_seed = 0; -bool g_initialized = false; -Libraries::Kernel::PthreadMutexT g_mutex_storage{}; +namespace { -void InitSignalingMutex() { - NpCommon::sceNpLwMutexInit(&g_mutex_storage, "SceNpSignalingLock", 1); -} -void DestroySignalingMutex() { - NpCommon::sceNpLwMutexDestroy(&g_mutex_storage); -} - -SignalingMutexGuard::SignalingMutexGuard() { - NpCommon::sceNpLwMutexLock(&g_mutex_storage); -} -SignalingMutexGuard::~SignalingMutexGuard() { - NpCommon::sceNpLwMutexUnlock(&g_mutex_storage); -} - -std::multimap g_dispatch_queue; -std::mutex g_dispatch_mutex; -std::condition_variable g_dispatch_cv; -bool g_dispatch_stop = false; - -NpCommon::OrbisNpCalloutContext g_callout_ctx{}; -bool g_callout_ctx_active = false; - -constexpr s64 kHandshakeConnectTimeoutMs = 30'000; -constexpr s64 kHandshakeRetransmitMs = 500; -constexpr s64 kKeepaliveIntervalMs = 45'000; -constexpr s64 kEstablishedLivenessTimeoutMs = 60'000; - -static void PS4_SYSV_ABI TimeoutCalloutHandler(u64 arg); -static void PS4_SYSV_ABI StepCalloutHandler(u64 arg); - -static void ArmTimeoutCalloutLocked(ConnectionInfo& ci, s64 delay_us) { - if (!g_callout_ctx_active) { - return; +std::string HexBytes(const void* data, size_t size) { + static constexpr char kHex[] = "0123456789abcdef"; + const auto* bytes = static_cast(data); + std::string result; + result.reserve(size * 2); + for (size_t i = 0; i < size; ++i) { + result.push_back(kHex[bytes[i] >> 4]); + result.push_back(kHex[bytes[i] & 0xf]); } - if (ci.timeout_callout_armed) { - u32 removed = 0; - NpCommon::sceNpCalloutStopOnCtx(&g_callout_ctx, &ci.timeout_callout, &removed); - ci.timeout_callout_armed = false; - } - NpCommon::sceNpCalloutStartOnCtx64(&g_callout_ctx, &ci.timeout_callout, delay_us, - reinterpret_cast(&TimeoutCalloutHandler), - static_cast(ci.conn_id)); - ci.timeout_callout_armed = true; + return result; } -static void CancelTimeoutCalloutLocked(ConnectionInfo& ci) { - if (ci.timeout_callout_armed) { - u32 removed = 0; - NpCommon::sceNpCalloutStopOnCtx(&g_callout_ctx, &ci.timeout_callout, &removed); - ci.timeout_callout_armed = false; +std::string PrintableOnlineId(const OrbisNpOnlineId& online_id) { + std::string result; + result.reserve(ORBIS_NP_ONLINEID_MAX_LENGTH); + for (const char value : online_id.data) { + const auto byte = static_cast(value); + result.push_back(std::isprint(byte) ? value : '.'); } + return result; } -static void ArmStepCalloutLocked(ConnectionInfo& ci, s64 delay_us) { - if (!g_callout_ctx_active) { - return; +const char* OnlineIdValidationFailure(const OrbisNpOnlineId& online_id) { + if (online_id.term != 0) { + return "term_nonzero"; } - if (ci.step_callout_armed) { - u32 removed = 0; - NpCommon::sceNpCalloutStopOnCtx(&g_callout_ctx, &ci.step_callout, &removed); - ci.step_callout_armed = false; + + const size_t len = strnlen(online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH + 1); + if (len < 3) { + return "length_below_minimum"; } - NpCommon::sceNpCalloutStartOnCtx64(&g_callout_ctx, &ci.step_callout, delay_us, - reinterpret_cast(&StepCalloutHandler), - static_cast(ci.conn_id)); - ci.step_callout_armed = true; + if (len > ORBIS_NP_ONLINEID_MAX_LENGTH) { + return "length_above_maximum"; + } + if (!std::isalnum(static_cast(online_id.data[0]))) { + return "first_character_invalid"; + } + for (size_t i = 1; i < len; ++i) { + const auto value = static_cast(online_id.data[i]); + if (!std::isalnum(value) && value != '_' && value != '-') { + return "character_invalid"; + } + } + return "validator_rejected"; } -static void CancelStepCalloutLocked(ConnectionInfo& ci) { - if (ci.step_callout_armed) { - u32 removed = 0; - NpCommon::sceNpCalloutStopOnCtx(&g_callout_ctx, &ci.step_callout, &removed); - ci.step_callout_armed = false; - } -} +} // namespace -static void CancelAllCalloutsLocked(ConnectionInfo& ci) { - CancelTimeoutCalloutLocked(ci); - CancelStepCalloutLocked(ci); -} - -void ArmConnectTimeoutLocked(OrbisNpSignalingConnectionId conn_id) { - const auto it = g_connections.find(conn_id); - if (it == g_connections.end()) { - return; - } - ArmTimeoutCalloutLocked(it->second, kHandshakeConnectTimeoutMs * 1000); -} - -void ClearLingerAndTimeoutLocked(OrbisNpSignalingConnectionId conn_id) { - const auto it = g_connections.find(conn_id); - if (it == g_connections.end()) { - return; - } - it->second.deactivate_lingering = false; - CancelTimeoutCalloutLocked(it->second); -} +SignalingMutexGuard::SignalingMutexGuard() + : lock(NpHandler::GetInstance().GetSignalingState().mutex) {} long long NowMs() { return std::chrono::duration_cast( @@ -178,6 +123,16 @@ s32 NormalizeNpId(const void* np_id, OrbisNpId* out_npid, OrbisNpOnlineId* out_o } std::memcpy(out_npid, np_id, sizeof(*out_npid)); if (!IsValidNpId(*out_npid)) { + const OrbisNpOnlineId& handle = out_npid->handle; + LOG_INFO(Lib_NpSignaling, + "rejected: reason={} handle_ascii='{}' handle_hex={} term={:#04x} " + "dummy_hex={} opt_hex={} reserved_hex={} raw_hex={}", + OnlineIdValidationFailure(handle), PrintableOnlineId(handle), + HexBytes(handle.data, sizeof(handle.data)), static_cast(handle.term), + HexBytes(handle.dummy, sizeof(handle.dummy)), + HexBytes(out_npid->opt, sizeof(out_npid->opt)), + HexBytes(out_npid->reserved, sizeof(out_npid->reserved)), + HexBytes(out_npid, sizeof(*out_npid))); return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT; } if (out_online_id) { @@ -194,18 +149,19 @@ CtxNpIdKey MakeCtxNpIdKey(s32 ctx_id, const OrbisNpId& npid) { } bool IsContextValidLocked(s32 ctx_id) { - const auto it = g_contexts.find(ctx_id); - return it != g_contexts.end() && it->second.active; + const auto it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctx_id); + return it != NpHandler::GetInstance().GetSignalingState().contexts.end() && it->second.active; } s32 AllocateContextIdLocked() { - u32 candidate = g_last_assigned_context_id + 1; - if (g_last_assigned_context_id == 8) { + u32 candidate = NpHandler::GetInstance().GetSignalingState().last_assigned_context_id + 1; + if (NpHandler::GetInstance().GetSignalingState().last_assigned_context_id == 8) { candidate = 1; } for (int tried = 0; tried < 8; ++tried) { - if (g_contexts.find(static_cast(candidate)) == g_contexts.end()) { - g_last_assigned_context_id = candidate; + if (NpHandler::GetInstance().GetSignalingState().contexts.find(static_cast( + candidate)) == NpHandler::GetInstance().GetSignalingState().contexts.end()) { + NpHandler::GetInstance().GetSignalingState().last_assigned_context_id = candidate; return static_cast(candidate); } candidate = (candidate == 8) ? 1u : candidate + 1u; @@ -214,22 +170,25 @@ s32 AllocateContextIdLocked() { } s32 AllocateConnectionIdLocked() { - if (g_connection_id_seed == 0) { + if (NpHandler::GetInstance().GetSignalingState().connection_id_seed == 0) { std::random_device rd; const u32 rnd = static_cast(rd()); - g_connection_id_seed = (rnd / 0x4001u) & 0x7fffu; - if (g_connection_id_seed == 0) { - g_connection_id_seed = 1; + NpHandler::GetInstance().GetSignalingState().connection_id_seed = (rnd / 0x4001u) & 0x7fffu; + if (NpHandler::GetInstance().GetSignalingState().connection_id_seed == 0) { + NpHandler::GetInstance().GetSignalingState().connection_id_seed = 1; } } for (int tried = 0; tried <= 0xffff; ++tried) { - const u32 candidate = g_connection_id_seed; - g_connection_id_seed = g_connection_id_seed + 1; - if (g_connection_id_seed == 0 || g_connection_id_seed > 0x7fff) { - g_connection_id_seed = 1; + const u32 candidate = NpHandler::GetInstance().GetSignalingState().connection_id_seed; + NpHandler::GetInstance().GetSignalingState().connection_id_seed = + NpHandler::GetInstance().GetSignalingState().connection_id_seed + 1; + if (NpHandler::GetInstance().GetSignalingState().connection_id_seed == 0 || + NpHandler::GetInstance().GetSignalingState().connection_id_seed > 0x7fff) { + NpHandler::GetInstance().GetSignalingState().connection_id_seed = 1; } - if (candidate != 0 && - g_connections.find(static_cast(candidate)) == g_connections.end()) { + if (candidate != 0 && NpHandler::GetInstance().GetSignalingState().connections.find( + static_cast(candidate)) == + NpHandler::GetInstance().GetSignalingState().connections.end()) { return static_cast(candidate); } } @@ -237,22 +196,22 @@ s32 AllocateConnectionIdLocked() { } void RemoveConnectionLocked(s32 conn_id) { - const auto it = g_connections.find(conn_id); - if (it == g_connections.end()) { + const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(conn_id); + if (it == NpHandler::GetInstance().GetSignalingState().connections.end()) { return; } - CancelAllCalloutsLocked(it->second); const auto map_key = MakeCtxNpIdKey(it->second.ctx_id, it->second.npid); - auto key_it = g_npid_to_conn.find(map_key); - if (key_it != g_npid_to_conn.end() && key_it->second == conn_id) { - g_npid_to_conn.erase(key_it); + auto key_it = NpHandler::GetInstance().GetSignalingState().npid_to_conn.find(map_key); + if (key_it != NpHandler::GetInstance().GetSignalingState().npid_to_conn.end() && + key_it->second == conn_id) { + NpHandler::GetInstance().GetSignalingState().npid_to_conn.erase(key_it); } - g_connections.erase(it); + NpHandler::GetInstance().GetSignalingState().connections.erase(it); } void RemoveContextConnectionsLocked(s32 ctx_id) { std::vector to_remove; - for (const auto& [cid, ci] : g_connections) { + for (const auto& [cid, ci] : NpHandler::GetInstance().GetSignalingState().connections) { if (ci.ctx_id == ctx_id) { to_remove.push_back(cid); } @@ -363,10 +322,10 @@ u32 ParseIpv4Nbo(const std::string& dotted) { namespace { -s32 AverageProbeRtt(const ConnectionInfo& ci) { +s32 AverageProbeRtt(const PeerTransport& transport) { s64 sum = 0; s32 count = 0; - for (const s32 sample : ci.probe_rtt_samples) { + for (const s32 sample : transport.probe_rtt_samples) { if (sample > 0) { sum += sample; ++count; @@ -378,10 +337,10 @@ s32 AverageProbeRtt(const ConnectionInfo& ci) { return static_cast(sum / count); } -s32 ProbeLossPercent(const ConnectionInfo& ci) { +s32 ProbeLossPercent(const PeerTransport& transport) { s32 zero_samples = 0; s32 present_samples = 0; - for (const s32 sample : ci.probe_rtt_samples) { + for (const s32 sample : transport.probe_rtt_samples) { if (sample != -1) { ++present_samples; if (sample == 0) { @@ -402,14 +361,24 @@ s32 GetConnectionInfoInternal(s32 ctx_id, s32 conn_id, s32 info_code, void* out_ return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; } - const auto it = g_connections.find(conn_id); - if (it == g_connections.end() || it->second.ctx_id != ctx_id) { + const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(conn_id); + if (it == NpHandler::GetInstance().GetSignalingState().connections.end() || + it->second.ctx_id != ctx_id) { return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND; } const ConnectionInfo& ci = it->second; + const auto transport_it = + NpHandler::GetInstance().GetSignalingState().peer_transports.find(ci.transport_id); + const PeerTransport* transport = + transport_it != NpHandler::GetInstance().GetSignalingState().peer_transports.end() + ? &transport_it->second + : nullptr; const s32 state = ConnectionStateFromStatus(ci.status); const bool established = state == 10; + if (established && !transport) { + return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND; + } s32 result = ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT; @@ -418,7 +387,7 @@ s32 GetConnectionInfoInternal(s32 ctx_id, s32 conn_id, s32 info_code, void* out_ if (!established) { return ORBIS_NP_SIGNALING_ERROR_CONN_IN_PROGRESS; } - const s32 avg = AverageProbeRtt(ci); + const s32 avg = AverageProbeRtt(*transport); if (out_a) { *reinterpret_cast(out_a) = avg; } @@ -433,10 +402,10 @@ s32 GetConnectionInfoInternal(s32 ctx_id, s32 conn_id, s32 info_code, void* out_ return ORBIS_NP_SIGNALING_ERROR_CONN_IN_PROGRESS; } if (out_a) { - *reinterpret_cast(out_a) = ci.echo_derived_value; + *reinterpret_cast(out_a) = 0; } if (out_b) { - *reinterpret_cast(out_b) = ci.echo_derived_value; + *reinterpret_cast(out_b) = 0; } result = ORBIS_OK; break; @@ -455,12 +424,12 @@ s32 GetConnectionInfoInternal(s32 ctx_id, s32 conn_id, s32 info_code, void* out_ return ORBIS_NP_SIGNALING_ERROR_CONN_IN_PROGRESS; } if (out_a) { - *reinterpret_cast(out_a) = ci.addr; - *reinterpret_cast(reinterpret_cast(out_a) + 4) = ci.port; + *reinterpret_cast(out_a) = transport->addr; + *reinterpret_cast(reinterpret_cast(out_a) + 4) = transport->port; } if (out_b) { - *reinterpret_cast(out_b) = ci.addr; - *reinterpret_cast(reinterpret_cast(out_b) + 4) = ci.port; + *reinterpret_cast(out_b) = transport->addr; + *reinterpret_cast(reinterpret_cast(out_b) + 4) = transport->port; } result = ORBIS_OK; break; @@ -470,12 +439,12 @@ s32 GetConnectionInfoInternal(s32 ctx_id, s32 conn_id, s32 info_code, void* out_ return ORBIS_NP_SIGNALING_ERROR_CONN_IN_PROGRESS; } if (out_a) { - *reinterpret_cast(out_a) = ci.local_addr; - *reinterpret_cast(reinterpret_cast(out_a) + 4) = ci.local_port; + *reinterpret_cast(out_a) = transport->local_addr; + *reinterpret_cast(reinterpret_cast(out_a) + 4) = transport->local_port; } if (out_b) { - *reinterpret_cast(out_b) = ci.local_addr; - *reinterpret_cast(reinterpret_cast(out_b) + 4) = ci.local_port; + *reinterpret_cast(out_b) = transport->local_addr; + *reinterpret_cast(reinterpret_cast(out_b) + 4) = transport->local_port; } result = ORBIS_OK; break; @@ -484,7 +453,7 @@ s32 GetConnectionInfoInternal(s32 ctx_id, s32 conn_id, s32 info_code, void* out_ if (!established) { return ORBIS_NP_SIGNALING_ERROR_CONN_IN_PROGRESS; } - const s32 loss = ProbeLossPercent(ci); + const s32 loss = ProbeLossPercent(*transport); if (out_a) { *reinterpret_cast(out_a) = loss; } @@ -500,8 +469,8 @@ s32 GetConnectionInfoInternal(s32 ctx_id, s32 conn_id, s32 info_code, void* out_ break; } if (out_b) { - const auto ctx_it = g_contexts.find(ctx_id); - if (ctx_it != g_contexts.end() && + const auto ctx_it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctx_id); + if (ctx_it != NpHandler::GetInstance().GetSignalingState().contexts.end() && (ctx_it->second.account_id != 0 || ctx_it->second.platform_type != 0)) { auto* pair = reinterpret_cast(out_b); pair->accountId = ctx_it->second.account_id; @@ -526,7 +495,7 @@ void SnapshotConnectionStatisticsLocked(u32* out_peak, u32* out_active, u32* out u32* out_established) { u32 transient = 0; u32 established = 0; - for (const auto& [cid, ci] : g_connections) { + for (const auto& [cid, ci] : NpHandler::GetInstance().GetSignalingState().connections) { const s32 state = ConnectionStateFromStatus(ci.status); if (state == 10) { ++established; @@ -535,11 +504,11 @@ void SnapshotConnectionStatisticsLocked(u32* out_peak, u32* out_active, u32* out } } const u32 active = transient + established; - if (active > g_peak_connection_count) { - g_peak_connection_count = active; + if (active > NpHandler::GetInstance().GetSignalingState().peak_connection_count) { + NpHandler::GetInstance().GetSignalingState().peak_connection_count = active; } if (out_peak) { - *out_peak = g_peak_connection_count; + *out_peak = NpHandler::GetInstance().GetSignalingState().peak_connection_count; } if (out_active) { *out_active = active; @@ -554,7 +523,8 @@ void SnapshotConnectionStatisticsLocked(u32* out_peak, u32* out_active, u32* out OrbisNpSignalingRequestId StagePeerNetInfoResultLocked(OrbisNpSignalingContextId ctx_id) { const OrbisNpSignalingRequestId req_id = - kPeerNetInfoRequestIdPrefix | (g_peer_netinfo_next_id++); + kPeerNetInfoRequestIdPrefix | + (NpHandler::GetInstance().GetSignalingState().peer_netinfo_next_id++); PeerNetInfoResult result{}; result.ctx_id = ctx_id; @@ -562,20 +532,22 @@ OrbisNpSignalingRequestId StagePeerNetInfoResultLocked(OrbisNpSignalingContextId auto* netinfo = Common::Singleton::Instance(); result.external_ipv4 = netinfo->GetExternalIp(); result.nat_route_kind = static_cast(netinfo->GetNatType()); - g_peer_netinfo_results[result.conn_id] = result; + NpHandler::GetInstance().GetSignalingState().peer_netinfo_results[result.conn_id] = result; return req_id; } bool TakePeerNetInfoResultLocked(OrbisNpSignalingContextId ctx_id, s32 req_or_conn_id, PeerNetInfoResult* out) { - const auto it = g_peer_netinfo_results.find(req_or_conn_id); - if (it == g_peer_netinfo_results.end() || it->second.ctx_id != ctx_id) { + const auto it = + NpHandler::GetInstance().GetSignalingState().peer_netinfo_results.find(req_or_conn_id); + if (it == NpHandler::GetInstance().GetSignalingState().peer_netinfo_results.end() || + it->second.ctx_id != ctx_id) { return false; } if (out) { *out = it->second; } - g_peer_netinfo_results.erase(it); + NpHandler::GetInstance().GetSignalingState().peer_netinfo_results.erase(it); return true; } @@ -583,66 +555,6 @@ bool DropPeerNetInfoResultLocked(OrbisNpSignalingContextId ctx_id, s32 req_or_co return TakePeerNetInfoResultLocked(ctx_id, req_or_conn_id, nullptr); } -void RecordRttSampleLocked(OrbisNpSignalingConnectionId conn_id, s32 rtt_us) { - const auto it = g_connections.find(conn_id); - if (it == g_connections.end()) { - return; - } - ConnectionInfo& ci = it->second; - const u32 idx = ci.probe_sample_write_index % ConnectionInfo::kProbeSampleCount; - ci.probe_rtt_samples[idx] = rtt_us; - ci.probe_sample_write_index = idx + 1; -} - -void SendEchoPings() { - constexpr s64 kEchoIntervalUs = 1'000'000; - - struct PingTarget { - s32 conn_id; - u32 addr; - u16 port; - s64 now_us; - }; - std::vector targets; - { - SignalingMutexGuard lock; - const s64 now = NowUs(); - for (auto& [cid, ci] : g_connections) { - if (ci.status != ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE || ci.addr == 0 || - ci.port == 0) { - continue; - } - if (ci.last_echo_ping_us != 0 && now - ci.last_echo_ping_us < kEchoIntervalUs) { - continue; - } - ci.last_echo_ping_us = now; - targets.push_back({cid, ci.addr, ci.port, now}); - } - } - - for (const PingTarget& t : targets) { - SignalingEchoPing ping{}; - ping.conn_id = static_cast(t.conn_id); - ping.send_ts_us = static_cast(t.now_us); - Stubs::SignalingSendTo(&ping, sizeof(ping), t.addr, t.port); - } -} - -static s32 StatusFromState(ConnState state) { - if (state == ConnState::Established) { - return ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE; - } - if (state == ConnState::Inactive) { - return ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE; - } - return ORBIS_NP_SIGNALING_CONN_STATUS_PENDING; -} - -static void SetConnStateLocked(ConnectionInfo& ci, ConnState new_state) { - ci.state = new_state; - ci.status = StatusFromState(new_state); -} - static void StageBasicCallbackLocked(const NpSignalingContext& ctx, s32 ctx_id, s32 conn_id, s32 event_type, s32 event_data) { if (!ctx.callback) { @@ -658,39 +570,44 @@ static void StageBasicCallbackLocked(const NpSignalingContext& ctx, s32 ctx_id, dispatch.callback = ctx.callback; dispatch.callback_arg = ctx.callback_arg; { - std::lock_guard dlock(g_dispatch_mutex); - if (g_dispatch_stop) { + std::lock_guard dlock( + NpHandler::GetInstance().GetSignalingState().dispatch_mutex); + if (NpHandler::GetInstance().GetSignalingState().dispatch_stop) { return; } - g_dispatch_queue.emplace(dispatch.fire_at, std::move(dispatch)); + NpHandler::GetInstance().GetSignalingState().dispatch_queue.emplace(dispatch.fire_at, + std::move(dispatch)); } - g_dispatch_cv.notify_all(); + NpHandler::GetInstance().GetSignalingState().dispatch_cv.notify_all(); } void DispatchConnectionEvent(s32 conn_id, s32 event_type, s32 event_data) { SignalingMutexGuard lock; - const auto conn_it = g_connections.find(conn_id); - if (conn_it == g_connections.end()) { + const auto conn_it = NpHandler::GetInstance().GetSignalingState().connections.find(conn_id); + if (conn_it == NpHandler::GetInstance().GetSignalingState().connections.end()) { return; } const s32 owner_ctx = conn_it->second.ctx_id; - const auto ctx_it = g_contexts.find(owner_ctx); - if (ctx_it != g_contexts.end() && ctx_it->second.active) { + const auto ctx_it = NpHandler::GetInstance().GetSignalingState().contexts.find(owner_ctx); + if (ctx_it != NpHandler::GetInstance().GetSignalingState().contexts.end() && + ctx_it->second.active) { StageBasicCallbackLocked(ctx_it->second, owner_ctx, conn_id, event_type, event_data); } } void DispatchPeerActivatedEvent(s32 conn_id) { SignalingMutexGuard lock; - const auto conn_it = g_connections.find(conn_id); - if (conn_it == g_connections.end()) { + const auto conn_it = NpHandler::GetInstance().GetSignalingState().connections.find(conn_id); + if (conn_it == NpHandler::GetInstance().GetSignalingState().connections.end()) { return; } const s32 owner_ctx = conn_it->second.ctx_id; - const auto owner_it = g_contexts.find(owner_ctx); + const auto owner_it = NpHandler::GetInstance().GetSignalingState().contexts.find(owner_ctx); const OrbisNpId local_npid = - owner_it != g_contexts.end() ? owner_it->second.owner_npid : OrbisNpId{}; - for (auto& [cid, ctx] : g_contexts) { + owner_it != NpHandler::GetInstance().GetSignalingState().contexts.end() + ? owner_it->second.owner_npid + : OrbisNpId{}; + for (auto& [cid, ctx] : NpHandler::GetInstance().GetSignalingState().contexts) { if (cid == owner_ctx || !ctx.active) { continue; } @@ -700,652 +617,9 @@ void DispatchPeerActivatedEvent(s32 conn_id) { } } -void CloseConnectionAndDispatchDead(s32 conn_id, s32 error_code) { - bool fire = false; - { - SignalingMutexGuard lock; - const auto it = g_connections.find(conn_id); - if (it == g_connections.end()) { - return; - } - ConnectionInfo& ci = it->second; - if (ci.state == ConnState::Inactive || ci.dead_fired) { - return; - } - SetConnStateLocked(ci, ConnState::Inactive); - ci.dead_fired = true; - CancelAllCalloutsLocked(ci); - fire = true; - } - if (fire) { - LOG_DEBUG(Lib_NpSignaling, "Connection {} -> DEAD (errorCode={:#x})", conn_id, - static_cast(error_code)); - DispatchConnectionEvent(conn_id, ORBIS_NP_SIGNALING_EVENT_DEAD, error_code); - SignalingMutexGuard lock; - RemoveConnectionLocked(conn_id); - } -} - -static SignalingControl MakeControlLocked(const ConnectionInfo& ci, ControlKind kind); - -void EstablishConnection(s32 conn_id, bool peer_activated_hint) { - bool first_establish = false; - bool fire_established = false; - bool fire_peer_activated = false; - bool fire_mutual = false; - SignalingControl established_pkt{}; - bool send_established = false; - u32 peer_addr = 0; - u16 peer_port = 0; - { - SignalingMutexGuard lock; - const auto it = g_connections.find(conn_id); - if (it == g_connections.end()) { - return; - } - ConnectionInfo& ci = it->second; - if (peer_activated_hint) { - ci.peer_activated = true; - } - - if (!ci.established_fired) { - ci.state = ConnState::Established; - ci.status = ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE; - ci.established_fired = true; - CancelTimeoutCalloutLocked(ci); - ArmStepCalloutLocked(ci, kKeepaliveIntervalMs * 1000); - ci.last_peer_rx_us = NowUs(); - first_establish = true; - - if (ci.addr != 0 && ci.port != 0) { - established_pkt = MakeControlLocked(ci, ControlKind::Established); - send_established = true; - peer_addr = ci.addr; - peer_port = ci.port; - } - - if (!ci.locally_activated && !ci.peer_activated_fired) { - ci.peer_activated_fired = true; - fire_peer_activated = true; - } - } - - if (ci.locally_activated && ci.state == ConnState::Established && - !ci.established_event_fired) { - ci.established_event_fired = true; - fire_established = true; - } - - if (ci.locally_activated && ci.state == ConnState::Established && ci.peer_established && - !ci.mutual_fired) { - ci.mutual_fired = true; - fire_mutual = true; - } - } - - if (send_established) { - Stubs::ControlSendTo(&established_pkt, sizeof(established_pkt), peer_addr, peer_port); - } - if (first_establish) { - LOG_DEBUG(Lib_NpSignaling, "Connection {} -> ESTABLISHED ({})", conn_id, - fire_established ? "owner" : "answerer/non-owner"); - } - if (fire_peer_activated) { - DispatchPeerActivatedEvent(conn_id); - } - if (fire_established) { - DispatchConnectionEvent(conn_id, ORBIS_NP_SIGNALING_EVENT_ESTABLISHED, 0); - } - if (fire_mutual) { - DispatchConnectionEvent(conn_id, ORBIS_NP_SIGNALING_EVENT_MUTUAL_ACTIVATED, 0); - } -} - -void DeactivateConnectionFaithful(s32 conn_id) { - bool transient_close = false; - { - SignalingMutexGuard lock; - const auto it = g_connections.find(conn_id); - if (it == g_connections.end()) { - return; - } - ConnectionInfo& ci = it->second; - if (ci.state == ConnState::Established && !ci.dead_fired && ci.deactivate_lingering) { - return; - } - if (ci.state == ConnState::Established && !ci.dead_fired) { - SendControlCloseLocked(ci, ControlReason::Deactivate); - ci.locally_activated = false; - ci.deactivate_lingering = true; - CancelStepCalloutLocked(ci); - ArmTimeoutCalloutLocked(ci, 60'000'000); - LOG_DEBUG(Lib_NpSignaling, - "Connection {} deactivated (established) -> 60s keepalive linger before DEAD", - conn_id); - } else if (!ci.dead_fired) { - transient_close = true; - } - } - if (transient_close) { - CloseConnectionAndDispatchDead(conn_id, ORBIS_NP_SIGNALING_ERROR_TERMINATED_BY_MYSELF); - } -} - -void TerminateConnectionFaithful(s32 conn_id) { - { - SignalingMutexGuard lock; - const auto it = g_connections.find(conn_id); - if (it != g_connections.end()) { - SendControlCloseLocked(it->second, ControlReason::Terminate); - } - } - CloseConnectionAndDispatchDead(conn_id, ORBIS_NP_SIGNALING_ERROR_TERMINATED_BY_MYSELF); -} - -namespace { - -SignalingHandshake MakeHandshakeLocked(const ConnectionInfo& ci, HandshakeKind kind) { - SignalingHandshake pkt{}; - pkt.kind = static_cast(kind); - pkt.from_conn_id = static_cast(ci.conn_id); - pkt.to_conn_id = 0; - const auto ctx_it = g_contexts.find(ci.ctx_id); - if (ctx_it != g_contexts.end()) { - std::memcpy(pkt.online_id_from, ctx_it->second.owner_online_id.data, - ORBIS_NP_ONLINEID_MAX_LENGTH); - } - std::memcpy(pkt.online_id_to, ci.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); - auto* netinfo = Common::Singleton::Instance(); - pkt.mapped_addr = netinfo->GetExternalIp(); - pkt.mapped_port = 0; - return pkt; -} - -void SendHandshakeLocked(ConnectionInfo& ci, const SignalingHandshake& pkt) { - if (ci.addr == 0 || ci.port == 0) { - return; - } - ci.last_handshake_send_us = NowUs(); - const int sent = Stubs::SignalingSendTo(&pkt, sizeof(pkt), ci.addr, ci.port); - LOG_DEBUG(Lib_NpSignaling, "Handshake[{}] SEND kind={} -> {:#x}:{} ({} bytes, rc={})", - ci.conn_id, pkt.kind, ci.addr, sceNetNtohs(ci.port), sizeof(pkt), sent); -} - -} // namespace - -void StartHandshakeInitiator(OrbisNpSignalingConnectionId conn_id) { - SignalingHandshake offer{}; - bool send = false; - { - SignalingMutexGuard lock; - const auto it = g_connections.find(conn_id); - if (it == g_connections.end()) { - return; - } - ConnectionInfo& ci = it->second; - ci.is_initiator = true; - SetConnStateLocked(ci, ConnState::SendingOffer); - if (!ci.timeout_callout_armed) { - ArmTimeoutCalloutLocked(ci, kHandshakeConnectTimeoutMs * 1000); - } - ArmStepCalloutLocked(ci, kHandshakeRetransmitMs * 1000); - offer = MakeHandshakeLocked(ci, HandshakeKind::Offer); - SendHandshakeLocked(ci, offer); - send = true; - } - if (send) { - LOG_DEBUG(Lib_NpSignaling, "Handshake[{}]: -> SENDING_OFFER", conn_id); - } -} - -void QueueActivationLocked(OrbisNpSignalingConnectionId conn_id, std::string_view peer_online_id, - bool start_handshake) { - g_pending_activations.push_back({conn_id, std::string(peer_online_id), start_handshake}); -} - -void ProcessPendingActivations() { - std::vector work; - { - SignalingMutexGuard lock; - if (g_pending_activations.empty()) { - return; - } - work.swap(g_pending_activations); - } - - for (const PendingActivation& act : work) { - bool already_established = false; - { - SignalingMutexGuard lock; - const auto it = g_connections.find(act.conn_id); - if (it == g_connections.end() || it->second.state == ConnState::Inactive) { - continue; - } - } - - u32 peer_addr = 0; - u16 peer_port = 0; - const bool resolved = Stubs::ResolvePeer(act.peer_online_id, &peer_addr, &peer_port) && - peer_addr != 0 && peer_port != 0; - - if (!resolved) { - LOG_WARNING(Lib_NpSignaling, "peer '{}' endpoint unresolved; connection {} 30s timeout", - act.peer_online_id, act.conn_id); - continue; - } - - { - SignalingMutexGuard lock; - const auto it = g_connections.find(act.conn_id); - if (it == g_connections.end() || it->second.state == ConnState::Inactive) { - continue; - } - it->second.addr = peer_addr; - it->second.port = peer_port; - SendActivationRequestLocked(it->second); - already_established = it->second.state == ConnState::Established; - LOG_INFO(Lib_NpSignaling, "connection {} sent local activation to '{}' at {:#x}:{}{}", - act.conn_id, act.peer_online_id, peer_addr, sceNetNtohs(peer_port), - act.start_handshake ? "" : " (existing connection)"); - } - if (act.start_handshake) { - StartHandshakeInitiator(act.conn_id); - } else if (already_established) { - EstablishConnection(act.conn_id, false); - } - } -} - -void HandleHandshakePacket(u32 from_addr, u16 from_port, const SignalingHandshake& pkt) { - char from_id_buf[ORBIS_NP_ONLINEID_MAX_LENGTH + 1]{}; - std::memcpy(from_id_buf, pkt.online_id_from, ORBIS_NP_ONLINEID_MAX_LENGTH); - const std::string from_id(from_id_buf); - const HandshakeKind kind = static_cast(pkt.kind); - - s32 establish_conn = 0; - SignalingHandshake reply{}; - bool send_reply = false; - s32 reply_conn = 0; - - { - SignalingMutexGuard lock; - - s32 conn_id = 0; - for (auto& [cid, ci] : g_connections) { - if (ci.state == ConnState::Inactive || ci.dead_fired) { - continue; - } - if (OnlineIdEqualsString(ci.online_id, from_id)) { - conn_id = cid; - break; - } - } - - if (conn_id != 0) { - g_connections[conn_id].last_peer_rx_us = NowUs(); - } - - if (kind == HandshakeKind::Offer) { - if (conn_id == 0) { - return; - } - ConnectionInfo& ci = g_connections[conn_id]; - ci.addr = from_addr; - ci.port = from_port; - ci.peer_activated = true; - if (pkt.mapped_addr != 0) { - ci.addr = pkt.mapped_addr; - } - if (ci.state != ConnState::Established) { - SetConnStateLocked(ci, ConnState::SendingAccept); - if (!ci.timeout_callout_armed) { - ArmTimeoutCalloutLocked(ci, kHandshakeConnectTimeoutMs * 1000); - } - ArmStepCalloutLocked(ci, kHandshakeRetransmitMs * 1000); - reply = MakeHandshakeLocked(ci, HandshakeKind::Accept); - send_reply = true; - reply_conn = conn_id; - } - } else if (conn_id == 0) { - return; - } else if (kind == HandshakeKind::Accept) { - ConnectionInfo& ci = g_connections[conn_id]; - ci.peer_activated = true; - if (pkt.mapped_addr != 0) { - ci.addr = pkt.mapped_addr; - } else { - ci.addr = from_addr; - ci.port = from_port; - } - if (ci.state == ConnState::SendingOffer || ci.state == ConnState::WaitAccept || - ci.state == ConnState::SendingAccept || ci.state == ConnState::WaitOffer) { - SetConnStateLocked(ci, ConnState::ConnCheck); - ArmStepCalloutLocked(ci, kHandshakeRetransmitMs * 1000); - reply = MakeHandshakeLocked(ci, HandshakeKind::Check); - reply.nonce = static_cast(NowUs()); - send_reply = true; - reply_conn = conn_id; - } - } else if (kind == HandshakeKind::Check) { - ConnectionInfo& ci = g_connections[conn_id]; - reply = MakeHandshakeLocked(ci, HandshakeKind::CheckAck); - reply.nonce = pkt.nonce; - send_reply = true; - reply_conn = conn_id; - if (ci.state == ConnState::SendingAccept || ci.state == ConnState::WaitOffer) { - SetConnStateLocked(ci, ConnState::ConnCheck); - } - } else if (kind == HandshakeKind::CheckAck) { - ConnectionInfo& ci = g_connections[conn_id]; - const s64 rtt_us = NowUs() - static_cast(pkt.nonce); - if (rtt_us >= 0) { - RecordRttSampleLocked(conn_id, static_cast(rtt_us)); - } - if (ci.state != ConnState::Established) { - establish_conn = conn_id; - } - } - } - - if (send_reply) { - SignalingMutexGuard lock; - const auto it = g_connections.find(reply_conn); - if (it != g_connections.end()) { - SendHandshakeLocked(it->second, reply); - } - } - - if (establish_conn != 0) { - EstablishConnection(establish_conn, true); - } -} - -static void PS4_SYSV_ABI TimeoutCalloutHandler(u64 arg) { - const s32 conn_id = static_cast(arg); - bool fire_dead = false; - { - SignalingMutexGuard lock; - const auto it = g_connections.find(conn_id); - if (it == g_connections.end()) { - return; - } - ConnectionInfo& ci = it->second; - ci.timeout_callout_armed = false; - if (ci.state != ConnState::Established) { - fire_dead = true; - } else if (ci.deactivate_lingering) { - fire_dead = true; - } - } - if (fire_dead) { - CloseConnectionAndDispatchDead(conn_id, ORBIS_NP_SIGNALING_ERROR_TIMEOUT); - } -} - -static void PS4_SYSV_ABI StepCalloutHandler(u64 arg) { - const s32 conn_id = static_cast(arg); - bool liveness_dead = false; - { - SignalingMutexGuard lock; - const auto it = g_connections.find(conn_id); - if (it == g_connections.end()) { - return; - } - ConnectionInfo& ci = it->second; - ci.step_callout_armed = false; - const s64 now_us = NowUs(); - - if (ci.state == ConnState::Established) { - if (!ci.deactivate_lingering && ci.addr != 0 && ci.port != 0) { - if (ci.last_peer_rx_us != 0 && - now_us - ci.last_peer_rx_us >= kEstablishedLivenessTimeoutMs * 1000) { - liveness_dead = true; - } else { - SignalingHandshake pkt = MakeHandshakeLocked(ci, HandshakeKind::Check); - pkt.nonce = static_cast(now_us); - SendHandshakeLocked(ci, pkt); - ArmStepCalloutLocked(ci, kKeepaliveIntervalMs * 1000); - } - } - } else if (ci.state != ConnState::Inactive) { - if (ci.addr != 0 && ci.port != 0) { - HandshakeKind kind = HandshakeKind::Offer; - switch (ci.state) { - case ConnState::SendingOffer: - case ConnState::WaitAccept: - kind = HandshakeKind::Offer; - break; - case ConnState::SendingAccept: - case ConnState::WaitOffer: - kind = HandshakeKind::Accept; - break; - case ConnState::ConnCheck: - kind = HandshakeKind::Check; - break; - default: - break; - } - SignalingHandshake pkt = MakeHandshakeLocked(ci, kind); - if (kind == HandshakeKind::Check) { - pkt.nonce = static_cast(now_us); - } - SendHandshakeLocked(ci, pkt); - } - ArmStepCalloutLocked(ci, kHandshakeRetransmitMs * 1000); - } - } - if (liveness_dead) { - CloseConnectionAndDispatchDead(conn_id, ORBIS_NP_SIGNALING_ERROR_TIMEOUT); - } -} - -static SignalingControl MakeControlLocked(const ConnectionInfo& ci, ControlKind kind) { - SignalingControl pkt{}; - pkt.kind = static_cast(kind); - pkt.from_conn_id = static_cast(ci.conn_id); - const auto ctx_it = g_contexts.find(ci.ctx_id); - if (ctx_it != g_contexts.end()) { - std::memcpy(pkt.online_id_from, ctx_it->second.owner_online_id.data, - ORBIS_NP_ONLINEID_MAX_LENGTH); - } - std::memcpy(pkt.online_id_to, ci.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); - auto* netinfo = Common::Singleton::Instance(); - pkt.mapped_addr = netinfo->GetExternalIp(); - pkt.mapped_port = 0; - return pkt; -} - -void SendActivationRequestLocked(const ConnectionInfo& ci) { - if (ci.addr == 0 || ci.port == 0) { - return; - } - SignalingControl pkt = MakeControlLocked(ci, ControlKind::ActivationRequest); - Stubs::ControlSendTo(&pkt, sizeof(pkt), ci.addr, ci.port); -} - -void SendControlCloseLocked(const ConnectionInfo& ci, ControlReason reason) { - if (ci.addr == 0 || ci.port == 0) { - LOG_WARNING(Lib_NpSignaling, - "Control Close SEND skipped: connId={} reason={} unresolved endpoint", - ci.conn_id, static_cast(reason)); - return; - } - SignalingControl pkt = MakeControlLocked(ci, ControlKind::Close); - pkt.reason = static_cast(reason); - const int rc = Stubs::ControlSendTo(&pkt, sizeof(pkt), ci.addr, ci.port); - LOG_INFO(Lib_NpSignaling, "Control Close SEND: connId={} reason={} dst={:#x}:{} rc={}", - ci.conn_id, static_cast(reason), ci.addr, sceNetNtohs(ci.port), rc); -} - -void HandleControlPacket(u32 from_addr, u16 from_port, const SignalingControl& pkt) { - char from_id_buf[ORBIS_NP_ONLINEID_MAX_LENGTH + 1]{}; - std::memcpy(from_id_buf, pkt.online_id_from, ORBIS_NP_ONLINEID_MAX_LENGTH); - const std::string from_id(from_id_buf); - const ControlKind kind = static_cast(pkt.kind); - - s32 start_handshake_conn = 0; - s32 establish_conn = 0; - s32 dead_conn = 0; - s32 peer_deactivated_conn = 0; - SignalingControl ack{}; - bool send_ack = false; - u32 ack_addr = 0; - u16 ack_port = 0; - - { - SignalingMutexGuard lock; - - s32 conn_id = 0; - for (auto& [cid, ci] : g_connections) { - if (ci.state == ConnState::Inactive || ci.dead_fired) { - continue; - } - if (OnlineIdEqualsString(ci.online_id, from_id)) { - conn_id = cid; - break; - } - } - - if (kind == ControlKind::ActivationRequest) { - if (conn_id == 0) { - s32 ctx_id = 0; - char to_id_buf[ORBIS_NP_ONLINEID_MAX_LENGTH + 1]{}; - std::memcpy(to_id_buf, pkt.online_id_to, ORBIS_NP_ONLINEID_MAX_LENGTH); - const std::string to_id(to_id_buf); - for (auto& [cid, ctx] : g_contexts) { - if (ctx.active && OnlineIdEqualsString(ctx.owner_online_id, to_id)) { - ctx_id = cid; - break; - } - } - if (ctx_id == 0) { - return; - } - conn_id = AllocateConnectionIdLocked(); - if (conn_id < 0) { - return; - } - ConnectionInfo ci{}; - ci.conn_id = conn_id; - ci.ctx_id = ctx_id; - ci.locally_activated = false; - SetNpOnlineId(ci.online_id, - std::string_view(reinterpret_cast(pkt.online_id_from), - ORBIS_NP_ONLINEID_MAX_LENGTH)); - ci.npid.handle = ci.online_id; - g_connections[conn_id] = std::move(ci); - g_npid_to_conn[MakeCtxNpIdKey(ctx_id, g_connections[conn_id].npid)] = conn_id; - LOG_INFO(Lib_NpSignaling, - "Control: ActivationRequest from '{}' -> created answerer connection {}", - from_id, conn_id); - } - ConnectionInfo& ci = g_connections[conn_id]; - ci.peer_activated = true; - ci.addr = pkt.mapped_addr != 0 ? pkt.mapped_addr : from_addr; - ci.port = from_port; - if (ci.state == ConnState::Inactive) { - if (!ci.timeout_callout_armed) { - ArmTimeoutCalloutLocked(ci, kHandshakeConnectTimeoutMs * 1000); - } - start_handshake_conn = conn_id; - } - ack = MakeControlLocked(ci, ControlKind::ActivationAck); - send_ack = true; - ack_addr = ci.addr; - ack_port = ci.port; - } else if (conn_id == 0) { - LOG_INFO(Lib_NpSignaling, - "Control RECV ignored: kind={} from='{}' src={:#x}:{} no matching connection", - pkt.kind, from_id, from_addr, sceNetNtohs(from_port)); - return; - } else if (kind == ControlKind::ActivationAck) { - ConnectionInfo& ci = g_connections[conn_id]; - ci.peer_activated = true; - if (pkt.mapped_addr != 0) { - ci.addr = pkt.mapped_addr; - } - } else if (kind == ControlKind::Established) { - ConnectionInfo& ci = g_connections[conn_id]; - ci.peer_established = true; - if (ci.state == ConnState::Established) { - establish_conn = conn_id; - } - } else if (kind == ControlKind::Close) { - ConnectionInfo& ci = g_connections[conn_id]; - const ControlReason reason = static_cast(pkt.reason); - LOG_INFO(Lib_NpSignaling, - "Control Close RECV: connId={} from='{}' reason={} src={:#x}:{} state={}", - conn_id, from_id, pkt.reason, from_addr, sceNetNtohs(from_port), - static_cast(ci.state)); - if (reason == ControlReason::Deactivate && ci.state == ConnState::Established) { - peer_deactivated_conn = conn_id; - } else { - dead_conn = conn_id; - } - } - } - - if (send_ack && ack_addr != 0 && ack_port != 0) { - Stubs::ControlSendTo(&ack, sizeof(ack), ack_addr, ack_port); - } - if (start_handshake_conn != 0) { - StartHandshakeInitiator(start_handshake_conn); - } - if (establish_conn != 0) { - EstablishConnection(establish_conn, false); - } - if (peer_deactivated_conn != 0) { - DispatchConnectionEvent(peer_deactivated_conn, ORBIS_NP_SIGNALING_EVENT_PEER_DEACTIVATED, - 0); - } - if (dead_conn != 0) { - CloseConnectionAndDispatchDead(dead_conn, ORBIS_NP_SIGNALING_ERROR_TERMINATED_BY_PEER); - } -} - -void SendStunPing(s32 ctx_id) { - if (!Stubs::Matching2Enabled()) { - return; - } - - OrbisNpOnlineId online_id{}; - - { - SignalingMutexGuard lock; - const auto it = g_contexts.find(ctx_id); - if (it == g_contexts.end() || !it->second.active) { - return; - } - online_id = it->second.owner_online_id; - } - - if (!Stubs::EnsureTransport()) { - return; - } - - const u32 server_addr = Stubs::MmServerAddr(); - const u16 server_udp = Stubs::MmServerUdpPort(); - - if (server_addr == 0 || server_udp == 0) { - LOG_WARNING(Lib_NpSignaling, "ctxId={} skipped (server_addr={:#x} udp_port={})", ctx_id, - server_addr, sceNetNtohs(server_udp)); - return; - } - - StunPing ping{}; - ping.cmd = 0x01; - std::memcpy(ping.online_id, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); - ping.local_ip = Stubs::AdvertisedAddr(); - - LOG_DEBUG(Lib_NpSignaling, "ctxId={} online_id='{}' server={:#x}:{} local_ip={:#x}", ctx_id, - OnlineIdToString(online_id), server_addr, sceNetNtohs(server_udp), ping.local_ip); - - Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_udp); -} - s32 GetActiveConnectionIdForPeer(std::string_view online_id) { SignalingMutexGuard lock; - for (const auto& [conn_id, ci] : g_connections) { + for (const auto& [conn_id, ci] : NpHandler::GetInstance().GetSignalingState().connections) { if (OnlineIdEqualsString(ci.online_id, online_id) && ci.status == ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE) { return conn_id; @@ -1356,7 +630,7 @@ s32 GetActiveConnectionIdForPeer(std::string_view online_id) { s32 GetConnectionStatusForPeer(std::string_view online_id, s32* out_conn_id) { SignalingMutexGuard lock; - for (const auto& [conn_id, ci] : g_connections) { + for (const auto& [conn_id, ci] : NpHandler::GetInstance().GetSignalingState().connections) { if (OnlineIdEqualsString(ci.online_id, online_id)) { if (out_conn_id) { *out_conn_id = conn_id; @@ -1370,18 +644,20 @@ s32 GetConnectionStatusForPeer(std::string_view online_id, s32* out_conn_id) { return ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE; } -void ClearActiveDeadlines() {} -void SetRoomLeft() {} -void ClearConnectionDeadlineForPeer(std::string_view) {} - bool GetPeerAddress(std::string_view online_id, u32* out_addr, u16* out_port) { SignalingMutexGuard lock; - for (const auto& [conn_id, ci] : g_connections) { + for (const auto& [conn_id, ci] : NpHandler::GetInstance().GetSignalingState().connections) { if (OnlineIdEqualsString(ci.online_id, online_id)) { + const auto transport_it = + NpHandler::GetInstance().GetSignalingState().peer_transports.find(ci.transport_id); + if (transport_it == + NpHandler::GetInstance().GetSignalingState().peer_transports.end()) { + return false; + } if (out_addr) - *out_addr = ci.addr; + *out_addr = transport_it->second.addr; if (out_port) - *out_port = ci.port; + *out_port = transport_it->second.port; return true; } } diff --git a/src/core/libraries/np/np_signaling/np_signaling_state.h b/src/core/libraries/np/np_signaling/np_signaling_state.h index d923fba2f..61b15e4f4 100644 --- a/src/core/libraries/np/np_signaling/np_signaling_state.h +++ b/src/core/libraries/np/np_signaling/np_signaling_state.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -21,9 +22,6 @@ namespace Libraries::Np::NpSignaling { -extern NpCommon::OrbisNpCalloutContext g_callout_ctx; -extern bool g_callout_ctx_active; - constexpr s64 kActivateCooldownUs = 6'000'000; constexpr s64 kActivateBudgetMaxUs = 600'000'000; constexpr u32 kSigRetryMs = 500; @@ -133,9 +131,8 @@ enum class ConnState : s32 { Established = 10, }; -struct ConnectionInfo { - OrbisNpSignalingConnectionId conn_id = 0; - OrbisNpSignalingContextId ctx_id = 0; +struct PeerTransport { + s32 transport_id = 0; u32 addr = 0; u16 port = 0; @@ -143,40 +140,41 @@ struct ConnectionInfo { u32 local_addr = 0; u16 local_port = 0; - s32 status = ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE; - OrbisNpId npid{}; OrbisNpOnlineId online_id{}; + OrbisNpOnlineId local_online_id{}; - s32 activation_mode = 0; + s32 status = ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE; + ConnState state = ConnState::Inactive; + + bool peer_activated = false; + bool peer_established = false; + bool transport_established_fired = false; + bool is_initiator = false; static constexpr u32 kProbeSampleCount = 6; s32 probe_rtt_samples[kProbeSampleCount] = {-1, -1, -1, -1, -1, -1}; u32 probe_sample_write_index = 0; s64 last_echo_ping_us = 0; - s64 last_peer_rx_us = 0; + s64 last_handshake_send_us = 0; +}; - ConnState state = ConnState::Inactive; - bool established_fired = false; - bool established_event_fired = false; - bool peer_activated = false; +struct ConnectionInfo { + OrbisNpSignalingConnectionId conn_id = 0; + OrbisNpSignalingContextId ctx_id = 0; + s32 transport_id = 0; + s32 status = ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE; + + OrbisNpId npid{}; + OrbisNpOnlineId online_id{}; + bool sig1_established_event_fired = false; bool locally_activated = false; + bool peer_activated = false; bool peer_activated_fired = false; - bool peer_established = false; bool mutual_fired = false; bool dead_fired = false; - bool is_initiator = false; - s64 last_handshake_send_us = 0; - - NpCommon::OrbisNpCalloutEntry timeout_callout{}; - bool timeout_callout_armed = false; - NpCommon::OrbisNpCalloutEntry step_callout{}; - bool step_callout_armed = false; - bool deactivate_lingering = false; - - u32 echo_derived_value = 0; }; struct NpSignalingContext { @@ -246,39 +244,66 @@ struct PeerNetInfoResult { u32 nat_route_kind = 0; }; -extern std::unordered_map g_contexts; -extern std::unordered_map g_connections; -extern std::unordered_map g_npid_to_conn; -extern std::unordered_map g_peer_netinfo_results; struct PendingActivation { OrbisNpSignalingConnectionId conn_id = 0; std::string peer_online_id; bool start_handshake = true; }; -extern std::vector g_pending_activations; -extern u32 g_peer_netinfo_next_id; -extern u32 g_peak_connection_count; -extern u32 g_last_assigned_context_id; -extern u32 g_connection_id_seed; -extern bool g_initialized; -extern Libraries::Kernel::PthreadMutexT g_mutex_storage; +struct NpSignalingState { + std::unordered_map contexts; + std::unordered_map connections; + std::unordered_map peer_transports; + std::unordered_map npid_to_conn; + std::unordered_map peer_netinfo_results; + std::vector pending_activations; + + u32 peer_netinfo_next_id = 1; + u32 peak_connection_count = 0; + u32 last_assigned_context_id = 0; + u32 connection_id_seed = 0; + u32 transport_id_seed = 0; + bool initialized = false; + + std::recursive_mutex mutex; + + std::multimap dispatch_queue; + std::mutex dispatch_mutex; + std::condition_variable dispatch_cv; + Libraries::Kernel::PthreadT dispatch_thread = nullptr; + bool dispatch_stop = false; + + std::atomic runtime_stop{false}; + std::mutex runtime_thread_mutex; + std::thread runtime_thread; + std::mutex activation_mutex; + std::condition_variable activation_cv; + std::thread activation_thread; + u32 context_refs = 0; + + void ResetSig1Data() { + contexts.clear(); + connections.clear(); + npid_to_conn.clear(); + peer_netinfo_results.clear(); + pending_activations.clear(); + peer_netinfo_next_id = 1; + peak_connection_count = 0; + last_assigned_context_id = 0; + connection_id_seed = 0; + } +}; struct SignalingMutexGuard { SignalingMutexGuard(); - ~SignalingMutexGuard(); + ~SignalingMutexGuard() = default; SignalingMutexGuard(const SignalingMutexGuard&) = delete; SignalingMutexGuard& operator=(const SignalingMutexGuard&) = delete; + +private: + std::unique_lock lock; }; -void InitSignalingMutex(); -void DestroySignalingMutex(); - -extern std::multimap g_dispatch_queue; -extern std::mutex g_dispatch_mutex; -extern std::condition_variable g_dispatch_cv; -extern bool g_dispatch_stop; - long long NowMs(); s64 NowUs(); std::string OnlineIdToString(const OrbisNpOnlineId& online_id_in); @@ -315,43 +340,11 @@ bool TakePeerNetInfoResultLocked(OrbisNpSignalingContextId ctx_id, s32 req_or_co PeerNetInfoResult* out); bool DropPeerNetInfoResultLocked(OrbisNpSignalingContextId ctx_id, s32 req_or_conn_id); -void RecordRttSampleLocked(OrbisNpSignalingConnectionId conn_id, s32 rtt_us); -void SendEchoPings(); - void DispatchConnectionEvent(OrbisNpSignalingConnectionId conn_id, s32 event_type, s32 event_data); void DispatchPeerActivatedEvent(OrbisNpSignalingConnectionId conn_id); -void CloseConnectionAndDispatchDead(OrbisNpSignalingConnectionId conn_id, s32 error_code); - -void EstablishConnection(OrbisNpSignalingConnectionId conn_id, bool peer_activated_hint); - -void DeactivateConnectionFaithful(OrbisNpSignalingConnectionId conn_id); - -void TerminateConnectionFaithful(OrbisNpSignalingConnectionId conn_id); - -void StartHandshakeInitiator(OrbisNpSignalingConnectionId conn_id); - -void QueueActivationLocked(OrbisNpSignalingConnectionId conn_id, std::string_view peer_online_id, - bool start_handshake = true); -void ProcessPendingActivations(); - -void HandleHandshakePacket(u32 from_addr, u16 from_port, const SignalingHandshake& pkt); - -void ArmConnectTimeoutLocked(OrbisNpSignalingConnectionId conn_id); -void ClearLingerAndTimeoutLocked(OrbisNpSignalingConnectionId conn_id); - -void SendActivationRequestLocked(const ConnectionInfo& ci); - -void SendControlCloseLocked(const ConnectionInfo& ci, ControlReason reason); - -void HandleControlPacket(u32 from_addr, u16 from_port, const SignalingControl& pkt); - const char* SignalingEventName(u32 event_type); bool ConsumeActivationBudgetLocked(NpSignalingContext& ctx); -void SendStunPing(OrbisNpSignalingContextId ctx_id); -void ClearActiveDeadlines(); -void ClearConnectionDeadlineForPeer(std::string_view npid); -void SetRoomLeft(); } // namespace Libraries::Np::NpSignaling diff --git a/src/core/libraries/np/np_signaling/np_signaling_stubs.cpp b/src/core/libraries/np/np_signaling/np_signaling_transport.cpp similarity index 93% rename from src/core/libraries/np/np_signaling/np_signaling_stubs.cpp rename to src/core/libraries/np/np_signaling/np_signaling_transport.cpp index bb2ce65f7..15450364d 100644 --- a/src/core/libraries/np/np_signaling/np_signaling_stubs.cpp +++ b/src/core/libraries/np/np_signaling/np_signaling_transport.cpp @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "core/libraries/np/np_signaling/np_signaling_stubs.h" +#include "core/libraries/np/np_signaling/np_signaling_transport.h" -namespace Libraries::Np::NpSignaling::Stubs { +namespace Libraries::Np::NpSignaling::Transport { namespace { TransportHooks g_transport{}; @@ -81,4 +81,4 @@ u16 MmServerUdpPort() { return g_mm_server_udp_port; } -} // namespace Libraries::Np::NpSignaling::Stubs +} // namespace Libraries::Np::NpSignaling::Transport diff --git a/src/core/libraries/np/np_signaling/np_signaling_stubs.h b/src/core/libraries/np/np_signaling/np_signaling_transport.h similarity index 93% rename from src/core/libraries/np/np_signaling/np_signaling_stubs.h rename to src/core/libraries/np/np_signaling/np_signaling_transport.h index a724b29af..11ff77de7 100644 --- a/src/core/libraries/np/np_signaling/np_signaling_stubs.h +++ b/src/core/libraries/np/np_signaling/np_signaling_transport.h @@ -4,9 +4,10 @@ #pragma once #include + #include "common/types.h" -namespace Libraries::Np::NpSignaling::Stubs { +namespace Libraries::Np::NpSignaling::Transport { struct TransportHooks { int (*signaling_send)(const void* data, u32 len, u32 dest_addr, u16 dest_port) = nullptr; @@ -39,4 +40,4 @@ bool Matching2Enabled(); u32 MmServerAddr(); u16 MmServerUdpPort(); -} // namespace Libraries::Np::NpSignaling::Stubs +} // namespace Libraries::Np::NpSignaling::Transport diff --git a/src/core/libraries/np/np_types2.h b/src/core/libraries/np/np_types2.h index cece77d3d..2c4262b66 100644 --- a/src/core/libraries/np/np_types2.h +++ b/src/core/libraries/np/np_types2.h @@ -67,6 +67,7 @@ enum OrbisNpMatching2Event : u16 { ORBIS_NP_MATCHING2_REQUEST_EVENT_SEND_ROOM_MESSAGE = 0x0108, ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_INTERNAL = 0x0109, ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_INTERNAL = 0x010A, + ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_MEMBER_DATA_INTERNAL = 0x010B, ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_LOBBY = 0x0201, ORBIS_NP_MATCHING2_REQUEST_EVENT_SIGNALING_GET_PING_INFO = 0x0E01, ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED = 0x1101, @@ -88,6 +89,7 @@ enum OrbisNpMatching2Event : u16 { ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_EXTERNAL_LIST_A = 0x7003, ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_EXTERNAL_LIST_A = 0x7005, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A = 0x7106, + ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_INTERNAL_A = 0x710C, ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED_A = 0x8101, ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_LEFT_A = 0x8102, ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_ROOM_MEMBER_DATA_INTERNAL_A = 0x8107, @@ -347,17 +349,24 @@ struct OrbisNpMatching2RoomMessageInfo { void* msg; u32 msgLen; }; +static_assert(sizeof(OrbisNpMatching2RoomMessageInfo) == 0x28); struct OrbisNpMatching2RoomMessageInfoA { bool filtered; OrbisNpMatching2CastType castType; - u8 padding[2]; + u8 padding[6]; + Libraries::Np::OrbisNpPeerAddressA srcMember; + Libraries::Np::OrbisNpOnlineId srcMemberOnlineId; OrbisNpMatching2RoomMessageDestination* dst; - Libraries::Np::OrbisNpPeerAddressA* srcMember; - Libraries::Np::OrbisNpOnlineId* srcOnlineId; - void* msg; - u32 msgLen; + const void* msg; + u64 msgLen; }; +static_assert(offsetof(OrbisNpMatching2RoomMessageInfoA, srcMember) == 0x08); +static_assert(offsetof(OrbisNpMatching2RoomMessageInfoA, srcMemberOnlineId) == 0x18); +static_assert(offsetof(OrbisNpMatching2RoomMessageInfoA, dst) == 0x30); +static_assert(offsetof(OrbisNpMatching2RoomMessageInfoA, msg) == 0x38); +static_assert(offsetof(OrbisNpMatching2RoomMessageInfoA, msgLen) == 0x40); +static_assert(sizeof(OrbisNpMatching2RoomMessageInfoA) == 0x48); // Room-internal binary attribute struct OrbisNpMatching2RoomBinAttrInternal { @@ -812,6 +821,39 @@ struct OrbisNpMatching2GetRoomDataInternalRequest { u64 attrIdNum; }; +// SetRoomMemberDataInternal request +struct OrbisNpMatching2SetRoomMemberDataInternalRequest { + OrbisNpMatching2RoomId roomId; + OrbisNpMatching2RoomMemberId memberId; + OrbisNpMatching2TeamId teamId; + u8 padding[5]; + OrbisNpMatching2FlagAttr flagFilter; + OrbisNpMatching2FlagAttr flagAttr; + const OrbisNpMatching2BinAttr* roomMemberBinAttrInternal; + u64 roomMemberBinAttrInternalNum; +}; +static_assert(sizeof(OrbisNpMatching2SetRoomMemberDataInternalRequest) == 0x28); + +// GetRoomMemberDataInternal request +struct OrbisNpMatching2GetRoomMemberDataInternalRequest { + OrbisNpMatching2RoomId roomId; + OrbisNpMatching2RoomMemberId memberId; + u8 padding[6]; + const OrbisNpMatching2AttributeId* attrId; + u64 attrIdNum; +}; +static_assert(sizeof(OrbisNpMatching2GetRoomMemberDataInternalRequest) == 0x20); + +// GetRoomMemberDataInternal response +struct OrbisNpMatching2GetRoomMemberDataInternalResponse { + OrbisNpMatching2RoomMemberDataInternal* roomMemberDataInternal; +}; + +// GetRoomMemberDataInternal response (account-id variant) +struct OrbisNpMatching2GetRoomMemberDataInternalResponseA { + OrbisNpMatching2RoomMemberDataInternalA* roomMemberDataInternal; +}; + // SetUserInfo request struct OrbisNpMatching2SetUserInfoRequest { OrbisNpMatching2ServerId serverId; @@ -890,6 +932,36 @@ struct OrbisNpMatching2RoomDataInternalUpdate { u64 chgRoomBinAttrInternalNum; }; +// Room member data internal update information +struct OrbisNpMatching2RoomMemberDataInternalUpdate { + OrbisNpMatching2RoomMemberDataInternal* chgRoomMemberDataInternal; + OrbisNpMatching2FlagAttr* chgFlagAttr; + OrbisNpMatching2FlagAttr* prevFlagAttr; + OrbisNpMatching2TeamId* chgTeamId; + OrbisNpMatching2RoomMemberBinAttrInternal** chgRoomMemberBinAttrInternal; + u64 chgRoomMemberBinAttrInternalNum; +}; +static_assert(sizeof(OrbisNpMatching2RoomMemberDataInternalUpdate) == 0x30); +static_assert(offsetof(OrbisNpMatching2RoomMemberDataInternalUpdate, + chgRoomMemberBinAttrInternal) == 0x20); +static_assert(offsetof(OrbisNpMatching2RoomMemberDataInternalUpdate, + chgRoomMemberBinAttrInternalNum) == 0x28); + +// Room member data internal update information (account-id variant) +struct OrbisNpMatching2RoomMemberDataInternalUpdateA { + OrbisNpMatching2RoomMemberDataInternalA* chgRoomMemberDataInternal; + OrbisNpMatching2FlagAttr* chgFlagAttr; + OrbisNpMatching2FlagAttr* prevFlagAttr; + OrbisNpMatching2TeamId* chgTeamId; + OrbisNpMatching2RoomMemberBinAttrInternal** chgRoomMemberBinAttrInternal; + u64 chgRoomMemberBinAttrInternalNum; +}; +static_assert(sizeof(OrbisNpMatching2RoomMemberDataInternalUpdateA) == 0x30); +static_assert(offsetof(OrbisNpMatching2RoomMemberDataInternalUpdateA, + chgRoomMemberBinAttrInternal) == 0x20); +static_assert(offsetof(OrbisNpMatching2RoomMemberDataInternalUpdateA, + chgRoomMemberBinAttrInternalNum) == 0x28); + // Room update information struct OrbisNpMatching2RoomUpdate { OrbisNpMatching2EventCause eventCause; diff --git a/src/core/libraries/np/signaling_handler.cpp b/src/core/libraries/np/signaling_handler.cpp new file mode 100644 index 000000000..fab66677f --- /dev/null +++ b/src/core/libraries/np/signaling_handler.cpp @@ -0,0 +1,1645 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/libraries/np/signaling_handler.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/logging/log.h" +#include "common/singleton.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_util.h" +#include "core/libraries/np/np_error.h" +#include "core/libraries/np/np_handler.h" +#include "core/libraries/np/np_matching2/np_matching2_internal.h" +#include "core/libraries/np/np_matching2/np_matching2_signaling.h" +#include "core/libraries/np/np_signaling/np_signaling_state.h" +#include "core/libraries/np/np_signaling/np_signaling_transport.h" + +namespace Libraries::Np::SignalingHandler { +namespace { + +constexpr s32 kMatching2ConnInactive = 0; +constexpr s32 kMatching2ConnPending = 1; +constexpr s32 kMatching2ConnActive = 2; +constexpr auto kMatching2StepInterval = std::chrono::milliseconds(500); +constexpr auto kMatching2Timeout = std::chrono::seconds(30); +constexpr auto kStunPingInterval = std::chrono::seconds(5); +constexpr s64 kHandshakeConnectTimeoutMs = 30'000; +constexpr s64 kHandshakeRetransmitMs = 500; +constexpr s64 kKeepaliveIntervalMs = 45'000; + +void HandleControlPacket(u32 from_addr, u16 from_port, const NpSignaling::SignalingControl& pkt); +void HandleHandshakePacket(u32 from_addr, u16 from_port, + const NpSignaling::SignalingHandshake& pkt); +std::string PacketOnlineIdString(const u8* online_id); + +std::string OnlineIdString(const OrbisNpOnlineId& online_id) { + return NpSignaling::OnlineIdToString(online_id); +} + +bool ShouldConnectToPeer(const NpMatching2::RoomCache& room, + NpMatching2::OrbisNpMatching2RoomMemberId self, + NpMatching2::OrbisNpMatching2RoomMemberId peer) { + if (peer == 0 || peer == self) { + return false; + } + if (room.signaling_type == NpMatching2::ORBIS_NP_MATCHING2_SIGNALING_TYPE_NONE) { + return false; + } + if (room.signaling_type == NpMatching2::ORBIS_NP_MATCHING2_SIGNALING_TYPE_STAR && + room.signaling_main_member != 0) { + return self == room.signaling_main_member || peer == room.signaling_main_member; + } + return true; +} + +bool ResolvePeerEndpoint(const NpMatching2::MemberCache& member, u32* out_addr, u16* out_port) { + if (member.addr != 0 && member.port != 0) { + *out_addr = member.addr; + *out_port = member.port; + return true; + } + + const std::string online_id = OnlineIdString(member.np_id.handle); + return !online_id.empty() && + NpSignaling::Transport::ResolvePeer(online_id, out_addr, out_port) && *out_addr != 0 && + *out_port != 0; +} + +bool HasSignalingMagic(const u8* buf, size_t nbytes) { + return nbytes >= sizeof(NpSignaling::kSignalingMagic) + 1 && + std::memcmp(buf, NpSignaling::kSignalingMagic, sizeof(NpSignaling::kSignalingMagic)) == + 0; +} + +void HandleStunEcho(const NpSignaling::StunEcho& echo) { + { + NpSignaling::SignalingMutexGuard lock; + for (auto& [ctx_id, ctx] : NpHandler::GetInstance().GetSignalingState().contexts) { + if (!ctx.active) { + continue; + } + ctx.ext_addr.store(echo.ext_ip); + ctx.ext_port.store(echo.ext_port); + ctx.stun_cv.notify_all(); + LOG_DEBUG(Lib_NpSignaling, "STUN echo: ctxId={} ext_addr={:#x} ext_port={}", ctx_id, + echo.ext_ip, Net::sceNetNtohs(echo.ext_port)); + } + } + + auto* netinfo = Common::Singleton::Instance(); + netinfo->SetExternalIp(echo.ext_ip); +} + +void ProcessSignalingPacket(const void* data, u32 len, u32 from_addr, u16 from_port) { + const auto* buf = static_cast(data); + const auto nbytes = static_cast(len); + + if (nbytes == sizeof(NpSignaling::StunEcho)) { + NpSignaling::StunEcho echo{}; + std::memcpy(&echo, buf, sizeof(echo)); + HandleStunEcho(echo); + return; + } + + if (!HasSignalingMagic(buf, nbytes)) { + return; + } + + const u8 type = buf[4]; + if (nbytes == sizeof(NpSignaling::SignalingControl) && + type == static_cast(NpSignaling::SignalingPacketType::Control)) { + NpSignaling::SignalingControl ctrl{}; + std::memcpy(&ctrl, buf, sizeof(ctrl)); + HandleControlPacket(from_addr, from_port, ctrl); + return; + } + + if (nbytes == sizeof(NpSignaling::SignalingEchoPing) && + type == static_cast(NpSignaling::SignalingPacketType::EchoPing)) { + NpSignaling::SignalingEchoPing ping{}; + std::memcpy(&ping, buf, sizeof(ping)); + NpSignaling::SignalingEchoPong pong{}; + pong.conn_id = ping.conn_id; + pong.orig_ts_us = ping.send_ts_us; + NpSignaling::Transport::SignalingSendTo(&pong, sizeof(pong), from_addr, from_port); + return; + } + + if (nbytes == sizeof(NpSignaling::SignalingEchoPong) && + type == static_cast(NpSignaling::SignalingPacketType::EchoPong)) { + NpSignaling::SignalingEchoPong pong{}; + std::memcpy(&pong, buf, sizeof(pong)); + s32 rtt_us = static_cast(NpSignaling::NowUs() - static_cast(pong.orig_ts_us)); + if (rtt_us < 0) { + rtt_us = 0; + } + NpSignaling::SignalingMutexGuard lock; + const auto transport_it = NpHandler::GetInstance().GetSignalingState().peer_transports.find( + static_cast(pong.conn_id)); + if (transport_it != NpHandler::GetInstance().GetSignalingState().peer_transports.end()) { + auto& transport = transport_it->second; + const u32 idx = + transport.probe_sample_write_index % NpSignaling::PeerTransport::kProbeSampleCount; + transport.probe_rtt_samples[idx] = rtt_us; + transport.probe_sample_write_index = idx + 1; + } + return; + } + + if (nbytes == sizeof(NpSignaling::SignalingHandshake) && + type == static_cast(NpSignaling::SignalingPacketType::Handshake)) { + NpSignaling::SignalingHandshake hs{}; + std::memcpy(&hs, buf, sizeof(hs)); + HandleHandshakePacket(from_addr, from_port, hs); + return; + } +} + +void DrainSignalingPackets() { + static constexpr u32 kBufSize = 256; + u8 buf[kBufSize]; + for (;;) { + u32 from_addr = 0; + u16 from_port = 0; + const int rc = + NpSignaling::Transport::SignalingRecvFrom(buf, kBufSize, &from_addr, &from_port); + if (rc <= 0) { + break; + } + + ProcessSignalingPacket(buf, static_cast(rc), from_addr, from_port); + } +} + +void DrainControlPackets() { + static constexpr u32 kBufSize = 256; + u8 buf[kBufSize]; + for (;;) { + u32 from_addr = 0; + u16 from_port = 0; + const int rc = + NpSignaling::Transport::ControlRecvFrom(buf, kBufSize, &from_addr, &from_port); + if (rc <= 0) { + break; + } + + ProcessSignalingPacket(buf, static_cast(rc), from_addr, from_port); + } +} + +bool PublishSignalingInfo(const OrbisNpOnlineId& online_id) { + if (!NpSignaling::Transport::Matching2Enabled() || online_id.data[0] == '\0') { + return false; + } + + const u32 server_addr = NpSignaling::Transport::MmServerAddr(); + const u16 server_udp = NpSignaling::Transport::MmServerUdpPort(); + if (server_addr == 0 || server_udp == 0) { + return false; + } + + NpSignaling::StunPing ping{}; + std::memcpy(ping.online_id, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); + ping.local_ip = NpSignaling::Transport::AdvertisedAddr(); + + const int rc = + NpSignaling::Transport::SignalingSendTo(&ping, sizeof(ping), server_addr, server_udp); + LOG_DEBUG(Lib_NpSignaling, "PublishSignalingInfo: id='{}' server={:#x}:{} local_ip={:#x} rc={}", + OnlineIdString(online_id), server_addr, Net::sceNetNtohs(server_udp), ping.local_ip, + rc); + return rc >= 0; +} + +u32 PublishSignalingInfo() { + std::vector online_ids; + { + NpSignaling::SignalingMutexGuard lock; + for (const auto& [ctx_id, ctx] : NpHandler::GetInstance().GetSignalingState().contexts) { + if (ctx.active) { + online_ids.push_back(ctx.owner_online_id); + } + } + } + + for (u32 id = 1; id <= NpMatching2::ContextManager::kMaxContexts; ++id) { + if (auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get( + static_cast(id)); + ctx && ctx->started) { + online_ids.push_back(ctx->online_id); + } + } + + std::vector sent; + u32 sent_count = 0; + for (const OrbisNpOnlineId& online_id : online_ids) { + const std::string name = OnlineIdString(online_id); + if (name.empty() || std::find(sent.begin(), sent.end(), name) != sent.end()) { + continue; + } + sent.push_back(name); + if (PublishSignalingInfo(online_id)) { + ++sent_count; + } + } + return sent_count; +} + +void SendEchoPings() { + constexpr s64 kEchoIntervalUs = 1'000'000; + + struct PingTarget { + s32 transport_id; + u32 addr; + u16 port; + s64 now_us; + }; + + std::vector targets; + { + NpSignaling::SignalingMutexGuard lock; + const s64 now = NpSignaling::NowUs(); + for (auto& [transport_id, transport] : + NpHandler::GetInstance().GetSignalingState().peer_transports) { + if (transport.status != NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE || + transport.addr == 0 || transport.port == 0) { + continue; + } + if (transport.last_echo_ping_us != 0 && + now - transport.last_echo_ping_us < kEchoIntervalUs) { + continue; + } + transport.last_echo_ping_us = now; + targets.push_back({transport_id, transport.addr, transport.port, now}); + } + } + + for (const PingTarget& target : targets) { + NpSignaling::SignalingEchoPing ping{}; + ping.conn_id = static_cast(target.transport_id); + ping.send_ts_us = static_cast(target.now_us); + NpSignaling::Transport::SignalingSendTo(&ping, sizeof(ping), target.addr, target.port); + } +} + +u32 AverageRtt(const NpSignaling::PeerTransport& transport) { + u64 total = 0; + u32 count = 0; + for (const s32 sample : transport.probe_rtt_samples) { + if (sample > 0) { + total += static_cast(sample); + ++count; + } + } + return count == 0 ? 0 : static_cast(total / count); +} + +s32 StatusFromState(NpSignaling::ConnState state) { + if (state == NpSignaling::ConnState::Established) { + return NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE; + } + if (state == NpSignaling::ConnState::Inactive) { + return NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE; + } + return NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_PENDING; +} + +void SetTransportStateLocked(NpSignaling::PeerTransport& transport, + NpSignaling::ConnState new_state) { + transport.state = new_state; + transport.status = StatusFromState(new_state); +} + +s32 AllocateTransportIdLocked() { + if (NpHandler::GetInstance().GetSignalingState().transport_id_seed == 0) { + NpHandler::GetInstance().GetSignalingState().transport_id_seed = 1; + } + for (int tried = 0; tried <= 0xffff; ++tried) { + const u32 candidate = NpHandler::GetInstance().GetSignalingState().transport_id_seed++; + if (NpHandler::GetInstance().GetSignalingState().transport_id_seed == 0 || + NpHandler::GetInstance().GetSignalingState().transport_id_seed > 0x7fff) { + NpHandler::GetInstance().GetSignalingState().transport_id_seed = 1; + } + if (candidate != 0 && + NpHandler::GetInstance().GetSignalingState().peer_transports.find(static_cast( + candidate)) == NpHandler::GetInstance().GetSignalingState().peer_transports.end()) { + return static_cast(candidate); + } + } + return -1; +} + +bool TransportMatchesOnlineId(const NpSignaling::PeerTransport& transport, + std::string_view online_id) { + return transport.state != NpSignaling::ConnState::Inactive && + NpSignaling::OnlineIdEqualsString(transport.online_id, online_id); +} + +s32 FindTransportByOnlineIdLocked(std::string_view online_id) { + for (const auto& [transport_id, transport] : + NpHandler::GetInstance().GetSignalingState().peer_transports) { + if (TransportMatchesOnlineId(transport, online_id)) { + return transport_id; + } + } + return 0; +} + +NpSignaling::PeerTransport* GetTransportLocked(s32 transport_id) { + const auto it = NpHandler::GetInstance().GetSignalingState().peer_transports.find(transport_id); + return it == NpHandler::GetInstance().GetSignalingState().peer_transports.end() ? nullptr + : &it->second; +} + +bool HasSig1ConnectionForTransportLocked(s32 transport_id) { + for (const auto& [conn_id, ci] : NpHandler::GetInstance().GetSignalingState().connections) { + if (ci.transport_id == transport_id && + ci.status != NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE && !ci.dead_fired) { + return true; + } + } + return false; +} + +void SyncSig1StatusLocked(NpSignaling::ConnectionInfo& ci, + const NpSignaling::PeerTransport& transport) { + ci.status = transport.status; +} + +void SyncSig1ConnectionsForTransportLocked(s32 transport_id) { + const auto transport_it = + NpHandler::GetInstance().GetSignalingState().peer_transports.find(transport_id); + if (transport_it == NpHandler::GetInstance().GetSignalingState().peer_transports.end()) { + return; + } + for (auto& [conn_id, ci] : NpHandler::GetInstance().GetSignalingState().connections) { + if (ci.transport_id == transport_id) { + SyncSig1StatusLocked(ci, transport_it->second); + } + } +} + +s32 CreateTransportLocked(const OrbisNpOnlineId& local_online_id, + const OrbisNpOnlineId& peer_online_id, const OrbisNpId& peer_npid, + u32 addr, u16 port) { + const s32 transport_id = AllocateTransportIdLocked(); + if (transport_id < 0) { + return transport_id; + } + NpSignaling::PeerTransport transport{}; + transport.transport_id = transport_id; + transport.local_online_id = local_online_id; + transport.online_id = peer_online_id; + transport.npid = peer_npid; + transport.addr = addr; + transport.port = port; + transport.status = NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_PENDING; + transport.state = NpSignaling::ConnState::SendingOffer; + transport.is_initiator = true; + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id] = transport; + return transport_id; +} + +s32 EnsureTransportLocked(const OrbisNpOnlineId& local_online_id, + const OrbisNpOnlineId& peer_online_id, const OrbisNpId& peer_npid, + u32 addr, u16 port) { + const std::string peer_id = OnlineIdString(peer_online_id); + s32 transport_id = FindTransportByOnlineIdLocked(peer_id); + if (transport_id == 0) { + transport_id = + CreateTransportLocked(local_online_id, peer_online_id, peer_npid, addr, port); + } else { + auto& transport = + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id]; + if (transport.local_online_id.data[0] == '\0') { + transport.local_online_id = local_online_id; + } + if (transport.online_id.data[0] == '\0') { + transport.online_id = peer_online_id; + transport.npid = peer_npid; + } + if (addr != 0 && port != 0) { + transport.addr = addr; + transport.port = port; + } + if (transport.state == NpSignaling::ConnState::Inactive) { + SetTransportStateLocked(transport, NpSignaling::ConnState::SendingOffer); + transport.is_initiator = true; + } + } + return transport_id; +} + +std::string PacketOnlineIdString(const u8* online_id) { + char buf[ORBIS_NP_ONLINEID_MAX_LENGTH + 1]{}; + std::memcpy(buf, online_id, ORBIS_NP_ONLINEID_MAX_LENGTH); + return std::string(buf); +} + +bool ConnectionMatchesOnlineId(const NpSignaling::ConnectionInfo& ci, std::string_view online_id) { + return !ci.dead_fired && ci.status != NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE && + NpSignaling::OnlineIdEqualsString(ci.online_id, online_id); +} + +s32 FindConnectionByOnlineIdLocked(std::string_view online_id, s32 preferred_ctx_id = 0) { + if (preferred_ctx_id != 0) { + for (const auto& [conn_id, ci] : NpHandler::GetInstance().GetSignalingState().connections) { + if (ci.ctx_id == preferred_ctx_id && ConnectionMatchesOnlineId(ci, online_id)) { + return conn_id; + } + } + } + for (const auto& [conn_id, ci] : NpHandler::GetInstance().GetSignalingState().connections) { + if (ConnectionMatchesOnlineId(ci, online_id)) { + return conn_id; + } + } + return 0; +} + +NpSignaling::SignalingHandshake MakeHandshakeLocked(const NpSignaling::PeerTransport& transport, + NpSignaling::HandshakeKind kind) { + NpSignaling::SignalingHandshake pkt{}; + pkt.kind = static_cast(kind); + pkt.from_conn_id = static_cast(transport.transport_id); + std::memcpy(pkt.online_id_from, transport.local_online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); + std::memcpy(pkt.online_id_to, transport.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); + auto* netinfo = Common::Singleton::Instance(); + pkt.mapped_addr = netinfo->GetExternalIp(); + pkt.mapped_port = 0; + return pkt; +} + +void SendHandshakeLocked(NpSignaling::PeerTransport& transport, + const NpSignaling::SignalingHandshake& pkt) { + if (transport.addr == 0 || transport.port == 0) { + LOG_DEBUG( + Lib_NpSignaling, "Signaling handshake skipped: transport={} kind={} endpoint={:#x}:{}", + transport.transport_id, pkt.kind, transport.addr, Net::sceNetNtohs(transport.port)); + return; + } + transport.last_handshake_send_us = NpSignaling::NowUs(); + const int rc = + NpSignaling::Transport::SignalingSendTo(&pkt, sizeof(pkt), transport.addr, transport.port); + LOG_DEBUG( + Lib_NpSignaling, "Signaling handshake send: transport={} kind={} endpoint={:#x}:{} rc={}", + transport.transport_id, pkt.kind, transport.addr, Net::sceNetNtohs(transport.port), rc); + SyncSig1ConnectionsForTransportLocked(transport.transport_id); +} + +NpSignaling::SignalingControl MakeControlLocked(const NpSignaling::ConnectionInfo& ci, + NpSignaling::ControlKind kind) { + const auto* transport = GetTransportLocked(ci.transport_id); + if (transport) { + NpSignaling::SignalingControl pkt{}; + pkt.kind = static_cast(kind); + pkt.from_conn_id = static_cast(transport->transport_id); + std::memcpy(pkt.online_id_from, transport->local_online_id.data, + ORBIS_NP_ONLINEID_MAX_LENGTH); + std::memcpy(pkt.online_id_to, transport->online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); + auto* netinfo = Common::Singleton::Instance(); + pkt.mapped_addr = netinfo->GetExternalIp(); + pkt.mapped_port = 0; + return pkt; + } + NpSignaling::SignalingControl pkt{}; + return pkt; +} + +NpSignaling::SignalingControl MakeControlLocked(const NpSignaling::PeerTransport& transport, + NpSignaling::ControlKind kind) { + NpSignaling::SignalingControl pkt{}; + pkt.kind = static_cast(kind); + pkt.from_conn_id = static_cast(transport.transport_id); + std::memcpy(pkt.online_id_from, transport.local_online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); + std::memcpy(pkt.online_id_to, transport.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH); + auto* netinfo = Common::Singleton::Instance(); + pkt.mapped_addr = netinfo->GetExternalIp(); + pkt.mapped_port = 0; + return pkt; +} + +void SendActivate(const NpSignaling::ConnectionInfo& ci) { + const auto* transport = GetTransportLocked(ci.transport_id); + if (!transport || transport->addr == 0 || transport->port == 0) { + LOG_DEBUG(Lib_NpSignaling, + "Signaling activate skipped: conn={} transport={} endpoint unavailable", + ci.conn_id, ci.transport_id); + return; + } + const NpSignaling::SignalingControl pkt = + MakeControlLocked(ci, NpSignaling::ControlKind::ActivationRequest); + const int rc = + NpSignaling::Transport::ControlSendTo(&pkt, sizeof(pkt), transport->addr, transport->port); + LOG_DEBUG(Lib_NpSignaling, + "Signaling activate send: conn={} transport={} peer='{}' endpoint={:#x}:{} rc={}", + ci.conn_id, ci.transport_id, OnlineIdString(ci.online_id), transport->addr, + Net::sceNetNtohs(transport->port), rc); +} + +void SendControlCloseLocked(const NpSignaling::ConnectionInfo& ci, + NpSignaling::ControlReason reason) { + const auto* transport = GetTransportLocked(ci.transport_id); + if (!transport || transport->addr == 0 || transport->port == 0) { + return; + } + NpSignaling::SignalingControl pkt = MakeControlLocked(ci, NpSignaling::ControlKind::Close); + pkt.reason = static_cast(reason); + NpSignaling::Transport::ControlSendTo(&pkt, sizeof(pkt), transport->addr, transport->port); +} + +void QueueActivationLocked(NpSignaling::OrbisNpSignalingConnectionId conn_id, + std::string_view peer_online_id, bool start_handshake) { + NpHandler::GetInstance().GetSignalingState().pending_activations.push_back( + {conn_id, std::string(peer_online_id), start_handshake}); + NpHandler::GetInstance().GetSignalingState().activation_cv.notify_one(); +} + +void StartTransportHandshakeInitiator(s32 transport_id) { + NpSignaling::SignalingHandshake pkt{}; + NpSignaling::HandshakeKind kind = NpSignaling::HandshakeKind::Offer; + { + NpSignaling::SignalingMutexGuard lock; + auto* transport = GetTransportLocked(transport_id); + if (!transport) { + return; + } + transport->is_initiator = true; + switch (transport->state) { + case NpSignaling::ConnState::SendingAccept: + kind = NpSignaling::HandshakeKind::Accept; + break; + case NpSignaling::ConnState::WaitOffer: + SetTransportStateLocked(*transport, NpSignaling::ConnState::SendingOffer); + kind = NpSignaling::HandshakeKind::Offer; + break; + case NpSignaling::ConnState::ConnCheck: + kind = NpSignaling::HandshakeKind::Check; + break; + case NpSignaling::ConnState::SendingOffer: + case NpSignaling::ConnState::WaitAccept: + kind = NpSignaling::HandshakeKind::Offer; + break; + case NpSignaling::ConnState::Established: + return; + case NpSignaling::ConnState::Inactive: + default: + SetTransportStateLocked(*transport, NpSignaling::ConnState::SendingOffer); + kind = NpSignaling::HandshakeKind::Offer; + break; + } + pkt = MakeHandshakeLocked(*transport, kind); + if (kind == NpSignaling::HandshakeKind::Check) { + pkt.nonce = static_cast(NpSignaling::NowUs()); + } + SendHandshakeLocked(*transport, pkt); + } +} + +void QueueMatching2DeadForTransport(s32 transport_id, s32 error_code) { + for (u32 id = 1; id <= NpMatching2::ContextManager::kMaxContexts; ++id) { + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get( + static_cast(id)); + if (!ctx) { + continue; + } + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx->ctx_id); + for (auto& [member_id, peer] : cache->peers) { + if (peer.conn_id != transport_id) { + continue; + } + const NpMatching2::OrbisNpMatching2RoomId room_id = [&]() { + for (const auto& [rid, room] : cache->rooms) { + if (room.members.find(member_id) != room.members.end()) { + return rid; + } + } + return static_cast(0); + }(); + peer.status = kMatching2ConnInactive; + peer.sent_established = false; + if (room_id != 0) { + NpMatching2::QueueMatching2SignalingEvent( + *ctx, room_id, member_id, NpMatching2::ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD, + error_code); + } + } + } +} + +void CloseConnectionAndDispatchDead(s32 conn_id, s32 sig_error_code, s32 matching2_error_code) { + bool fire_sig1 = false; + s32 transport_id = 0; + { + NpSignaling::SignalingMutexGuard lock; + const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(conn_id); + if (it == NpHandler::GetInstance().GetSignalingState().connections.end()) { + return; + } + NpSignaling::ConnectionInfo& ci = it->second; + if (ci.status == NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE || ci.dead_fired) { + return; + } + transport_id = ci.transport_id; + if (auto* transport = GetTransportLocked(transport_id)) { + SetTransportStateLocked(*transport, NpSignaling::ConnState::Inactive); + } + ci.status = NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE; + ci.dead_fired = true; + fire_sig1 = ci.ctx_id != 0; + } + + if (transport_id != 0) { + QueueMatching2DeadForTransport(transport_id, matching2_error_code); + } + if (fire_sig1) { + NpSignaling::DispatchConnectionEvent(conn_id, NpSignaling::ORBIS_NP_SIGNALING_EVENT_DEAD, + sig_error_code); + } + { + NpSignaling::SignalingMutexGuard lock; + NpSignaling::RemoveConnectionLocked(conn_id); + } +} + +void CloseTransportAndDispatchDead(s32 transport_id, s32 sig_error_code, s32 matching2_error_code) { + std::vector sig1_conns; + { + NpSignaling::SignalingMutexGuard lock; + auto* transport = GetTransportLocked(transport_id); + if (!transport || transport->state == NpSignaling::ConnState::Inactive) { + return; + } + SetTransportStateLocked(*transport, NpSignaling::ConnState::Inactive); + for (auto& [conn_id, ci] : NpHandler::GetInstance().GetSignalingState().connections) { + if (ci.transport_id != transport_id || ci.dead_fired) { + continue; + } + ci.dead_fired = true; + ci.status = NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE; + if (ci.ctx_id != 0) { + sig1_conns.push_back(conn_id); + } + } + } + + QueueMatching2DeadForTransport(transport_id, matching2_error_code); + for (const s32 conn_id : sig1_conns) { + NpSignaling::DispatchConnectionEvent(conn_id, NpSignaling::ORBIS_NP_SIGNALING_EVENT_DEAD, + sig_error_code); + } + { + NpSignaling::SignalingMutexGuard lock; + for (const s32 conn_id : sig1_conns) { + NpSignaling::RemoveConnectionLocked(conn_id); + } + NpHandler::GetInstance().GetSignalingState().peer_transports.erase(transport_id); + } +} + +void EstablishConnection(s32 transport_id, bool peer_activated_hint) { + bool first_establish = false; + NpSignaling::SignalingControl established_pkt{}; + bool send_established = false; + u32 peer_addr = 0; + u16 peer_port = 0; + std::vector sig1_peer_activated; + std::vector sig1_established; + std::vector sig1_mutual; + { + NpSignaling::SignalingMutexGuard lock; + auto* transport = GetTransportLocked(transport_id); + if (!transport) { + return; + } + if (peer_activated_hint) { + transport->peer_activated = true; + } + + if (!transport->transport_established_fired) { + SetTransportStateLocked(*transport, NpSignaling::ConnState::Established); + transport->transport_established_fired = true; + transport->last_peer_rx_us = NpSignaling::NowUs(); + first_establish = true; + + if (transport->addr != 0 && transport->port != 0) { + established_pkt = + MakeControlLocked(*transport, NpSignaling::ControlKind::Established); + send_established = true; + peer_addr = transport->addr; + peer_port = transport->port; + } + } + + for (auto& [conn_id, ci] : NpHandler::GetInstance().GetSignalingState().connections) { + if (ci.transport_id != transport_id) { + continue; + } + SyncSig1StatusLocked(ci, *transport); + + if (ci.peer_activated && !ci.locally_activated && !ci.peer_activated_fired) { + ci.peer_activated_fired = true; + sig1_peer_activated.push_back(conn_id); + } + if (ci.ctx_id != 0 && ci.locally_activated && + ci.status == NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE && + !ci.sig1_established_event_fired) { + ci.sig1_established_event_fired = true; + sig1_established.push_back(conn_id); + } + if (ci.ctx_id != 0 && ci.locally_activated && + ci.status == NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE && + ci.peer_activated && !ci.mutual_fired) { + ci.mutual_fired = true; + sig1_mutual.push_back(conn_id); + } + } + } + + if (send_established) { + NpSignaling::Transport::ControlSendTo(&established_pkt, sizeof(established_pkt), peer_addr, + peer_port); + } + for (const s32 conn_id : sig1_peer_activated) { + NpSignaling::DispatchPeerActivatedEvent(conn_id); + } + for (const s32 conn_id : sig1_established) { + NpSignaling::DispatchConnectionEvent(conn_id, + NpSignaling::ORBIS_NP_SIGNALING_EVENT_ESTABLISHED, 0); + } + for (const s32 conn_id : sig1_mutual) { + NpSignaling::DispatchConnectionEvent( + conn_id, NpSignaling::ORBIS_NP_SIGNALING_EVENT_MUTUAL_ACTIVATED, 0); + } +} + +void ProcessPendingActivations() { + std::vector work; + std::vector retry; + { + NpSignaling::SignalingMutexGuard lock; + if (NpHandler::GetInstance().GetSignalingState().pending_activations.empty()) { + return; + } + work.swap(NpHandler::GetInstance().GetSignalingState().pending_activations); + } + + for (const NpSignaling::PendingActivation& act : work) { + s32 transport_id = 0; + { + NpSignaling::SignalingMutexGuard lock; + const auto it = + NpHandler::GetInstance().GetSignalingState().connections.find(act.conn_id); + if (it == NpHandler::GetInstance().GetSignalingState().connections.end() || + it->second.status == NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE) { + continue; + } + transport_id = it->second.transport_id; + } + + u32 peer_addr = 0; + u16 peer_port = 0; + const bool resolved = + NpSignaling::Transport::ResolvePeer(act.peer_online_id, &peer_addr, &peer_port) && + peer_addr != 0 && peer_port != 0; + + if (!resolved) { + LOG_DEBUG(Lib_NpSignaling, + "Signaling activation pending: conn={} peer='{}' resolve failed", act.conn_id, + act.peer_online_id); + retry.push_back(act); + continue; + } + + bool already_established = false; + { + NpSignaling::SignalingMutexGuard lock; + const auto it = + NpHandler::GetInstance().GetSignalingState().connections.find(act.conn_id); + if (it == NpHandler::GetInstance().GetSignalingState().connections.end() || + it->second.status == NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE) { + continue; + } + auto* transport = GetTransportLocked(it->second.transport_id); + if (!transport) { + continue; + } + transport->addr = peer_addr; + transport->port = peer_port; + SyncSig1StatusLocked(it->second, *transport); + LOG_DEBUG(Lib_NpSignaling, + "Signaling activation resolved: conn={} transport={} peer='{}' " + "endpoint={:#x}:{} start_handshake={}", + act.conn_id, it->second.transport_id, act.peer_online_id, peer_addr, + Net::sceNetNtohs(peer_port), act.start_handshake); + SendActivate(it->second); + already_established = transport->state == NpSignaling::ConnState::Established; + } + + if (act.start_handshake) { + StartTransportHandshakeInitiator(transport_id); + } else if (already_established) { + EstablishConnection(transport_id, false); + } + } + + if (!retry.empty()) { + NpSignaling::SignalingMutexGuard lock; + NpHandler::GetInstance().GetSignalingState().pending_activations.insert( + NpHandler::GetInstance().GetSignalingState().pending_activations.end(), retry.begin(), + retry.end()); + } +} + +void HandleHandshakePacket(u32 from_addr, u16 from_port, + const NpSignaling::SignalingHandshake& pkt) { + const std::string from_id = PacketOnlineIdString(pkt.online_id_from); + const NpSignaling::HandshakeKind kind = static_cast(pkt.kind); + + s32 establish_transport = 0; + NpSignaling::SignalingHandshake reply{}; + bool send_reply = false; + s32 reply_transport = 0; + + { + NpSignaling::SignalingMutexGuard lock; + + const s32 transport_id = FindTransportByOnlineIdLocked(from_id); + if (transport_id != 0) { + NpHandler::GetInstance() + .GetSignalingState() + .peer_transports[transport_id] + .last_peer_rx_us = NpSignaling::NowUs(); + } + + if (kind == NpSignaling::HandshakeKind::Offer) { + if (transport_id == 0) { + return; + } + auto& transport = + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id]; + transport.addr = pkt.mapped_addr != 0 ? pkt.mapped_addr : from_addr; + transport.port = from_port; + transport.peer_activated = true; + if (transport.state != NpSignaling::ConnState::Established) { + SetTransportStateLocked(transport, NpSignaling::ConnState::SendingAccept); + reply = MakeHandshakeLocked(transport, NpSignaling::HandshakeKind::Accept); + send_reply = true; + reply_transport = transport_id; + } + } else if (transport_id == 0) { + return; + } else if (kind == NpSignaling::HandshakeKind::Accept) { + auto& transport = + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id]; + transport.peer_activated = true; + transport.addr = pkt.mapped_addr != 0 ? pkt.mapped_addr : from_addr; + transport.port = from_port; + if (transport.state == NpSignaling::ConnState::SendingOffer || + transport.state == NpSignaling::ConnState::WaitAccept || + transport.state == NpSignaling::ConnState::SendingAccept || + transport.state == NpSignaling::ConnState::WaitOffer) { + SetTransportStateLocked(transport, NpSignaling::ConnState::ConnCheck); + reply = MakeHandshakeLocked(transport, NpSignaling::HandshakeKind::Check); + reply.nonce = static_cast(NpSignaling::NowUs()); + send_reply = true; + reply_transport = transport_id; + } + } else if (kind == NpSignaling::HandshakeKind::Check) { + auto& transport = + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id]; + reply = MakeHandshakeLocked(transport, NpSignaling::HandshakeKind::CheckAck); + reply.nonce = pkt.nonce; + send_reply = true; + reply_transport = transport_id; + if (transport.state == NpSignaling::ConnState::SendingAccept || + transport.state == NpSignaling::ConnState::WaitOffer) { + SetTransportStateLocked(transport, NpSignaling::ConnState::ConnCheck); + } + } else if (kind == NpSignaling::HandshakeKind::CheckAck) { + const s64 rtt_us = NpSignaling::NowUs() - static_cast(pkt.nonce); + if (rtt_us >= 0) { + auto& transport = + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id]; + const u32 idx = transport.probe_sample_write_index % + NpSignaling::PeerTransport::kProbeSampleCount; + transport.probe_rtt_samples[idx] = static_cast(rtt_us); + transport.probe_sample_write_index = idx + 1; + } + if (NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id].state != + NpSignaling::ConnState::Established) { + establish_transport = transport_id; + } + } + SyncSig1ConnectionsForTransportLocked(transport_id); + } + + if (send_reply) { + NpSignaling::SignalingMutexGuard lock; + auto* transport = GetTransportLocked(reply_transport); + if (transport) { + SendHandshakeLocked(*transport, reply); + } + } + if (establish_transport != 0) { + EstablishConnection(establish_transport, true); + } +} + +void HandleControlPacket(u32 from_addr, u16 from_port, const NpSignaling::SignalingControl& pkt) { + const std::string from_id = PacketOnlineIdString(pkt.online_id_from); + const NpSignaling::ControlKind kind = static_cast(pkt.kind); + + s32 establish_transport = 0; + s32 dead_conn = 0; + s32 peer_deactivated_conn = 0; + NpSignaling::SignalingControl ack{}; + bool send_ack = false; + u32 ack_addr = 0; + u16 ack_port = 0; + + { + NpSignaling::SignalingMutexGuard lock; + + s32 transport_id = FindTransportByOnlineIdLocked(from_id); + + if (kind == NpSignaling::ControlKind::ActivationRequest) { + const std::string to_id = PacketOnlineIdString(pkt.online_id_to); + s32 ctx_id = 0; + for (auto& [cid, ctx] : NpHandler::GetInstance().GetSignalingState().contexts) { + if (ctx.active && NpSignaling::OnlineIdEqualsString(ctx.owner_online_id, to_id)) { + ctx_id = cid; + break; + } + } + OrbisNpOnlineId peer_online_id{}; + SetNpOnlineId(peer_online_id, + std::string_view(reinterpret_cast(pkt.online_id_from), + ORBIS_NP_ONLINEID_MAX_LENGTH)); + OrbisNpId peer_npid = NpSignaling::NpIdFromOnlineId(peer_online_id); + OrbisNpOnlineId local_online_id{}; + if (ctx_id != 0) { + local_online_id = + NpHandler::GetInstance().GetSignalingState().contexts[ctx_id].owner_online_id; + } else { + SetNpOnlineId(local_online_id, + std::string_view(reinterpret_cast(pkt.online_id_to), + ORBIS_NP_ONLINEID_MAX_LENGTH)); + } + if (transport_id == 0) { + transport_id = EnsureTransportLocked( + local_online_id, peer_online_id, peer_npid, + pkt.mapped_addr != 0 ? pkt.mapped_addr : from_addr, from_port); + if (transport_id < 0) { + return; + } + } + auto& transport = + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id]; + transport.peer_activated = true; + transport.addr = pkt.mapped_addr != 0 ? pkt.mapped_addr : from_addr; + transport.port = from_port; + if (transport.state == NpSignaling::ConnState::Inactive) { + SetTransportStateLocked(transport, NpSignaling::ConnState::WaitOffer); + } + + s32 conn_id = ctx_id != 0 ? FindConnectionByOnlineIdLocked(from_id, ctx_id) : 0; + if (ctx_id != 0 && conn_id == 0) { + conn_id = NpSignaling::AllocateConnectionIdLocked(); + if (conn_id < 0) { + return; + } + NpSignaling::ConnectionInfo ci{}; + ci.conn_id = conn_id; + ci.ctx_id = ctx_id; + ci.transport_id = transport_id; + ci.locally_activated = false; + ci.peer_activated = true; + ci.online_id = peer_online_id; + ci.npid = peer_npid; + SyncSig1StatusLocked(ci, transport); + NpHandler::GetInstance().GetSignalingState().connections[conn_id] = std::move(ci); + NpHandler::GetInstance() + .GetSignalingState() + .npid_to_conn[NpSignaling::MakeCtxNpIdKey( + ctx_id, + NpHandler::GetInstance().GetSignalingState().connections[conn_id].npid)] = + conn_id; + } + if (conn_id != 0) { + auto& ci = NpHandler::GetInstance().GetSignalingState().connections[conn_id]; + ci.peer_activated = true; + ci.transport_id = transport_id; + LOG_DEBUG(Lib_NpSignaling, + "Signaling activation request received: conn={} transport={} peer='{}' " + "state={} local_active={}", + conn_id, transport_id, from_id, static_cast(transport.state), + ci.locally_activated); + } + SyncSig1ConnectionsForTransportLocked(transport_id); + if (transport.state == NpSignaling::ConnState::Established) { + establish_transport = transport_id; + } + ack = MakeControlLocked(transport, NpSignaling::ControlKind::ActivationAck); + send_ack = true; + ack_addr = transport.addr; + ack_port = transport.port; + } else if (transport_id == 0) { + return; + } else if (kind == NpSignaling::ControlKind::ActivationAck) { + auto& transport = + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id]; + transport.peer_activated = true; + if (pkt.mapped_addr != 0) { + transport.addr = pkt.mapped_addr; + } + const s32 conn_id = FindConnectionByOnlineIdLocked(from_id); + if (conn_id != 0) { + const auto& ci = NpHandler::GetInstance().GetSignalingState().connections[conn_id]; + LOG_DEBUG(Lib_NpSignaling, + "Signaling activation ack received: conn={} transport={} peer='{}' " + "state={} local_active={}", + conn_id, transport_id, from_id, static_cast(transport.state), + ci.locally_activated); + } + SyncSig1ConnectionsForTransportLocked(transport_id); + } else if (kind == NpSignaling::ControlKind::Established) { + auto& transport = + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id]; + transport.peer_established = true; + if (transport.state == NpSignaling::ConnState::Established) { + establish_transport = transport_id; + } + } else if (kind == NpSignaling::ControlKind::Close) { + const s32 conn_id = FindConnectionByOnlineIdLocked(from_id); + const NpSignaling::ControlReason reason = + static_cast(pkt.reason); + if (reason == NpSignaling::ControlReason::Deactivate && conn_id != 0 && + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id].state == + NpSignaling::ConnState::Established) { + peer_deactivated_conn = conn_id; + } else { + dead_conn = conn_id; + if (dead_conn == 0) { + SetTransportStateLocked( + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id], + NpSignaling::ConnState::Inactive); + QueueMatching2DeadForTransport( + transport_id, ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_PEER); + } + } + } + } + + if (send_ack && ack_addr != 0 && ack_port != 0) { + NpSignaling::Transport::ControlSendTo(&ack, sizeof(ack), ack_addr, ack_port); + } + if (establish_transport != 0) { + EstablishConnection(establish_transport, false); + } + if (peer_deactivated_conn != 0) { + NpSignaling::DispatchConnectionEvent( + peer_deactivated_conn, NpSignaling::ORBIS_NP_SIGNALING_EVENT_PEER_DEACTIVATED, 0); + } + if (dead_conn != 0) { + CloseConnectionAndDispatchDead(dead_conn, ORBIS_NP_SIGNALING_ERROR_TERMINATED_BY_PEER, + ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_PEER); + } +} + +void SyncMatching2Peers() { + struct Snapshot { + s32 ctx_id = 0; + u64 room_id = 0; + u16 member_id = 0; + s32 transport_id = 0; + s32 status = 0; + u32 addr = 0; + u16 port = 0; + u32 rtt = 0; + }; + + std::vector snapshots; + { + NpSignaling::SignalingMutexGuard lock; + for (u32 id = 1; id <= NpMatching2::ContextManager::kMaxContexts; ++id) { + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get( + static_cast(id)); + if (!ctx || !ctx->signaling_callback) { + continue; + } + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx->ctx_id); + for (const auto& [member_id, peer] : cache->peers) { + if (peer.conn_id == 0) { + continue; + } + const auto transport_it = + NpHandler::GetInstance().GetSignalingState().peer_transports.find(peer.conn_id); + if (transport_it == + NpHandler::GetInstance().GetSignalingState().peer_transports.end()) { + continue; + } + NpMatching2::OrbisNpMatching2RoomId room_id = 0; + for (const auto& [rid, room] : cache->rooms) { + if (room.members.find(member_id) != room.members.end()) { + room_id = rid; + break; + } + } + if (room_id == 0) { + continue; + } + const auto& transport = transport_it->second; + snapshots.push_back({ + .ctx_id = ctx->ctx_id, + .room_id = room_id, + .member_id = member_id, + .transport_id = peer.conn_id, + .status = transport.status, + .addr = transport.addr, + .port = transport.port, + .rtt = AverageRtt(transport), + }); + } + } + } + + for (const Snapshot& s : snapshots) { + auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get( + static_cast(s.ctx_id)); + if (!ctx || !ctx->signaling_callback) { + continue; + } + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx->ctx_id); + auto& peer = + cache->peers[static_cast(s.member_id)]; + peer.conn_id = s.transport_id; + peer.member_id = static_cast(s.member_id); + peer.addr = s.addr; + peer.port = s.port; + peer.ping_us = s.rtt; + peer.status = s.status == NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE + ? kMatching2ConnActive + : s.status == NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_PENDING + ? kMatching2ConnPending + : kMatching2ConnInactive; + if (peer.status == kMatching2ConnActive && !peer.sent_established) { + peer.sent_established = true; + NpMatching2::QueueMatching2SignalingEvent( + *ctx, static_cast(s.room_id), + static_cast(s.member_id), + NpMatching2::ORBIS_NP_MATCHING2_SIGNALING_EVENT_ESTABLISHED, ORBIS_OK); + LOG_INFO(Lib_NpMatching2, + "Matching2 signaling established: ctx={} room={} member={} addr={:#x}:{}", + s.ctx_id, s.room_id, s.member_id, s.addr, Net::sceNetNtohs(s.port)); + } + } +} + +void StepPendingConnections() { + const s64 now = NpSignaling::NowUs(); + std::vector resend; + std::vector timeout; + + { + NpSignaling::SignalingMutexGuard lock; + for (const auto& [transport_id, transport] : + NpHandler::GetInstance().GetSignalingState().peer_transports) { + if (transport.status == NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE || + transport.state == NpSignaling::ConnState::Inactive) { + continue; + } + + if (transport.last_handshake_send_us != 0 && + now - transport.last_handshake_send_us > + std::chrono::duration_cast(kMatching2Timeout) + .count()) { + timeout.push_back(transport_id); + continue; + } + + if (transport.last_handshake_send_us == 0 || + now - transport.last_handshake_send_us >= + std::chrono::duration_cast(kMatching2StepInterval) + .count()) { + resend.push_back(transport_id); + } + } + } + + for (const s32 transport_id : resend) { + StartTransportHandshakeInitiator(transport_id); + } + for (const s32 transport_id : timeout) { + CloseTransportAndDispatchDead(transport_id, ORBIS_NP_SIGNALING_ERROR_TIMEOUT, + ORBIS_NP_MATCHING2_SIGNALING_ERROR_TIMEOUT); + } +} + +void ThreadMain() { + LOG_DEBUG(Lib_NpSignaling, "Signaling runtime thread started"); + auto& state = NpHandler::GetInstance().GetSignalingState(); + auto last_publish = std::chrono::steady_clock::time_point{}; + while (!state.runtime_stop.load(std::memory_order_relaxed)) { + if (!NpSignaling::Transport::EnsureTransport()) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + continue; + } + + const auto now = std::chrono::steady_clock::now(); + DrainControlPackets(); + DrainSignalingPackets(); + StepPendingConnections(); + SendEchoPings(); + SyncMatching2Peers(); + + if (last_publish.time_since_epoch().count() == 0 || + now - last_publish >= kStunPingInterval) { + if (PublishSignalingInfo() != 0) { + last_publish = now; + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + LOG_DEBUG(Lib_NpSignaling, "Signaling runtime thread stopped"); +} + +void ActivationThreadMain() { + auto& state = NpHandler::GetInstance().GetSignalingState(); + while (!state.runtime_stop.load(std::memory_order_relaxed)) { + { + std::unique_lock lock(state.activation_mutex); + state.activation_cv.wait_for(lock, std::chrono::milliseconds(250), [&state] { + if (state.runtime_stop.load(std::memory_order_relaxed)) { + return true; + } + NpSignaling::SignalingMutexGuard signaling_lock; + return !state.pending_activations.empty(); + }); + } + + if (state.runtime_stop.load(std::memory_order_relaxed)) { + break; + } + + ProcessPendingActivations(); + } +} + +} // namespace + +void Start() { + auto& state = NpHandler::GetInstance().GetSignalingState(); + std::lock_guard lock(state.runtime_thread_mutex); + if (state.runtime_thread.joinable()) { + LOG_DEBUG(Lib_NpSignaling, "Signaling runtime already started refs={}", state.context_refs); + return; + } + state.runtime_stop.store(false, std::memory_order_relaxed); + LOG_DEBUG(Lib_NpSignaling, "Signaling runtime starting refs={}", state.context_refs); + state.runtime_thread = std::thread(ThreadMain); + state.activation_thread = std::thread(ActivationThreadMain); +} + +void Stop() { + auto& state = NpHandler::GetInstance().GetSignalingState(); + { + std::lock_guard lock(state.runtime_thread_mutex); + if (state.context_refs > 0) { + return; + } + state.runtime_stop.store(true, std::memory_order_relaxed); + state.activation_cv.notify_all(); + } + if (state.runtime_thread.joinable()) { + state.runtime_thread.join(); + } + if (state.activation_thread.joinable()) { + state.activation_thread.join(); + } +} + +void AddContextRef() { + auto& state = NpHandler::GetInstance().GetSignalingState(); + u32 refs = 0; + { + std::lock_guard lock(state.runtime_thread_mutex); + refs = ++state.context_refs; + } + LOG_DEBUG(Lib_NpSignaling, "Signaling runtime ref added refs={}", refs); + Start(); +} + +void ReleaseContextRef() { + auto& state = NpHandler::GetInstance().GetSignalingState(); + bool should_stop = false; + u32 refs = 0; + { + std::lock_guard lock(state.runtime_thread_mutex); + if (state.context_refs > 0) { + --state.context_refs; + } + refs = state.context_refs; + should_stop = state.context_refs == 0; + } + LOG_DEBUG(Lib_NpSignaling, "Signaling runtime ref released refs={}", refs); + if (should_stop) { + Stop(); + } +} + +s32 ActivateSig1(NpSignaling::OrbisNpSignalingContextId ctx_id, const OrbisNpId& peer_npid, + const OrbisNpOnlineId& peer_online_id, + NpSignaling::OrbisNpSignalingConnectionId* out_conn_id) { + if (!out_conn_id) { + return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT; + } + + const std::string peer_online_id_str = OnlineIdString(peer_online_id); + s32 conn_id = 0; + bool reused_established = false; + bool queue_activation = false; + bool start_handshake = true; + + { + NpSignaling::SignalingMutexGuard lock; + if (!NpHandler::GetInstance().GetSignalingState().initialized) { + return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED; + } + + const auto ctx_it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctx_id); + if (ctx_it == NpHandler::GetInstance().GetSignalingState().contexts.end() || + !ctx_it->second.active) { + return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND; + } + if (std::memcmp(&ctx_it->second.owner_npid, &peer_npid, sizeof(peer_npid)) == 0) { + return ORBIS_NP_SIGNALING_ERROR_OWN_NP_ID; + } + if (!NpSignaling::ConsumeActivationBudgetGatedLocked(ctx_it->second)) { + return ORBIS_NP_SIGNALING_ERROR_EXCEED_RATE_LIMIT; + } + + const NpSignaling::CtxNpIdKey lookup_key = NpSignaling::MakeCtxNpIdKey(ctx_id, peer_npid); + const auto existing_for_ctx = + NpHandler::GetInstance().GetSignalingState().npid_to_conn.find(lookup_key); + if (existing_for_ctx != NpHandler::GetInstance().GetSignalingState().npid_to_conn.end()) { + const auto conn_it = NpHandler::GetInstance().GetSignalingState().connections.find( + existing_for_ctx->second); + if (conn_it != NpHandler::GetInstance().GetSignalingState().connections.end() && + conn_it->second.status == NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE) { + NpSignaling::RemoveConnectionLocked(existing_for_ctx->second); + } else if (conn_it != NpHandler::GetInstance().GetSignalingState().connections.end()) { + conn_id = existing_for_ctx->second; + } + } + + bool created_new = false; + const s32 transport_id = + EnsureTransportLocked(ctx_it->second.owner_online_id, peer_online_id, peer_npid, 0, 0); + if (transport_id < 0) { + return ORBIS_NP_SIGNALING_ERROR_OUT_OF_MEMORY; + } + auto& transport = + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id]; + if (conn_id == 0) { + conn_id = NpSignaling::AllocateConnectionIdLocked(); + if (conn_id < 0) { + return ORBIS_NP_SIGNALING_ERROR_OUT_OF_MEMORY; + } + NpSignaling::ConnectionInfo ci{}; + ci.conn_id = conn_id; + ci.ctx_id = ctx_id; + ci.transport_id = transport_id; + ci.locally_activated = true; + ci.npid = peer_npid; + ci.online_id = peer_online_id; + SyncSig1StatusLocked(ci, transport); + if (ci.status == NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE) { + ci.status = NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_PENDING; + } + NpHandler::GetInstance().GetSignalingState().connections[conn_id] = std::move(ci); + created_new = true; + } else { + NpSignaling::ConnectionInfo& ci = + NpHandler::GetInstance().GetSignalingState().connections[conn_id]; + const bool was_locally_activated = ci.locally_activated; + ci.ctx_id = ctx_id; + ci.transport_id = transport_id; + ci.locally_activated = true; + ci.npid = peer_npid; + ci.online_id = peer_online_id; + SyncSig1StatusLocked(ci, transport); + if (transport.state == NpSignaling::ConnState::Established) { + reused_established = true; + start_handshake = false; + } else { + if (transport.state == NpSignaling::ConnState::Inactive || + transport.state == NpSignaling::ConnState::WaitOffer) { + SetTransportStateLocked(transport, NpSignaling::ConnState::SendingOffer); + SyncSig1StatusLocked(ci, transport); + } + start_handshake = true; + } + queue_activation = !was_locally_activated; + } + + NpHandler::GetInstance().GetSignalingState().npid_to_conn[lookup_key] = conn_id; + if (created_new) { + queue_activation = true; + if (transport.state == NpSignaling::ConnState::Established) { + reused_established = true; + start_handshake = false; + } else { + start_handshake = true; + } + } + if (queue_activation) { + LOG_DEBUG(Lib_NpSignaling, + "Signaling activation queued: ctx={} conn={} transport={} peer='{}' " + "start_handshake={} state={} status={}", + ctx_id, conn_id, transport_id, peer_online_id_str, start_handshake, + static_cast(transport.state), transport.status); + QueueActivationLocked(conn_id, peer_online_id_str, start_handshake); + } else { + LOG_DEBUG(Lib_NpSignaling, + "Signaling activation reused: ctx={} conn={} transport={} peer='{}' " + "state={} status={} established={}", + ctx_id, conn_id, transport_id, peer_online_id_str, + static_cast(transport.state), transport.status, reused_established); + } + } + + *out_conn_id = conn_id; + if (reused_established) { + s32 transport_id = 0; + { + NpSignaling::SignalingMutexGuard lock; + const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(conn_id); + if (it != NpHandler::GetInstance().GetSignalingState().connections.end()) { + transport_id = it->second.transport_id; + } + } + if (transport_id != 0) { + EstablishConnection(transport_id, false); + } + } + return ORBIS_OK; +} + +void DeactivateSig1(NpSignaling::OrbisNpSignalingConnectionId conn_id) { + { + NpSignaling::SignalingMutexGuard lock; + const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(conn_id); + if (it == NpHandler::GetInstance().GetSignalingState().connections.end()) { + return; + } + NpSignaling::ConnectionInfo& ci = it->second; + auto* transport = GetTransportLocked(ci.transport_id); + const bool active = transport && transport->state == NpSignaling::ConnState::Established; + if (active && !ci.dead_fired) { + SendControlCloseLocked(ci, NpSignaling::ControlReason::Deactivate); + } + } + CloseConnectionAndDispatchDead(conn_id, ORBIS_NP_SIGNALING_ERROR_TERMINATED_BY_MYSELF, + ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_MYSELF); +} + +void TerminateSig1(NpSignaling::OrbisNpSignalingConnectionId conn_id) { + { + NpSignaling::SignalingMutexGuard lock; + const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(conn_id); + if (it != NpHandler::GetInstance().GetSignalingState().connections.end()) { + SendControlCloseLocked(it->second, NpSignaling::ControlReason::Terminate); + } + } + CloseConnectionAndDispatchDead(conn_id, ORBIS_NP_SIGNALING_ERROR_TERMINATED_BY_MYSELF, + ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_MYSELF); +} + +void StartMatching2(NpMatching2::ContextObject& ctx) { + if (!ctx.signaling_callback) { + return; + } + + if (!NpSignaling::Transport::EnsureTransport()) { + return; + } + + struct PeerTarget { + NpMatching2::OrbisNpMatching2RoomMemberId member_id; + NpMatching2::MemberCache member; + }; + + std::vector targets; + { + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + for (const auto& [room_id, room] : cache->rooms) { + for (const auto& [member_id, member] : room.members) { + if (ShouldConnectToPeer(room, ctx.my_member_id, member_id)) { + targets.push_back({member_id, member}); + } + } + } + } + + std::vector pending_transports; + for (const auto& target : targets) { + const auto member_id = target.member_id; + const auto& member = target.member; + + u32 peer_addr = 0; + u16 peer_port = 0; + if (!ResolvePeerEndpoint(member, &peer_addr, &peer_port)) { + continue; + } + + s32 transport_id = 0; + s32 status = NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_PENDING; + { + NpSignaling::SignalingMutexGuard lock; + transport_id = EnsureTransportLocked(ctx.online_id, member.np_id.handle, member.np_id, + peer_addr, peer_port); + if (transport_id < 0) { + continue; + } + auto& transport = + NpHandler::GetInstance().GetSignalingState().peer_transports[transport_id]; + if (transport.state == NpSignaling::ConnState::WaitOffer) { + SetTransportStateLocked(transport, NpSignaling::ConnState::SendingOffer); + } + status = transport.status; + if (transport.state != NpSignaling::ConnState::Established) { + pending_transports.push_back(transport_id); + } + } + + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + auto& peer = cache->peers[member_id]; + peer.conn_id = transport_id; + peer.member_id = member_id; + peer.addr = peer_addr; + peer.port = peer_port; + peer.status = status == NpSignaling::ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE + ? kMatching2ConnActive + : kMatching2ConnPending; + } + + SyncMatching2Peers(); + for (const s32 transport_id : pending_transports) { + StartTransportHandshakeInitiator(transport_id); + } +} + +void StopMatching2(NpMatching2::ContextObject& ctx, NpMatching2::OrbisNpMatching2RoomId room_id, + s32 error_code) { + std::vector transports; + { + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + const auto room_it = cache->rooms.find(room_id); + if (room_it != cache->rooms.end()) { + for (const auto& [member_id, member] : room_it->second.members) { + const auto peer_it = cache->peers.find(member_id); + if (peer_it != cache->peers.end() && peer_it->second.conn_id != 0) { + transports.push_back(peer_it->second.conn_id); + } + } + } + } + + for (const s32 transport_id : transports) { + bool close_transport = false; + { + NpSignaling::SignalingMutexGuard lock; + close_transport = !HasSig1ConnectionForTransportLocked(transport_id); + } + if (close_transport) { + CloseTransportAndDispatchDead(transport_id, error_code, error_code); + } + } +} + +void StopMatching2(NpMatching2::ContextObject& ctx, s32 error_code) { + std::vector rooms; + { + auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id); + rooms.reserve(cache->rooms.size()); + for (const auto& [room_id, room] : cache->rooms) { + rooms.push_back(room_id); + } + } + for (const auto room_id : rooms) { + StopMatching2(ctx, room_id, error_code); + } +} + +} // namespace Libraries::Np::SignalingHandler diff --git a/src/core/libraries/np/signaling_handler.h b/src/core/libraries/np/signaling_handler.h new file mode 100644 index 000000000..135a67066 --- /dev/null +++ b/src/core/libraries/np/signaling_handler.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "core/libraries/np/np_matching2/np_matching2.h" +#include "core/libraries/np/np_types.h" +#include "core/libraries/np/np_types2.h" + +namespace Libraries::Np::NpMatching2 { +struct ContextObject; +} + +namespace Libraries::Np::SignalingHandler { + +void Start(); +void Stop(); +void AddContextRef(); +void ReleaseContextRef(); + +s32 ActivateSig1(NpSignaling::OrbisNpSignalingContextId ctx_id, const OrbisNpId& peer_npid, + const OrbisNpOnlineId& peer_online_id, + NpSignaling::OrbisNpSignalingConnectionId* out_conn_id); +void DeactivateSig1(NpSignaling::OrbisNpSignalingConnectionId conn_id); +void TerminateSig1(NpSignaling::OrbisNpSignalingConnectionId conn_id); + +void StartMatching2(NpMatching2::ContextObject& ctx); +void StopMatching2(NpMatching2::ContextObject& ctx, s32 error_code); +void StopMatching2(NpMatching2::ContextObject& ctx, NpMatching2::OrbisNpMatching2RoomId room_id, + s32 error_code); + +} // namespace Libraries::Np::SignalingHandler diff --git a/src/core/net/guest_net.cpp b/src/core/net/guest_net.cpp new file mode 100644 index 000000000..bed28aa5c --- /dev/null +++ b/src/core/net/guest_net.cpp @@ -0,0 +1,1740 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/net/guest_net.h" +#include "core/net/p2p_transport.h" + +namespace Core::Net { + +using Host::Error; + +namespace { + +// Blocking calls cut short by abort or close fail with EINTR. +constexpr Error AbortedError = Error::Intr; +// Max bytes per host send() on a stream socket, see SocketSendTo. +constexpr size_t MaxStreamSendChunk = 64 * 1024; + +struct GuestEpoll; + +enum class Side { Recv = 0, Send = 1 }; + +// A blocking call in progress, on the caller's stack. +struct Waiter { + const Host::WakeHandle* wake; + Side side; + bool aborted = false; // owner's wait_mutex +}; + +// Per-thread wake handle. Abort and close signal it to interrupt a blocking call. +const Host::WakeHandle* ThreadWake() { + thread_local Host::WakeHandle wake; + return wake.Valid() ? &wake : nullptr; +} + +struct GuestSocket { + GuestSocket(Host::NativeSocket native_, int type_, u32 generation_) + : native(native_), type(type_), generation(generation_) {} + GuestSocket(std::shared_ptr dgram, u32 generation_) + : native(Host::InvalidSocket), type(SOCK_DGRAM), generation(generation_), + p2p_dgram(std::move(dgram)) {} + GuestSocket(std::shared_ptr stream, u32 generation_) + : native(Host::InvalidSocket), type(SOCK_STREAM), generation(generation_), + p2p_stream(std::move(stream)) {} + ~GuestSocket() { + // P2P streams still close gracefully (FIN, TIME-WAIT) inside the transport. + if (p2p_stream) { + p2p_stream->Close(); + } + if (p2p_dgram) { + p2p_dgram->Close(); + } + if (native != Host::InvalidSocket && type == SOCK_STREAM) { + Host::PrepareClose(native, !nonblocking); + } + Host::CloseSocket(native); + } + GuestSocket(const GuestSocket&) = delete; + GuestSocket& operator=(const GuestSocket&) = delete; + + bool IsP2P() const { + return p2p_dgram || p2p_stream; + } + const P2P::Readiness* P2PHandles() const { + return p2p_dgram ? &p2p_dgram->Handles() : p2p_stream ? &p2p_stream->Handles() : nullptr; + } + u32 P2PEvents() const { + return p2p_dgram ? p2p_dgram->Events() : p2p_stream ? p2p_stream->Events() : 0; + } + + const Host::NativeSocket native; // invalid for P2P + const int type; + const u32 generation; // tells reused ids apart in epoll tags + const std::shared_ptr p2p_dgram; + const std::shared_ptr p2p_stream; + + std::atomic nonblocking{false}; + std::atomic reuse_addr{false}; + std::atomic reuse_port{false}; + std::atomic rcv_timeout_us{0}; // 0 = no timeout + std::atomic snd_timeout_us{0}; + std::atomic connect_timeout_us{0}; // SO_CONNECTTIMEO + std::atomic accept_timeout_us{0}; // SO_ACCEPTTIMEO + std::atomic connected{false}; // also set while connecting + std::atomic listening{false}; + + std::mutex attr_mutex; + SocketAttributes attributes; + + // Guarded by wait_mutex. + std::mutex wait_mutex; + std::vector waiters; + std::array pending_abort{}; // preserved aborts, by Side + bool send_again = false; // SND_PRESERVATION_AGAIN + bool closing = false; + + // Guarded by reg_mutex. Lock order: GuestEpoll::mutex first. + std::mutex reg_mutex; + bool closed = false; + std::vector> epolls; +}; + +struct GuestEpoll { + struct Registration { + u32 generation; + u64 data; + u32 events; + bool p2p; + bool has_writable; + bool external; + bool armed; + bool reported = false; + }; + + Host::HostEpoll host; + std::mutex mutex; + std::unordered_map regs; + std::vector waiters; + int flagged_waiters = 0; + bool pending_abort = false; + bool destroyed = false; + std::vector> pending_hups; + bool WakeIdle() const { + return flagged_waiters == 0 && pending_hups.empty() && !destroyed; + } +}; + +struct ExternalObject { + explicit ExternalObject(u32 generation_) : generation(generation_) {} + + const u32 generation; + P2P::ReadinessFlag done; + std::atomic signaled{false}; + std::atomic hangup{false}; + + std::mutex reg_mutex; + bool closed = false; + std::vector> epolls; +}; + +class ObjectTable { +public: + using Object = std::variant, std::shared_ptr, + std::shared_ptr>; + + NetResult Insert(Object object) { + // Epolls don't count towards the limit. + const bool counted = !std::holds_alternative>(object); + { + std::scoped_lock lock{mutex_}; + if (counted && counted_ >= MaxSockets) { + return NetResult::Fail(Error::MFile); + } + counted_ += counted ? 1 : 0; // reserve the slot + } + const s32 id = Allocate(); + std::scoped_lock lock{mutex_}; + if (id < 0) { + counted_ -= counted ? 1 : 0; + return NetResult::Fail(Error::NFile); + } + objects_[id] = {std::move(object), counted}; + return NetResult::Ok(id); + } + + template + std::shared_ptr Get(s32 id) const { + std::scoped_lock lock{mutex_}; + const auto it = objects_.find(id); + if (it == objects_.end()) { + return nullptr; + } + const auto* object = std::get_if>(&it->second.object); + return object ? *object : nullptr; + } + + template + std::shared_ptr Remove(s32 id) { + std::shared_ptr removed; + { + std::scoped_lock lock{mutex_}; + const auto it = objects_.find(id); + if (it == objects_.end()) { + return nullptr; + } + auto* object = std::get_if>(&it->second.object); + if (object == nullptr) { + return nullptr; + } + removed = std::move(*object); + counted_ -= it->second.counted ? 1 : 0; + objects_.erase(it); + } + Release(id); + return removed; + } + + std::optional Kind(s32 id) const { + std::scoped_lock lock{mutex_}; + const auto it = objects_.find(id); + if (it == objects_.end()) { + return std::nullopt; + } + switch (it->second.object.index()) { + case 0: + return NetObjectKind::Socket; + case 1: + return NetObjectKind::Epoll; + default: + return NetObjectKind::External; + } + } + + template + std::vector Ids() const { + std::vector ids; + std::scoped_lock lock{mutex_}; + for (const auto& [id, entry] : objects_) { + if (std::holds_alternative>(entry.object)) { + ids.push_back(id); + } + } + std::sort(ids.begin(), ids.end()); + return ids; + } + + void SetAllocator(IdAllocator allocator) { + std::scoped_lock lock{alloc_mutex_}; + allocator_ = std::move(allocator); + } + +private: + struct Entry { + Object object; + bool counted; + }; + + s32 Allocate() { + std::unique_lock lock{alloc_mutex_}; + if (allocator_.allocate) { + const auto allocate = allocator_.allocate; + lock.unlock(); + return allocate(); + } + auto it = std::find(used_.begin() + 1, used_.end(), false); + if (it == used_.end()) { + used_.push_back(false); + it = used_.end() - 1; + } + *it = true; + return static_cast(it - used_.begin()); + } + + void Release(s32 id) { + std::unique_lock lock{alloc_mutex_}; + if (allocator_.release) { + const auto release = allocator_.release; + lock.unlock(); + release(id); + } else if (static_cast(id) < used_.size()) { + used_[static_cast(id)] = false; + } + } + + mutable std::mutex mutex_; + std::unordered_map objects_; + int counted_ = 0; + + std::mutex alloc_mutex_; + IdAllocator allocator_; + std::vector used_{true}; // id 0 is never used +}; + +ObjectTable g_objects; +std::atomic g_next_generation{1}; + +std::mutex g_p2p_mutex; +std::shared_ptr g_p2p_transports[2]; +constexpr u64 WritableHandleBit = 1ull << 31; + +u64 MakeTag(s32 id, u32 generation, bool writable_handle = false) { + return (static_cast(generation) << 32) | static_cast(id) | + (writable_handle ? WritableHandleBit : 0); +} + +NetResult FromError(Error e) { + return e == Error::Ok ? NetResult::Ok() : NetResult::Fail(e); +} + +std::pair WaitTarget(const GuestSocket& s, u32 interest) { + if (const auto* handles = s.P2PHandles()) { + const auto& flag = (interest & Host::Writable) ? handles->writable : handles->readable; + return {flag.PollHandle(), Host::Readable}; + } + return {s.native, interest}; +} + +bool ConsumePendingAbort(GuestSocket& s, Side side) { + std::scoped_lock lock{s.wait_mutex}; + return std::exchange(s.pending_abort[static_cast(side)], false); +} + +class WaitScope { +public: + WaitScope(GuestSocket& s, Side side) : s_(s) { + waiter_.wake = ThreadWake(); + waiter_.side = side; + if (waiter_.wake == nullptr) { + entry_error_ = Error::NoBufs; + return; + } + std::scoped_lock lock{s_.wait_mutex}; + waiter_.wake->Drain(); // stale signal from a previous call + if (s_.closing) { + entry_error_ = Error::BadF; + } else if (std::exchange(s_.pending_abort[static_cast(side)], false)) { + entry_error_ = AbortedError; + } else { + s_.waiters.push_back(&waiter_); + registered_ = true; + } + } + ~WaitScope() { + if (registered_) { + std::scoped_lock lock{s_.wait_mutex}; + std::erase(s_.waiters, &waiter_); + } + } + WaitScope(const WaitScope&) = delete; + WaitScope& operator=(const WaitScope&) = delete; + + Error Entry() const { + return entry_error_; + } + bool Interrupted() { + std::scoped_lock lock{s_.wait_mutex}; + return waiter_.aborted; + } + const Host::WakeHandle& Wake() const { + return *waiter_.wake; + } + +private: + GuestSocket& s_; + Waiter waiter_{}; + Error entry_error_ = Error::Ok; + bool registered_ = false; +}; + +// Called with wait_mutex held. Returns which sides had a blocked call. +std::array InterruptWaiters(GuestSocket& s) { + std::array blocked{}; + for (Waiter* w : s.waiters) { + w->aborted = true; + w->wake->Signal(); + blocked[static_cast(w->side)] = true; + } + return blocked; +} + +template +NetResult RunBlocking(GuestSocket& s, Side side, u32 interest, Host::Deadline deadline, + bool blocking, Op&& op) { + if (!blocking) { + if (ConsumePendingAbort(s, side)) { + return NetResult::Fail(AbortedError); + } + return op(); + } + WaitScope scope(s, side); + if (const Error e = scope.Entry(); e != Error::Ok) { + return NetResult::Fail(e); + } + const auto [handle, handle_interest] = WaitTarget(s, interest); + for (;;) { + if (scope.Interrupted()) { + return NetResult::Fail(AbortedError); + } + const NetResult r = op(); + if (r.error != Error::WouldBlock) { + return r; + } + switch (Host::WaitOne(handle, handle_interest, scope.Wake(), deadline)) { + case Host::WaitResult::Ready: + case Host::WaitResult::Woken: + continue; + case Host::WaitResult::TimedOut: + // BSD: an expired SO_RCVTIMEO/SO_SNDTIMEO is EAGAIN, not ETIMEDOUT. + return NetResult::Fail(Error::WouldBlock); + case Host::WaitResult::Failed: + return NetResult::Fail(Host::LastError()); + } + } +} + +Host::Deadline SocketDeadline(const std::atomic& timeout_us) { + return Host::DeadlineFromSocketTimeout(std::chrono::microseconds{timeout_us.load()}); +} + +template +NetResult SendAll(GuestSocket& s, size_t len, bool blocking, bool stream, SendSome&& send_some) { + const auto deadline = SocketDeadline(s.snd_timeout_us); + size_t sent = 0; + for (;;) { + const NetResult r = RunBlocking(s, Side::Send, Host::Writable, deadline, blocking, + [&] { return send_some(sent); }); + if (r.error != Error::Ok) { + if (r.error == AbortedError) { + std::scoped_lock lock{s.wait_mutex}; + if (std::exchange(s.send_again, false) && sent > 0) { + s.pending_abort[static_cast(Side::Send)] = true; + } + } + return sent > 0 ? NetResult::Ok(static_cast(sent)) : r; + } + sent += static_cast(r.value); + if (!blocking || !stream || sent >= len) { + return NetResult::Ok(static_cast(sent)); + } + } +} + +template +NetResult ReceiveAll(size_t len, bool waitall, RecvSome&& recv_some) { + if (!waitall) { + return recv_some(0); + } + size_t got = 0; + while (got < len) { + const NetResult r = recv_some(got); + if (r.error != Error::Ok) { + return got > 0 ? NetResult::Ok(static_cast(got)) : r; + } + if (r.value == 0) { + break; // FIN + } + got += static_cast(r.value); + } + return NetResult::Ok(static_cast(got)); +} + +Error HostRegister(GuestEpoll& ep, s32 id, GuestSocket& s, u32 events, u32 flags, + GuestEpoll::Registration& reg) { + if (!s.IsP2P()) { + return ep.host.Add(s.native, MakeTag(id, s.generation), events, flags); + } + if (flags != 0) { + return Error::Inval; // TODO: one-shot for P2P + } + const auto* handles = s.P2PHandles(); + if (const Error e = + ep.host.Add(handles->readable.PollHandle(), MakeTag(id, s.generation), Host::EvIn, 0); + e != Error::Ok) { + return e; + } + if (events & Host::EvOut) { + if (const Error e = ep.host.Add(handles->writable.PollHandle(), + MakeTag(id, s.generation, true), Host::EvIn, 0); + e != Error::Ok) { + ep.host.Remove(handles->readable.PollHandle()); + return e; + } + reg.has_writable = true; + } + return Error::Ok; +} + +Error HostModify(GuestEpoll& ep, s32 id, GuestSocket& s, u32 events, u32 flags, + GuestEpoll::Registration& reg) { + if (!s.IsP2P()) { + return ep.host.Modify(s.native, MakeTag(id, s.generation), events, flags); + } + if (flags != 0) { + return Error::Inval; + } + const auto* handles = s.P2PHandles(); + const bool want_writable = (events & Host::EvOut) != 0; + if (want_writable && !reg.has_writable) { + if (const Error e = ep.host.Add(handles->writable.PollHandle(), + MakeTag(id, s.generation, true), Host::EvIn, 0); + e != Error::Ok) { + return e; + } + } else if (!want_writable && reg.has_writable) { + ep.host.Remove(handles->writable.PollHandle()); + } + reg.has_writable = want_writable; + return Error::Ok; +} + +Error HostUnregister(GuestEpoll& ep, GuestSocket& s, const GuestEpoll::Registration& reg) { + if (!s.IsP2P()) { + return ep.host.Remove(s.native); + } + const auto* handles = s.P2PHandles(); + if (reg.has_writable) { + ep.host.Remove(handles->writable.PollHandle()); + } + return ep.host.Remove(handles->readable.PollHandle()); +} + +std::shared_ptr P2PTransportFor(int family) { + std::scoped_lock lock{g_p2p_mutex}; + return g_p2p_transports[family == AF_INET6 ? 1 : 0]; +} + +} // namespace + +void SetIdAllocator(IdAllocator allocator) { + g_objects.SetAllocator(std::move(allocator)); +} + +std::optional GetObjectKind(s32 id) { + return g_objects.Kind(id); +} + +NetResult CloseObject(s32 id) { + switch (g_objects.Kind(id).value_or(NetObjectKind::Socket)) { + case NetObjectKind::Socket: + return SocketClose(id); // EBADF for unknown ids + case NetObjectKind::Epoll: + return EpollDestroy(id); + case NetObjectKind::External: + break; // released by its owner + } + return NetResult::Fail(Error::BadF); +} + +NetResult ReserveExternalId() { + auto object = std::make_shared(g_next_generation.fetch_add(1)); + if (!object->done.Valid()) { + return NetResult::Fail(Error::NoBufs); + } + return g_objects.Insert(std::move(object)); +} + +NetResult SignalExternal(s32 id, bool hangup) { + const auto x = g_objects.Get(id); + if (!x) { + return NetResult::Fail(Error::BadF); + } + x->hangup = hangup; + x->signaled = true; + x->done.Set(true); + return NetResult::Ok(); +} + +NetResult ReleaseExternalId(s32 id) { + const auto x = g_objects.Remove(id); + if (!x) { + return NetResult::Fail(Error::BadF); + } + std::vector> epolls; + { + std::scoped_lock lock{x->reg_mutex}; + x->closed = true; + epolls.swap(x->epolls); + } + for (const auto& weak : epolls) { + const auto ep = weak.lock(); + if (!ep) { + continue; + } + std::scoped_lock lock{ep->mutex}; + const auto it = ep->regs.find(id); + if (it != ep->regs.end() && it->second.generation == x->generation) { + if (it->second.armed) { + ep->host.Remove(x->done.PollHandle()); + } + ep->regs.erase(it); + } + } + return NetResult::Ok(); +} + +std::vector ListSockets() { + return g_objects.Ids(); +} + +s32 ToOrbisReturn(NetResult result) { + if (result.error == Error::Ok) { + return static_cast(result.value); + } + // TODO: set the guest errno (sceNetErrnoLoc) and use the ORBIS_NET_ERROR_* constants. + return static_cast(0x80410100u | static_cast(result.error)); +} + +namespace { + +bool IsLimitedBroadcast(const sockaddr* addr, socklen_t len) { + if (addr == nullptr || addr->sa_family != AF_INET || len < sizeof(sockaddr_in)) { + return false; + } + return reinterpret_cast(addr)->sin_addr.s_addr == htonl(INADDR_BROADCAST); +} + +bool BroadcastAllowed(Host::NativeSocket s) { + int on = 0; + socklen_t len = sizeof(on); + // Windows may write a 1-byte BOOL, hence on = 0. + return getsockopt(s, SOL_SOCKET, SO_BROADCAST, reinterpret_cast(&on), &len) == 0 && + on != 0; +} + +} // namespace + +NetResult SocketCreate(int family, int type, int protocol) { + // FreeBSD returns EPROTONOSUPPORT for a mismatched protocol, macOS EPROTOTYPE. + if ((family == AF_INET || family == AF_INET6) && protocol != 0 && + ((type == SOCK_STREAM && protocol != IPPROTO_TCP) || + (type == SOCK_DGRAM && protocol != IPPROTO_UDP))) { + return NetResult::Fail(Error::ProtoNoSupport); + } + Error e; + const Host::NativeSocket native = Host::CreateSocket(family, type, protocol, &e); + if (native == Host::InvalidSocket) { + return NetResult::Fail(e); + } + auto s = std::make_shared(native, type, g_next_generation.fetch_add(1)); + return g_objects.Insert(std::move(s)); +} + +NetResult SocketCreatePair(int family, int type, int protocol, s32 ids[2]) { + Host::NativeSocket natives[2]; + if (const Error e = Host::CreateSocketPair(family, type, protocol, natives); e != Error::Ok) { + return NetResult::Fail(e); + } + auto first = std::make_shared(natives[0], type, g_next_generation.fetch_add(1)); + auto second = std::make_shared(natives[1], type, g_next_generation.fetch_add(1)); + const NetResult a = g_objects.Insert(std::move(first)); + if (a.error != Error::Ok) { + return a; + } + const NetResult b = g_objects.Insert(std::move(second)); + if (b.error != Error::Ok) { + SocketClose(static_cast(a.value)); + return b; + } + ids[0] = static_cast(a.value); + ids[1] = static_cast(b.value); + return NetResult::Ok(); +} + +NetResult SocketBind(s32 id, const sockaddr* addr, socklen_t len) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->IsP2P()) { + return NetResult::Fail(Error::Inval); // use P2PSocketBind + } + if (const Error e = Host::PrepareBind(s->native, s->reuse_addr, s->reuse_port); + e != Error::Ok) { + return NetResult::Fail(e); + } + return FromError(Host::Bind(s->native, addr, len)); +} + +NetResult SocketListen(s32 id, int backlog) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->p2p_dgram) { + return NetResult::Fail(Error::OpNotSupp); + } + const Error e = + s->p2p_stream ? s->p2p_stream->Listen(backlog) : Host::Listen(s->native, backlog); + if (e == Error::Ok) { + s->listening = true; + } + return FromError(e); +} + +NetResult SocketConnect(s32 id, const sockaddr* addr, socklen_t len) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->IsP2P()) { + return NetResult::Fail(Error::Inval); // use P2PSocketConnect + } + if (s->listening) { + return NetResult::Fail(Error::OpNotSupp); + } + if (s->nonblocking) { + if (ConsumePendingAbort(*s, Side::Send)) { + return NetResult::Fail(AbortedError); + } + const Error e = Host::Connect(s->native, addr, len); + if (e == Error::Ok || e == Error::InProgress) { + s->connected = true; + } + return FromError(e); + } + // Register first so a preserved abort stops it before it starts. + WaitScope scope(*s, Side::Send); + if (const Error e = scope.Entry(); e != Error::Ok) { + return NetResult::Fail(e); + } + const Error started = Host::Connect(s->native, addr, len); + if (started == Error::Ok || started == Error::InProgress) { + s->connected = true; + } + if (started != Error::InProgress) { + return FromError(started); + } + // Poll in 500 ms slices, old WSAPoll never signals a failed connect. On SO_CONNECTTIMEO + // expiry return EWOULDBLOCK and leave the connect running. + using namespace std::chrono_literals; + const auto deadline = SocketDeadline(s->connect_timeout_us); + for (;;) { + if (scope.Interrupted()) { + return NetResult::Fail(AbortedError); + } + auto slice = Host::Clock::now() + 500ms; + if (deadline && *deadline < slice) { + if (Host::Clock::now() >= *deadline) { + return NetResult::Fail(Error::WouldBlock); + } + slice = *deadline; + } + const auto w = Host::WaitOne(s->native, Host::Writable, scope.Wake(), slice); + if (w == Host::WaitResult::Failed) { + return NetResult::Fail(Host::LastError()); + } + if (w == Host::WaitResult::Woken) { + continue; + } + if (const Error pending = Host::PendingSocketError(s->native); pending != Error::Ok) { + return NetResult::Fail(pending); + } + if (w == Host::WaitResult::Ready) { + return NetResult::Ok(); + } + } +} + +NetResult SocketAccept(s32 id, sockaddr* addr, socklen_t* len) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->IsP2P()) { + return NetResult::Fail(Error::Inval); // use P2PSocketAccept + } + Host::NativeSocket client = Host::InvalidSocket; + const NetResult r = RunBlocking( + *s, Side::Recv, Host::Readable, SocketDeadline(s->accept_timeout_us), !s->nonblocking, [&] { + Error e; + client = Host::Accept(s->native, addr, len, &e); + return client == Host::InvalidSocket ? NetResult::Fail(e) : NetResult::Ok(); + }); + if (r.error != Error::Ok) { + return r; + } + auto c = std::make_shared(client, s->type, g_next_generation.fetch_add(1)); + c->nonblocking = s->nonblocking.load(); + c->reuse_addr = s->reuse_addr.load(); + c->reuse_port = s->reuse_port.load(); + c->rcv_timeout_us = s->rcv_timeout_us.load(); + c->snd_timeout_us = s->snd_timeout_us.load(); + c->connect_timeout_us = s->connect_timeout_us.load(); + c->accept_timeout_us = s->accept_timeout_us.load(); + c->connected = true; + { + std::scoped_lock lock{s->attr_mutex}; + c->attributes = s->attributes; + } + return g_objects.Insert(std::move(c)); +} + +NetResult SocketSendTo(s32 id, const void* buf, size_t len, int host_flags, bool dontwait, + const sockaddr* addr, socklen_t addr_len) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->IsP2P()) { + return NetResult::Fail(Error::Inval); // use P2PSocketSendTo + } + if (s->type != SOCK_STREAM) { + // UDP/RAW sends never block on PS4. A full buffer silently drops the datagram. + if (ConsumePendingAbort(*s, Side::Send)) { + return NetResult::Fail(AbortedError); + } + if (IsLimitedBroadcast(addr, addr_len) && !BroadcastAllowed(s->native)) { + // FreeBSD gives EACCES without SO_BROADCAST. macOS would say EHOSTUNREACH. + return NetResult::Fail(Error::Acces); + } + const auto io = Host::SendTo(s->native, buf, len, host_flags, addr, addr_len); + if (io.error == Error::WouldBlock || io.error == Error::NoBufs) { + return NetResult::Ok(static_cast(len)); + } + if (io.error == Error::NotConn && addr == nullptr) { + // BSD and Linux give EDESTADDRREQ here, Windows ENOTCONN. + return NetResult::Fail(Error::DestAddrReq); + } + return io.error == Error::Ok ? NetResult::Ok(io.value) : NetResult::Fail(io.error); + } + const auto* bytes = static_cast(buf); + return SendAll(*s, len, !dontwait && !s->nonblocking, s->type == SOCK_STREAM, [&](size_t sent) { + const size_t chunk = std::min(len - sent, MaxStreamSendChunk); + const auto io = Host::SendTo(s->native, bytes + sent, chunk, host_flags, addr, addr_len); + return NetResult{io.value, io.error}; + }); +} + +NetResult SocketRecvFrom(s32 id, void* buf, size_t len, int host_flags, bool dontwait, + sockaddr* addr, socklen_t* addr_len) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->IsP2P()) { + return NetResult::Fail(Error::Inval); // use P2PSocketRecvFrom + } + const bool blocking = !dontwait && !s->nonblocking; + // Host socket is non-blocking, so MSG_WAITALL is handled here. + const bool waitall = (host_flags & MSG_WAITALL) != 0; + host_flags &= ~MSG_WAITALL; + const auto deadline = SocketDeadline(s->rcv_timeout_us); + auto* bytes = static_cast(buf); + return ReceiveAll(len, blocking && waitall && s->type == SOCK_STREAM, [&](size_t got) { + return RunBlocking(*s, Side::Recv, Host::Readable, deadline, blocking, [&] { + const auto io = + Host::RecvFrom(s->native, bytes + got, len - got, host_flags, + got == 0 ? addr : nullptr, got == 0 ? addr_len : nullptr); + return NetResult{io.value, io.error}; + }); + }); +} + +NetResult SocketShutdown(s32 id, int how) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->p2p_stream) { + return FromError(s->p2p_stream->Shutdown(how)); + } + if (s->p2p_dgram) { + return NetResult::Ok(); + } + return FromError(Host::ShutdownSocket(s->native, how)); +} + +NetResult SocketGetName(s32 id, sockaddr* addr, socklen_t* len) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->IsP2P()) { + return NetResult::Fail(Error::Inval); // use P2PSocketGetName + } + return FromError(getsockname(s->native, addr, len) == 0 ? Error::Ok : Host::LastError()); +} + +NetResult SocketGetPeerName(s32 id, sockaddr* addr, socklen_t* len) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->IsP2P()) { + return NetResult::Fail(Error::Inval); // use P2PSocketGetPeerName + } + return FromError(getpeername(s->native, addr, len) == 0 ? Error::Ok : Host::LastError()); +} + +NetResult SocketSetHostOption(s32 id, int level, int name, const void* value, socklen_t len) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->IsP2P()) { + return NetResult::Fail(Error::NoProtoOpt); + } + return FromError(setsockopt(s->native, level, name, static_cast(value), len) == 0 + ? Error::Ok + : Host::LastError()); +} + +NetResult SocketGetHostOption(s32 id, int level, int name, void* value, socklen_t* len) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->IsP2P()) { + return NetResult::Fail(Error::NoProtoOpt); + } + return FromError(getsockopt(s->native, level, name, static_cast(value), len) == 0 + ? Error::Ok + : Host::LastError()); +} + +NetResult SocketGetInfo(s32 id, SocketInfo* info) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + info->type = s->type; + info->p2p = s->IsP2P(); + info->nonblocking = s->nonblocking; + info->reuse_addr = s->reuse_addr; + info->rcv_timeout_us = s->rcv_timeout_us; + info->snd_timeout_us = s->snd_timeout_us; + info->connect_timeout_us = s->connect_timeout_us; + info->accept_timeout_us = s->accept_timeout_us; + info->connected = s->connected; + info->listening = s->listening; + if (s->p2p_stream) { + const Error e = s->p2p_stream->ConnectResult(); + info->pending_error = + e == Error::InProgress || e == Error::NotConn ? Error::Ok : e; // never cleared + } else if (s->p2p_dgram) { + info->pending_error = Error::Ok; + } else { + info->pending_error = Host::PendingSocketError(s->native); + } + return NetResult::Ok(); +} + +NetResult SocketGetAttributes(s32 id, SocketAttributes* out) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + std::scoped_lock lock{s->attr_mutex}; + *out = s->attributes; + return NetResult::Ok(); +} + +NetResult SocketUpdateAttributes(s32 id, const std::function& update) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + std::scoped_lock lock{s->attr_mutex}; + update(s->attributes); + return NetResult::Ok(); +} + +NetResult SocketBytesReadable(s32 id) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + if (s->IsP2P()) { + return NetResult::Fail(Error::Inval); + } + const auto r = Host::BytesReadable(s->native); + return r.error == Error::Ok ? NetResult::Ok(r.value) : NetResult::Fail(r.error); +} + +NetResult SocketSetNonBlocking(s32 id, bool enable) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + s->nonblocking = enable; // host socket is always non-blocking + return NetResult::Ok(); +} + +NetResult SocketSetReuseAddr(s32 id, bool enable) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + s->reuse_addr = enable; + return NetResult::Ok(); +} + +NetResult SocketSetReusePort(s32 id, bool enable) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + s->reuse_port = enable; + return NetResult::Ok(); +} + +NetResult SocketSetRecvTimeout(s32 id, std::chrono::microseconds value) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + s->rcv_timeout_us = value.count(); + return NetResult::Ok(); +} + +NetResult SocketSetConnectTimeout(s32 id, std::chrono::microseconds value) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + s->connect_timeout_us = std::max(value.count(), 0); + return NetResult::Ok(); +} + +NetResult SocketSetAcceptTimeout(s32 id, std::chrono::microseconds value) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + s->accept_timeout_us = std::max(value.count(), 0); + return NetResult::Ok(); +} + +NetResult SocketSetSendTimeout(s32 id, std::chrono::microseconds value) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + s->snd_timeout_us = value.count(); + return NetResult::Ok(); +} + +NetResult SocketAbort(s32 id, u32 flags) { + const auto s = g_objects.Get(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + bool hangup = false; + { + std::vector> epolls; + { + std::scoped_lock lock{s->reg_mutex}; + epolls = s->epolls; + } + for (const auto& weak : epolls) { + const auto ep = weak.lock(); + if (!ep) { + continue; + } + std::scoped_lock lock{ep->mutex}; + const auto it = ep->regs.find(id); + if (ep->destroyed || ep->waiters.empty() || it == ep->regs.end() || + it->second.generation != s->generation) { + continue; + } + ep->pending_hups.emplace_back(id, s->generation); + ep->host.Wake(); + hangup = true; + } + } + + // Abort doesn't touch queued data. + std::scoped_lock lock{s->wait_mutex}; + const auto blocked = InterruptWaiters(*s); + bool preserved = false; + const bool send_again = (flags & kSocketAbortPreserveSendAgain) != 0; + if ((flags & kSocketAbortPreserveRecv) && !blocked[static_cast(Side::Recv)]) { + s->pending_abort[static_cast(Side::Recv)] = true; + preserved = true; + } + if ((flags & kSocketAbortPreserveSend) || send_again) { + if (!blocked[static_cast(Side::Send)]) { + s->pending_abort[static_cast(Side::Send)] = true; + preserved = true; + } else if (send_again) { + s->send_again = true; // resolved in SendAll + } + } + // Nothing to abort: ENOTBLK (PS4 behaviour). + if (!blocked[0] && !blocked[1] && !preserved && !hangup) { + return NetResult::Fail(Error::NotBlk); + } + return NetResult::Ok(); +} + +NetResult SocketClose(s32 id) { + const auto s = g_objects.Remove(id); + if (!s) { + return NetResult::Fail(Error::BadF); + } + // Wake blocked threads. The host socket is closed when the last reference drops. + // BSD would leave a recv blocked across close, we wake it on purpose. + { + std::scoped_lock lock{s->wait_mutex}; + s->closing = true; + InterruptWaiters(*s); + } + + std::vector> epolls; + { + std::scoped_lock lock{s->reg_mutex}; + s->closed = true; // blocks later EpollControl(Add) + epolls.swap(s->epolls); + } + for (const auto& weak : epolls) { + const auto ep = weak.lock(); + if (!ep) { + continue; + } + std::scoped_lock lock{ep->mutex}; + const auto it = ep->regs.find(id); + if (it != ep->regs.end() && it->second.generation == s->generation) { + HostUnregister(*ep, *s, it->second); + ep->regs.erase(it); + } + } + return NetResult::Ok(); +} + +void SetP2PTransport(int family, std::shared_ptr transport) { + std::scoped_lock lock{g_p2p_mutex}; + g_p2p_transports[family == AF_INET6 ? 1 : 0] = std::move(transport); +} + +NetResult P2PSocketCreate(int family, bool stream) { + const auto transport = P2PTransportFor(family); + if (!transport) { + return NetResult::Fail(Error::NetDown); // TODO: real error when P2P is down + } + const u32 generation = g_next_generation.fetch_add(1); + auto s = stream ? std::make_shared(transport->CreateStream(), generation) + : std::make_shared(transport->CreateDatagram(), generation); + const auto* handles = s->P2PHandles(); + if (!handles->readable.Valid() || !handles->writable.Valid()) { + return NetResult::Fail(Error::NoBufs); + } + return g_objects.Insert(std::move(s)); +} + +NetResult P2PSocketSetProtection(s32 id, bool crypto, bool signature) { + const auto s = g_objects.Get(id); + if (!s || !s->IsP2P()) { + return NetResult::Fail(s ? Error::Inval : Error::BadF); + } + const P2P::Protection protection{crypto, signature}; + if (s->p2p_stream) { + s->p2p_stream->SetProtection(protection); + } else { + s->p2p_dgram->SetProtection(protection); + } + return NetResult::Ok(); +} + +NetResult P2PSocketGetProtection(s32 id, bool* crypto, bool* signature) { + const auto s = g_objects.Get(id); + if (!s || !s->IsP2P()) { + return NetResult::Fail(s ? Error::Inval : Error::BadF); + } + const auto protection = + s->p2p_stream ? s->p2p_stream->GetProtection() : s->p2p_dgram->GetProtection(); + *crypto = protection.crypto; + *signature = protection.signature; + return NetResult::Ok(); +} + +NetResult P2PSocketBind(s32 id, u16 vport) { + const auto s = g_objects.Get(id); + if (!s || !s->IsP2P()) { + return NetResult::Fail(s ? Error::Inval : Error::BadF); + } + return FromError(s->p2p_stream ? s->p2p_stream->Bind(vport) : s->p2p_dgram->Bind(vport)); +} + +NetResult P2PSocketConnect(s32 id, const P2P::Endpoint& peer, u16 peer_vport) { + const auto s = g_objects.Get(id); + if (!s || !s->IsP2P()) { + return NetResult::Fail(s ? Error::Inval : Error::BadF); + } + if (s->p2p_dgram) { + const Error e = s->p2p_dgram->SetPeer(peer, peer_vport); + if (e == Error::Ok) { + s->connected = true; + } + return FromError(e); + } + if (s->listening) { + return NetResult::Fail(Error::OpNotSupp); + } + if (s->nonblocking) { + if (ConsumePendingAbort(*s, Side::Send)) { + return NetResult::Fail(AbortedError); + } + const Error e = s->p2p_stream->Connect(peer, peer_vport); + if (e == Error::Ok || e == Error::InProgress) { + s->connected = true; + } + return FromError(e); + } + WaitScope scope(*s, Side::Send); + if (const Error e = scope.Entry(); e != Error::Ok) { + return NetResult::Fail(e); + } + const Error started = s->p2p_stream->Connect(peer, peer_vport); + if (started == Error::Ok || started == Error::InProgress) { + s->connected = true; + } + if (started != Error::InProgress) { + return FromError(started); + } + // Writable is set on success and on failure. A timed out or aborted connect keeps going + // in the background, as on BSD. + const auto handle = s->P2PHandles()->writable.PollHandle(); + const auto deadline = SocketDeadline(s->connect_timeout_us); + for (;;) { + if (scope.Interrupted()) { + return NetResult::Fail(AbortedError); + } + if (const Error result = s->p2p_stream->ConnectResult(); result != Error::InProgress) { + return FromError(result); + } + switch (Host::WaitOne(handle, Host::Readable, scope.Wake(), deadline)) { + case Host::WaitResult::Failed: + return NetResult::Fail(Host::LastError()); + case Host::WaitResult::TimedOut: + return NetResult::Fail(Error::WouldBlock); + default: + break; + } + } +} + +NetResult P2PSocketAccept(s32 id, P2P::Endpoint* peer, u16* peer_vport) { + const auto s = g_objects.Get(id); + if (!s || !s->IsP2P()) { + return NetResult::Fail(s ? Error::Inval : Error::BadF); + } + if (s->p2p_dgram) { + return NetResult::Fail(Error::OpNotSupp); + } + std::shared_ptr child; + const NetResult r = RunBlocking(*s, Side::Recv, Host::Readable, + SocketDeadline(s->accept_timeout_us), !s->nonblocking, [&] { + Error e; + child = s->p2p_stream->Accept(peer, peer_vport, &e); + return child ? NetResult::Ok() : NetResult::Fail(e); + }); + if (r.error != Error::Ok) { + return r; + } + auto c = std::make_shared(std::move(child), g_next_generation.fetch_add(1)); + c->nonblocking = s->nonblocking.load(); + c->connected = true; + { + std::scoped_lock lock{s->attr_mutex}; + c->attributes = s->attributes; + } + return g_objects.Insert(std::move(c)); +} + +NetResult P2PSocketSendTo(s32 id, const void* buf, size_t len, bool dontwait, + const P2P::Endpoint* peer, u16 peer_vport, P2PSendProtection protection) { + const auto s = g_objects.Get(id); + if (!s || !s->IsP2P()) { + return NetResult::Fail(s ? Error::Inval : Error::BadF); + } + const auto* bytes = static_cast(buf); + return SendAll( + *s, len, !dontwait && !s->nonblocking, s->p2p_stream != nullptr, [&](size_t sent) { + const std::span rest{bytes + sent, len - sent}; + const auto io = s->p2p_stream + ? s->p2p_stream->Send(rest) + : s->p2p_dgram->SendTo(rest, peer, peer_vport, + {protection.crypto, protection.signature}); + return io.error == Error::Ok ? NetResult::Ok(static_cast(io.bytes)) + : NetResult::Fail(io.error); + }); +} + +NetResult P2PSocketRecvFrom(s32 id, void* buf, size_t len, bool peek, bool dontwait, + P2P::Endpoint* from, u16* from_vport, bool waitall) { + const auto s = g_objects.Get(id); + if (!s || !s->IsP2P()) { + return NetResult::Fail(s ? Error::Inval : Error::BadF); + } + const bool blocking = !dontwait && !s->nonblocking; + const auto deadline = SocketDeadline(s->rcv_timeout_us); + auto* bytes = static_cast(buf); + const bool loop = blocking && waitall && !peek && s->p2p_stream != nullptr; + return ReceiveAll(len, loop, [&](size_t got) { + const std::span out{bytes + got, len - got}; + return RunBlocking(*s, Side::Recv, Host::Readable, deadline, blocking, [&] { + P2P::IoResult io; + if (s->p2p_stream) { + io = s->p2p_stream->Recv(out, peek); + if (io.error == Error::Ok && from && from_vport) { + s->p2p_stream->GetPeer(from, from_vport); + } + } else { + io = s->p2p_dgram->RecvFrom(out, peek, from, from_vport); + } + return io.error == Error::Ok ? NetResult::Ok(static_cast(io.bytes)) + : NetResult::Fail(io.error); + }); + }); +} + +NetResult P2PSocketGetName(s32 id, u16* udp_port, u16* vport) { + const auto s = g_objects.Get(id); + if (!s || !s->IsP2P()) { + return NetResult::Fail(s ? Error::Inval : Error::BadF); + } + *vport = s->p2p_stream ? s->p2p_stream->BoundVport() : s->p2p_dgram->BoundVport(); + *udp_port = s->p2p_stream ? s->p2p_stream->TransportPort() : s->p2p_dgram->TransportPort(); + return NetResult::Ok(); +} + +NetResult P2PSocketGetPeerName(s32 id, P2P::Endpoint* peer, u16* peer_vport) { + const auto s = g_objects.Get(id); + if (!s || !s->IsP2P()) { + return NetResult::Fail(s ? Error::Inval : Error::BadF); + } + const bool connected = s->p2p_stream ? s->p2p_stream->GetPeer(peer, peer_vport) + : s->p2p_dgram->GetPeer(peer, peer_vport); + return connected ? NetResult::Ok() : NetResult::Fail(Error::NotConn); +} + +NetResult EpollCreate() { + auto ep = std::make_shared(); + if (!ep->host.Valid()) { + return NetResult::Fail(Error::NoBufs); + } + return g_objects.Insert(std::move(ep)); +} + +namespace { + +NetResult ExternalEpollControl(GuestEpoll& ep, const std::shared_ptr& ep_ref, s32 id, + ExternalObject& x, EpollOp op, u32 events, u64 data) { + std::scoped_lock ep_lock{ep.mutex}; + std::scoped_lock x_lock{x.reg_mutex}; + if (x.closed) { + return NetResult::Fail(Error::BadF); + } + const auto it = ep.regs.find(id); + const bool exists = it != ep.regs.end(); + switch (op) { + case EpollOp::Add: { + if (exists) { + return NetResult::Fail(Error::Exist); + } + const bool arm = (events & Host::EvIn) != 0; + if (arm) { + const auto tag = MakeTag(id, x.generation); + if (const Error e = ep.host.Add(x.done.PollHandle(), tag, Host::EvIn, 0); + e != Error::Ok) { + return NetResult::Fail(e); + } + } + ep.regs.emplace( + id, GuestEpoll::Registration{x.generation, data, events, false, false, true, arm}); + x.epolls.push_back(ep_ref); + return NetResult::Ok(); + } + case EpollOp::Modify: { + if (!exists) { + return NetResult::Fail(Error::NoEnt); + } + auto& reg = it->second; + const bool want = (events & Host::EvIn) != 0 && !reg.reported; + if (want && !reg.armed) { + const auto tag = MakeTag(id, x.generation); + if (const Error e = ep.host.Add(x.done.PollHandle(), tag, Host::EvIn, 0); + e != Error::Ok) { + return NetResult::Fail(e); + } + reg.armed = true; + } else if (!want && reg.armed) { + ep.host.Remove(x.done.PollHandle()); + reg.armed = false; + } + reg.data = data; + reg.events = events; + return NetResult::Ok(); + } + case EpollOp::Delete: + if (!exists) { + return NetResult::Fail(Error::NoEnt); + } + if (it->second.armed) { + ep.host.Remove(x.done.PollHandle()); + } + ep.regs.erase(it); + std::erase_if(x.epolls, [&](const std::weak_ptr& w) { + const auto p = w.lock(); + return !p || p == ep_ref; + }); + return NetResult::Ok(); + } + return NetResult::Fail(Error::Inval); +} + +} // namespace + +// Also used by SocketSelect with an epoll that has no id. +static NetResult EpollControlOn(const std::shared_ptr& ep, EpollOp op, s32 socket_id, + u32 events, u32 flags, u64 data) { + if (ep) { + if (const auto x = g_objects.Get(socket_id)) { + return ExternalEpollControl(*ep, ep, socket_id, *x, op, events, data); + } + } + const auto s = g_objects.Get(socket_id); + if (!ep || !s) { + return NetResult::Fail(Error::BadF); + } + + std::scoped_lock ep_lock{ep->mutex}; + std::scoped_lock s_lock{s->reg_mutex}; + if (s->closed) { + return NetResult::Fail(Error::BadF); + } + const auto it = ep->regs.find(socket_id); + const bool exists = it != ep->regs.end(); + + switch (op) { + case EpollOp::Add: { + if (exists) { + return NetResult::Fail(Error::Exist); + } + GuestEpoll::Registration reg{s->generation, data, events, s->IsP2P(), false, false, false}; + const Error e = HostRegister(*ep, socket_id, *s, events, flags, reg); + if (e == Error::Ok) { + ep->regs.emplace(socket_id, reg); + s->epolls.push_back(ep); + } + return FromError(e); + } + case EpollOp::Modify: { + if (!exists) { + return NetResult::Fail(Error::NoEnt); + } + const Error e = HostModify(*ep, socket_id, *s, events, flags, it->second); + if (e == Error::Ok) { + it->second.data = data; + it->second.events = events; + } + return FromError(e); + } + case EpollOp::Delete: { + if (!exists) { + return NetResult::Fail(Error::NoEnt); + } + const Error e = HostUnregister(*ep, *s, it->second); + ep->regs.erase(it); + std::erase_if(s->epolls, [&](const std::weak_ptr& w) { + const auto p = w.lock(); + return !p || p == ep; + }); + return FromError(e); + } + } + return NetResult::Fail(Error::Inval); +} + +NetResult EpollControl(s32 eid, EpollOp op, s32 socket_id, u32 events, u32 flags, u64 data) { + return EpollControlOn(g_objects.Get(eid), op, socket_id, events, flags, data); +} + +// Also used by SocketSelect. +static NetResult EpollWaitOn(const std::shared_ptr& ep, std::span out, + s64 timeout_us) { + if (!ep) { + return NetResult::Fail(Error::BadF); + } + if (out.empty()) { + return NetResult::Fail(Error::Inval); + } + const Host::Deadline deadline = + timeout_us < 0 ? Host::Deadline{} + : Host::Clock::now() + std::chrono::microseconds{timeout_us}; + std::array buf; + const auto host_out = std::span{buf}.first(std::min(out.size(), buf.size())); + + // The wake handle is drained once every interrupted wait has left. + Waiter me{}; + { + std::scoped_lock lock{ep->mutex}; + if (ep->destroyed) { + return NetResult::Fail(Error::BadF); + } + if (std::exchange(ep->pending_abort, false)) { + return NetResult::Fail(AbortedError); + } + ep->waiters.push_back(&me); + } + struct Unregister { + GuestEpoll& ep; + Waiter& me; + ~Unregister() { + std::scoped_lock lock{ep.mutex}; + std::erase(ep.waiters, &me); + if (me.aborted && --ep.flagged_waiters == 0 && ep.WakeIdle()) { + ep.host.DrainWake(); + } + } + } unregister{*ep, me}; + // Called with ep->mutex held. + const auto add_hangups = [&](size_t n) { + auto& hups = ep->pending_hups; + size_t kept = 0; + for (const auto& [id, generation] : hups) { + const auto it = ep->regs.find(id); + if (it == ep->regs.end() || it->second.generation != generation) { + continue; // gone since + } + const auto existing = + std::find_if(out.begin(), out.begin() + n, [&](auto& e) { return e.ident == id; }); + if (existing != out.begin() + n) { + existing->events |= Host::EvHup; + } else if (n < out.size()) { + out[n++] = {Host::EvHup, id, it->second.data}; + } else { + hups[kept++] = {id, generation}; // no room, keep for next wait + } + } + hups.resize(kept); + if (ep->WakeIdle()) { + ep->host.DrainWake(); + } + return n; + }; + + for (;;) { + { + std::scoped_lock lock{ep->mutex}; + if (me.aborted) { + return NetResult::Fail(AbortedError); + } + if (!ep->pending_hups.empty()) { + if (const size_t n = add_hangups(0); n > 0) { + return NetResult::Ok(static_cast(n)); + } + } + } + const auto r = ep->host.Wait(host_out, deadline); + if (r.error != Error::Ok) { + return NetResult::Fail(r.error); + } + size_t n = 0; + { + std::scoped_lock lock{ep->mutex}; + for (s32 i = 0; i < r.count; ++i) { + const auto socket_id = static_cast(host_out[i].tag & 0x7fffffffu); + const auto generation = static_cast(host_out[i].tag >> 32); + const auto it = ep->regs.find(socket_id); + // Removed since the host wait. + if (it == ep->regs.end() || it->second.generation != generation) { + continue; + } + u32 events = host_out[i].events; + if (it->second.external) { + // Report once, the flag stays set. + const auto x = g_objects.Get(socket_id); + if (!x || x->generation != generation || !it->second.armed) { + continue; + } + ep->host.Remove(x->done.PollHandle()); + it->second.armed = false; + it->second.reported = true; + // EPOLLIN | EPOLLDESCID, plus EPOLLHUP if the lookup was aborted. + events = Host::EvIn | Host::EvDescId | (x->hangup ? Host::EvHup : 0u); + } else if (it->second.p2p) { + // The handle only signals a change, recompute the state. + const auto s = g_objects.Get(socket_id); + if (!s || s->generation != generation) { + continue; + } + events = s->P2PEvents() & (it->second.events | Host::EvErr | Host::EvHup); + } + if (!it->second.external && (events & (Host::EvErr | Host::EvHup)) != 0) { + events = (events & ~static_cast(Host::EvErr | Host::EvHup)) | + (it->second.events & (Host::EvIn | Host::EvOut)); + } + if (events == 0) { + continue; // another thread got it, or nothing registered fired + } + // P2P sockets can fire on both handles. + const auto existing = std::find_if(out.begin(), out.begin() + n, + [&](auto& e) { return e.ident == socket_id; }); + if (existing != out.begin() + n) { + existing->events |= events; + } else { + out[n++] = {events, socket_id, it->second.data}; + } + } + if (!ep->pending_hups.empty()) { + n = add_hangups(n); + } + } + if (n > 0) { + return NetResult::Ok(static_cast(n)); + } + if (r.count == 0 && !r.woken) { + return NetResult::Ok(0); // timed out + } + } +} + +NetResult EpollWait(s32 eid, std::span out, s64 timeout_us) { + return EpollWaitOn(g_objects.Get(eid), out, timeout_us); +} + +namespace { + +// Called with ep.mutex held. +bool InterruptEpollWaiters(GuestEpoll& ep) { + for (Waiter* w : ep.waiters) { + if (!w->aborted) { + w->aborted = true; + ++ep.flagged_waiters; + } + } + if (ep.waiters.empty()) { + return false; + } + ep.host.Wake(); + return true; +} + +} // namespace + +NetResult EpollAbort(s32 eid, u32 flags) { + const auto ep = g_objects.Get(eid); + if (!ep) { + return NetResult::Fail(Error::BadF); + } + std::scoped_lock lock{ep->mutex}; + if (InterruptEpollWaiters(*ep)) { + return NetResult::Ok(); + } + if (flags & kEpollAbortPreserve) { + ep->pending_abort = true; + return NetResult::Ok(); + } + return NetResult::Fail(Error::NotBlk); +} + +NetResult EpollDestroy(s32 eid) { + const auto ep = g_objects.Remove(eid); + if (!ep) { + return NetResult::Fail(Error::BadF); + } + // Waiters hold a reference, so the host epoll is freed after the last one returns. + std::scoped_lock lock{ep->mutex}; + ep->destroyed = true; + InterruptEpollWaiters(*ep); + return NetResult::Ok(); +} + +NetResult SocketSelect(std::span entries, s64 timeout_us) { + for (auto& e : entries) { + e.ready = 0; + } + if (entries.empty()) { + return NetResult::Ok(0); + } + // Private epoll with no id. + const auto ep = std::make_shared(); + if (!ep->host.Valid()) { + return NetResult::Fail(Error::NoBufs); + } + std::vector registered; + const auto unregister_all = [&] { + for (const size_t i : registered) { + EpollControlOn(ep, EpollOp::Delete, entries[i].id, 0, 0, 0); + } + registered.clear(); + }; + for (size_t i = 0; i < entries.size(); ++i) { + if (!g_objects.Get(entries[i].id)) { + unregister_all(); + return NetResult::Fail(Error::BadF); + } + // Merge duplicate entries into the first one. + const auto first = + std::find_if(entries.begin(), entries.begin() + i, + [&](const SelectEntry& e) { return e.id == entries[i].id; }); + if (first != entries.begin() + i) { + continue; + } + u32 interest = 0; + for (size_t j = i; j < entries.size(); ++j) { + if (entries[j].id == entries[i].id) { + interest |= entries[j].interest; + } + } + const auto r = EpollControlOn(ep, EpollOp::Add, entries[i].id, interest, 0, i); + if (r.error != Error::Ok) { + unregister_all(); + return r; + } + registered.push_back(i); + } + + std::vector out(registered.size()); + std::vector still; + s64 wait = timeout_us; + for (;;) { + const auto r = EpollWaitOn(ep, out, wait); + if (r.error != Error::Ok) { + unregister_all(); + return r; + } + if (r.value == 0) { + break; + } + for (s64 k = 0; k < r.value; ++k) { + const s32 id = out[k].ident; + // As on BSD, an error or hangup makes the socket readable and writable. + u32 ready = out[k].events; + if (ready & (Host::EvErr | Host::EvHup)) { + ready |= Host::EvIn | Host::EvOut; + } + for (auto& e : entries) { + if (e.id == id) { + e.ready = ready & e.interest; + } + } + EpollControlOn(ep, EpollOp::Delete, id, 0, 0, 0); + std::erase_if(registered, [&](size_t i) { return entries[i].id == id; }); + } + if (registered.empty()) { + break; + } + wait = 0; + } + unregister_all(); + s64 count = 0; + for (const auto& e : entries) { + count += std::popcount(e.ready); + } + return NetResult::Ok(count); +} + +} // namespace Core::Net diff --git a/src/core/net/guest_net.h b/src/core/net/guest_net.h new file mode 100644 index 000000000..9e208075c --- /dev/null +++ b/src/core/net/guest_net.h @@ -0,0 +1,154 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/net/host_net.h" + +namespace Core::Net { + +namespace P2P { +class Transport; +struct Endpoint; +} // namespace P2P + +struct NetResult { + s64 value; // only valid when error == Ok + Host::Error error; + + static NetResult Ok(s64 value = 0) { + return {value, Host::Error::Ok}; + } + static NetResult Fail(Host::Error error) { + return {-1, error}; + } +}; + +inline constexpr int MaxSockets = 128; + +struct IdAllocator { + std::function allocate; // -1 when out of ids + std::function release; +}; +void SetIdAllocator(IdAllocator allocator); + +enum class NetObjectKind { Socket, Epoll, External }; + +std::optional GetObjectKind(s32 id); +NetResult CloseObject(s32 id); +NetResult ReserveExternalId(); +NetResult SignalExternal(s32 id, bool hangup = false); +NetResult ReleaseExternalId(s32 id); +std::vector ListSockets(); +s32 ToOrbisReturn(NetResult result); + +NetResult SocketCreate(int family, int type, int protocol); +NetResult SocketCreatePair(int family, int type, int protocol, s32 ids[2]); +NetResult SocketBind(s32 id, const sockaddr* addr, socklen_t len); +NetResult SocketListen(s32 id, int backlog); +NetResult SocketConnect(s32 id, const sockaddr* addr, socklen_t len); +NetResult SocketAccept(s32 id, sockaddr* addr, socklen_t* len); +NetResult SocketSendTo(s32 id, const void* buf, size_t len, int host_flags, bool dontwait, + const sockaddr* addr, socklen_t addr_len); +NetResult SocketRecvFrom(s32 id, void* buf, size_t len, int host_flags, bool dontwait, + sockaddr* addr, socklen_t* addr_len); +NetResult SocketShutdown(s32 id, int how); +NetResult SocketGetName(s32 id, sockaddr* addr, socklen_t* len); +NetResult SocketGetPeerName(s32 id, sockaddr* addr, socklen_t* len); +NetResult SocketSetHostOption(s32 id, int level, int name, const void* value, socklen_t len); +NetResult SocketGetHostOption(s32 id, int level, int name, void* value, socklen_t* len); + +struct SocketInfo { + int type; // P2P sockets report their base type + bool p2p; + bool nonblocking; + bool reuse_addr; + s64 rcv_timeout_us; + s64 snd_timeout_us; + s64 connect_timeout_us; + s64 accept_timeout_us; + bool connected; + bool listening; + Host::Error pending_error; +}; +NetResult SocketGetInfo(s32 id, SocketInfo* info); + +struct SocketAttributes { + std::array name{}; // SO_NAME + s32 snd_buf = 0; // 0 = PS4 default + s32 rcv_buf = 0; + s32 policy = 0; // 0-15 + s32 priority = 16; // TODO: find real value + // Stored for getsockopt only, keyed by (level, name). + std::map, s32> stored_options; + // A STREAM_P2P socket bound to a non-zero TCP port can listen, but connect gives EPROTO. + bool p2p_tcp_port_bound = false; + // ip_mreq joins issued before bind. Windows refuses them on an unbound socket, so they + // are retried after bind. + std::vector> pending_memberships; +}; +NetResult SocketGetAttributes(s32 id, SocketAttributes* out); +NetResult SocketUpdateAttributes(s32 id, const std::function& update); +NetResult SocketSetNonBlocking(s32 id, bool enable); +NetResult SocketBytesReadable(s32 id); +NetResult SocketSetReuseAddr(s32 id, bool enable); +NetResult SocketSetReusePort(s32 id, bool enable); +NetResult SocketSetRecvTimeout(s32 id, std::chrono::microseconds value); +NetResult SocketSetSendTimeout(s32 id, std::chrono::microseconds value); +NetResult SocketSetConnectTimeout(s32 id, std::chrono::microseconds value); +NetResult SocketSetAcceptTimeout(s32 id, std::chrono::microseconds value); +inline constexpr u32 kSocketAbortPreserveRecv = 0x1; +inline constexpr u32 kSocketAbortPreserveSend = 0x2; +inline constexpr u32 kSocketAbortPreserveSendAgain = 0x4; +NetResult SocketAbort(s32 id, u32 flags); +NetResult SocketClose(s32 id); +void SetP2PTransport(int family, std::shared_ptr transport); +NetResult P2PSocketCreate(int family, bool stream); +NetResult P2PSocketSetProtection(s32 id, bool crypto, bool signature); +NetResult P2PSocketGetProtection(s32 id, bool* crypto, bool* signature); +NetResult P2PSocketBind(s32 id, u16 vport); // 0 = any free vport +NetResult P2PSocketConnect(s32 id, const P2P::Endpoint& peer, u16 peer_vport); +NetResult P2PSocketAccept(s32 id, P2P::Endpoint* peer, u16* peer_vport); +struct P2PSendProtection { + bool crypto = false; + bool signature = false; +}; +NetResult P2PSocketSendTo(s32 id, const void* buf, size_t len, bool dontwait, + const P2P::Endpoint* peer, u16 peer_vport, + P2PSendProtection protection = {}); +NetResult P2PSocketRecvFrom(s32 id, void* buf, size_t len, bool peek, bool dontwait, + P2P::Endpoint* from, u16* from_vport, bool waitall = false); +NetResult P2PSocketGetName(s32 id, u16* udp_port, u16* vport); +NetResult P2PSocketGetPeerName(s32 id, P2P::Endpoint* peer, u16* peer_vport); + +enum class EpollOp { Add, Modify, Delete }; + +struct GuestEpollEvent { + u32 events; // Host::EventBits + s32 ident; // socket id + u64 data; +}; + +NetResult EpollCreate(); +NetResult EpollControl(s32 eid, EpollOp op, s32 socket_id, u32 events, u32 flags, u64 data); +NetResult EpollWait(s32 eid, std::span out, s64 timeout_us); +inline constexpr u32 kEpollAbortPreserve = 0x1; +NetResult EpollAbort(s32 eid, u32 flags); +NetResult EpollDestroy(s32 eid); +struct SelectEntry { + s32 id; + u32 interest; + u32 ready; +}; +NetResult SocketSelect(std::span entries, s64 timeout_us); + +} // namespace Core::Net diff --git a/src/core/net/host_net.cpp b/src/core/net/host_net.cpp new file mode 100644 index 000000000..fa8cd412c --- /dev/null +++ b/src/core/net/host_net.cpp @@ -0,0 +1,920 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +#include "core/net/host_net.h" + +#ifdef _WIN32 +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "Ws2_32.lib") +#endif +#ifndef SIO_UDP_CONNRESET +#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12) +#endif +#define HOST_NET_EPOLL 1 +#else +#include +#include +#include +#include +#include +#include +#ifdef __linux__ +#include +#include +#define HOST_NET_EPOLL 1 +#else +#include +#include +#define HOST_NET_KQUEUE 1 +#endif +#endif + +namespace Core::Net::Host { + +int TimeoutMs(const Deadline& deadline) { + if (!deadline) { + return -1; + } + const auto left = *deadline - Clock::now(); + if (left <= Clock::duration::zero()) { + return 0; + } + // Round up to avoid waking early. Callers loop anyway. + const auto ms = std::chrono::ceil(left).count(); + return static_cast(std::min(ms, INT_MAX)); +} + +bool Expired(const Deadline& deadline) { + return deadline && Clock::now() >= *deadline; +} + +bool SetNonBlocking(NativeSocket s) { +#ifdef _WIN32 + u_long on = 1; + return ioctlsocket(s, FIONBIO, &on) == 0; +#else + const int flags = fcntl(s, F_GETFL, 0); + return flags >= 0 && fcntl(s, F_SETFL, flags | O_NONBLOCK) == 0; +#endif +} + +#ifndef _WIN32 +bool SetCloseOnExec(int fd) { + const int flags = fcntl(fd, F_GETFD, 0); + return flags >= 0 && fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0; +} +#endif + +Deadline DeadlineFromSocketTimeout(std::chrono::microseconds timeout) { + if (timeout.count() <= 0) { + return std::nullopt; + } + return Clock::now() + timeout; +} + +// Errors +Error TranslateNative(int code) { + struct Entry { + int host; + Error error; + }; +#ifdef _WIN32 + static constexpr Entry Table[] = { + {WSAEINTR, Error::Intr}, + {WSAEBADF, Error::BadF}, + {WSAEACCES, Error::Acces}, + {WSAEFAULT, Error::Fault}, + {WSAEINVAL, Error::Inval}, + {WSAEMFILE, Error::MFile}, + {WSAEWOULDBLOCK, Error::WouldBlock}, + {WSAEINPROGRESS, Error::InProgress}, + {WSAEALREADY, Error::Already}, + {WSAENOTSOCK, Error::NotSock}, + {WSAEDESTADDRREQ, Error::DestAddrReq}, + {WSAEMSGSIZE, Error::MsgSize}, + {WSAEPROTOTYPE, Error::ProtoType}, + {WSAENOPROTOOPT, Error::NoProtoOpt}, + {WSAEPROTONOSUPPORT, Error::ProtoNoSupport}, + {WSAESOCKTNOSUPPORT, Error::SocktNoSupport}, + {WSAEOPNOTSUPP, Error::OpNotSupp}, + {WSAEPFNOSUPPORT, Error::PfNoSupport}, + {WSAEAFNOSUPPORT, Error::AfNoSupport}, + {WSAEADDRINUSE, Error::AddrInUse}, + {WSAEADDRNOTAVAIL, Error::AddrNotAvail}, + {WSAENETDOWN, Error::NetDown}, + {WSAENETUNREACH, Error::NetUnreach}, + {WSAENETRESET, Error::NetReset}, + {WSAECONNABORTED, Error::ConnAborted}, + {WSAECONNRESET, Error::ConnReset}, + {WSAENOBUFS, Error::NoBufs}, + {WSAEISCONN, Error::IsConn}, + {WSAENOTCONN, Error::NotConn}, + {WSAESHUTDOWN, Error::Pipe}, // BSD gives EPIPE after shutdown + {WSAETOOMANYREFS, Error::TooManyRefs}, + {WSAETIMEDOUT, Error::TimedOut}, + {WSAECONNREFUSED, Error::ConnRefused}, + {WSAELOOP, Error::Loop}, + {WSAENAMETOOLONG, Error::NameTooLong}, + {WSAEHOSTDOWN, Error::HostDown}, + {WSAEHOSTUNREACH, Error::HostUnreach}, + {WSAENOTEMPTY, Error::NotEmpty}, + {WSA_NOT_ENOUGH_MEMORY, Error::NoMem}, + {WSAECANCELLED, Error::Canceled}, + }; +#else + static constexpr Entry Table[] = { + {EPERM, Error::Perm}, + {ENOENT, Error::NoEnt}, + {EINTR, Error::Intr}, + {EIO, Error::Io}, + {EBADF, Error::BadF}, + {ENOMEM, Error::NoMem}, + {EACCES, Error::Acces}, + {EFAULT, Error::Fault}, + {EBUSY, Error::Busy}, + {EEXIST, Error::Exist}, + {ENODEV, Error::NoDev}, + {EINVAL, Error::Inval}, + {ENFILE, Error::NFile}, + {EMFILE, Error::MFile}, + {ENOSPC, Error::NoSpc}, + {EPIPE, Error::Pipe}, + {EAGAIN, Error::WouldBlock}, +#if EWOULDBLOCK != EAGAIN + {EWOULDBLOCK, Error::WouldBlock}, +#endif + {EINPROGRESS, Error::InProgress}, + {EALREADY, Error::Already}, + {ENOTSOCK, Error::NotSock}, + {EDESTADDRREQ, Error::DestAddrReq}, + {EMSGSIZE, Error::MsgSize}, + {EPROTOTYPE, Error::ProtoType}, + {ENOPROTOOPT, Error::NoProtoOpt}, + {EPROTONOSUPPORT, Error::ProtoNoSupport}, + {ESOCKTNOSUPPORT, Error::SocktNoSupport}, + {EOPNOTSUPP, Error::OpNotSupp}, +#if ENOTSUP != EOPNOTSUPP + {ENOTSUP, Error::OpNotSupp}, +#endif + {EPFNOSUPPORT, Error::PfNoSupport}, + {EAFNOSUPPORT, Error::AfNoSupport}, + {EADDRINUSE, Error::AddrInUse}, + {EADDRNOTAVAIL, Error::AddrNotAvail}, + {ENETDOWN, Error::NetDown}, + {ENETUNREACH, Error::NetUnreach}, + {ENETRESET, Error::NetReset}, + {ECONNABORTED, Error::ConnAborted}, + {ECONNRESET, Error::ConnReset}, + {ENOBUFS, Error::NoBufs}, + {EISCONN, Error::IsConn}, + {ENOTCONN, Error::NotConn}, + {ESHUTDOWN, Error::Pipe}, + {ETOOMANYREFS, Error::TooManyRefs}, + {ETIMEDOUT, Error::TimedOut}, + {ECONNREFUSED, Error::ConnRefused}, + {ELOOP, Error::Loop}, + {ENAMETOOLONG, Error::NameTooLong}, + {EHOSTDOWN, Error::HostDown}, + {EHOSTUNREACH, Error::HostUnreach}, + {ENOTEMPTY, Error::NotEmpty}, + {ECANCELED, Error::Canceled}, + }; +#endif + if (code == 0) { + return Error::Ok; + } + for (const auto& entry : Table) { + if (entry.host == code) { + return entry.error; + } + } + return Error::Internal; +} + +Error LastError() { +#ifdef _WIN32 + return TranslateNative(WSAGetLastError()); +#else + return TranslateNative(errno); +#endif +} + +bool Initialize() { +#ifdef _WIN32 + WSADATA data; + return WSAStartup(MAKEWORD(2, 2), &data) == 0; +#else + return true; +#endif +} + +void Shutdown() { +#ifdef _WIN32 + WSACleanup(); +#endif +} + +// Sockets +bool ConfigureSocket(NativeSocket s, int type) { + if (!SetNonBlocking(s)) { + return false; + } +#ifdef _WIN32 + if (type == SOCK_DGRAM) { + // Otherwise an ICMP port-unreachable from an earlier sendto() makes the next + // recvfrom() fail with WSAECONNRESET. BSD doesn't do that for UDP. + BOOL report = FALSE; + DWORD bytes = 0; + WSAIoctl(s, SIO_UDP_CONNRESET, &report, sizeof(report), nullptr, 0, &bytes, nullptr, + nullptr); + } +#else + (void)type; + SetCloseOnExec(s); +#ifdef SO_NOSIGPIPE + // Don't let a dead peer SIGPIPE the emulator. + int on = 1; + setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); +#endif +#endif + return true; +} + +NativeSocket CreateSocket(int family, int type, int protocol, Error* error) { +#ifdef _WIN32 + NativeSocket s = WSASocketW(family, type, protocol, nullptr, 0, + WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); +#elif defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC) + NativeSocket s = socket(family, type | SOCK_NONBLOCK | SOCK_CLOEXEC, protocol); +#else + NativeSocket s = socket(family, type, protocol); +#endif + if (s == InvalidSocket) { + *error = LastError(); + return InvalidSocket; + } + if (!ConfigureSocket(s, type)) { + *error = LastError(); + CloseSocket(s); + return InvalidSocket; + } + *error = Error::Ok; + return s; +} + +Error CreateSocketPair(int family, int type, int protocol, NativeSocket out[2]) { + out[0] = out[1] = InvalidSocket; +#ifdef _WIN32 + (void)family; + (void)protocol; + if (type != SOCK_STREAM) { + return Error::ProtoNoSupport; + } + // Connect through a loopback listener and make sure we accepted our own client. + Error e; + NativeSocket listener = CreateSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, &e); + if (listener == InvalidSocket) { + return e; + } + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + socklen_t len = sizeof(addr); + NativeSocket client = InvalidSocket; + NativeSocket server = InvalidSocket; + const auto fail = [&](Error error) { + CloseSocket(listener); + CloseSocket(client); + CloseSocket(server); + return error; + }; + if (bind(listener, reinterpret_cast(&addr), sizeof(addr)) != 0 || + listen(listener, 1) != 0 || + getsockname(listener, reinterpret_cast(&addr), &len) != 0) { + return fail(LastError()); + } + client = CreateSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, &e); + if (client == InvalidSocket) { + return fail(e); + } + if (connect(client, reinterpret_cast(&addr), sizeof(addr)) != 0 && + LastError() != Error::WouldBlock && LastError() != Error::InProgress) { + return fail(LastError()); + } + WSAPOLLFD poll{listener, POLLRDNORM, 0}; + if (WSAPoll(&poll, 1, 5000) != 1) { + return fail(Error::TimedOut); + } + server = Accept(listener, nullptr, nullptr, &e); + if (server == InvalidSocket) { + return fail(e); + } + sockaddr_in client_name{}; + sockaddr_in server_peer{}; + socklen_t client_len = sizeof(client_name); + socklen_t peer_len = sizeof(server_peer); + getsockname(client, reinterpret_cast(&client_name), &client_len); + getpeername(server, reinterpret_cast(&server_peer), &peer_len); + if (client_name.sin_port != server_peer.sin_port) { + return fail(Error::ConnAborted); // someone else got in first + } + WSAPOLLFD writable{client, POLLWRNORM, 0}; + WSAPoll(&writable, 1, 5000); + CloseSocket(listener); + out[0] = client; + out[1] = server; + return Error::Ok; +#else + int fds[2]; + if (socketpair(family, type, protocol, fds) != 0) { + return LastError(); + } + if (!ConfigureSocket(fds[0], type) || !ConfigureSocket(fds[1], type)) { + const Error e = LastError(); + CloseSocket(fds[0]); + CloseSocket(fds[1]); + return e; + } + out[0] = fds[0]; + out[1] = fds[1]; + return Error::Ok; +#endif +} + +void CloseSocket(NativeSocket s) { + if (s == InvalidSocket) { + return; + } +#ifdef _WIN32 + closesocket(s); +#else + close(s); // don't retry on EINTR, the fd may already be gone +#endif +} + +Error PrepareBind(NativeSocket s, bool reuse_addr, bool reuse_port) { +#ifdef _WIN32 + // Windows SO_REUSEADDR behaves like BSD SO_REUSEPORT, which is also what games want for + // multicast. Without reuse, SO_EXCLUSIVEADDRUSE is the closest match to BSD. + const int opt = (reuse_addr || reuse_port) ? SO_REUSEADDR : SO_EXCLUSIVEADDRUSE; + BOOL on = TRUE; + if (setsockopt(s, SOL_SOCKET, opt, reinterpret_cast(&on), sizeof(on)) != 0) { + return LastError(); + } +#else + int on = reuse_addr ? 1 : 0; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) { + return LastError(); + } +#ifdef SO_REUSEPORT + on = reuse_port ? 1 : 0; + if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) != 0) { + return LastError(); + } +#endif +#endif + return Error::Ok; +} + +void PrepareClose(NativeSocket s, bool guest_blocking) { + linger value{}; + socklen_t len = sizeof(value); + if (getsockopt(s, SOL_SOCKET, LingerOption, reinterpret_cast(&value), &len) != 0 || + value.l_onoff == 0 || value.l_linger <= 0) { + return; // no linger or linger 0, same in every mode + } + if (guest_blocking) { +#ifdef _WIN32 + u_long off = 0; + ioctlsocket(s, FIONBIO, &off); +#else + const int flags = fcntl(s, F_GETFL, 0); + if (flags >= 0) { + fcntl(s, F_SETFL, flags & ~O_NONBLOCK); + } +#endif + return; + } + value.l_onoff = 0; + setsockopt(s, SOL_SOCKET, LingerOption, reinterpret_cast(&value), sizeof(value)); +} + +Error Bind(NativeSocket s, const sockaddr* addr, socklen_t len) { + return bind(s, addr, len) == 0 ? Error::Ok : LastError(); +} + +Error Listen(NativeSocket s, int backlog) { + return listen(s, backlog) == 0 ? Error::Ok : LastError(); +} + +Error ShutdownSocket(NativeSocket s, int how) { + return shutdown(s, how) == 0 ? Error::Ok : LastError(); +} + +Error Connect(NativeSocket s, const sockaddr* addr, socklen_t len) { + if (connect(s, addr, len) == 0) { + return Error::Ok; + } + const Error e = LastError(); +#ifdef _WIN32 + // Winsock says WSAEWOULDBLOCK where BSD says EINPROGRESS. + if (e == Error::WouldBlock) { + return Error::InProgress; + } +#endif + return e; +} + +Error PendingSocketError(NativeSocket s) { + int value = 0; + socklen_t len = sizeof(value); + if (getsockopt(s, SOL_SOCKET, SO_ERROR, reinterpret_cast(&value), &len) != 0) { + return LastError(); + } + return TranslateNative(value); +} + +IoResult BytesReadable(NativeSocket s) { +#ifdef _WIN32 + u_long n = 0; + if (ioctlsocket(s, FIONREAD, &n) != 0) { + return {0, LastError()}; + } +#else + int n = 0; + if (ioctl(s, FIONREAD, &n) != 0) { + return {0, LastError()}; + } +#endif + return {static_cast(n), Error::Ok}; +} + +NativeSocket Accept(NativeSocket s, sockaddr* addr, socklen_t* len, Error* error) { + for (;;) { +#if defined(__linux__) || defined(__FreeBSD__) + NativeSocket c = accept4(s, addr, len, SOCK_NONBLOCK | SOCK_CLOEXEC); +#else + NativeSocket c = accept(s, addr, len); +#endif + if (c == InvalidSocket) { + const Error e = LastError(); + if (e == Error::Intr) { + continue; + } + *error = e; + return InvalidSocket; + } + // Whether accept() inherits O_NONBLOCK varies by platform. + if (!ConfigureSocket(c, SOCK_STREAM)) { + *error = LastError(); + CloseSocket(c); + return InvalidSocket; + } + *error = Error::Ok; + return c; + } +} + +IoResult SendTo(NativeSocket s, const void* buf, size_t len, int flags, const sockaddr* addr, + socklen_t addr_len) { +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif +#ifdef _WIN32 + const int n_len = static_cast(std::min(len, INT_MAX)); + const char* p = static_cast(buf); + const int n = addr ? sendto(s, p, n_len, flags, addr, addr_len) : send(s, p, n_len, flags); +#else + ssize_t n; + do { + n = addr ? sendto(s, buf, len, flags, addr, addr_len) : send(s, buf, len, flags); + } while (n < 0 && errno == EINTR); +#endif + if (n < 0) { + return {-1, LastError()}; + } + return {static_cast(n), Error::Ok}; +} + +IoResult RecvFrom(NativeSocket s, void* buf, size_t len, int flags, sockaddr* addr, + socklen_t* addr_len) { +#ifdef _WIN32 + const int n_len = static_cast(std::min(len, INT_MAX)); + char* p = static_cast(buf); + const int n = addr ? recvfrom(s, p, n_len, flags, addr, addr_len) : recv(s, p, n_len, flags); +#else + ssize_t n; + do { + n = addr ? recvfrom(s, buf, len, flags, addr, addr_len) : recv(s, buf, len, flags); + } while (n < 0 && errno == EINTR); +#endif + if (n < 0) { + const Error e = LastError(); +#ifdef _WIN32 + // Winsock fails truncated datagrams with WSAEMSGSIZE. BSD returns the length read. + if (e == Error::MsgSize) { + return {n_len, Error::Ok}; + } +#endif + return {-1, e}; + } + return {static_cast(n), Error::Ok}; +} + +WakeHandle::WakeHandle() { +#if defined(__linux__) + read_ = write_ = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); +#elif defined(_WIN32) + // WSAPoll and wepoll only take sockets, so use a loopback UDP pair. + read_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + write_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (read_ == InvalidSocket || write_ == InvalidSocket) { + Reset(); + return; + } + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + int addr_len = sizeof(addr); + sockaddr_in peer{}; + int peer_len = sizeof(peer); + const bool ok = bind(read_, reinterpret_cast(&addr), sizeof(addr)) == 0 && + getsockname(read_, reinterpret_cast(&addr), &addr_len) == 0 && + connect(write_, reinterpret_cast(&addr), sizeof(addr)) == 0 && + getsockname(write_, reinterpret_cast(&peer), &peer_len) == 0 && + // Only accept datagrams from our writer. + connect(read_, reinterpret_cast(&peer), sizeof(peer)) == 0 && + ConfigureSocket(read_, SOCK_DGRAM) && ConfigureSocket(write_, SOCK_DGRAM); + if (!ok) { + Reset(); + } +#else + int fds[2]; + if (pipe(fds) != 0) { + return; + } + read_ = fds[0]; + write_ = fds[1]; + for (const int fd : fds) { + if (!SetNonBlocking(fd) || !SetCloseOnExec(fd)) { + Reset(); + return; + } + } +#endif +} + +WakeHandle::~WakeHandle() { + Reset(); +} + +void WakeHandle::Reset() { +#if defined(_WIN32) + CloseSocket(read_); + CloseSocket(write_); +#else + if (read_ >= 0) { + close(read_); + } + if (write_ >= 0 && write_ != read_) { + close(write_); + } +#endif + read_ = write_ = InvalidSocket; +} + +void WakeHandle::Signal() const { + // Fails only when already full, which means already signalled. +#if defined(__linux__) + const u64 one = 1; + [[maybe_unused]] const auto r = write(write_, &one, sizeof(one)); +#elif defined(_WIN32) + const char byte = 1; + send(write_, &byte, 1, 0); +#else + const char byte = 1; + [[maybe_unused]] const auto r = write(write_, &byte, 1); +#endif +} + +void WakeHandle::Drain() const { + char buf[64]; // eventfd needs at least 8 +#ifdef _WIN32 + while (recv(read_, buf, sizeof(buf), 0) > 0) { + } +#else + while (read(read_, buf, sizeof(buf)) > 0) { + } +#endif +} + +WaitResult WaitOne(NativeSocket s, u32 interest, const WakeHandle& wake, Deadline deadline) { +#ifdef _WIN32 + WSAPOLLFD fds[2]{}; +#else + pollfd fds[2]{}; +#endif + fds[0].fd = s; + fds[0].events = static_cast(((interest & Readable) ? POLLIN : 0) | + ((interest & Writable) ? POLLOUT : 0)); + fds[1].fd = wake.PollHandle(); + fds[1].events = POLLIN; + + for (;;) { + fds[0].revents = fds[1].revents = 0; +#ifdef _WIN32 + const int n = WSAPoll(fds, 2, TimeoutMs(deadline)); +#else + const int n = poll(fds, 2, TimeoutMs(deadline)); +#endif + if (n < 0) { +#ifndef _WIN32 + if (errno == EINTR) { + continue; + } +#endif + return WaitResult::Failed; + } + if (fds[1].revents != 0) { + return WaitResult::Woken; // abort wins + } + if (fds[0].revents != 0) { + return WaitResult::Ready; // the retry reports any error + } + if (Expired(deadline)) { + return WaitResult::TimedOut; + } + // Woke early, wait again. + } +} + +#if HOST_NET_EPOLL // epoll or wepoll + +u32 ToEpoll(u32 events, u32 flags) { + u32 ev = EPOLLRDHUP; + if (events & EvIn) { + ev |= EPOLLIN; + } + if (events & EvOut) { + ev |= EPOLLOUT; + } + if (flags & OneShot) { + ev |= EPOLLONESHOT; + } + return ev; +} + +u32 FromEpoll(u32 ev) { + u32 out = 0; + if (ev & EPOLLIN) { + out |= EvIn; + } + if (ev & EPOLLOUT) { + out |= EvOut; + } + if (ev & EPOLLERR) { + out |= EvErr; + } + if (ev & (EPOLLHUP | EPOLLRDHUP)) { + out |= EvHup; // TODO: guest may need RDHUP kept apart from HUP + } + return out; +} + +Error EpollError() { +#ifdef _WIN32 + // wepoll gives Win32 codes, not WSA ones. EEXIST/ENOENT are checked by the guest layer. + return Error::Internal; +#else + return LastError(); +#endif +} + +HostEpoll::HostEpoll() { +#ifdef _WIN32 + handle_ = epoll_create1(0); +#else + handle_ = epoll_create1(EPOLL_CLOEXEC); +#endif + if (!Valid() || !wake_.Valid()) { + return; + } + epoll_event ev{}; + ev.events = EPOLLIN; + ev.data.u64 = ReservedTag; + epoll_ctl(handle_, EPOLL_CTL_ADD, wake_.PollHandle(), &ev); +} + +HostEpoll::~HostEpoll() { + if (!Valid()) { + return; + } +#ifdef _WIN32 + epoll_close(handle_); +#else + close(handle_); +#endif +} + +bool HostEpoll::Valid() const { +#ifdef _WIN32 + return handle_ != nullptr; +#else + return handle_ >= 0; +#endif +} + +Error HostEpoll::Add(NativeSocket s, u64 tag, u32 events, u32 flags) { + epoll_event ev{}; + ev.events = ToEpoll(events, flags); + ev.data.u64 = tag; + return epoll_ctl(handle_, EPOLL_CTL_ADD, s, &ev) == 0 ? Error::Ok : EpollError(); +} + +Error HostEpoll::Modify(NativeSocket s, u64 tag, u32 events, u32 flags) { + epoll_event ev{}; + ev.events = ToEpoll(events, flags); + ev.data.u64 = tag; + return epoll_ctl(handle_, EPOLL_CTL_MOD, s, &ev) == 0 ? Error::Ok : EpollError(); +} + +Error HostEpoll::Remove(NativeSocket s) { + epoll_event ev{}; // must be non-null before Linux 2.6.9 + return epoll_ctl(handle_, EPOLL_CTL_DEL, s, &ev) == 0 ? Error::Ok : EpollError(); +} + +EpollWaitResult HostEpoll::Wait(std::span out, Deadline deadline) { + std::array buf; + const int cap = static_cast(std::min(out.size(), buf.size())); + if (cap == 0) { + return {0, false, Error::Inval}; + } + for (;;) { + const int n = epoll_wait(handle_, buf.data(), cap, TimeoutMs(deadline)); + if (n < 0) { +#ifndef _WIN32 + if (errno == EINTR) { + continue; + } +#endif + return {0, false, EpollError()}; + } + EpollWaitResult r{0, false, Error::Ok}; + for (int i = 0; i < n; ++i) { + if (buf[i].data.u64 == ReservedTag) { + r.woken = true; + continue; + } + out[r.count++] = {buf[i].data.u64, FromEpoll(buf[i].events)}; + } + if (n > 0 || Expired(deadline)) { + return r; + } + } +} + +#else // HOST_NET_KQUEUE (macOS, FreeBSD) + +void* TagToUdata(u64 tag) { + static_assert(sizeof(void*) >= sizeof(u64), "tags need 64-bit udata"); + return reinterpret_cast(static_cast(tag)); +} + +u64 UdataToTag(void* udata) { + return static_cast(reinterpret_cast(udata)); +} + +Error KqueueApply(int kq, int s, u64 tag, u32 events, u32 flags) { + u16 arm = EV_ADD | EV_ENABLE | EV_RECEIPT; + if (flags & OneShot) { + arm |= EV_DISPATCH; // like EPOLLONESHOT + } + const u16 disarm = EV_DELETE | EV_RECEIPT; + // TODO: with no EvIn/EvOut, epoll still reports ERR/HUP but kqueue reports nothing. + // A hidden EVFILT_READ would fix it but give spurious EvIn. + struct kevent changes[2]; + struct kevent results[2]; + EV_SET(&changes[0], s, EVFILT_READ, (events & EvIn) ? arm : disarm, 0, 0, TagToUdata(tag)); + EV_SET(&changes[1], s, EVFILT_WRITE, (events & EvOut) ? arm : disarm, 0, 0, TagToUdata(tag)); + const int n = kevent(kq, changes, 2, results, 2, nullptr); + if (n < 0) { + return LastError(); + } + for (int i = 0; i < n; ++i) { + // EV_RECEIPT returns an EV_ERROR entry per change, data 0 on success. ENOENT is + // deleting a filter that was never added. + if ((results[i].flags & EV_ERROR) && results[i].data != 0 && results[i].data != ENOENT) { + return TranslateNative(static_cast(results[i].data)); + } + } + return Error::Ok; +} + +HostEpoll::HostEpoll() { + handle_ = kqueue(); + if (handle_ < 0 || !wake_.Valid()) { + return; + } + SetCloseOnExec(handle_); + struct kevent kev; + EV_SET(&kev, wake_.PollHandle(), EVFILT_READ, EV_ADD, 0, 0, TagToUdata(ReservedTag)); + kevent(handle_, &kev, 1, nullptr, 0, nullptr); +} + +HostEpoll::~HostEpoll() { + if (handle_ >= 0) { + close(handle_); + } +} + +bool HostEpoll::Valid() const { + return handle_ >= 0; +} + +// Guest layer checks EEXIST/ENOENT, so Add and Modify are the same here. +Error HostEpoll::Add(NativeSocket s, u64 tag, u32 events, u32 flags) { + return KqueueApply(handle_, s, tag, events, flags); +} + +Error HostEpoll::Modify(NativeSocket s, u64 tag, u32 events, u32 flags) { + return KqueueApply(handle_, s, tag, events, flags); +} + +Error HostEpoll::Remove(NativeSocket s) { + return KqueueApply(handle_, s, 0, 0, 0); +} + +EpollWaitResult HostEpoll::Wait(std::span out, Deadline deadline) { + std::array buf; + const int cap = static_cast(std::min(out.size(), buf.size())); + if (cap == 0) { + return {0, false, Error::Inval}; + } + for (;;) { + timespec ts{}; + timespec* pts = nullptr; + if (deadline) { + const auto left = std::max(*deadline - Clock::now(), Clock::duration::zero()); + const auto ns = std::chrono::duration_cast(left).count(); + ts.tv_sec = static_cast(ns / 1'000'000'000); + ts.tv_nsec = static_cast(ns % 1'000'000'000); + pts = &ts; + } + const int n = kevent(handle_, nullptr, 0, buf.data(), cap, pts); + if (n < 0) { + if (errno == EINTR) { + continue; + } + return {0, false, LastError()}; + } + EpollWaitResult r{0, false, Error::Ok}; + for (int i = 0; i < n; ++i) { + const u64 tag = UdataToTag(buf[i].udata); + if (tag == ReservedTag) { + r.woken = true; + continue; + } + u32 ev = buf[i].filter == EVFILT_READ ? EvIn : EvOut; + if (buf[i].flags & EV_EOF) { + ev |= EvHup; + if (buf[i].fflags != 0) { + ev |= EvErr; // fflags holds the socket error + } + } + // kqueue reports read and write separately, merge them. + bool merged = false; + for (s32 j = 0; j < r.count; ++j) { + if (out[j].tag == tag) { + out[j].events |= ev; + merged = true; + break; + } + } + if (!merged) { + out[r.count++] = {tag, ev}; + } + } + if (n > 0 || Expired(deadline)) { + return r; + } + } +} + +#endif + +void HostEpoll::Wake() { + wake_.Signal(); +} + +void HostEpoll::DrainWake() { + wake_.Drain(); +} + +} // namespace Core::Net::Host diff --git a/src/core/net/host_net.h b/src/core/net/host_net.h new file mode 100644 index 000000000..174d7e060 --- /dev/null +++ b/src/core/net/host_net.h @@ -0,0 +1,238 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Backends: +// * Linux: poll + eventfd, epoll +// * macOS/FreeBSD: poll + pipe, kqueue +// * Windows: WSAPoll + loopback UDP pair, wepoll + +#pragma once + +#include +#include +#include +#include + +#include "common/types.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#endif + +namespace Core::Net::Host { + +#ifdef _WIN32 +using NativeSocket = SOCKET; +inline constexpr NativeSocket InvalidSocket = INVALID_SOCKET; +#else +using NativeSocket = int; +inline constexpr NativeSocket InvalidSocket = -1; +#endif + +using Clock = std::chrono::steady_clock; +// nullopt = no deadline +using Deadline = std::optional; + +// BSD SO_RCVTIMEO semantics, 0 = wait forever. +Deadline DeadlineFromSocketTimeout(std::chrono::microseconds timeout); + +enum class Error : s32 { + Ok = 0, + Perm = 1, + NoEnt = 2, + Intr = 4, + Io = 5, + BadF = 9, + NoMem = 12, + Acces = 13, + Fault = 14, + NotBlk = 15, // abort with nothing blocked + Busy = 16, + Exist = 17, + NoDev = 19, + Inval = 22, + NFile = 23, + MFile = 24, + NoSpc = 28, + Pipe = 32, + WouldBlock = 35, // also EAGAIN + InProgress = 36, + Already = 37, + NotSock = 38, + DestAddrReq = 39, + MsgSize = 40, + ProtoType = 41, + NoProtoOpt = 42, + ProtoNoSupport = 43, + SocktNoSupport = 44, + OpNotSupp = 45, + PfNoSupport = 46, + AfNoSupport = 47, + AddrInUse = 48, + AddrNotAvail = 49, + NetDown = 50, + NetUnreach = 51, + NetReset = 52, + ConnAborted = 53, + ConnReset = 54, + NoBufs = 55, + IsConn = 56, + NotConn = 57, + TooManyRefs = 59, + TimedOut = 60, + ConnRefused = 61, + Loop = 62, + NameTooLong = 63, + HostDown = 64, + HostUnreach = 65, + NotEmpty = 66, + Canceled = 85, + Internal = 204, // host errors with no FreeBSD equivalent +}; + +Error TranslateNative(int native_code); +Error LastError(); + +struct IoResult { + s64 value; + Error error; +}; + +bool Initialize(); +void Shutdown(); +NativeSocket CreateSocket(int family, int type, int protocol, Error* error); +bool ConfigureSocket(NativeSocket s, int type); +void CloseSocket(NativeSocket s); +// Windows has no socketpair(), so it uses a loopback TCP connection (stream only). +Error CreateSocketPair(int family, int type, int protocol, NativeSocket out[2]); + +Error PrepareBind(NativeSocket s, bool reuse_addr, bool reuse_port); +// SO_LINGER in seconds. Darwin's SO_LINGER counts clock ticks, SO_LINGER_SEC is the BSD one. +#ifdef __APPLE__ +inline constexpr int LingerOption = SO_LINGER_SEC; +#else +inline constexpr int LingerOption = SO_LINGER; +#endif + +// Call before closing a stream socket. Sorts out SO_LINGER for the guest's blocking mode. +void PrepareClose(NativeSocket s, bool guest_blocking); +Error Bind(NativeSocket s, const sockaddr* addr, socklen_t len); +Error Listen(NativeSocket s, int backlog); +Error ShutdownSocket(NativeSocket s, int how); +// Returns InProgress, never WouldBlock, while connecting. +Error Connect(NativeSocket s, const sockaddr* addr, socklen_t len); +// Reads and clears SO_ERROR. +Error PendingSocketError(NativeSocket s); +// Bytes ready to read (FIONREAD). +IoResult BytesReadable(NativeSocket s); +NativeSocket Accept(NativeSocket s, sockaddr* addr, socklen_t* len, Error* error); +// addr == nullptr uses send()/recv(). +IoResult SendTo(NativeSocket s, const void* buf, size_t len, int flags, const sockaddr* addr, + socklen_t addr_len); +IoResult RecvFrom(NativeSocket s, void* buf, size_t len, int flags, sockaddr* addr, + socklen_t* addr_len); + +// Pollable object another thread can signal to interrupt a wait. +class WakeHandle { +public: + WakeHandle(); + ~WakeHandle(); + WakeHandle(const WakeHandle&) = delete; + WakeHandle& operator=(const WakeHandle&) = delete; + + bool Valid() const { + return read_ != InvalidSocket; + } + // Thread-safe. Stays signalled until Drain(). + void Signal() const; + void Drain() const; + NativeSocket PollHandle() const { + return read_; + } + +private: + void Reset(); + + NativeSocket read_ = InvalidSocket; + NativeSocket write_ = InvalidSocket; // same as read_ for eventfd +}; + +// Wait on a single socket, for blocking guest calls. +enum Interest : u32 { + Readable = 1u << 0, + Writable = 1u << 1, +}; + +enum class WaitResult { + Ready, // includes error/hangup, retry the operation + TimedOut, + Woken, // abort or close + Failed, // see LastError() +}; + +WaitResult WaitOne(NativeSocket s, u32 interest, const WakeHandle& wake, Deadline deadline); + +// Host epoll/kqueue backing one guest epoll. +enum EventBits : u32 { + EvIn = 1u << 0, + EvOut = 1u << 1, + EvErr = 1u << 2, + EvHup = 1u << 3, + // Guest only. A non-socket object such as a resolver has finished. + EvDescId = 1u << 4, +}; + +// Always level-triggered. The PS4 has no edge-triggered mode and wepoll ignores EPOLLET. +enum EpollFlags : u32 { + OneShot = 1u << 0, // re-arm with Modify() +}; + +// Used for the wake handle, not a valid caller tag. +inline constexpr u64 ReservedTag = ~0ull; + +struct EpollEvent { + u64 tag; + u32 events; // EventBits +}; + +struct EpollWaitResult { + s32 count; + bool woken; // not drained, call DrainWake() + Error error; +}; + +class HostEpoll { +public: + HostEpoll(); + ~HostEpoll(); + HostEpoll(const HostEpoll&) = delete; + HostEpoll& operator=(const HostEpoll&) = delete; + + bool Valid() const; + + Error Add(NativeSocket s, u64 tag, u32 events, u32 flags); + Error Modify(NativeSocket s, u64 tag, u32 events, u32 flags); + Error Remove(NativeSocket s); + + // At most 64 events per call. + EpollWaitResult Wait(std::span out, Deadline deadline); + // Thread-safe. Stays signalled until DrainWake(). + void Wake(); + void DrainWake(); + +private: +#ifdef _WIN32 + void* handle_ = nullptr; // wepoll HANDLE +#else + int handle_ = -1; +#endif + WakeHandle wake_; +}; + +} // namespace Core::Net::Host diff --git a/src/core/net/p2p_codec.cpp b/src/core/net/p2p_codec.cpp new file mode 100644 index 000000000..854eb2636 --- /dev/null +++ b/src/core/net/p2p_codec.cpp @@ -0,0 +1,266 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "core/net/p2p_codec.h" + +namespace Core::Net::P2P { + +using namespace Wire; + +void Keyring::AddPeerKey(const Endpoint& peer, const KeyEntry& entry) { + std::scoped_lock lock{mutex}; + const auto it = peer_keys.find(peer); + if (it != peer_keys.end() && it->second.entry.key == entry.key) { + ++it->second.references; + } else { + peer_keys[peer] = {entry, 1}; + } +} + +bool Keyring::ReleasePeerKey(const Endpoint& peer, const P2PKey& key, u8 id) { + std::scoped_lock lock{mutex}; + const auto it = peer_keys.find(peer); + if (it == peer_keys.end() || !(it->second.entry.key == key) || it->second.entry.id != id) { + return false; + } + if (--it->second.references == 0) { + peer_keys.erase(it); + } + return true; +} + +void Keyring::RemoveKeysWithId(u8 id) { + std::scoped_lock lock{mutex}; + std::erase_if(peer_keys, [id](const auto& entry) { return entry.second.entry.id == id; }); +} + +void Keyring::SetPeerKey(const Endpoint& peer, const P2PKey& key) { + std::scoped_lock lock{mutex}; + peer_keys[peer] = {KeyEntry{key}, 1}; +} + +void Keyring::RemovePeerKey(const Endpoint& peer) { + std::scoped_lock lock{mutex}; + peer_keys.erase(peer); +} + +void Keyring::SetWildcardKey(std::optional key) { + std::scoped_lock lock{mutex}; + wildcard_key = key; +} + +std::optional Keyring::GetWildcardKey() const { + std::scoped_lock lock{mutex}; + return wildcard_key; +} + +void Keyring::SetDefaultKey(std::optional key) { + std::scoped_lock lock{mutex}; + default_key = key; +} + +std::optional Keyring::GetDefaultKey() const { + std::scoped_lock lock{mutex}; + return default_key; +} + +std::optional Keyring::Find(const Endpoint& peer) const { + std::scoped_lock lock{mutex}; + if (const auto it = peer_keys.find(peer); it != peer_keys.end()) { + return it->second.entry.key; + } + return wildcard_key ? wildcard_key : default_key; +} + +std::optional Keyring::FindPeerEntry(const Endpoint& peer) const { + std::scoped_lock lock{mutex}; + if (const auto it = peer_keys.find(peer); it != peer_keys.end()) { + return it->second.entry; + } + return std::nullopt; +} + +void Keyring::SetCommunicationId(std::optional id) { + std::scoped_lock lock{mutex}; + communication_id = id; +} + +std::optional Keyring::GetCommunicationId() const { + std::scoped_lock lock{mutex}; + return communication_id; +} + +FramingCodec::FramingCodec(std::shared_ptr keyring, BroadcastObserver broadcast_observer) + : keyring(std::move(keyring)), broadcast_observer(std::move(broadcast_observer)) {} + +Codec::Decoded FramingCodec::Decode(std::span packet, const Endpoint& from) { + Decoded out; + if (packet.size() < 2 || packet[0] != Marker || (packet[1] & FlagValid) == 0) { + return out; + } + const u8 flags = packet[1]; + const u8 kind = flags & 0x0F; + if (kind < KindDatagram || kind > KindStream + 3) { + return out; + } + const bool stream = kind >= KindStream; + + size_t offset = 2; + u16 src_vport = 0; + u16 dst_vport = 0; + if (flags & FlagBroadcast) { + // Layout unconfirmed, report a few so we can check + if (broadcast_observer && + reported_broadcasts.fetch_add(1, std::memory_order_relaxed) < MaxReportedBroadcasts) { + broadcast_observer(packet, from); + } + if (stream) { + return out; // no broadcast TCP + } + offset = 4; + if (packet.size() < offset) { + return out; + } + dst_vport = static_cast((packet[2] << 8) | packet[3]); + src_vport = dst_vport; + } else if (!stream) { + offset = 6; + if (packet.size() < offset) { + return out; + } + src_vport = static_cast((packet[2] << 8) | packet[3]); + dst_vport = static_cast((packet[4] << 8) | packet[5]); + } + + std::optional communication_id; + if (flags & FlagDigest) { + if (packet.size() < offset + 4) { + return out; + } + communication_id.emplace(); + std::memcpy(communication_id->data(), packet.data() + offset, 4); + offset += 4; + } + + const bool signaling = !stream && dst_vport == SignalingVport; + // TODO: drop signaling once the communication ID is set? + if (!signaling) { + if (const auto expected = keyring->GetCommunicationId(); + expected && communication_id != expected) { + return out; + } + } + + const u8 mode = kind - (stream ? KindStream : KindDatagram); + const auto body = packet.subspan(offset); + if (mode != 0) { + const auto key = keyring->Find(from); + if (!key || !UnprotectPayload(body, (mode & 1) != 0, (mode & 2) != 0, *key, out.payload)) { + return out; // no key or bad signature + } + } else { + out.payload.assign(body.begin(), body.end()); + } + + out.kind = stream ? Kind::Stream : signaling ? Kind::Signaling : Kind::Datagram; + out.src_vport = src_vport; + out.dst_vport = dst_vport; + return out; +} + +std::vector FramingCodec::Protect(std::span data, const Endpoint& to, + Protection protection, u8& mode) const { + mode = 0; + if (protection.crypto || protection.signature) { + if (const auto key = keyring->Find(to)) { + std::vector out; + if (ProtectPayload(data, protection.crypto, protection.signature, *key, out)) { + mode = + static_cast((protection.crypto ? 1 : 0) | (protection.signature ? 2 : 0)); + return out; + } + } + // TODO: this sends plain. Should we drop instead? + } + return {data.begin(), data.end()}; +} + +std::vector FramingCodec::EncodeStream(std::span tcp_segment, const Endpoint& to, + Protection protection) { + u8 mode = 0; + const auto body = Protect(tcp_segment, to, protection, mode); + const auto communication_id = keyring->GetCommunicationId(); + std::vector packet{Marker, static_cast(FlagValid | (KindStream + mode) | + (communication_id ? FlagDigest : 0))}; + if (communication_id) { + packet.insert(packet.end(), communication_id->begin(), communication_id->end()); + } + packet.insert(packet.end(), body.begin(), body.end()); + return packet; +} + +std::vector FrameDatagram(u16 src_vport, u16 dst_vport, u8 kind, + const std::optional& communication_id, + std::span body) { + std::vector packet{Marker, + static_cast(FlagValid | kind | (communication_id ? FlagDigest : 0)), + static_cast(src_vport >> 8), + static_cast(src_vport), + static_cast(dst_vport >> 8), + static_cast(dst_vport)}; + if (communication_id) { + packet.insert(packet.end(), communication_id->begin(), communication_id->end()); + } + packet.insert(packet.end(), body.begin(), body.end()); + return packet; +} + +std::vector FramingCodec::EncodeDatagram(u16 src_vport, u16 dst_vport, + std::span payload, const Endpoint& to, + Protection protection) { + u8 mode = 0; + const auto body = Protect(payload, to, protection, mode); + return FrameDatagram(src_vport, dst_vport, KindDatagram + mode, keyring->GetCommunicationId(), + body); +} + +std::vector FramingCodec::EncodeSignaling(std::span data, const Endpoint&) { + return FrameDatagram(SignalingVport, SignalingVport, KindDatagram, std::nullopt, data); +} + +PseudoHeader FramingCodec::StreamPseudoHeader(const Endpoint& peer) { + std::scoped_lock lock{pseudo_mutex}; + if (const auto it = pseudo_cache.find(peer); it != pseudo_cache.end()) { + return it->second; + } + PseudoHeader pseudo; + pseudo.family = peer.Family(); + const bool v6 = peer.Family() == AF_INET6; + const size_t size = v6 ? 16 : 4; + const auto address_of = [v6](const sockaddr_storage& ss) -> const void* { + return v6 ? static_cast(&reinterpret_cast(&ss)->sin6_addr) + : static_cast(&reinterpret_cast(&ss)->sin_addr); + }; + std::memcpy(pseudo.remote.data(), address_of(peer.addr), size); + + Host::Error error; + const auto probe = Host::CreateSocket(peer.Family(), SOCK_DGRAM, 0, &error); + if (probe != Host::InvalidSocket) { + sockaddr_storage local{}; + socklen_t local_len = sizeof(local); + if (Host::Connect(probe, peer.Sockaddr(), peer.Length()) == Host::Error::Ok && + getsockname(probe, reinterpret_cast(&local), &local_len) == 0) { + std::memcpy(pseudo.local.data(), address_of(local), size); + } + Host::CloseSocket(probe); + } + if (pseudo_cache.size() >= 256) { + pseudo_cache.clear(); + } + pseudo_cache.emplace(peer, pseudo); + return pseudo; +} + +} // namespace Core::Net::P2P diff --git a/src/core/net/p2p_codec.h b/src/core/net/p2p_codec.h new file mode 100644 index 000000000..2f88ef7a5 --- /dev/null +++ b/src/core/net/p2p_codec.h @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// PS4 P2P wire format. vports are big endian. +// +// Datagram: [0xFF][flags|kind][src vport 2][dst vport 2][digest 4?][payload] +// Broadcast: [0xFF][flags|0x40|kind][port 2][digest 4?][payload], datagrams only. +// Broadcast layout is assumed, not confirmed yet. +// Stream: [0xFF][flags|kind][digest 4?][TCP segment] +// Signaling: plain datagram from and to vport 0xFFFF. +// +// flags: 0x80 valid, 0x40 broadcast, 0x20 digest. +// kind: UDP 3-6, TCP 7-10. Base +1 for crypto, +2 for signature. +// digest: truncated HMAC-SHA1 of the 16-byte value NP installs (ioctl 0xd8). +// We call it the communication ID. + +#pragma once + +#include +#include +#include + +#include "core/net/p2p_crypto.h" +#include "core/net/p2p_transport.h" + +namespace Core::Net::P2P { + +namespace Wire { +constexpr u8 Marker = 0xFF; +constexpr u8 FlagValid = 0x80; +constexpr u8 FlagBroadcast = 0x40; +constexpr u8 FlagDigest = 0x20; // communication ID follows +constexpr u8 KindDatagram = 3; +constexpr u8 KindStream = 7; +constexpr u16 SignalingVport = 0xFFFF; +} // namespace Wire + +using CommunicationId = std::array; + +struct KeyEntry { + P2PKey key; + u16 local_port = 0; // network byte order + u8 flags = 0; + u8 id = 0; // groups keys for removal +}; + +class Keyring { +public: + // Replaces the peer's key. The same key again just adds a reference, + // since NP installs it once per user. + void AddPeerKey(const Endpoint& peer, const KeyEntry& entry); + // Drops a reference. False if that key and id aren't installed. + bool ReleasePeerKey(const Endpoint& peer, const P2PKey& key, u8 id); + void RemoveKeysWithId(u8 id); + void SetPeerKey(const Endpoint& peer, const P2PKey& key); + void RemovePeerKey(const Endpoint& peer); + void SetWildcardKey(std::optional key); + std::optional GetWildcardKey() const; + void SetDefaultKey(std::optional key); + std::optional GetDefaultKey() const; + std::optional Find(const Endpoint& peer) const; + std::optional FindPeerEntry(const Endpoint& peer) const; + void SetCommunicationId(std::optional id); + std::optional GetCommunicationId() const; + +private: + struct Installed { + KeyEntry entry; + int references = 1; + }; + + mutable std::mutex mutex; + std::map peer_keys; + std::optional wildcard_key; + std::optional default_key; + std::optional communication_id; +}; + +class FramingCodec final : public Codec { +public: + // Gets dropped broadcast packets, for debugging. + using BroadcastObserver = std::function, const Endpoint&)>; + static constexpr int MaxReportedBroadcasts = 16; // per session + + explicit FramingCodec(std::shared_ptr keyring, + BroadcastObserver broadcast_observer = {}); + + Decoded Decode(std::span packet, const Endpoint& from) override; + std::vector EncodeStream(std::span tcp_segment, const Endpoint& to, + Protection protection) override; + std::vector EncodeDatagram(u16 src_vport, u16 dst_vport, std::span payload, + const Endpoint& to, Protection protection) override; + std::vector EncodeSignaling(std::span data, const Endpoint& to) override; + PseudoHeader StreamPseudoHeader(const Endpoint& peer) override; + +private: + // Sends plain if there is no key. mode gets the kind offset used. + std::vector Protect(std::span data, const Endpoint& to, Protection protection, + u8& mode) const; + + std::shared_ptr keyring; + BroadcastObserver broadcast_observer; + std::atomic reported_broadcasts{0}; + std::mutex pseudo_mutex; + std::map pseudo_cache; +}; + +} // namespace Core::Net::P2P diff --git a/src/core/net/p2p_crypto.cpp b/src/core/net/p2p_crypto.cpp new file mode 100644 index 000000000..f98a206f6 --- /dev/null +++ b/src/core/net/p2p_crypto.cpp @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include +#include +#include +#include + +#include "core/net/p2p_crypto.h" + +namespace Core::Net::P2P { + +constexpr size_t IvSeedSize = 4; +constexpr size_t SignatureSize = 4; + +bool Crypt(std::vector& payload, const P2PKey& key, const std::array& iv_seed, + bool encrypt) { + std::array iv; + for (size_t i = 0; i < iv.size(); ++i) { + iv[i] = iv_seed[i & 3]; + } + EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new(); + if (!context) { + return false; + } + int output_size = 0; + int final_size = 0; + const bool ok = EVP_CipherInit_ex(context, EVP_aes_128_cfb128(), nullptr, key.value.data(), + iv.data(), encrypt ? 1 : 0) == 1 && + EVP_CipherUpdate(context, payload.data(), &output_size, payload.data(), + static_cast(payload.size())) == 1 && + EVP_CipherFinal_ex(context, payload.data() + output_size, &final_size) == 1; + EVP_CIPHER_CTX_free(context); + return ok && static_cast(output_size + final_size) == payload.size(); +} + +std::array Sign(std::span body, const P2PKey& key) { + std::array digest{}; + unsigned int digest_size = 0; + HMAC(EVP_sha1(), key.value.data(), static_cast(key.value.size()), body.data(), body.size(), + digest.data(), &digest_size); + std::array signature{}; + std::copy_n(digest.begin(), signature.size(), signature.begin()); + return signature; +} + +std::array DeriveCommunicationId(std::span value) { + std::array digest{}; + unsigned int digest_size = 0; + const u8 empty = 0; + HMAC(EVP_sha1(), value.data(), static_cast(value.size()), &empty, 0, digest.data(), + &digest_size); + std::array id{}; + std::copy_n(digest.begin(), id.size(), id.begin()); + return id; +} + +bool ProtectPayload(std::span data, bool encrypt, bool sign, const P2PKey& key, + std::vector& output) { + std::vector body(data.begin(), data.end()); + std::array iv{}; + if (encrypt && + (RAND_bytes(iv.data(), static_cast(iv.size())) != 1 || !Crypt(body, key, iv, true))) { + return false; + } + output.clear(); + output.reserve(body.size() + (encrypt ? IvSeedSize : 0) + (sign ? SignatureSize : 0)); + if (encrypt) { + output.insert(output.end(), iv.begin(), iv.end()); + } + if (sign) { + const auto signature = Sign(body, key); // signs ciphertext + output.insert(output.end(), signature.begin(), signature.end()); + } + output.insert(output.end(), body.begin(), body.end()); + return true; +} + +bool UnprotectPayload(std::span data, bool encrypt, bool sign, const P2PKey& key, + std::vector& output) { + const size_t prefix = (encrypt ? IvSeedSize : 0) + (sign ? SignatureSize : 0); + if (data.size() < prefix) { + return false; + } + std::array iv{}; + std::array signature{}; + size_t offset = 0; + if (encrypt) { + std::copy_n(data.begin(), iv.size(), iv.begin()); + offset += iv.size(); + } + if (sign) { + std::copy_n(data.begin() + offset, signature.size(), signature.begin()); + offset += signature.size(); + } + output.assign(data.begin() + offset, data.end()); + if (sign) { + const auto expected = Sign(output, key); + if (CRYPTO_memcmp(signature.data(), expected.data(), signature.size()) != 0) { + return false; + } + } + return !encrypt || Crypt(output, key, iv, false); +} + +} // namespace Core::Net::P2P diff --git a/src/core/net/p2p_crypto.h b/src/core/net/p2p_crypto.h new file mode 100644 index 000000000..d207b90c1 --- /dev/null +++ b/src/core/net/p2p_crypto.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/types.h" + +namespace Core::Net::P2P { + +struct P2PKey { + std::array value{}; + bool operator==(const P2PKey&) const = default; +}; + +bool ProtectPayload(std::span data, bool encrypt, bool sign, const P2PKey& key, + std::vector& output); + +// Fails on short packet, bad signature or cipher error. +bool UnprotectPayload(std::span data, bool encrypt, bool sign, const P2PKey& key, + std::vector& output); + +// First 4 bytes of HMAC-SHA1 over an empty message, keyed with the title's 16-byte value. +std::array DeriveCommunicationId(std::span value); + +} // namespace Core::Net::P2P diff --git a/src/core/net/p2p_tcp.cpp b/src/core/net/p2p_tcp.cpp new file mode 100644 index 000000000..3ab237f1c --- /dev/null +++ b/src/core/net/p2p_tcp.cpp @@ -0,0 +1,878 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "core/net/p2p_tcp.h" + +namespace Core::Net::P2P { + +using namespace TcpFlag; + +namespace { + +u16 Read16(const u8* p) { + return static_cast((p[0] << 8) | p[1]); +} + +u32 Read32(const u8* p) { + return (static_cast(p[0]) << 24) | (static_cast(p[1]) << 16) | + (static_cast(p[2]) << 8) | p[3]; +} + +void Write16(u8* p, u16 v) { + p[0] = static_cast(v >> 8); + p[1] = static_cast(v); +} + +void Write32(u8* p, u32 v) { + p[0] = static_cast(v >> 24); + p[1] = static_cast(v >> 16); + p[2] = static_cast(v >> 8); + p[3] = static_cast(v); +} + +u64 SumWords(u64 sum, const u8* data, size_t size) { + size_t i = 0; + for (; i + 1 < size; i += 2) { + sum += Read16(data + i); + } + if (i < size) { + sum += static_cast(data[i]) << 8; + } + return sum; +} + +} // namespace + +const char* ToString(TcpState state) { + switch (state) { + case TcpState::Closed: + return "Closed"; + case TcpState::SynSent: + return "SynSent"; + case TcpState::SynReceived: + return "SynReceived"; + case TcpState::Established: + return "Established"; + case TcpState::FinWait1: + return "FinWait1"; + case TcpState::FinWait2: + return "FinWait2"; + case TcpState::CloseWait: + return "CloseWait"; + case TcpState::Closing: + return "Closing"; + case TcpState::LastAck: + return "LastAck"; + case TcpState::TimeWait: + return "TimeWait"; + } + return "?"; +} + +u16 TcpChecksum(int family, const u8* src_addr, const u8* dst_addr, std::span segment) { + const size_t address_size = family == AF_INET6 ? 16 : 4; + u64 sum = 0; + sum = SumWords(sum, src_addr, address_size); + sum = SumWords(sum, dst_addr, address_size); + const u64 length = segment.size(); + sum += (length >> 16) + (length & 0xffff); + sum += 6; // IPPROTO_TCP + sum = SumWords(sum, segment.data(), segment.size()); + while (sum >> 16) { + sum = (sum & 0xffff) + (sum >> 16); + } + return static_cast(~sum); +} + +std::optional ParseTcpSegment(std::span bytes, const PseudoHeader* pseudo) { + if (bytes.size() < 20) { + return std::nullopt; + } + const u8* b = bytes.data(); + const size_t header_size = static_cast(b[12] >> 4) * 4; + if (header_size < 20 || header_size > bytes.size()) { + return std::nullopt; + } + if (pseudo != nullptr && + TcpChecksum(pseudo->family, pseudo->remote.data(), pseudo->local.data(), bytes) != 0) { + return std::nullopt; + } + TcpSegment seg{}; + seg.src_port = Read16(b); + seg.dst_port = Read16(b + 2); + seg.seq = Read32(b + 4); + seg.ack = Read32(b + 8); + seg.flags = b[13]; + seg.window = Read16(b + 14); + for (size_t i = 20; i < header_size;) { + const u8 kind = b[i]; + if (kind == 0) { + break; + } + if (kind == 1) { + ++i; + continue; + } + if (i + 1 >= header_size) { + break; + } + const u8 length = b[i + 1]; + if (length < 2 || i + length > header_size) { + break; + } + if (kind == 2 && length == 4) { + seg.mss = Read16(b + i + 2); + } + i += length; + } + seg.payload = bytes.subspan(header_size); + return seg; +} + +std::vector BuildTcpSegment(const PseudoHeader& pseudo, u16 src_port, u16 dst_port, u32 seq, + u32 ack, u8 flags, u16 window, std::optional mss, + std::span payload) { + const size_t header_size = mss ? 24 : 20; + std::vector s(header_size + payload.size()); + Write16(s.data(), src_port); + Write16(s.data() + 2, dst_port); + Write32(s.data() + 4, seq); + Write32(s.data() + 8, ack); + s[12] = static_cast((header_size / 4) << 4); + s[13] = flags; + Write16(s.data() + 14, window); + if (mss) { + s[20] = 2; + s[21] = 4; + Write16(s.data() + 22, *mss); + } + std::copy(payload.begin(), payload.end(), s.begin() + header_size); + Write16(s.data() + 16, + TcpChecksum(pseudo.family, pseudo.local.data(), pseudo.remote.data(), s)); + return s; +} + +std::optional> BuildResetFor(const PseudoHeader& pseudo, const TcpSegment& seg) { + if (seg.flags & Rst) { + return std::nullopt; + } + if (seg.flags & Ack) { + return BuildTcpSegment(pseudo, seg.dst_port, seg.src_port, seg.ack, 0, Rst, 0, std::nullopt, + {}); + } + const u32 length = static_cast(seg.payload.size()) + ((seg.flags & Syn) ? 1 : 0) + + ((seg.flags & Fin) ? 1 : 0); + return BuildTcpSegment(pseudo, seg.dst_port, seg.src_port, 0, seg.seq + length, Rst | Ack, 0, + std::nullopt, {}); +} + +TcpConnection::TcpConnection(const TcpConfig& config, const PseudoHeader& pseudo, u16 local_port, + u16 remote_port, u32 iss, Emit emit) + : config_(config), pseudo_(pseudo), local_port_(local_port), remote_port_(remote_port), + emit_(std::move(emit)), iss_(iss), snd_una_(iss), snd_nxt_(iss), snd_max_(iss), + buf_seq_(iss + 1), rto_(config.initial_rto) {} + +bool TcpConnection::Synchronized() const { + switch (state_) { + case TcpState::Closed: + case TcpState::SynSent: + case TcpState::SynReceived: + return false; + default: + return true; + } +} + +u16 TcpConnection::ReceiveWindow() const { + const size_t used = recv_buf_.size(); + if (used >= config_.recv_buffer) { + return 0; + } + return static_cast(std::min(config_.recv_buffer - used, 65535)); +} + +u32 TcpConnection::FinSeq() const { + return buf_seq_ + static_cast(send_buf_.size()); +} + +bool TcpConnection::FinSent() const { + return fin_queued_ && SeqGt(snd_max_, FinSeq()); +} + +bool TcpConnection::FinAcked() const { + return fin_queued_ && SeqGt(snd_una_, FinSeq()); +} + +size_t TcpConnection::SendSpace() const { + return send_buf_.size() >= config_.send_buffer ? 0 : config_.send_buffer - send_buf_.size(); +} + +bool TcpConnection::Readable() const { + return !recv_buf_.empty() || fin_received_ || error_ != Error::Ok || state_ == TcpState::Closed; +} + +bool TcpConnection::Writable() const { + if (error_ != Error::Ok || state_ == TcpState::Closed) { + return true; // wake waiters so they see the error + } + return (state_ == TcpState::Established || state_ == TcpState::CloseWait) && !fin_queued_ && + SendSpace() > 0; +} + +void TcpConnection::EmitSegment(u32 seq, u8 flags, std::span payload, bool with_mss) { + const u16 window = ReceiveWindow(); + const u32 ack = (flags & Ack) ? rcv_nxt_ : 0; + auto segment = + BuildTcpSegment(pseudo_, local_port_, remote_port_, seq, ack, flags, window, + with_mss ? std::optional{config_.mss} : std::nullopt, payload); + if (flags & Ack) { + last_advertised_window_ = window; + unacked_segments_ = 0; + delayed_ack_deadline_.reset(); + } + emit_(segment); +} + +void TcpConnection::SendAck() { + // Not snd_nxt_, which is rewound after a timeout. BSD drops ACKs with an old seq. + EmitSegment(snd_max_, Ack, {}); +} + +void TcpConnection::EmitReset(u32 seq) { + emit_(BuildTcpSegment(pseudo_, local_port_, remote_port_, seq, 0, Rst, 0, std::nullopt, {})); +} + +void TcpConnection::SendData(u32 seq, u32 len) { + std::vector payload(len); + const auto begin = send_buf_.begin() + static_cast(seq - buf_seq_); + std::copy(begin, begin + len, payload.begin()); + const bool last = seq + len == FinSeq(); + EmitSegment(seq, Ack | (last ? Psh : 0), payload); +} + +void TcpConnection::ArmRetransmit(Clock::time_point now) { + if (!retransmit_deadline_) { + retransmit_deadline_ = now + rto_; + } +} + +void TcpConnection::TrySend(Clock::time_point now) { + switch (state_) { + case TcpState::Established: + case TcpState::CloseWait: + case TcpState::FinWait1: + case TcpState::Closing: + case TcpState::LastAck: + break; + default: + return; + } + for (;;) { + if (FinSent() && snd_nxt_ == FinSeq() + 1) { + return; // FIN in flight + } + const u32 offset = snd_nxt_ - buf_seq_; + const u32 available = + offset < send_buf_.size() ? static_cast(send_buf_.size()) - offset : 0; + const u32 flight = snd_nxt_ - snd_una_; + const u32 window = std::min(snd_wnd_, cwnd_); + const u32 usable = window > flight ? window - flight : 0; + const u32 len = std::min({available, usable, static_cast(snd_mss_)}); + + if (len == 0) { + if (available == 0 && fin_queued_ && snd_nxt_ == FinSeq()) { + EmitSegment(snd_nxt_, Fin | Ack, {}); + ++snd_nxt_; + if (SeqGt(snd_nxt_, snd_max_)) { + snd_max_ = snd_nxt_; + } + ArmRetransmit(now); + } else if (available > 0 && snd_wnd_ == 0 && snd_max_ == snd_una_ && + !persist_deadline_) { + persist_interval_ = rto_; + persist_deadline_ = now + persist_interval_; + } + return; + } + + SendData(snd_nxt_, len); + if (!rtt_active_ && snd_nxt_ == snd_max_) { + rtt_active_ = true; // don't time retransmits + rtt_seq_ = snd_nxt_; + rtt_start_ = now; + } + snd_nxt_ += len; + if (SeqGt(snd_nxt_, snd_max_)) { + snd_max_ = snd_nxt_; + } + ArmRetransmit(now); + } +} + +void TcpConnection::RetransmitFirst() { + ++retransmissions_; + rtt_active_ = false; + const u32 offset = snd_una_ - buf_seq_; + if (offset < send_buf_.size()) { + const u32 len = std::min(static_cast(send_buf_.size()) - offset, snd_mss_); + SendData(snd_una_, len); + } else if (FinSent()) { + EmitSegment(FinSeq(), Fin | Ack, {}); + } +} + +void TcpConnection::Connect(Clock::time_point now) { + state_ = TcpState::SynSent; + snd_nxt_ = snd_max_ = iss_ + 1; + EmitSegment(iss_, Syn, {}, true); + rtt_active_ = true; + rtt_seq_ = iss_; + rtt_start_ = now; + ArmRetransmit(now); +} + +void TcpConnection::AcceptSyn(const TcpSegment& syn, Clock::time_point now) { + irs_ = syn.seq; + rcv_nxt_ = irs_ + 1; + snd_mss_ = std::min(syn.mss.value_or(536), config_.mss); + cwnd_ = std::min(4u * snd_mss_, std::max(2u * snd_mss_, 4380)); + snd_wnd_ = syn.window; + snd_wl1_ = syn.seq; + snd_wl2_ = iss_; + state_ = TcpState::SynReceived; + snd_nxt_ = snd_max_ = iss_ + 1; + EmitSegment(iss_, Syn | Ack, {}, true); + rtt_active_ = true; + rtt_seq_ = iss_; + rtt_start_ = now; + ArmRetransmit(now); +} + +void TcpConnection::HandleSynSent(const TcpSegment& seg, Clock::time_point now) { + const bool ack_ok = SeqGt(seg.ack, iss_) && SeqLe(seg.ack, snd_max_); + if ((seg.flags & Ack) && !ack_ok) { + if (!(seg.flags & Rst)) { + EmitReset(seg.ack); + } + return; + } + if (seg.flags & Rst) { + if (seg.flags & Ack) { + Terminate(Error::ConnRefused); + } + return; + } + if (!(seg.flags & Syn)) { + return; + } + irs_ = seg.seq; + rcv_nxt_ = irs_ + 1; + snd_mss_ = std::min(seg.mss.value_or(536), config_.mss); + cwnd_ = std::min(4u * snd_mss_, std::max(2u * snd_mss_, 4380)); + snd_wnd_ = seg.window; + snd_wl1_ = seg.seq; + snd_wl2_ = seg.ack; + if (seg.flags & Ack) { + snd_una_ = seg.ack; + if (rtt_active_ && SeqGt(seg.ack, rtt_seq_)) { + SampleRtt(now - rtt_start_); + rtt_active_ = false; + } + retries_ = 0; + retransmit_deadline_.reset(); + state_ = TcpState::Established; + SendAck(); + TrySend(now); + } else { + state_ = TcpState::SynReceived; + EmitSegment(iss_, Syn | Ack, {}, true); + } +} + +bool TcpConnection::Acceptable(u32 seq, u32 len) const { + const u32 window = ReceiveWindow(); + const auto in_window = [&](u32 s) { return SeqLe(rcv_nxt_, s) && SeqLt(s, rcv_nxt_ + window); }; + if (len == 0) { + return window == 0 ? seq == rcv_nxt_ : in_window(seq); + } + if (window == 0) { + return false; + } + return in_window(seq) || in_window(seq + len - 1); +} + +void TcpConnection::OnSegment(const TcpSegment& seg, Clock::time_point now) { + switch (state_) { + case TcpState::Closed: + return; + case TcpState::SynSent: + HandleSynSent(seg, now); + return; + case TcpState::TimeWait: + if ((seg.flags & Fin) && !(seg.flags & Rst)) { + SendAck(); + time_wait_deadline_ = now + config_.time_wait; + } + return; + default: + break; + } + + // Peer resent its SYN, so our SYN-ACK was lost. + if (state_ == TcpState::SynReceived && (seg.flags & (Syn | Ack | Rst)) == Syn && + seg.seq == irs_) { + ++retransmissions_; + EmitSegment(iss_, Syn | Ack, {}, true); + return; + } + + const u32 length = static_cast(seg.payload.size()) + ((seg.flags & Syn) ? 1 : 0) + + ((seg.flags & Fin) ? 1 : 0); + bool ack_only = false; + if (!Acceptable(seg.seq, length)) { + if (ReceiveWindow() == 0 && seg.seq == rcv_nxt_ && (seg.flags & Ack) && + !(seg.flags & (Rst | Syn))) { + ack_only = true; + } else { + if (!(seg.flags & Rst)) { + SendAck(); + } + return; + } + } + + if (seg.flags & Rst) { + if (seg.seq == rcv_nxt_) { + const bool closing = state_ == TcpState::Closing || state_ == TcpState::LastAck; + Terminate(closing ? Error::Ok + : state_ == TcpState::SynReceived ? Error::ConnRefused + : Error::ConnReset); + } else { + SendAck(); + } + return; + } + if (seg.flags & Syn) { + SendAck(); + return; + } + if (!(seg.flags & Ack)) { + return; + } + + if (state_ == TcpState::SynReceived) { + if (!(SeqGt(seg.ack, snd_una_) && SeqLe(seg.ack, snd_max_))) { + EmitReset(seg.ack); + return; + } + state_ = TcpState::Established; + snd_wnd_ = seg.window; + snd_wl1_ = seg.seq; + snd_wl2_ = seg.ack; + } + + if (!ProcessAck(seg, now)) { + return; + } + if (ack_only) { + SendAck(); // window still closed + return; + } + ProcessData(seg, now); + TrySend(now); +} + +bool TcpConnection::ProcessAck(const TcpSegment& seg, Clock::time_point now) { + if (SeqGt(seg.ack, snd_max_)) { + SendAck(); // acks unsent data + return false; + } + if (SeqGt(seg.ack, snd_una_)) { + OnNewAck(seg.ack, now); + } else if (seg.ack == snd_una_ && seg.payload.empty() && !(seg.flags & (Syn | Fin)) && + seg.window == snd_wnd_ && snd_max_ != snd_una_) { + OnDuplicateAck(); + } + + if (SeqLt(snd_wl1_, seg.seq) || (snd_wl1_ == seg.seq && SeqLe(snd_wl2_, seg.ack))) { + snd_wnd_ = seg.window; + snd_wl1_ = seg.seq; + snd_wl2_ = seg.ack; + if (snd_wnd_ > 0) { + persist_deadline_.reset(); + } + } + + if (FinAcked()) { + switch (state_) { + case TcpState::FinWait1: + state_ = TcpState::FinWait2; + break; + case TcpState::Closing: + EnterTimeWait(now); + return false; + case TcpState::LastAck: + state_ = TcpState::Closed; + ClearTimers(); + return false; + default: + break; + } + } + return true; +} + +void TcpConnection::OnNewAck(u32 ack, Clock::time_point now) { + const u32 acked = ack - snd_una_; + const u32 data_acked = SeqGt(ack, buf_seq_) + ? std::min(ack - buf_seq_, static_cast(send_buf_.size())) + : 0; + send_buf_.erase(send_buf_.begin(), send_buf_.begin() + data_acked); + buf_seq_ += data_acked; + snd_una_ = ack; + if (SeqLt(snd_nxt_, snd_una_)) { + snd_nxt_ = snd_una_; + } + + if (rtt_active_ && SeqGt(ack, rtt_seq_)) { + SampleRtt(now - rtt_start_); + rtt_active_ = false; + } + retries_ = 0; + + if (in_recovery_) { + if (SeqGe(ack, recover_)) { + in_recovery_ = false; + cwnd_ = ssthresh_; + } else { + // NewReno partial ACK + RetransmitFirst(); + cwnd_ = cwnd_ > acked ? cwnd_ - acked + snd_mss_ : snd_mss_; + } + } else if (cwnd_ < ssthresh_) { + cwnd_ += std::min(acked, snd_mss_); // slow start + } else { + cwnd_ += std::max(1, snd_mss_ * snd_mss_ / std::max(cwnd_, 1)); + } + cwnd_ = std::min(cwnd_, 1u << 30); + dupacks_ = 0; + + retransmit_deadline_.reset(); + if (snd_una_ != snd_max_) { + retransmit_deadline_ = now + rto_; + } +} + +void TcpConnection::OnDuplicateAck() { + if (in_recovery_) { + cwnd_ += snd_mss_; + return; + } + if (++dupacks_ == 3) { + const u32 flight = snd_max_ - snd_una_; + ssthresh_ = std::max(flight / 2, 2u * snd_mss_); + recover_ = snd_max_; + in_recovery_ = true; + RetransmitFirst(); + cwnd_ = ssthresh_ + 3u * snd_mss_; + } +} + +void TcpConnection::ProcessData(const TcpSegment& seg, Clock::time_point now) { + if (state_ != TcpState::Established && state_ != TcpState::FinWait1 && + state_ != TcpState::FinWait2) { + // Peer already sent FIN, this is a retransmission. + if (seg.flags & Fin) { + SendAck(); + } + return; + } + + u32 seq = seg.seq; + std::span data = seg.payload; + if (seg.flags & Fin) { + peer_fin_seq_ = seg.seq + static_cast(seg.payload.size()); + } + if (SeqLt(seq, rcv_nxt_)) { + const u32 skip = rcv_nxt_ - seq; + data = skip >= data.size() ? std::span{} : data.subspan(skip); + seq = rcv_nxt_; + } + const u32 window = ReceiveWindow(); + const u32 room = seq - rcv_nxt_ < window ? rcv_nxt_ + window - seq : 0; + if (data.size() > room) { + data = data.first(room); + } + + bool ack_now = false; + if (!data.empty()) { + if (seq == rcv_nxt_) { + recv_buf_.insert(recv_buf_.end(), data.begin(), data.end()); + rcv_nxt_ += static_cast(data.size()); + rcv_offset_ += data.size(); + if (!out_of_order_.empty()) { + PullOutOfOrder(); + ack_now = true; // filled a hole + } + ++unacked_segments_; + } else { + const u64 offset = rcv_offset_ + (seq - rcv_nxt_); + if (out_of_order_bytes_ + data.size() <= config_.recv_buffer) { + auto& slot = out_of_order_[offset]; + if (slot.size() < data.size()) { + out_of_order_bytes_ += data.size() - slot.size(); + slot.assign(data.begin(), data.end()); + } + } + ack_now = true; // dup ACK for fast retransmit + } + } + + if (peer_fin_seq_ && *peer_fin_seq_ == rcv_nxt_ && !fin_received_) { + ++rcv_nxt_; + fin_received_ = true; + ack_now = true; + switch (state_) { + case TcpState::Established: + state_ = TcpState::CloseWait; + break; + case TcpState::FinWait1: + if (FinAcked()) { + EnterTimeWait(now); + } else { + state_ = TcpState::Closing; + } + break; + case TcpState::FinWait2: + EnterTimeWait(now); + break; + default: + break; + } + } + + if (ack_now || unacked_segments_ >= 2) { + SendAck(); + } else if (unacked_segments_ > 0 && !delayed_ack_deadline_) { + delayed_ack_deadline_ = now + config_.delayed_ack; + } +} + +void TcpConnection::PullOutOfOrder() { + while (!out_of_order_.empty()) { + auto it = out_of_order_.begin(); + if (it->first > rcv_offset_) { + return; + } + const u64 end = it->first + it->second.size(); + if (end > rcv_offset_) { + const auto skip = static_cast(rcv_offset_ - it->first); + recv_buf_.insert(recv_buf_.end(), it->second.begin() + skip, it->second.end()); + const u32 added = static_cast(end - rcv_offset_); + rcv_nxt_ += added; + rcv_offset_ += added; + } + out_of_order_bytes_ -= it->second.size(); + out_of_order_.erase(it); + } +} + +std::optional TcpConnection::NextDeadline() const { + std::optional next; + for (const auto& deadline : + {retransmit_deadline_, persist_deadline_, delayed_ack_deadline_, time_wait_deadline_}) { + if (deadline && (!next || *deadline < *next)) { + next = deadline; + } + } + return next; +} + +void TcpConnection::OnTimer(Clock::time_point now) { + if (time_wait_deadline_ && now >= *time_wait_deadline_) { + state_ = TcpState::Closed; + ClearTimers(); + return; + } + if (delayed_ack_deadline_ && now >= *delayed_ack_deadline_) { + SendAck(); + } + if (retransmit_deadline_ && now >= *retransmit_deadline_) { + retransmit_deadline_.reset(); + OnRetransmitTimeout(now); + } + if (persist_deadline_ && now >= *persist_deadline_) { + EmitSegment(snd_una_ - 1, Ack, {}); + persist_interval_ = std::min(persist_interval_ * 2, config_.max_rto); + persist_deadline_ = now + persist_interval_; + } +} + +void TcpConnection::OnRetransmitTimeout(Clock::time_point now) { + const bool handshake = state_ == TcpState::SynSent || state_ == TcpState::SynReceived; + if (++retries_ > (handshake ? config_.syn_retries : config_.data_retries)) { + if (Synchronized()) { + EmitReset(snd_max_); + } + Terminate(Error::TimedOut); + return; + } + ++retransmissions_; + rtt_active_ = false; + rto_ = std::min(rto_ * 2, config_.max_rto); + + if (state_ == TcpState::SynSent) { + EmitSegment(iss_, Syn, {}, true); + } else if (state_ == TcpState::SynReceived) { + EmitSegment(iss_, Syn | Ack, {}, true); + } else { + const u32 flight = snd_max_ - snd_una_; + ssthresh_ = std::max(flight / 2, 2u * snd_mss_); + cwnd_ = snd_mss_; + in_recovery_ = false; + dupacks_ = 0; + snd_nxt_ = snd_una_; + TrySend(now); + } + retransmit_deadline_ = now + rto_; +} + +void TcpConnection::SampleRtt(Clock::duration rtt) { + if (!have_rtt_) { + srtt_ = rtt; + rttvar_ = rtt / 2; + have_rtt_ = true; + } else { + const auto delta = srtt_ > rtt ? srtt_ - rtt : rtt - srtt_; + rttvar_ = (3 * rttvar_ + delta) / 4; + srtt_ = (7 * srtt_ + rtt) / 8; + } + const auto variance = std::max(4 * rttvar_, std::chrono::milliseconds(10)); + rto_ = std::clamp(srtt_ + variance, config_.min_rto, config_.max_rto); +} + +void TcpConnection::EnterTimeWait(Clock::time_point now) { + ClearTimers(); + state_ = TcpState::TimeWait; + time_wait_deadline_ = now + config_.time_wait; +} + +void TcpConnection::Terminate(Error error) { + error_ = error; + state_ = TcpState::Closed; + ClearTimers(); + send_buf_.clear(); + out_of_order_.clear(); + out_of_order_bytes_ = 0; + // Received data stays readable, like BSD. +} + +void TcpConnection::ClearTimers() { + retransmit_deadline_.reset(); + persist_deadline_.reset(); + delayed_ack_deadline_.reset(); + time_wait_deadline_.reset(); +} + +TcpConnection::IoResult TcpConnection::Send(std::span data, Clock::time_point now) { + if (error_ != Error::Ok) { + return {0, error_}; + } + switch (state_) { + case TcpState::SynSent: + case TcpState::SynReceived: + return {0, Error::WouldBlock}; + case TcpState::Established: + case TcpState::CloseWait: + break; + default: + return {0, fin_queued_ ? Error::Pipe : Error::NotConn}; + } + if (fin_queued_) { + return {0, Error::Pipe}; + } + const size_t n = std::min(SendSpace(), data.size()); + if (n == 0) { + return {0, data.empty() ? Error::Ok : Error::WouldBlock}; + } + send_buf_.insert(send_buf_.end(), data.begin(), data.begin() + n); + TrySend(now); + return {n, Error::Ok}; +} + +TcpConnection::IoResult TcpConnection::Recv(std::span out, bool peek, Clock::time_point) { + if (!recv_buf_.empty()) { + const size_t n = std::min(out.size(), recv_buf_.size()); + std::copy_n(recv_buf_.begin(), n, out.begin()); + if (!peek) { + recv_buf_.erase(recv_buf_.begin(), recv_buf_.begin() + n); + MaybeSendWindowUpdate(); + } + return {n, Error::Ok}; + } + if (fin_received_) { + return {0, Error::Ok}; // EOF + } + if (error_ != Error::Ok) { + return {0, error_}; + } + if (state_ == TcpState::Closed) { + return {0, Error::NotConn}; + } + return {0, Error::WouldBlock}; +} + +void TcpConnection::MaybeSendWindowUpdate() { + if (!Synchronized() || fin_received_) { + return; + } + // Only update once the window opened enough to avoid silly window syndrome. + const u32 threshold = std::min(config_.recv_buffer / 2, 2u * snd_mss_); + if (static_cast(ReceiveWindow()) - static_cast(last_advertised_window_) >= + static_cast(threshold)) { + SendAck(); + } +} + +void TcpConnection::Shutdown(Clock::time_point now) { + if (fin_queued_) { + return; + } + switch (state_) { + case TcpState::SynSent: + Terminate(Error::Ok); + return; + case TcpState::SynReceived: + Abort(); // not accepted yet + return; + case TcpState::Established: + state_ = TcpState::FinWait1; + break; + case TcpState::CloseWait: + state_ = TcpState::LastAck; + break; + default: + return; + } + fin_queued_ = true; + TrySend(now); +} + +void TcpConnection::Close(Clock::time_point now) { + if (state_ == TcpState::SynSent || (Synchronized() && !recv_buf_.empty())) { + Abort(); // BSD resets on close with unread data + return; + } + Shutdown(now); +} + +void TcpConnection::Abort() { + if (state_ != TcpState::Closed && state_ != TcpState::SynSent && state_ != TcpState::TimeWait) { + EmitReset(snd_max_); + } + Terminate(Error::Ok); +} + +} // namespace Core::Net::P2P diff --git a/src/core/net/p2p_tcp.h b/src/core/net/p2p_tcp.h new file mode 100644 index 000000000..1d2b9fd67 --- /dev/null +++ b/src/core/net/p2p_tcp.h @@ -0,0 +1,233 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/net/host_net.h" + +namespace Core::Net::P2P { + +using Clock = std::chrono::steady_clock; +using Host::Error; + +// Sequence-number comparisons modulo 2^32. +constexpr bool SeqLt(u32 a, u32 b) { + return static_cast(a - b) < 0; +} +constexpr bool SeqLe(u32 a, u32 b) { + return static_cast(a - b) <= 0; +} +constexpr bool SeqGt(u32 a, u32 b) { + return SeqLt(b, a); +} +constexpr bool SeqGe(u32 a, u32 b) { + return SeqLe(b, a); +} + +namespace TcpFlag { +constexpr u8 Fin = 0x01; +constexpr u8 Syn = 0x02; +constexpr u8 Rst = 0x04; +constexpr u8 Psh = 0x08; +constexpr u8 Ack = 0x10; +} // namespace TcpFlag + +struct PseudoHeader { + int family = AF_INET; + std::array local{}; // IPv4 uses first 4 bytes + std::array remote{}; +}; + +struct TcpSegment { + u16 src_port; // host byte order + u16 dst_port; + u32 seq; + u32 ack; + u8 flags; + u16 window; + std::optional mss; + std::span payload; +}; + +u16 TcpChecksum(int family, const u8* src_addr, const u8* dst_addr, std::span segment); +std::optional ParseTcpSegment(std::span bytes, + const PseudoHeader* pseudo = nullptr); + +std::vector BuildTcpSegment(const PseudoHeader& pseudo, u16 src_port, u16 dst_port, u32 seq, + u32 ack, u8 flags, u16 window, std::optional mss, + std::span payload); +std::optional> BuildResetFor(const PseudoHeader& pseudo, const TcpSegment& seg); + +struct TcpConfig { + // PS4 defaults for TCP over UDPP2P + u32 recv_buffer = 65535; + u32 send_buffer = 32 * 1024; + u16 mss = 1200; // fits one P2P datagram after overhead + std::chrono::milliseconds initial_rto{1000}; + std::chrono::milliseconds min_rto{200}; + std::chrono::milliseconds max_rto{60000}; + std::chrono::milliseconds delayed_ack{40}; + std::chrono::milliseconds time_wait{2000}; // TODO: FreeBSD 2MSL is 60 s + int syn_retries = 6; + int data_retries = 12; +}; + +enum class TcpState { + Closed, + SynSent, + SynReceived, + Established, + FinWait1, + FinWait2, + CloseWait, + Closing, + LastAck, + TimeWait, +}; + +const char* ToString(TcpState state); + +class TcpConnection { +public: + using Emit = std::function segment)>; + + struct IoResult { + size_t bytes; + Error error; + }; + + TcpConnection(const TcpConfig& config, const PseudoHeader& pseudo, u16 local_port, + u16 remote_port, u32 iss, Emit emit); + + void Connect(Clock::time_point now); + void AcceptSyn(const TcpSegment& syn, Clock::time_point now); + void OnSegment(const TcpSegment& seg, Clock::time_point now); + void OnTimer(Clock::time_point now); + std::optional NextDeadline() const; + + IoResult Send(std::span data, Clock::time_point now); + IoResult Recv(std::span out, bool peek, Clock::time_point now); + void Shutdown(Clock::time_point now); + void Close(Clock::time_point now); + void Abort(); + + TcpState State() const { + return state_; + } + Error PendingError() const { + return error_; + } + bool Readable() const; + bool Writable() const; + bool ReceivedFin() const { + return fin_received_; + } + size_t SendSpace() const; + size_t BytesToRead() const { + return recv_buf_.size(); + } + u16 LocalPort() const { + return local_port_; + } + u16 RemotePort() const { + return remote_port_; + } + + u32 Retransmissions() const { + return retransmissions_; + } + +private: + bool Synchronized() const; + u16 ReceiveWindow() const; + bool FinSent() const; + bool FinAcked() const; + u32 FinSeq() const; + + void EmitSegment(u32 seq, u8 flags, std::span payload, bool with_mss = false); + void SendAck(); + void EmitReset(u32 seq); + void SendData(u32 seq, u32 len); + void TrySend(Clock::time_point now); + void RetransmitFirst(); + + bool Acceptable(u32 seq, u32 len) const; + void HandleSynSent(const TcpSegment& seg, Clock::time_point now); + bool ProcessAck(const TcpSegment& seg, Clock::time_point now); + void OnNewAck(u32 ack, Clock::time_point now); + void OnDuplicateAck(); + void ProcessData(const TcpSegment& seg, Clock::time_point now); + void PullOutOfOrder(); + void MaybeSendWindowUpdate(); + + void OnRetransmitTimeout(Clock::time_point now); + void SampleRtt(Clock::duration rtt); + void ArmRetransmit(Clock::time_point now); + void EnterTimeWait(Clock::time_point now); + void Terminate(Error error); + void ClearTimers(); + + const TcpConfig config_; + const PseudoHeader pseudo_; + const u16 local_port_; + const u16 remote_port_; + Emit emit_; + + TcpState state_ = TcpState::Closed; + Error error_ = Error::Ok; + + u32 iss_; + u32 snd_una_; + u32 snd_nxt_; + u32 snd_max_; + u32 buf_seq_; + u32 snd_wnd_ = 0; + u32 snd_wl1_ = 0; + u32 snd_wl2_ = 0; + u16 snd_mss_ = 536; + std::deque send_buf_; + bool fin_queued_ = false; + + u32 cwnd_ = 0; + u32 ssthresh_ = 0x7fffffff; + int dupacks_ = 0; + bool in_recovery_ = false; + u32 recover_ = 0; + Clock::duration srtt_{}; + Clock::duration rttvar_{}; + Clock::duration rto_; + bool have_rtt_ = false; + bool rtt_active_ = false; + u32 rtt_seq_ = 0; + Clock::time_point rtt_start_{}; + int retries_ = 0; + Clock::duration persist_interval_{}; + + std::optional retransmit_deadline_; + std::optional persist_deadline_; + std::optional delayed_ack_deadline_; + std::optional time_wait_deadline_; + u32 irs_ = 0; + u32 rcv_nxt_ = 0; + u64 rcv_offset_ = 0; // stream offset of rcv_nxt_ + std::deque recv_buf_; + std::map> out_of_order_; + size_t out_of_order_bytes_ = 0; + std::optional peer_fin_seq_; + bool fin_received_ = false; + int unacked_segments_ = 0; + u16 last_advertised_window_ = 0; + + u32 retransmissions_ = 0; +}; + +} // namespace Core::Net::P2P diff --git a/src/core/net/p2p_transport.cpp b/src/core/net/p2p_transport.cpp new file mode 100644 index 000000000..a8134dcb1 --- /dev/null +++ b/src/core/net/p2p_transport.cpp @@ -0,0 +1,866 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "core/net/p2p_transport.h" + +#ifndef _WIN32 +#include +#include +#endif + +namespace Core::Net::P2P { + +struct DatagramBinding { + struct Packet { + std::vector payload; + Endpoint from; + u16 src_vport; + }; + u16 vport = 0; + std::shared_ptr readiness; + std::optional> peer; // set by SetPeer + std::deque queue; + size_t bytes = 0; +}; + +struct Connection { + ConnectionKey key; + Protection protection; + std::unique_ptr tcp; + std::shared_ptr readiness; + std::weak_ptr listener; // during handshake + bool pending_accept = false; // not in accept queue yet + bool released = true; // no socket owns it +}; + +struct ListenerState { + u16 vport = 0; + int backlog = 1; + Protection protection; + std::shared_ptr readiness; + std::deque> accept_queue; + int half_open = 0; + bool closed = false; +}; + +namespace { + +auto Canonical(const Endpoint& e) { + std::array address{}; + u16 port = 0; + u32 scope = 0; + if (e.Family() == AF_INET) { + const auto* in = reinterpret_cast(&e.addr); + std::memcpy(address.data(), &in->sin_addr, 4); + port = in->sin_port; + } else if (e.Family() == AF_INET6) { + const auto* in6 = reinterpret_cast(&e.addr); + std::memcpy(address.data(), &in6->sin6_addr, 16); + port = in6->sin6_port; + scope = in6->sin6_scope_id; + } + return std::tuple{e.Family(), address, port, scope}; +} + +} // namespace + +Endpoint Endpoint::FromSockaddr(const sockaddr* sa, socklen_t len) { + Endpoint e; + std::memcpy(&e.addr, sa, std::min(static_cast(len), sizeof(e.addr))); + return e; +} + +Endpoint Endpoint::IPv4(const char* dotted, u16 port) { + Endpoint e; + auto* in = reinterpret_cast(&e.addr); + in->sin_family = AF_INET; + in->sin_port = htons(port); + inet_pton(AF_INET, dotted, &in->sin_addr); + return e; +} + +socklen_t Endpoint::Length() const { + return Family() == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); +} + +u16 Endpoint::Port() const { + return ntohs(std::get<2>(Canonical(*this))); +} + +bool Endpoint::operator<(const Endpoint& other) const { + return Canonical(*this) < Canonical(other); +} + +bool Endpoint::operator==(const Endpoint& other) const { + return Canonical(*this) == Canonical(other); +} + +bool ConnectionKey::operator<(const ConnectionKey& other) const { + if (!(peer == other.peer)) { + return peer < other.peer; + } + return std::tie(peer_vport, local_vport) < std::tie(other.peer_vport, other.local_vport); +} + +void ReadinessFlag::Set(bool ready) { + if (ready == ready_) { + return; + } + ready_ = ready; + if (ready) { + wake_.Signal(); + } else { + wake_.Drain(); + } +} + +Transport::Transport(int family, std::unique_ptr codec, const TransportConfig& config) + : family_(family), codec_(std::move(codec)), config_(config), + signaling_(std::max(config.signaling_channels, 1)), + iss_state_(static_cast(Clock::now().time_since_epoch().count()) ^ + reinterpret_cast(this)), + receive_buffer_(65536) {} + +std::shared_ptr Transport::Create(int family, const Endpoint& bind_address, + std::unique_ptr codec, + const TransportConfig& config, Error* error) { + std::shared_ptr t(new Transport(family, std::move(codec), config)); + if (!t->kick_.Valid()) { + *error = Error::NoBufs; + return nullptr; + } + t->socket_ = Host::CreateSocket(family, SOCK_DGRAM, 0, error); + if (t->socket_ == Host::InvalidSocket) { + return nullptr; + } + // Exclusive port. Nobody else should get the port we advertise. + if ((*error = Host::PrepareBind(t->socket_, false, false)) != Error::Ok || + (*error = Host::Bind(t->socket_, bind_address.Sockaddr(), bind_address.Length())) != + Error::Ok) { + return nullptr; + } + const int buffer = 1 << 20; // absorb bursts + setsockopt(t->socket_, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&buffer), + sizeof(buffer)); + sockaddr_storage actual{}; + socklen_t actual_len = sizeof(actual); + getsockname(t->socket_, reinterpret_cast(&actual), &actual_len); + t->bound_port_ = + Endpoint::FromSockaddr(reinterpret_cast(&actual), actual_len).Port(); + t->thread_ = std::thread([raw = t.get()] { raw->ThreadMain(); }); + *error = Error::Ok; + return t; +} + +Transport::~Transport() { + stop_.store(true); + kick_.Signal(); + if (thread_.joinable()) { + thread_.join(); + } + Host::CloseSocket(socket_); // thread is stopped by now +} + +void Transport::SetOutgoingLossForTesting(double probability) { + std::scoped_lock lock{mutex_}; + loss_for_testing_ = probability; +} + +std::shared_ptr Transport::CreateDatagram() { + return std::shared_ptr(new DatagramSocket(shared_from_this())); +} + +std::shared_ptr Transport::CreateStream() { + return std::shared_ptr(new StreamSocket(shared_from_this(), nullptr)); +} + +Error Transport::SendSignaling(std::span data, const Endpoint& to) { + std::scoped_lock lock{mutex_}; + return SendPacket(codec_->EncodeSignaling(data, to), to); +} + +bool Transport::RecvSignaling(size_t channel, std::vector* data, Endpoint* from) { + std::scoped_lock lock{mutex_}; + if (channel >= signaling_.size() || signaling_[channel].empty()) { + return false; + } + auto& queue = signaling_[channel]; + *data = std::move(queue.front().first); + *from = queue.front().second; + queue.pop_front(); + return true; +} + +void Transport::ThreadMain() { + while (!stop_.load()) { + std::optional deadline; + { + std::scoped_lock lock{mutex_}; + deadline = EarliestDeadline(); + sleeping_until_ = deadline.value_or(Clock::time_point::max()); + } + const auto result = Host::WaitOne(socket_, Host::Readable, kick_, deadline); + if (stop_.load()) { + return; + } + if (result == Host::WaitResult::Woken) { + kick_.Drain(); + } + + std::scoped_lock lock{mutex_}; + sleeping_until_ = Clock::time_point::min(); + const auto now = Clock::now(); + for (int i = 0; i < 1024; ++i) { // don't starve timers + sockaddr_storage from{}; + socklen_t from_len = sizeof(from); + const auto r = Host::RecvFrom(socket_, receive_buffer_.data(), receive_buffer_.size(), + 0, reinterpret_cast(&from), &from_len); + if (r.error != Error::Ok) { + break; + } + HandlePacket({receive_buffer_.data(), static_cast(r.value)}, + Endpoint::FromSockaddr(reinterpret_cast(&from), from_len), now); + } + RunTimers(now); + } +} + +std::optional Transport::EarliestDeadline() const { + std::optional earliest; + for (const auto& [_, connection] : connections_) { + const auto d = connection->tcp->NextDeadline(); + if (d && (!earliest || *d < *earliest)) { + earliest = d; + } + } + return earliest; +} + +void Transport::RunTimers(Clock::time_point now) { + std::vector> due; + for (const auto& [_, connection] : connections_) { + if (const auto d = connection->tcp->NextDeadline(); d && *d <= now) { + due.push_back(connection); + } + } + for (const auto& connection : due) { + connection->tcp->OnTimer(now); + Update(connection); + } +} + +void Transport::Reschedule(const Connection& connection) { + if (const auto d = connection.tcp->NextDeadline(); d && *d < sleeping_until_) { + sleeping_until_ = *d; + kick_.Signal(); + } +} + +Error Transport::SendPacket(std::span packet, const Endpoint& to) { + if (loss_for_testing_ > 0) { + loss_rng_ ^= loss_rng_ << 13; + loss_rng_ ^= loss_rng_ >> 7; + loss_rng_ ^= loss_rng_ << 17; + if (static_cast(loss_rng_ >> 11) / static_cast(1ull << 53) < + loss_for_testing_) { + return Error::Ok; + } + } + const auto r = + Host::SendTo(socket_, packet.data(), packet.size(), 0, to.Sockaddr(), to.Length()); + return r.error; +} + +u32 Transport::NewIss() { + iss_state_ += 0x9e3779b97f4a7c15ull; + u64 z = iss_state_; + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ull; + z = (z ^ (z >> 27)) * 0x94d049bb133111ebull; + z ^= z >> 31; + const auto clock = + std::chrono::duration_cast(Clock::now().time_since_epoch()) + .count() / + 4; + return static_cast(z) + static_cast(clock); +} + +u16 Transport::AllocateVport(bool stream) { + const u16 first = stream ? 49152 : 32768; + const u16 last = stream ? 65535 : 49999; + u16& next = stream ? next_stream_port_ : next_datagram_vport_; + for (int attempt = 0; attempt <= last - first; ++attempt) { + const u16 vport = next; + next = next == last ? first : static_cast(next + 1); + const bool used = + stream ? (stream_vports_in_use_.contains(vport) || listeners_.contains(vport)) + : datagram_bindings_.contains(vport); + if (!used) { + return vport; + } + } + return 0; +} + +void Transport::HandlePacket(std::span packet, const Endpoint& from, + Clock::time_point now) { + Codec::Decoded decoded = codec_->Decode(packet, from); + switch (decoded.kind) { + case Codec::Kind::Stream: + HandleStreamSegment(decoded.payload, from, now); + break; + case Codec::Kind::Datagram: + HandleDatagram(decoded, from); + break; + case Codec::Kind::Signaling: { + const size_t channel = + config_.classify_signaling ? config_.classify_signaling(decoded.payload) : 0; + if (channel < signaling_.size() && + signaling_[channel].size() < config_.signaling_queue_packets) { + signaling_[channel].emplace_back(std::move(decoded.payload), from); + } + break; + } + case Codec::Kind::Invalid: + break; + } +} + +void Transport::HandleDatagram(Codec::Decoded& decoded, const Endpoint& from) { + const auto it = datagram_bindings_.find(decoded.dst_vport); + if (it == datagram_bindings_.end()) { + return; + } + DatagramBinding& binding = *it->second; + if (binding.peer && + !(binding.peer->first == from && binding.peer->second == decoded.src_vport)) { + return; // not from the connected peer + } + if (binding.bytes + decoded.payload.size() > config_.datagram_queue_bytes) { + return; // queue full, drop like UDP + } + binding.bytes += decoded.payload.size(); + binding.queue.push_back({std::move(decoded.payload), from, decoded.src_vport}); + binding.readiness->readable.Set(true); +} + +void Transport::HandleStreamSegment(std::span bytes, const Endpoint& from, + Clock::time_point now) { + std::optional pseudo; + if (config_.verify_stream_checksum) { + pseudo = codec_->StreamPseudoHeader(from); + } + const auto seg = ParseTcpSegment(bytes, pseudo ? &*pseudo : nullptr); + if (!seg) { + return; + } + const ConnectionKey key{from, seg->src_port, seg->dst_port}; + if (const auto it = connections_.find(key); it != connections_.end()) { + const auto connection = it->second; + if (connection->tcp->State() != TcpState::Closed) { + connection->tcp->OnSegment(*seg, now); + Update(connection); + return; + } + if (connection->released) { + connections_.erase(it); + } + } + + const bool bare_syn = + (seg->flags & (TcpFlag::Syn | TcpFlag::Ack | TcpFlag::Rst)) == TcpFlag::Syn; + if (bare_syn && !connections_.contains(key)) { + const auto lit = listeners_.find(seg->dst_port); + if (lit != listeners_.end()) { + const auto listener = lit->second; + if (static_cast(listener->accept_queue.size()) + listener->half_open >= + listener->backlog) { + return; // backlog full, peer will retry + } + const auto connection = + NewConnection(key, listener->protection, std::make_shared()); + connection->listener = listener; + connection->pending_accept = true; + ++listener->half_open; + connection->tcp->AcceptSyn(*seg, now); + Update(connection); + return; + } + } + // Nobody listening, RST so the peer fails fast. + if (!pseudo) { + pseudo = codec_->StreamPseudoHeader(from); + } + if (const auto rst = BuildResetFor(*pseudo, *seg)) { + SendPacket(codec_->EncodeStream(*rst, from, {}), from); + } +} + +std::shared_ptr Transport::NewConnection(const ConnectionKey& key, + Protection protection, + std::shared_ptr readiness) { + auto connection = std::make_shared(); + connection->key = key; + connection->protection = protection; + connection->readiness = std::move(readiness); + const Endpoint peer = key.peer; + connection->tcp = std::make_unique( + config_.tcp, codec_->StreamPseudoHeader(peer), key.local_vport, key.peer_vport, NewIss(), + [this, peer, protection](std::span segment) { + SendPacket(codec_->EncodeStream(segment, peer, protection), peer); + }); + connections_[key] = connection; + return connection; +} + +void Transport::Update(const std::shared_ptr& connection) { + TcpConnection& tcp = *connection->tcp; + if (connection->pending_accept) { + const auto listener = connection->listener.lock(); + const TcpState state = tcp.State(); + if (!listener || listener->closed) { + connection->pending_accept = false; + tcp.Abort(); // listener is gone + } else if (state != TcpState::SynReceived) { + connection->pending_accept = false; + --listener->half_open; + connection->listener.reset(); + if (state != TcpState::Closed) { + listener->accept_queue.push_back(connection); + listener->readiness->readable.Set(true); + } + } + } + connection->readiness->readable.Set(tcp.Readable()); + connection->readiness->writable.Set(tcp.Writable()); + if (tcp.State() == TcpState::Closed && connection->released) { + const auto it = connections_.find(connection->key); + if (it != connections_.end() && it->second == connection) { + connections_.erase(it); + } + } +} + +DatagramSocket::DatagramSocket(std::shared_ptr transport) + : transport_(std::move(transport)), readiness_(std::make_shared()) { + readiness_->writable.Set(true); // UDP is always writable +} + +DatagramSocket::~DatagramSocket() { + Close(); +} + +Error DatagramSocket::Bind(u16 vport) { + std::scoped_lock lock{transport_->mutex_}; + return BindLocked(vport); +} + +Error DatagramSocket::BindLocked(u16 vport) { + if (binding_) { + return Error::Inval; + } + if (vport == 0 && (vport = transport_->AllocateVport(false)) == 0) { + return Error::AddrNotAvail; + } + if (transport_->datagram_bindings_.contains(vport)) { + return Error::AddrInUse; + } + binding_ = std::make_shared(); + binding_->vport = vport; + binding_->readiness = readiness_; + binding_->peer = peer_; + transport_->datagram_bindings_[vport] = binding_; + return Error::Ok; +} + +Error DatagramSocket::SetPeer(const Endpoint& peer, u16 vport) { + std::scoped_lock lock{transport_->mutex_}; + if (vport == 0) { + return Error::AddrNotAvail; + } + peer_ = std::pair{peer, vport}; + if (binding_) { + binding_->peer = peer_; + } + return Error::Ok; +} + +void DatagramSocket::SetProtection(Protection protection) { + std::scoped_lock lock{transport_->mutex_}; + protection_ = protection; +} + +IoResult DatagramSocket::SendTo(std::span data, const Endpoint* to, u16 to_vport, + Protection extra) { + std::scoped_lock lock{transport_->mutex_}; + if (!binding_ && BindLocked(0) != Error::Ok) { // implicit bind + return {0, Error::AddrNotAvail}; + } + if (to == nullptr) { + if (!peer_) { + return {0, Error::DestAddrReq}; + } + to = &peer_->first; + to_vport = peer_->second; + } + if (to_vport == 0) { + return {0, Error::AddrNotAvail}; + } + const Protection protection{protection_.crypto || extra.crypto, + protection_.signature || extra.signature}; + const auto packet = + transport_->codec_->EncodeDatagram(binding_->vport, to_vport, data, *to, protection); + const Error e = transport_->SendPacket(packet, *to); + if (e == Error::WouldBlock || e == Error::NoBufs) { + // PS4 drops instead of returning ENOBUFS + return {data.size(), Error::Ok}; + } + return e == Error::Ok ? IoResult{data.size(), Error::Ok} : IoResult{0, e}; +} + +IoResult DatagramSocket::RecvFrom(std::span out, bool peek, Endpoint* from, u16* from_vport) { + std::scoped_lock lock{transport_->mutex_}; + if (!binding_ || binding_->queue.empty()) { + return {0, Error::WouldBlock}; + } + auto& packet = binding_->queue.front(); + const size_t n = std::min(out.size(), packet.payload.size()); // rest is discarded + std::copy_n(packet.payload.begin(), n, out.begin()); + if (from) { + *from = packet.from; + } + if (from_vport) { + *from_vport = packet.src_vport; + } + if (!peek) { + binding_->bytes -= packet.payload.size(); + binding_->queue.pop_front(); + if (binding_->queue.empty()) { + readiness_->readable.Set(false); + } + } + return {n, Error::Ok}; +} + +u32 DatagramSocket::Events() const { + std::scoped_lock lock{transport_->mutex_}; + return Host::EvOut | (binding_ && !binding_->queue.empty() ? static_cast(Host::EvIn) : 0u); +} + +u16 DatagramSocket::BoundVport() const { + std::scoped_lock lock{transport_->mutex_}; + return binding_ ? binding_->vport : 0; +} + +u16 DatagramSocket::TransportPort() const { + return transport_->BoundPort(); +} + +bool DatagramSocket::GetPeer(Endpoint* peer, u16* peer_vport) const { + std::scoped_lock lock{transport_->mutex_}; + if (!peer_) { + return false; + } + *peer = peer_->first; + *peer_vport = peer_->second; + return true; +} + +Protection DatagramSocket::GetProtection() const { + std::scoped_lock lock{transport_->mutex_}; + return protection_; +} + +void DatagramSocket::Close() { + std::scoped_lock lock{transport_->mutex_}; + if (!binding_) { + return; + } + const auto it = transport_->datagram_bindings_.find(binding_->vport); + if (it != transport_->datagram_bindings_.end() && it->second == binding_) { + transport_->datagram_bindings_.erase(it); + } + binding_.reset(); +} + +StreamSocket::StreamSocket(std::shared_ptr transport, + std::shared_ptr readiness) + : transport_(std::move(transport)), + readiness_(readiness ? std::move(readiness) : std::make_shared()) {} + +StreamSocket::~StreamSocket() { + Close(); +} + +void StreamSocket::SetProtection(Protection protection) { + std::scoped_lock lock{transport_->mutex_}; + protection_ = protection; +} + +Error StreamSocket::Bind(u16 vport) { + std::scoped_lock lock{transport_->mutex_}; + return BindLocked(vport); +} + +Error StreamSocket::BindLocked(u16 vport) { + if (bound_vport_ != 0 || closed_) { + return Error::Inval; + } + if (vport == 0 && (vport = transport_->AllocateVport(true)) == 0) { + return Error::AddrNotAvail; + } + if (transport_->stream_vports_in_use_.contains(vport)) { + return Error::AddrInUse; + } + ++transport_->stream_vports_in_use_[vport]; + bound_vport_ = vport; + owns_vport_ = true; + return Error::Ok; +} + +Error StreamSocket::Listen(int backlog) { + std::scoped_lock lock{transport_->mutex_}; + if (connection_ || closed_) { + return Error::Inval; + } + if (bound_vport_ == 0 && BindLocked(0) != Error::Ok) { + return Error::AddrNotAvail; + } + const int clamped = std::clamp(backlog, 1, transport_->config_.max_backlog); + if (listener_) { + listener_->backlog = clamped; + return Error::Ok; + } + listener_ = std::make_shared(); + listener_->vport = bound_vport_; + listener_->backlog = clamped; + listener_->protection = protection_; + listener_->readiness = readiness_; + transport_->listeners_[bound_vport_] = listener_; + return Error::Ok; +} + +Error StreamSocket::Connect(const Endpoint& peer, u16 peer_vport) { + std::scoped_lock lock{transport_->mutex_}; + if (listener_ || closed_) { + return Error::Inval; + } + if (connection_) { + const TcpState state = connection_->tcp->State(); + return state == TcpState::SynSent || state == TcpState::SynReceived ? Error::Already + : Error::IsConn; + } + if (peer_vport == 0) { + return Error::AddrNotAvail; + } + if (bound_vport_ == 0 && BindLocked(0) != Error::Ok) { + return Error::AddrNotAvail; + } + const ConnectionKey key{peer, peer_vport, bound_vport_}; + if (transport_->connections_.contains(key)) { + return Error::AddrInUse; // e.g. old connection in TIME-WAIT + } + connection_ = transport_->NewConnection(key, protection_, readiness_); + connection_->released = false; + connection_->tcp->Connect(Clock::now()); + transport_->Update(connection_); + transport_->Reschedule(*connection_); + return Error::InProgress; +} + +Error StreamSocket::ConnectResult() const { + std::scoped_lock lock{transport_->mutex_}; + if (!connection_) { + return Error::NotConn; + } + const TcpConnection& tcp = *connection_->tcp; + if (tcp.PendingError() != Error::Ok) { + return tcp.PendingError(); + } + switch (tcp.State()) { + case TcpState::SynSent: + case TcpState::SynReceived: + return Error::InProgress; + case TcpState::Closed: + return Error::NotConn; + default: + return Error::Ok; + } +} + +std::shared_ptr StreamSocket::Accept(Endpoint* peer, u16* peer_vport, Error* error) { + std::scoped_lock lock{transport_->mutex_}; + if (!listener_) { + *error = Error::Inval; + return nullptr; + } + if (listener_->accept_queue.empty()) { + *error = Error::WouldBlock; + return nullptr; + } + auto connection = std::move(listener_->accept_queue.front()); + listener_->accept_queue.pop_front(); + if (listener_->accept_queue.empty()) { + readiness_->readable.Set(false); + } + connection->released = false; + std::shared_ptr accepted(new StreamSocket(transport_, connection->readiness)); + accepted->protection_ = connection->protection; + accepted->bound_vport_ = connection->key.local_vport; // listener owns the vport + accepted->connection_ = connection; + if (peer) { + *peer = connection->key.peer; + } + if (peer_vport) { + *peer_vport = connection->key.peer_vport; + } + transport_->Update(connection); + *error = Error::Ok; + return accepted; +} + +IoResult StreamSocket::Send(std::span data) { + std::scoped_lock lock{transport_->mutex_}; + if (!connection_) { + return {0, Error::NotConn}; + } + const auto r = connection_->tcp->Send(data, Clock::now()); + transport_->Update(connection_); + transport_->Reschedule(*connection_); + return {r.bytes, r.error}; +} + +IoResult StreamSocket::Recv(std::span out, bool peek) { + std::scoped_lock lock{transport_->mutex_}; + if (!connection_) { + return {0, Error::NotConn}; + } + if (read_shutdown_) { + return {0, Error::Ok}; + } + const auto r = connection_->tcp->Recv(out, peek, Clock::now()); + transport_->Update(connection_); + return {r.bytes, r.error}; +} + +Error StreamSocket::Shutdown(int how) { + std::scoped_lock lock{transport_->mutex_}; + if (!connection_) { + return Error::NotConn; + } + if (how < 0 || how > 2) { + return Error::Inval; + } + if (how != 1) { + read_shutdown_ = true; + readiness_->readable.Set(true); // reads return EOF now + } + if (how != 0) { + connection_->tcp->Shutdown(Clock::now()); + transport_->Update(connection_); + transport_->Reschedule(*connection_); + } + return Error::Ok; +} + +void StreamSocket::Close() { + std::scoped_lock lock{transport_->mutex_}; + if (closed_) { + return; + } + closed_ = true; + if (listener_) { + listener_->closed = true; + const auto it = transport_->listeners_.find(listener_->vport); + if (it != transport_->listeners_.end() && it->second == listener_) { + transport_->listeners_.erase(it); + } + for (const auto& pending : listener_->accept_queue) { + pending->tcp->Abort(); // reset unaccepted, like BSD + transport_->Update(pending); + } + listener_->accept_queue.clear(); + listener_.reset(); + } + if (connection_) { + connection_->released = true; + connection_->tcp->Close(Clock::now()); // FIN, or RST if data is unread + transport_->Update(connection_); + transport_->Reschedule(*connection_); + connection_.reset(); + } + if (owns_vport_) { + auto& count = transport_->stream_vports_in_use_[bound_vport_]; + if (--count <= 0) { + transport_->stream_vports_in_use_.erase(bound_vport_); + } + owns_vport_ = false; + } +} + +void StreamSocket::Abort() { + std::scoped_lock lock{transport_->mutex_}; + if (connection_) { + connection_->tcp->Abort(); + transport_->Update(connection_); + } +} + +u32 StreamSocket::Events() const { + std::scoped_lock lock{transport_->mutex_}; + if (listener_) { + return listener_->accept_queue.empty() ? 0u : static_cast(Host::EvIn); + } + if (!connection_) { + return 0; + } + const TcpConnection& tcp = *connection_->tcp; + u32 events = 0; + if (tcp.Readable() || read_shutdown_) { + events |= Host::EvIn; + } + if (tcp.Writable()) { + events |= Host::EvOut; + } + if (tcp.PendingError() != Error::Ok) { + events |= Host::EvErr; + } + if (tcp.State() == TcpState::Closed || tcp.State() == TcpState::TimeWait || tcp.ReceivedFin()) { + events |= Host::EvHup; // TODO: peer FIN alone is RDHUP on Linux, not HUP + } + return events; +} + +std::optional StreamSocket::State() const { + std::scoped_lock lock{transport_->mutex_}; + return connection_ ? std::optional{connection_->tcp->State()} : std::nullopt; +} + +bool StreamSocket::GetPeer(Endpoint* peer, u16* peer_vport) const { + std::scoped_lock lock{transport_->mutex_}; + if (!connection_) { + return false; + } + *peer = connection_->key.peer; + *peer_vport = connection_->key.peer_vport; + return true; +} + +u16 StreamSocket::BoundVport() const { + std::scoped_lock lock{transport_->mutex_}; + return bound_vport_; +} + +u16 StreamSocket::TransportPort() const { + return transport_->BoundPort(); +} + +Protection StreamSocket::GetProtection() const { + std::scoped_lock lock{transport_->mutex_}; + return protection_; +} + +} // namespace Core::Net::P2P diff --git a/src/core/net/p2p_transport.h b/src/core/net/p2p_transport.h new file mode 100644 index 000000000..084d05b28 --- /dev/null +++ b/src/core/net/p2p_transport.h @@ -0,0 +1,256 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// One UDP socket shared by P2P datagrams, P2P TCP streams and signaling. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "core/net/p2p_tcp.h" + +namespace Core::Net::P2P { + +struct Endpoint { + sockaddr_storage addr{}; + + static Endpoint FromSockaddr(const sockaddr* sa, socklen_t len); + static Endpoint IPv4(const char* dotted, u16 port); + int Family() const { + return addr.ss_family; + } + socklen_t Length() const; + const sockaddr* Sockaddr() const { + return reinterpret_cast(&addr); + } + u16 Port() const; // host byte order + bool operator<(const Endpoint& other) const; + bool operator==(const Endpoint& other) const; +}; + +struct Protection { + bool crypto = false; + bool signature = false; +}; + +class Codec { +public: + enum class Kind { Invalid, Stream, Datagram, Signaling }; + struct Decoded { + Kind kind = Kind::Invalid; + u16 src_vport = 0; // datagrams only + u16 dst_vport = 0; + std::vector payload; + }; + + virtual ~Codec() = default; + virtual Decoded Decode(std::span packet, const Endpoint& from) = 0; + virtual std::vector EncodeStream(std::span tcp_segment, const Endpoint& to, + Protection protection) = 0; + virtual std::vector EncodeDatagram(u16 src_vport, u16 dst_vport, + std::span payload, const Endpoint& to, + Protection protection) = 0; + virtual std::vector EncodeSignaling(std::span data, const Endpoint& to) = 0; + virtual PseudoHeader StreamPseudoHeader(const Endpoint& peer) = 0; +}; + +struct TransportConfig { + TcpConfig tcp; + size_t datagram_queue_bytes = 40 * 1024; // per vport, PS4's SO_RCVBUF + size_t signaling_queue_packets = 256; // per channel + size_t signaling_channels = 1; + std::function)> classify_signaling; + int max_backlog = 64; + // TODO: NAT changes the addresses in the pseudo-header, so peer checksums may not + // match. Off until tested. + bool verify_stream_checksum = false; +}; + +class ReadinessFlag { +public: + bool Valid() const { + return wake_.Valid(); + } + void Set(bool ready); + Host::NativeSocket PollHandle() const { + return wake_.PollHandle(); + } + +private: + Host::WakeHandle wake_; + bool ready_ = false; +}; + +struct Readiness { + ReadinessFlag readable; // data, EOF, error or accept + ReadinessFlag writable; // space or error +}; + +struct IoResult { + size_t bytes; + Error error; +}; + +struct ConnectionKey { + Endpoint peer; + u16 peer_vport; + u16 local_vport; + bool operator<(const ConnectionKey& other) const; +}; + +class Transport; +struct DatagramBinding; +struct Connection; +struct ListenerState; + +class DatagramSocket { +public: + ~DatagramSocket(); + + Error Bind(u16 vport); // 0 = any free vport + Error SetPeer(const Endpoint& peer, u16 vport); + void SetProtection(Protection protection); + // to == nullptr sends to the SetPeer peer. extra is per-message protection. + IoResult SendTo(std::span data, const Endpoint* to, u16 to_vport, + Protection extra = {}); + IoResult RecvFrom(std::span out, bool peek, Endpoint* from, u16* from_vport); + u32 Events() const; // Host::EventBits + const Readiness& Handles() const { + return *readiness_; + } + u16 BoundVport() const; + u16 TransportPort() const; + bool GetPeer(Endpoint* peer, u16* peer_vport) const; + Protection GetProtection() const; + void Close(); + +private: + friend class Transport; + explicit DatagramSocket(std::shared_ptr transport); + Error BindLocked(u16 vport); + + std::shared_ptr transport_; + std::shared_ptr readiness_; + std::shared_ptr binding_; + Protection protection_; + std::optional> peer_; +}; + +class StreamSocket { +public: + ~StreamSocket(); + + void SetProtection(Protection protection); + Error Bind(u16 vport); + Error Listen(int backlog); + Error Connect(const Endpoint& peer, u16 peer_vport); + Error ConnectResult() const; + std::shared_ptr Accept(Endpoint* peer, u16* peer_vport, Error* error); + IoResult Send(std::span data); + IoResult Recv(std::span out, bool peek); + Error Shutdown(int how); // 0 = read, 1 = write, 2 = both + void Close(); + void Abort(); + + u32 Events() const; + const Readiness& Handles() const { + return *readiness_; + } + std::optional State() const; + bool GetPeer(Endpoint* peer, u16* peer_vport) const; + u16 BoundVport() const; + u16 TransportPort() const; + Protection GetProtection() const; + +private: + friend class Transport; + StreamSocket(std::shared_ptr transport, std::shared_ptr readiness); + Error BindLocked(u16 vport); // transport mutex held + + std::shared_ptr transport_; + std::shared_ptr readiness_; + Protection protection_; + u16 bound_vport_ = 0; + bool owns_vport_ = false; + bool read_shutdown_ = false; + bool closed_ = false; + std::shared_ptr listener_; + std::shared_ptr connection_; +}; + +class Transport : public std::enable_shared_from_this { +public: + static std::shared_ptr Create(int family, const Endpoint& bind_address, + std::unique_ptr codec, + const TransportConfig& config, Error* error); + ~Transport(); + + u16 BoundPort() const { + return bound_port_; + } + int Family() const { + return family_; + } + + std::shared_ptr CreateDatagram(); + std::shared_ptr CreateStream(); + + Error SendSignaling(std::span data, const Endpoint& to); + bool RecvSignaling(size_t channel, std::vector* data, Endpoint* from); + + void SetOutgoingLossForTesting(double probability); + +private: + friend class DatagramSocket; + friend class StreamSocket; + + Transport(int family, std::unique_ptr codec, const TransportConfig& config); + + void ThreadMain(); + void HandlePacket(std::span packet, const Endpoint& from, Clock::time_point now); + void HandleStreamSegment(std::span bytes, const Endpoint& from, + Clock::time_point now); + void HandleDatagram(Codec::Decoded& decoded, const Endpoint& from); + void RunTimers(Clock::time_point now); + std::optional EarliestDeadline() const; + + std::shared_ptr NewConnection(const ConnectionKey& key, Protection protection, + std::shared_ptr readiness); + void Update(const std::shared_ptr& connection); + void Reschedule(const Connection& connection); + Error SendPacket(std::span packet, const Endpoint& to); + u16 AllocateVport(bool stream); + u32 NewIss(); + + const int family_; + std::unique_ptr codec_; + const TransportConfig config_; + Host::NativeSocket socket_ = Host::InvalidSocket; + u16 bound_port_ = 0; + + mutable std::mutex mutex_; + std::map> datagram_bindings_; + std::map> listeners_; + std::map> connections_; + std::map stream_vports_in_use_; + std::vector, Endpoint>>> signaling_; + u16 next_stream_port_ = 49152; // 49152-65535 + u16 next_datagram_vport_ = 32768; // 32768-49999 + u64 iss_state_; + double loss_for_testing_ = 0.0; + u64 loss_rng_ = 0x9e3779b97f4a7c15ull; + + std::thread thread_; + std::atomic stop_{false}; + Host::WakeHandle kick_; + Clock::time_point sleeping_until_ = Clock::time_point::min(); + std::vector receive_buffer_; +}; + +} // namespace Core::Net::P2P diff --git a/src/emulator.cpp b/src/emulator.cpp index 53b7c9092..0b3bae00d 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -479,6 +479,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, LOG_INFO(Config, "General isRedZonePatchingEnabled: {}", EmulatorSettings.IsRedZonePatchingEnabled()); #endif + LOG_INFO(Config, "General p2pPort: {}", EmulatorSettings.GetP2PPort()); LOG_INFO(Config, "Log sync: {}", EmulatorSettings.IsLogSync()); LOG_INFO(Config, "Log skipDuplicate: {}", EmulatorSettings.IsLogSkipDuplicate()); #ifdef _WIN32 diff --git a/src/shadnet/client.cpp b/src/shadnet/client.cpp index 4da11f720..e7e7e7cf6 100644 --- a/src/shadnet/client.cpp +++ b/src/shadnet/client.cpp @@ -217,8 +217,17 @@ void ShadNetClient::ConnectThread() { LOG_INFO(ShadNet, "Login packet sent for '{}'", m_npid); } +namespace { +thread_local bool t_reader_thread = false; +} + +bool ShadNetClient::OnReaderThread() { + return t_reader_thread; +} + void ShadNetClient::ReaderThread() { Common::SetCurrentThreadName("ShadNet:Reader"); + t_reader_thread = true; while (!m_terminate) { u8 hdr[SHAD_HEADER_SIZE]; if (!RecvN(hdr, SHAD_HEADER_SIZE)) { @@ -932,6 +941,7 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector& paylo case NotificationType::RoomMessage: { shadnet::NotifyRoomMessage pb; if (!pb.ParseFromString(blob)) { + LOG_WARNING(ShadNet, "RoomMessage parse error"); break; } NotifyRoomMessage n; @@ -947,6 +957,8 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector& paylo n.src_account_id = pb.src_account_id(); n.src_platform = pb.src_platform(); n.msg.assign(pb.msg().begin(), pb.msg().end()); + LOG_DEBUG(ShadNet, "RoomMessage room_id={} src={} event={:#x} bytes={}", n.room_id, + n.src_member_id, n.event, n.msg.size()); if (onRoomMessage) onRoomMessage(n); break; diff --git a/src/shadnet/client.h b/src/shadnet/client.h index 9c8edcb21..f65b62c4d 100644 --- a/src/shadnet/client.h +++ b/src/shadnet/client.h @@ -97,6 +97,7 @@ enum class CommandType : u16 { GetUserInfoList = 113, GetRoomMemberDataExternalList = 114, SendRoomMessage = 115, + SetRoomMemberDataInternal = 116, // Title User Storage (TUS) TusSetData = 201, TusGetData = 202, @@ -304,6 +305,8 @@ public: u32 GetAddrLocal() const; u32 GetAddrServer() const; bool IsMatching2Enabled() const; + // True on the thread that reads replies. Code running there must not wait for a reply. + static bool OnReaderThread(); bool IsTrophiesEnabled() const; u64 ReportClientVersion(); static std::string BuildVersionString(); diff --git a/src/shadnet/shadnet.proto b/src/shadnet/shadnet.proto index 90e2795fd..427a785fc 100644 --- a/src/shadnet/shadnet.proto +++ b/src/shadnet/shadnet.proto @@ -374,6 +374,16 @@ message SetRoomDataInternalRequest { uint64 passwd_slot_mask = 7; } +message SetRoomMemberDataInternalRequest { + uint32 req_id = 1; + uint64 room_id = 2; + uint32 member_id = 3; + uint32 team_id = 4; + uint32 flag_filter = 5; + uint32 flag_attr = 6; + repeated MatchingBinAttr bin_attrs = 7; +} + message SetRoomDataExternalRequest { uint32 req_id = 1; uint64 room_id = 2; @@ -510,6 +520,7 @@ message RequestSignalingInfosReply { message LeaveRoomReply { uint64 room_id = 1; } message SetRoomDataInternalReply { uint64 room_id = 1; } +message SetRoomMemberDataInternalReply { uint64 room_id = 1; } message SetRoomDataExternalReply { uint64 room_id = 1; } message KickoutRoomMemberReply { uint64 room_id = 1; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9f1fa5839..5aa2901c2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,6 +9,12 @@ include(FetchContent) ) FetchContent_MakeAvailable(googletest) +foreach(gtest_target gtest gtest_main gmock gmock_main) + if (TARGET ${gtest_target}) + target_compile_options(${gtest_target} PRIVATE $,/WX-,-Wno-error>) + endif() +endforeach() + set(TEST_TARGETS shadps4_settings_test shadps4_ngs2_test @@ -340,3 +346,212 @@ gtest_discover_tests(shadps4_http_test WORKING_DIRECTORY ${CMAKE_BINARY_DIR} DISCOVERY_TIMEOUT 60 ) + +# =========================================================================== +# Core::Net tests (host sockets, guest layer, P2P transport, TCP, wire codec) +# =========================================================================== + +set(NET_CORE_SOURCES + ${CMAKE_SOURCE_DIR}/src/core/net/guest_net.cpp + ${CMAKE_SOURCE_DIR}/src/core/net/host_net.cpp + ${CMAKE_SOURCE_DIR}/src/core/net/p2p_codec.cpp + ${CMAKE_SOURCE_DIR}/src/core/net/p2p_crypto.cpp + ${CMAKE_SOURCE_DIR}/src/core/net/p2p_transport.cpp + ${CMAKE_SOURCE_DIR}/src/core/net/p2p_tcp.cpp +) + +# libcrypto for p2p_crypto.cpp: +get_directory_property(NET_TEST_SSL_LIBS DIRECTORY ${CMAKE_SOURCE_DIR}/externals + DEFINITION SSL_DEPENDENCIES) +if (NOT NET_TEST_SSL_LIBS) + find_package(OpenSSL REQUIRED COMPONENTS Crypto) + set(NET_TEST_SSL_LIBS OpenSSL::Crypto) +endif() + +add_executable(shadps4_net_test + ${NET_CORE_SOURCES} + network/test_net_host.cpp + network/test_net_p2p_codec.cpp + network/test_net_p2p_guest.cpp + network/test_net_p2p_tcp.cpp +) + +# Our TCP against the Linux kernel's TCP through a TUN device. Needs root or CAP_NET_ADMIN +# and skips itself otherwise (and on other platforms). Separate target so it can run serially: +# every test creates the same TUN interface. +add_executable(shadps4_net_interop_test + ${CMAKE_SOURCE_DIR}/src/core/net/p2p_tcp.cpp + network/test_net_p2p_tcp_interop.cpp +) + +foreach(t shadps4_net_test shadps4_net_interop_test) + list(APPEND TEST_TARGETS ${t}) + target_include_directories(${t} PRIVATE + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR} + ) + target_compile_features(${t} PRIVATE cxx_std_23) + target_link_libraries(${t} PRIVATE GTest::gtest_main) + + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR + CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + include(CheckCXXSymbolExists) + check_cxx_symbol_exists(_LIBCPP_VERSION version LIBCPP) + if (LIBCPP) + target_compile_options(${t} PRIVATE -fexperimental-library) + endif() + endif() + + if (WIN32) + target_link_libraries(${t} PRIVATE onecore ws2_32) + target_compile_definitions(${t} PRIVATE + NOMINMAX + WIN32_LEAN_AND_MEAN + NTDDI_VERSION=0x0A000006 + _WIN32_WINNT=0x0A00 + WINVER=0x0A00 + ) + if (MSVC) + target_compile_definitions(${t} PRIVATE + _CRT_SECURE_NO_WARNINGS + _CRT_NONSTDC_NO_DEPRECATE + _SCL_SECURE_NO_WARNINGS + _TIMESPEC_DEFINED + ) + endif() + endif() +endforeach() + +target_link_libraries(shadps4_net_test PRIVATE ${NET_TEST_SSL_LIBS}) +if (WIN32) + target_link_libraries(shadps4_net_test PRIVATE wepoll) +endif() + +gtest_discover_tests(shadps4_net_test + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + DISCOVERY_TIMEOUT 60 + PROPERTIES TIMEOUT 300 +) +gtest_discover_tests(shadps4_net_interop_test + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + DISCOVERY_TIMEOUT 60 + PROPERTIES RUN_SERIAL TRUE TIMEOUT 600 +) + +# =========================================================================== +# New libSceNet (src/core/libraries/net) on top of Core::Net +# =========================================================================== +# The library with the same logging/HLE support as the HTTP test; its kernel glue +# (net_kernel.cpp) is tested separately against a stub of the kernel. + +set(NET_LIB_TEST_SOURCES + # Under test + ${NET_CORE_SOURCES} + ${CMAKE_SOURCE_DIR}/src/core/libraries/net/net.cpp + ${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_log.cpp + ${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_p2p.cpp + ${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_p2p_ioctl.cpp + ${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_resolver.cpp + ${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_translate.cpp + + # Logging and RegisterLib support + ${CMAKE_SOURCE_DIR}/src/core/emulator_settings.cpp + ${CMAKE_SOURCE_DIR}/src/core/emulator_state.cpp + ${CMAKE_SOURCE_DIR}/src/common/path_util.cpp + ${CMAKE_SOURCE_DIR}/src/common/assert.cpp + ${CMAKE_SOURCE_DIR}/src/common/error.cpp + ${CMAKE_SOURCE_DIR}/src/common/string_util.cpp + ${CMAKE_SOURCE_DIR}/src/common/logging/classes.cpp + ${CMAKE_SOURCE_DIR}/src/common/logging/log.cpp + + # Stubs that replace dependencies + stubs/common_stub.cpp + stubs/scm_rev_stub.cpp + stubs/sdl_stub.cpp + stubs/loader_stub.cpp + stubs/core_stub.cpp + stubs/signals_stub.cpp + stubs/file_sys_stub.cpp + stubs/net_lib_kernel_stub.cpp +) + +# Both targets below share this setup. +function(add_net_lib_test target) + add_executable(${target} ${ARGN}) + list(APPEND TEST_TARGETS ${target}) + + target_include_directories(${target} PRIVATE + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR} + ) + target_compile_features(${target} PRIVATE cxx_std_23) + target_compile_definitions(${target} PRIVATE BOOST_ASIO_STANDALONE) + target_link_libraries(${target} PRIVATE + GTest::gtest_main + fmt::fmt + magic_enum::magic_enum + nlohmann_json::nlohmann_json + toml11::toml11 + SDL3::SDL3 + spdlog::spdlog + ${NET_TEST_SSL_LIBS} + ) + + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR + CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + include(CheckCXXSymbolExists) + check_cxx_symbol_exists(_LIBCPP_VERSION version LIBCPP) + if (LIBCPP) + target_compile_options(${target} PRIVATE -fexperimental-library) + endif() + endif() + + if (WIN32) + target_link_libraries(${target} PRIVATE onecore ws2_32 wepoll) + target_compile_definitions(${target} PRIVATE + NOMINMAX + WIN32_LEAN_AND_MEAN + NTDDI_VERSION=0x0A000006 + _WIN32_WINNT=0x0A00 + WINVER=0x0A00 + ) + if (MSVC) + target_compile_definitions(${target} PRIVATE + _CRT_SECURE_NO_WARNINGS + _CRT_NONSTDC_NO_DEPRECATE + _SCL_SECURE_NO_WARNINGS + _TIMESPEC_DEFINED + ) + endif() + endif() + + gtest_discover_tests(${target} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + DISCOVERY_TIMEOUT 60 + PROPERTIES TIMEOUT 300 + ) + set(TEST_TARGETS ${TEST_TARGETS} PARENT_SCOPE) +endfunction() + +add_net_lib_test(shadps4_net_lib_test + ${NET_LIB_TEST_SOURCES} + network/test_net_lib.cpp + network/test_net_lib_p2p_ioctl.cpp + network/test_net_lib_translate.cpp + network/test_net_sample_sockets.cpp +) + +# The kernel glue: net ids as kernel file descriptors, the BSD socket calls. The kernel's +# errno and file table come from a stub; everything else is the real code. +add_net_lib_test(shadps4_net_kernel_test + ${NET_LIB_TEST_SOURCES} + ${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_kernel.cpp + ${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_upnp.cpp + ${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_util.cpp + stubs/net_kernel_stub.cpp + network/test_net_kernel.cpp +) +target_link_libraries(shadps4_net_kernel_test PRIVATE + tsl::robin_map # fs.h + miniupnpc::miniupnpc # UPnP forwarding of the P2P port +) diff --git a/tests/network/test_net_host.cpp b/tests/network/test_net_host.cpp new file mode 100644 index 000000000..0bad30626 --- /dev/null +++ b/tests/network/test_net_host.cpp @@ -0,0 +1,593 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Native sockets through Core::Net's guest layer: blocking semantics enforced on non-blocking +// host sockets, SO_RCVTIMEO, abort, epoll and error translation. + +#include +#include +#include +#include +#include + +#include + +#include "core/net/guest_net.h" + +using namespace Core::Net; +using namespace std::chrono_literals; +using Host::Error; + +namespace { + +class NetHostTest : public ::testing::Test { +protected: + static void SetUpTestSuite() { + ASSERT_TRUE(Host::Initialize()); + } + + static sockaddr_in Loopback(u16 port = 0) { + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = htons(port); + return addr; + } + + /// A listening TCP socket on an ephemeral loopback port. + static s32 MakeListener(sockaddr_in* bound) { + const s32 listener = static_cast(SocketCreate(AF_INET, SOCK_STREAM, 0).value); + EXPECT_GT(listener, 0); + auto addr = Loopback(); + EXPECT_EQ(SocketBind(listener, reinterpret_cast(&addr), sizeof(addr)).error, + Error::Ok); + EXPECT_EQ(SocketListen(listener, 4).error, Error::Ok); + socklen_t len = sizeof(*bound); + EXPECT_EQ(SocketGetName(listener, reinterpret_cast(bound), &len).error, + Error::Ok); + return listener; + } + + static double Millis(std::chrono::steady_clock::time_point since) { + return std::chrono::duration(std::chrono::steady_clock::now() - since) + .count(); + } +}; + +TEST_F(NetHostTest, BlockingConnectAcceptSendRecv) { + sockaddr_in addr{}; + const s32 listener = MakeListener(&addr); + + NetResult accepted{}; + std::thread server([&] { accepted = SocketAccept(listener, nullptr, nullptr); }); + const s32 client = static_cast(SocketCreate(AF_INET, SOCK_STREAM, 0).value); + ASSERT_EQ(SocketConnect(client, reinterpret_cast(&addr), sizeof(addr)).error, + Error::Ok); + server.join(); + ASSERT_EQ(accepted.error, Error::Ok); + const s32 conn = static_cast(accepted.value); + + EXPECT_EQ(SocketSendTo(client, "hello", 5, 0, false, nullptr, 0).value, 5); + char buf[16]{}; + EXPECT_EQ(SocketRecvFrom(conn, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 5); + EXPECT_EQ(std::memcmp(buf, "hello", 5), 0); + + // Peer close is end-of-stream (0), not an error. + SocketClose(client); + EXPECT_EQ(SocketRecvFrom(conn, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 0); + SocketClose(conn); + SocketClose(listener); +} + +TEST_F(NetHostTest, RecvTimeoutIsEagain) { + sockaddr_in addr{}; + const s32 listener = MakeListener(&addr); + const s32 client = static_cast(SocketCreate(AF_INET, SOCK_STREAM, 0).value); + ASSERT_EQ(SocketConnect(client, reinterpret_cast(&addr), sizeof(addr)).error, + Error::Ok); + + SocketSetRecvTimeout(client, 200ms); + char buf[4]; + const auto start = std::chrono::steady_clock::now(); + const auto r = SocketRecvFrom(client, buf, sizeof(buf), 0, false, nullptr, nullptr); + // BSD: an expired SO_RCVTIMEO is EAGAIN, not ETIMEDOUT. + EXPECT_EQ(r.error, Error::WouldBlock); + EXPECT_GE(Millis(start), 190.0); + EXPECT_LT(Millis(start), 2000.0); + SocketClose(client); + SocketClose(listener); +} + +TEST_F(NetHostTest, AbortWakesBlockedRecv) { + sockaddr_in addr{}; + const s32 listener = MakeListener(&addr); + const s32 client = static_cast(SocketCreate(AF_INET, SOCK_STREAM, 0).value); + ASSERT_EQ(SocketConnect(client, reinterpret_cast(&addr), sizeof(addr)).error, + Error::Ok); + + std::thread aborter([&] { + std::this_thread::sleep_for(100ms); + SocketAbort(client, kSocketAbortPreserveRecv); + }); + char buf[4]; + const auto start = std::chrono::steady_clock::now(); + EXPECT_EQ(SocketRecvFrom(client, buf, sizeof(buf), 0, false, nullptr, nullptr).error, + Error::Intr); + EXPECT_LT(Millis(start), 2000.0); + aborter.join(); + SocketClose(client); + SocketClose(listener); +} + +/// A bound UDP socket plus its own address, for abort tests that need a socket to wait on. +struct UdpPair { + s32 sock; + sockaddr_in addr; +}; + +UdpPair MakeUdp() { + UdpPair p{static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value), {}}; + p.addr.sin_family = AF_INET; + p.addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + SocketBind(p.sock, reinterpret_cast(&p.addr), sizeof(p.addr)); + socklen_t len = sizeof(p.addr); + SocketGetName(p.sock, reinterpret_cast(&p.addr), &len); + return p; +} + +TEST_F(NetHostTest, AbortOnlyInterruptsCurrentWaits) { + auto [sock, addr] = MakeUdp(); + char buf[4]; + SocketSetRecvTimeout(sock, 5s); // bounds the test if the abort fired too early + // Wake a blocked recv. + std::thread aborter([&] { + std::this_thread::sleep_for(150ms); + SocketAbort(sock, 0); + }); + EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).error, + Error::Intr); + aborter.join(); + + // The socket is still usable: data sent now is received. + SocketSendTo(sock, "ok", 2, 0, false, reinterpret_cast(&addr), sizeof(addr)); + EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 2); + + // Nothing blocked and no preservation flag: the abort has no lasting effect. + SocketAbort(sock, 0); + SocketSetRecvTimeout(sock, 100ms); + EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).error, + Error::WouldBlock); // timed out normally + SocketClose(sock); +} + +TEST_F(NetHostTest, PreservedAbortHitsNextBlockingCallOnce) { + auto [sock, addr] = MakeUdp(); + char buf[4]; + SocketSetRecvTimeout(sock, 100ms); + + // Preserved for the next receive of any kind, non-blocking included (sceNetSocketAbort: + // "the next receive or send function will return an error immediately"). + EXPECT_EQ(SocketAbort(sock, kSocketAbortPreserveRecv).error, Error::Ok); + // Send-side calls do not consume a receive-side abort. + EXPECT_EQ(SocketSendTo(sock, "x", 1, 0, false, reinterpret_cast(&addr), sizeof(addr)) + .value, + 1); + EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, true, nullptr, nullptr).error, Error::Intr); + // ... and only that one: the blocking receive after it gets the queued datagram. + const auto start = std::chrono::steady_clock::now(); + EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 1); + EXPECT_LT(Millis(start), 50.0); + + // A blocking receive consumes it the same way, even with data queued. + SocketSendTo(sock, "z", 1, 0, false, reinterpret_cast(&addr), sizeof(addr)); + SocketAbort(sock, kSocketAbortPreserveRecv); + EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).error, + Error::Intr); + EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 1); + + // UDP sends never wait, but a preserved send abort still fails the next one. + SocketAbort(sock, kSocketAbortPreserveSend); + EXPECT_EQ(SocketSendTo(sock, "y", 1, 0, false, reinterpret_cast(&addr), sizeof(addr)) + .error, + Error::Intr); + EXPECT_EQ(SocketSendTo(sock, "y", 1, 0, false, reinterpret_cast(&addr), sizeof(addr)) + .value, + 1); + EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 1); + EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).error, + Error::WouldBlock); + + SocketClose(sock); + + // A send-side preservation hits the next blocking send-side call (here a connect, before it + // starts), not receives. UDP sends never wait on the PS4, so they are not affected. + sockaddr_in listen_addr{}; + const s32 listener = MakeListener(&listen_addr); + const s32 tcp = static_cast(SocketCreate(AF_INET, SOCK_STREAM, 0).value); + SocketAbort(tcp, kSocketAbortPreserveSend); + EXPECT_EQ(SocketRecvFrom(tcp, buf, sizeof(buf), 0, false, nullptr, nullptr).error, + Error::NotConn); + const auto* to = reinterpret_cast(&listen_addr); + EXPECT_EQ(SocketConnect(tcp, to, sizeof(listen_addr)).error, Error::Intr); + EXPECT_EQ(SocketConnect(tcp, to, sizeof(listen_addr)).error, Error::Ok); + SocketClose(tcp); + SocketClose(listener); +} + +TEST_F(NetHostTest, EpollAbortPreservation) { + const s32 ep = static_cast(EpollCreate().value); + GuestEpollEvent events[4]; + + // Nothing waiting, no flag: ENOTBLK, and no lasting effect. + EXPECT_EQ(EpollAbort(ep, 0).error, Error::NotBlk); + auto start = std::chrono::steady_clock::now(); + EXPECT_EQ(EpollWait(ep, events, 50'000).value, 0); + EXPECT_GE(Millis(start), 45.0); + + // Preserved: the next wait fails at once, the one after waits normally. + EXPECT_EQ(EpollAbort(ep, kEpollAbortPreserve).error, Error::Ok); + EXPECT_EQ(EpollWait(ep, events, -1).error, Error::Intr); + start = std::chrono::steady_clock::now(); + EXPECT_EQ(EpollWait(ep, events, 50'000).value, 0); + EXPECT_GE(Millis(start), 45.0); + + // After waking a wait, the wake is drained: later waits are not woken by it. + std::thread aborter([&] { + std::this_thread::sleep_for(100ms); + EpollAbort(ep, 0); + }); + EXPECT_EQ(EpollWait(ep, events, 5'000'000).error, Error::Intr); + aborter.join(); + start = std::chrono::steady_clock::now(); + EXPECT_EQ(EpollWait(ep, events, 50'000).value, 0); + EXPECT_GE(Millis(start), 45.0); + EpollDestroy(ep); +} + +TEST_F(NetHostTest, CloseWakesBlockedAccept) { + sockaddr_in addr{}; + const s32 listener = MakeListener(&addr); + NetResult r{}; + std::thread t([&] { r = SocketAccept(listener, nullptr, nullptr); }); + std::this_thread::sleep_for(100ms); + SocketClose(listener); + t.join(); + EXPECT_EQ(r.error, Error::Intr); +} + +TEST_F(NetHostTest, ConnectRefused) { + // Bind a port, then close it, so nothing listens there. + sockaddr_in addr{}; + SocketClose(MakeListener(&addr)); + const s32 client = static_cast(SocketCreate(AF_INET, SOCK_STREAM, 0).value); + EXPECT_EQ(SocketConnect(client, reinterpret_cast(&addr), sizeof(addr)).error, + Error::ConnRefused); + SocketClose(client); +} + +TEST_F(NetHostTest, NonBlockingRecvWouldBlock) { + const s32 sock = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + auto addr = Loopback(); + SocketBind(sock, reinterpret_cast(&addr), sizeof(addr)); + char buf[4]; + // MSG_DONTWAIT on a blocking socket, then SO_NBIO. + EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, true, nullptr, nullptr).error, + Error::WouldBlock); + SocketSetNonBlocking(sock, true); + EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).error, + Error::WouldBlock); + SocketClose(sock); +} + +TEST_F(NetHostTest, EpollLevelTriggeredAndAbort) { + const s32 sock = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + auto addr = Loopback(); + SocketBind(sock, reinterpret_cast(&addr), sizeof(addr)); + socklen_t len = sizeof(addr); + SocketGetName(sock, reinterpret_cast(&addr), &len); + + const s32 ep = static_cast(EpollCreate().value); + ASSERT_EQ(EpollControl(ep, EpollOp::Add, sock, Host::EvIn, 0, 0xabcd).error, Error::Ok); + EXPECT_EQ(EpollControl(ep, EpollOp::Add, sock, Host::EvIn, 0, 0).error, Error::Exist); + + GuestEpollEvent events[4]; + EXPECT_EQ(EpollWait(ep, events, 0).value, 0); // nothing pending + + const s32 sender = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + SocketSendTo(sender, "x", 1, 0, false, reinterpret_cast(&addr), sizeof(addr)); + ASSERT_EQ(EpollWait(ep, events, 2'000'000).value, 1); + EXPECT_EQ(events[0].data, 0xabcdu); + EXPECT_EQ(events[0].ident, sock); + EXPECT_TRUE(events[0].events & Host::EvIn); + EXPECT_EQ(EpollWait(ep, events, 0).value, 1); // level-triggered: still pending + + char buf[4]; + SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr); + EXPECT_EQ(EpollWait(ep, events, 0).value, 0); + + // Deleting, then closing: no stale events. + EXPECT_EQ(EpollControl(ep, EpollOp::Delete, sock, 0, 0, 0).error, Error::Ok); + EXPECT_EQ(EpollControl(ep, EpollOp::Delete, sock, 0, 0, 0).error, Error::NoEnt); + + std::thread aborter([&] { + std::this_thread::sleep_for(100ms); + EpollAbort(ep, kEpollAbortPreserve); + }); + EXPECT_EQ(EpollWait(ep, events, -1).error, Error::Intr); + aborter.join(); + + SocketClose(sender); + SocketClose(sock); + EpollDestroy(ep); +} + +TEST_F(NetHostTest, ClosedSocketLeavesEpoll) { + const s32 sock = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + const s32 ep = static_cast(EpollCreate().value); + EpollControl(ep, EpollOp::Add, sock, Host::EvOut, 0, 1); + GuestEpollEvent events[4]; + EXPECT_EQ(EpollWait(ep, events, 0).value, 1); // UDP is writable + SocketClose(sock); + EXPECT_EQ(EpollWait(ep, events, 0).value, 0); + EXPECT_EQ(SocketRecvFrom(sock, nullptr, 0, 0, false, nullptr, nullptr).error, Error::BadF); + EpollDestroy(ep); +} + +TEST_F(NetHostTest, SocketsAndEpollsShareOneIdSpace) { + const s32 sock = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + const s32 ep = static_cast(EpollCreate().value); + ASSERT_GE(sock, 0); + ASSERT_GE(ep, 0); + EXPECT_NE(sock, ep); + EXPECT_EQ(GetObjectKind(sock), NetObjectKind::Socket); + EXPECT_EQ(GetObjectKind(ep), NetObjectKind::Epoll); + + // An id of the wrong kind is a bad descriptor, and is left alone. + EXPECT_EQ(SocketClose(ep).error, Error::BadF); + EXPECT_EQ(EpollDestroy(sock).error, Error::BadF); + EXPECT_EQ(EpollControl(ep, EpollOp::Add, ep, Host::EvIn, 0, 0).error, Error::BadF); + EXPECT_EQ(GetObjectKind(ep), NetObjectKind::Epoll); + + // Lowest free id first, whatever kind takes it. + ASSERT_EQ(CloseObject(sock).error, Error::Ok); + EXPECT_FALSE(GetObjectKind(sock)); + const s32 ep2 = static_cast(EpollCreate().value); + EXPECT_EQ(ep2, sock); + EXPECT_EQ(CloseObject(ep2).error, Error::Ok); + EXPECT_EQ(CloseObject(ep).error, Error::Ok); + EXPECT_EQ(CloseObject(ep).error, Error::BadF); +} + +TEST_F(NetHostTest, SocketLimitIs128) { + std::vector sockets; + for (int i = 0; i < MaxSockets + 1; ++i) { + const auto r = SocketCreate(AF_INET, SOCK_DGRAM, 0); + if (r.error != Error::Ok) { + EXPECT_EQ(r.error, Error::MFile); + break; + } + sockets.push_back(static_cast(r.value)); + } + EXPECT_EQ(sockets.size(), static_cast(MaxSockets)); // no other test leaks sockets + // Epolls do not count against the limit. + const auto ep = EpollCreate(); + EXPECT_EQ(ep.error, Error::Ok); + // Closing one makes room for one. + SocketClose(sockets.back()); + sockets.pop_back(); + const auto again = SocketCreate(AF_INET, SOCK_DGRAM, 0); + ASSERT_EQ(again.error, Error::Ok); + sockets.push_back(static_cast(again.value)); + EXPECT_EQ(SocketCreate(AF_INET, SOCK_DGRAM, 0).error, Error::MFile); + + for (const s32 id : sockets) { + SocketClose(id); + } + EpollDestroy(static_cast(ep.value)); +} + +TEST_F(NetHostTest, IdAllocatorHook) { + // Stands in for the kernel's file table. + std::mutex mutex; + s32 next = 1000; + std::vector released; + SetIdAllocator({ + [&] { + std::scoped_lock lock{mutex}; + return next++; + }, + [&](s32 id) { + std::scoped_lock lock{mutex}; + released.push_back(id); + }, + }); + const s32 sock = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + const s32 ep = static_cast(EpollCreate().value); + EXPECT_EQ(sock, 1000); + EXPECT_EQ(ep, 1001); + CloseObject(sock); + CloseObject(ep); + EXPECT_EQ(released, (std::vector{1000, 1001})); + + // A table that is full fails creation with ENFILE. + SetIdAllocator({[] { return -1; }, [](s32) {}}); + EXPECT_EQ(SocketCreate(AF_INET, SOCK_DGRAM, 0).error, Error::NFile); + EXPECT_EQ(EpollCreate().error, Error::NFile); + + SetIdAllocator({}); // back to the built-in allocator + const s32 plain = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + EXPECT_LT(plain, 1000); + SocketClose(plain); +} + +TEST_F(NetHostTest, HostErrorsKeepTheirMeaning) { + // Errors that used to fall through to EIO. + const s32 udp = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + EXPECT_EQ(SocketAccept(udp, nullptr, nullptr).error, Error::OpNotSupp); + auto broadcast = Loopback(9); + broadcast.sin_addr.s_addr = htonl(INADDR_BROADCAST); + EXPECT_EQ(SocketSendTo(udp, "x", 1, 0, false, reinterpret_cast(&broadcast), + sizeof(broadcast)) + .error, + Error::Acces); // no SO_BROADCAST + SocketClose(udp); + EXPECT_EQ(SocketCreate(AF_INET, SOCK_DGRAM, IPPROTO_TCP).error, Error::ProtoNoSupport); + + // The enum carries FreeBSD's numbering, whatever the host's is. +#ifndef _WIN32 + EXPECT_EQ(Host::TranslateNative(EACCES), Error::Acces); + EXPECT_EQ(Host::TranslateNative(EOPNOTSUPP), Error::OpNotSupp); + EXPECT_EQ(Host::TranslateNative(EMFILE), Error::MFile); + EXPECT_EQ(Host::TranslateNative(ENETRESET), Error::NetReset); + EXPECT_EQ(Host::TranslateNative(EHOSTDOWN), Error::HostDown); +#endif + EXPECT_EQ(static_cast(Error::OpNotSupp), 45); + EXPECT_EQ(static_cast(Error::NetReset), 52); + EXPECT_EQ(static_cast(Error::NotEmpty), 66); + // Anything unknown is the PS4's EINTERNAL. + EXPECT_EQ(Host::TranslateNative(987654), Error::Internal); + EXPECT_EQ(static_cast(Error::Internal), 204); +} + +TEST_F(NetHostTest, ExternalIdsReportCompletionOnce) { + // What resolvers use: an id from the same space that an epoll can watch. + const s32 ep = static_cast(EpollCreate().value); + const auto reserved = ReserveExternalId(); + ASSERT_EQ(reserved.error, Error::Ok); + const s32 id = static_cast(reserved.value); + EXPECT_EQ(GetObjectKind(id), NetObjectKind::External); + EXPECT_EQ(CloseObject(id).error, Error::BadF); // released by its owner only + ASSERT_EQ(EpollControl(ep, EpollOp::Add, id, Host::EvIn, 0, 0xABC).error, Error::Ok); + EXPECT_EQ(EpollControl(ep, EpollOp::Add, id, Host::EvIn, 0, 0).error, Error::Exist); + + GuestEpollEvent events[4]; + EXPECT_EQ(EpollWait(ep, events, 0).value, 0); + std::thread signaller([&] { + std::this_thread::sleep_for(50ms); + SignalExternal(id); + }); + ASSERT_EQ(EpollWait(ep, events, 2'000'000).value, 1); // woken by the signal + signaller.join(); + EXPECT_EQ(events[0].events, Host::EvIn | Host::EvDescId); + EXPECT_EQ(events[0].ident, id); + EXPECT_EQ(events[0].data, 0xABCu); + // Reported once: the next wait times out instead of spinning on the set flag. + const auto start = std::chrono::steady_clock::now(); + EXPECT_EQ(EpollWait(ep, events, 50'000).value, 0); + EXPECT_GE(Millis(start), 45.0); + + // Releasing removes it from the epoll; the id is gone. + EXPECT_EQ(ReleaseExternalId(id).error, Error::Ok); + EXPECT_FALSE(GetObjectKind(id)); + EXPECT_EQ(ReleaseExternalId(id).error, Error::BadF); + EXPECT_EQ(EpollControl(ep, EpollOp::Delete, id, 0, 0, 0).error, Error::BadF); + EpollDestroy(ep); +} + +TEST_F(NetHostTest, ExternalIdsCountTowardsTheLimit) { + std::vector ids; + for (int i = 0; i < MaxSockets; ++i) { + ids.push_back(static_cast(ReserveExternalId().value)); + } + EXPECT_EQ(SocketCreate(AF_INET, SOCK_DGRAM, 0).error, Error::MFile); + EXPECT_EQ(ReserveExternalId().error, Error::MFile); + for (const s32 id : ids) { + ReleaseExternalId(id); + } +} + +TEST_F(NetHostTest, SocketList) { + const s32 a = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + const s32 ep = static_cast(EpollCreate().value); + const s32 b = static_cast(SocketCreate(AF_INET, SOCK_STREAM, 0).value); + EXPECT_EQ(ListSockets(), (std::vector{a, b})); // sockets only, ascending + SocketClose(a); + SocketClose(b); + EpollDestroy(ep); + EXPECT_TRUE(ListSockets().empty()); +} + +TEST_F(NetHostTest, SelectOnNativeSockets) { + const s32 udp = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + ASSERT_EQ(SocketBind(udp, reinterpret_cast(&addr), sizeof(addr)).error, Error::Ok); + socklen_t len = sizeof(addr); + SocketGetName(udp, reinterpret_cast(&addr), &len); + + // Writable at once, not readable; the same socket listed twice is fine. + SelectEntry entries[] = {{udp, Host::EvIn, 0}, {udp, Host::EvOut, 0}}; + auto r = SocketSelect(entries, 0); + ASSERT_EQ(r.error, Error::Ok); + EXPECT_EQ(r.value, 1); + EXPECT_EQ(entries[0].ready, 0u); + EXPECT_EQ(entries[1].ready, Host::EvOut); + + // Readable once a datagram is queued; a wait with a timeout wakes for it. + SelectEntry read_only[] = {{udp, Host::EvIn, 0}}; + const auto start = std::chrono::steady_clock::now(); + EXPECT_EQ(SocketSelect(read_only, 50'000).value, 0); // times out + EXPECT_GE(std::chrono::steady_clock::now() - start, std::chrono::milliseconds{40}); + std::thread sender([&] { + std::this_thread::sleep_for(std::chrono::milliseconds{100}); + Error e; + const auto helper = Host::CreateSocket(AF_INET, SOCK_DGRAM, 0, &e); + Host::SendTo(helper, "y", 1, 0, reinterpret_cast(&addr), sizeof(addr)); + Host::CloseSocket(helper); + }); + r = SocketSelect(read_only, -1); + sender.join(); + EXPECT_EQ(r.value, 1); + EXPECT_EQ(read_only[0].ready, Host::EvIn); + + // Not a socket: EBADF. Nothing listed: nothing ready. + const s32 ep = static_cast(EpollCreate().value); + SelectEntry bad[] = {{ep, Host::EvIn, 0}}; + EXPECT_EQ(SocketSelect(bad, 0).error, Error::BadF); + EXPECT_EQ(SocketSelect({}, 0).value, 0); + EpollDestroy(ep); + + // Many sockets at once: every ready one is reported, beyond one host wait's 64. + std::vector many; + std::vector all; + for (int i = 0; i < 100; ++i) { + many.push_back(static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value)); + all.push_back({many.back(), Host::EvOut, 0}); + } + EXPECT_EQ(SocketSelect(all, 0).value, 100); + for (const s32 id : many) { + SocketClose(id); + } + SocketClose(udp); +} + +TEST_F(NetHostTest, SocketPair) { + s32 ids[2]; +#ifdef _WIN32 + const int family = AF_INET; // emulated with a loopback TCP connection +#else + const int family = AF_UNIX; +#endif + ASSERT_EQ(SocketCreatePair(family, SOCK_STREAM, 0, ids).error, Error::Ok); + EXPECT_NE(ids[0], ids[1]); + EXPECT_EQ(SocketSendTo(ids[0], "ping", 4, 0, false, nullptr, 0).value, 4); + char buf[8]{}; + EXPECT_EQ(SocketRecvFrom(ids[1], buf, sizeof(buf), 0, false, nullptr, nullptr).value, 4); + EXPECT_STREQ(buf, "ping"); + EXPECT_EQ(SocketSendTo(ids[1], "pong", 4, 0, false, nullptr, 0).value, 4); + EXPECT_EQ(SocketRecvFrom(ids[0], buf, sizeof(buf), 0, false, nullptr, nullptr).value, 4); + // Both ends are ordinary sockets: closing one gives the other EOF. + SocketClose(ids[0]); + EXPECT_EQ(SocketRecvFrom(ids[1], buf, sizeof(buf), 0, false, nullptr, nullptr).value, 0); + SocketClose(ids[1]); +} + +TEST_F(NetHostTest, OrbisReturnCodes) { + EXPECT_EQ(ToOrbisReturn(NetResult::Ok(7)), 7); + // ORBIS_NET_ERROR_EWOULDBLOCK / EINTR: 0x80410100 | FreeBSD errno. + EXPECT_EQ(static_cast(ToOrbisReturn(NetResult::Fail(Error::WouldBlock))), 0x80410123u); + EXPECT_EQ(static_cast(ToOrbisReturn(NetResult::Fail(Error::Intr))), 0x80410104u); +} + +} // namespace diff --git a/tests/network/test_net_kernel.cpp b/tests/network/test_net_kernel.cpp new file mode 100644 index 000000000..12fba9504 --- /dev/null +++ b/tests/network/test_net_kernel.cpp @@ -0,0 +1,162 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// The net library inside the kernel: net ids are file descriptors from the kernel's file table, +// the kernel's close/read/write/fstat reach them, and the libkernel / libScePosix socket calls +// report errors the POSIX way (-1 and the kernel errno). + +#include + +#include + +#include "common/singleton.h" +#include "core/emulator_settings.h" +#include "core/file_sys/fs.h" +#include "core/libraries/kernel/file_system.h" +#include "core/libraries/kernel/kernel.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_error.h" +#include "core/libraries/net/net_kernel.h" +#include "core/libraries/net/net_p2p.h" +#include "core/net/guest_net.h" + +using namespace Libraries::Net; +using FDTable = Common::Singleton; + +namespace { + +class NetKernel : public ::testing::Test { +protected: + static void SetUpTestSuite() { + FDTable::Instance()->CreateStdHandles(); + InstallKernelIntegration(); + ASSERT_EQ(sceNetInit(), 0); + } + + static int Errno() { + return *Libraries::Kernel::__Error(); + } + + static OrbisNetSockaddrIn Loopback() { + OrbisNetSockaddrIn in{}; + in.sin_len = sizeof(in); + in.sin_family = ORBIS_NET_AF_INET; + in.sin_addr = sceNetHtonl(0x7F000001); + return in; + } +}; + +TEST_F(NetKernel, SocketsAreFileDescriptors) { + const int fd = sys_socket(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + ASSERT_GE(fd, 3); // after stdin, stdout, stderr + auto* file = FDTable::Instance()->GetFile(fd); + ASSERT_NE(file, nullptr); + EXPECT_EQ(file->type, Core::FileSys::FileType::Socket); + EXPECT_TRUE(file->is_opened); + // select() on it: a fresh UDP socket is writable, not readable. + Core::Net::SelectEntry sel[] = {{fd, Core::Net::Host::EvIn | Core::Net::Host::EvOut, 0}}; + EXPECT_EQ(KernelSelect(sel, 0), 1); + EXPECT_EQ(sel[0].ready, Core::Net::Host::EvOut); + + // write() and read() on the descriptor are send and recv. + auto addr = Loopback(); + ASSERT_EQ(sys_bind(fd, reinterpret_cast(&addr), sizeof(addr)), 0); + u32 len = sizeof(addr); + ASSERT_EQ(sys_getsockname(fd, reinterpret_cast(&addr), &len), 0); + ASSERT_EQ(sys_connect(fd, reinterpret_cast(&addr), sizeof(addr)), 0); + EXPECT_EQ(KernelWrite(fd, "data", 4), 4); + char buf[8]{}; + EXPECT_EQ(KernelRead(fd, buf, sizeof(buf)), 4); + EXPECT_STREQ(buf, "data"); + + Libraries::Kernel::OrbisKernelStat st{}; + ASSERT_EQ(KernelFstat(fd, &st), 0); + EXPECT_EQ(st.st_mode & 0170000, 0140000); // S_IFSOCK + + ASSERT_EQ(KernelClose(fd), 0); + EXPECT_EQ(FDTable::Instance()->GetFile(fd), nullptr); + EXPECT_FALSE(Core::Net::GetObjectKind(fd)); + EXPECT_EQ(KernelClose(fd), -1); + EXPECT_EQ(Errno(), ORBIS_NET_EBADF); + EXPECT_EQ(sceNetSocketClose(fd), ORBIS_NET_ERROR_EBADF); +} + +TEST_F(NetKernel, DescriptorsAreReused) { + const int a = sys_socket(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + ASSERT_EQ(sys_socketclose(a), 0); // the sceNet way frees the descriptor as well + EXPECT_EQ(FDTable::Instance()->GetFile(a), nullptr); + const int b = sys_socket(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + EXPECT_EQ(b, a); // lowest free descriptor, as the kernel hands them out + KernelClose(b); +} + +TEST_F(NetKernel, PosixErrorsUseTheKernelErrno) { + *sceNetErrnoLoc() = 1234; + *Libraries::Kernel::__Error() = 0; + EXPECT_EQ(sys_listen(1234, 1), -1); + EXPECT_EQ(Errno(), ORBIS_NET_EBADF); + EXPECT_EQ(sys_socket(99, ORBIS_NET_SOCK_STREAM, 0), -1); + EXPECT_EQ(Errno(), ORBIS_NET_EPROTONOSUPPORT); // as sceNetSocket reports it + EXPECT_EQ(*sceNetErrnoLoc(), 1234); + char buf[4]; + EXPECT_EQ(KernelRead(1234, buf, sizeof(buf)), -1); + EXPECT_EQ(*sceNetErrnoLoc(), 1234); + // The sceNet* call itself does set it. + EXPECT_EQ(sceNetListen(1234, 1), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EBADF); + EXPECT_EQ(sys_htons(0x1234), 0x3412); +} + +TEST_F(NetKernel, SocketPair) { + int sv[2]{-1, -1}; + EXPECT_EQ(sys_socketpair(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0, sv), -1); + EXPECT_EQ(Errno(), ORBIS_NET_EAFNOSUPPORT); // AF_UNIX only, as on FreeBSD + ASSERT_EQ(sys_socketpair(ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_STREAM, 0, sv), 0); + EXPECT_NE(FDTable::Instance()->GetFile(sv[0]), nullptr); + EXPECT_NE(FDTable::Instance()->GetFile(sv[1]), nullptr); + EXPECT_EQ(KernelWrite(sv[0], "abc", 3), 3); + char buf[4]{}; + EXPECT_EQ(sys_recv(sv[1], buf, sizeof(buf), 0), 3); + EXPECT_STREQ(buf, "abc"); + KernelClose(sv[0]); + KernelClose(sv[1]); +} + +TEST_F(NetKernel, EpollsAndResolversCloseToo) { + const int ep = sceNetEpollCreate("ep", 0); + const s32 pool = sceNetPoolCreate("pool", 16 * 1024, 0); + const int rid = sceNetResolverCreate("rid", pool, 0); + ASSERT_GE(ep, 3); + ASSERT_GE(rid, 3); + EXPECT_NE(FDTable::Instance()->GetFile(ep), nullptr); + EXPECT_NE(FDTable::Instance()->GetFile(rid), nullptr); + // Descriptors, but nothing to select on: never ready, and not an error. + Core::Net::SelectEntry sel[] = {{ep, Core::Net::Host::EvIn, 0}, + {rid, Core::Net::Host::EvIn, 0}}; + EXPECT_EQ(KernelSelect(sel, 0), 0); + EXPECT_EQ(sel[0].ready, 0u); + char buf[4]; + EXPECT_EQ(KernelRead(ep, buf, sizeof(buf)), -1); // not readable + EXPECT_EQ(KernelClose(ep), 0); + EXPECT_EQ(KernelClose(rid), 0); + EXPECT_EQ(FDTable::Instance()->GetFile(ep), nullptr); + EXPECT_EQ(FDTable::Instance()->GetFile(rid), nullptr); + EXPECT_EQ(sceNetResolverDestroy(rid), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(sceNetPoolDestroy(pool), ORBIS_OK); // close() gave the pool back +} + +TEST_F(NetKernel, P2PPortComesFromTheSettings) { + EmulatorSettings.SetP2PPort(0); // any free port + EXPECT_EQ(GetP2PConfiguredPort(), 0); + ASSERT_TRUE(EnsureP2PTransport()); + EXPECT_NE(GetP2PBoundPort(), 0); + EXPECT_EQ(GetP2PAdvertisedPort(), GetP2PBoundPort()); + StopP2P(); // offline by default: no UPnP involved either way + EXPECT_FALSE(P2PTransportIsReady()); + + EmulatorSettings.SetP2PPort(70000); // not a port: the console's + EXPECT_EQ(GetP2PConfiguredPort(), 3658); + EmulatorSettings.SetP2PPort(3658); +} + +} // namespace diff --git a/tests/network/test_net_lib.cpp b/tests/network/test_net_lib.cpp new file mode 100644 index 000000000..7bb2dea05 --- /dev/null +++ b/tests/network/test_net_lib.cpp @@ -0,0 +1,1931 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// The new libSceNet (src/core/libraries/net) driven through its exports, exactly as a game +// calls them: guest sockaddrs, guest constants, ORBIS_NET_ERROR_* returns and sceNetErrnoLoc. +// P2P runs over the real wire format against a second transport standing in for another +// console. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef _WIN32 +#include +#endif + +#include "core/libraries/error_codes.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_p2p.h" +#include "core/libraries/net/net_error.h" +#include "core/libraries/net/net_log.h" +#include "core/libraries/net/net_translate.h" +#include "core/loader/symbols_resolver.h" +#include "core/net/p2p_codec.h" + +using namespace Libraries::Net; +using namespace std::chrono_literals; +namespace Host = Core::Net::Host; +namespace P2P = Core::Net::P2P; + +namespace { + +OrbisNetSockaddrIn Loopback(u16 port, u16 vport = 0) { + OrbisNetSockaddrIn in{}; + in.sin_len = sizeof(in); + in.sin_family = ORBIS_NET_AF_INET; + in.sin_port = sceNetHtons(port); + in.sin_addr = sceNetHtonl(0x7F000001); + in.sin_vport = sceNetHtons(vport); + return in; +} + +OrbisNetSockaddr* Guest(OrbisNetSockaddrIn* in) { + return reinterpret_cast(in); +} + +double Millis(std::chrono::steady_clock::time_point since) { + return std::chrono::duration(std::chrono::steady_clock::now() - since) + .count(); +} + +class NetLib : public ::testing::Test { +protected: + static void SetUpTestSuite() { + ASSERT_EQ(sceNetInit(), ORBIS_OK); + } + static void TearDownTestSuite() { + EXPECT_EQ(sceNetTerm(), ORBIS_OK); + } + + /// A TCP listener on an ephemeral loopback port; returns its port. + static u16 Listen(OrbisNetId* listener) { + *listener = sceNetSocket("listener", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + EXPECT_GT(*listener, 0); + auto addr = Loopback(0); + EXPECT_EQ(sceNetBind(*listener, Guest(&addr), sizeof(addr)), ORBIS_OK); + EXPECT_EQ(sceNetListen(*listener, 4), ORBIS_OK); + OrbisNetSockaddrIn bound{}; + u32 len = sizeof(bound); + EXPECT_EQ(sceNetGetsockname(*listener, Guest(&bound), &len), ORBIS_OK); + return sceNetNtohs(bound.sin_port); + } + + static s32 GetInt(OrbisNetId s, s32 level, s32 name) { + s32 value = -1; + u32 len = sizeof(value); + EXPECT_EQ(sceNetGetsockopt(s, level, name, &value, &len), ORBIS_OK); + EXPECT_EQ(len, sizeof(value)); + return value; + } + + static s32 SetInt(OrbisNetId s, s32 level, s32 name, s32 value) { + return sceNetSetsockopt(s, level, name, &value, sizeof(value)); + } + struct ScopedPool { + s32 id = sceNetPoolCreate("pool", 16 * 1024, 0); + ~ScopedPool() { + EXPECT_EQ(sceNetPoolDestroy(id), ORBIS_OK); + } + }; +}; + +// --------------------------------------------------------------------------------------------- +// Helpers and error reporting +// --------------------------------------------------------------------------------------------- + +TEST_F(NetLib, ErrorCodesAndErrno) { + EXPECT_EQ(sceNetSocketClose(12345), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EBADF); + // sceNetSocket: "invalid socket type or protocol family" is EPROTONOSUPPORT. + EXPECT_EQ(sceNetSocket("bad", 99, ORBIS_NET_SOCK_STREAM, 0), ORBIS_NET_ERROR_EPROTONOSUPPORT); + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EPROTONOSUPPORT); + // The PS4 stack is IPv4-only. + EXPECT_EQ(sceNetSocket("v6", ORBIS_NET_AF_INET6, ORBIS_NET_SOCK_DGRAM, 0), + ORBIS_NET_ERROR_EPROTONOSUPPORT); + // A protocol that does not fit the type, and an over-long debug name. + EXPECT_EQ(sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, ORBIS_NET_IPPROTO_UDP), + ORBIS_NET_ERROR_BASE | ORBIS_NET_EPROTOTYPE); + const OrbisNetId tcp = + sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, ORBIS_NET_IPPROTO_TCP); + EXPECT_GT(tcp, 0); + sceNetSocketClose(tcp); + EXPECT_EQ(sceNetSocket("0123456789abcdefghijklmnopqrstuvwxyz", ORBIS_NET_AF_INET, + ORBIS_NET_SOCK_DGRAM, 0), + ORBIS_NET_ERROR_ENAMETOOLONG); + + // errno is per thread. + std::thread([] { EXPECT_EQ(*sceNetErrnoLoc(), 0); }).join(); +} + +TEST_F(NetLib, LogFormatting) { + // What the log shows for errors, addresses, options and epoll events. + EXPECT_EQ(ErrnoName(ORBIS_NET_EWOULDBLOCK), "EWOULDBLOCK"); + EXPECT_EQ(ErrnoName(ORBIS_NET_ECONNREFUSED), "ECONNREFUSED"); + EXPECT_EQ(ErrnoName(250), "E250"); + EXPECT_EQ(ErrorCodeName(ORBIS_NET_ERROR_RESOLVER_ENODNS), "RESOLVER_ENODNS"); + EXPECT_EQ(ErrorCodeName(0x12345), "0x12345"); + auto in = Loopback(8080, 3658); + EXPECT_EQ(FormatSockaddr(Guest(&in), sizeof(in)), "127.0.0.1:8080 vport 3658"); + in.sin_vport = 0; + EXPECT_EQ(FormatSockaddr(Guest(&in), sizeof(in)), "127.0.0.1:8080"); + EXPECT_EQ(FormatSockaddr(nullptr, 0), "null"); + EXPECT_EQ(SocketTypeName(ORBIS_NET_SOCK_DGRAM_P2P), "DGRAM_P2P"); + EXPECT_EQ(OptionName(ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO), "SOL_SOCKET/SO_NBIO"); + EXPECT_EQ(OptionName(ORBIS_NET_IPPROTO_TCP, 0x77), "IPPROTO_TCP/option 0x77"); + EXPECT_EQ(OptionName(0x55, 1), "level 0x55/option 0x1"); + EXPECT_EQ(EpollEventsName(ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLDESCID), "IN|DESCID"); + EXPECT_EQ(EpollEventsName(0), "0"); +} + +TEST_F(NetLib, InitAndTerm) { + EXPECT_EQ(sceNetInit(), ORBIS_OK); + EXPECT_EQ(sceNetTerm(), ORBIS_OK); + EXPECT_EQ(sceNetTerm(), ORBIS_OK); + EXPECT_EQ(sceNetInit(), ORBIS_OK); + // Nothing was released: sockets still work. + const OrbisNetId s = sceNetSocket("after", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + EXPECT_GT(s, 0); + sceNetSocketClose(s); +} + +TEST_F(NetLib, ErrorTableValues) { + EXPECT_EQ(ORBIS_NET_ERROR_ECANCELED, static_cast(0x80410155)); + EXPECT_EQ(ORBIS_NET_ECANCELED, 85); + EXPECT_EQ(ToOrbisErrno(Host::Error::Canceled), 85); + EXPECT_EQ(ORBIS_NET_ERROR_EBUSY, static_cast(0x80410110)); + EXPECT_EQ(ORBIS_NET_ERROR_RESOLVER_ENOTINIT, static_cast(0x804101ec)); + EXPECT_EQ(ORBIS_NET_ERROR_ENOTTY, static_cast(0x80410119)); + EXPECT_EQ(ORBIS_NET_ERROR_ENOTBLK, static_cast(0x8041010f)); +} + +TEST_F(NetLib, AbortWithNothingWaitingIsENOTBLK) { + const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + EXPECT_EQ(sceNetSocketAbort(s, 0), ORBIS_NET_ERROR_ENOTBLK); + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_ENOTBLK); + // With a preservation flag it is kept for the next call instead: not an error. + EXPECT_EQ(sceNetSocketAbort(s, 1 /* RCV_PRESERVATION */), ORBIS_OK); + char buf[4]; + EXPECT_EQ(sceNetRecv(s, buf, sizeof(buf), 0), ORBIS_NET_ERROR_EINTR); + + const OrbisNetId ep = sceNetEpollCreate("ep", 0); + EXPECT_EQ(sceNetEpollAbort(ep, 0), ORBIS_NET_ERROR_ENOTBLK); + EXPECT_EQ(sceNetEpollAbort(ep, 1 /* PRESERVATION */), ORBIS_OK); + sceNetEpollDestroy(ep); + sceNetSocketClose(s); +} + +TEST_F(NetLib, UnconnectedDatagramSendNeedsAnAddress) { + const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + EXPECT_EQ(sceNetSend(s, "x", 1, 0), ORBIS_NET_ERROR_EDESTADDRREQ); // every host alike + sceNetSocketClose(s); +} + +TEST_F(NetLib, ByteOrder) { + EXPECT_EQ(sceNetHtons(0x1234), 0x3412); + EXPECT_EQ(sceNetHtonl(0x12345678u), 0x78563412u); + EXPECT_EQ(sceNetHtonll(0x0102030405060708ull), 0x0807060504030201ull); + EXPECT_EQ(sceNetNtohll(sceNetHtonll(0xDEADBEEFCAFEF00Dull)), 0xDEADBEEFCAFEF00Dull); + EXPECT_EQ(sceNetNtohl(sceNetHtonl(42)), 42u); + EXPECT_EQ(sceNetNtohs(sceNetHtons(42)), 42); +} + +TEST_F(NetLib, InetPtonNtop) { + u32 v4 = 0; + EXPECT_EQ(sceNetInetPton(ORBIS_NET_AF_INET, "192.168.1.20", &v4), 1); + EXPECT_EQ(sceNetNtohl(v4), 0xC0A80114u); + EXPECT_EQ(sceNetInetPton(ORBIS_NET_AF_INET, "not an address", &v4), 0); + EXPECT_EQ(sceNetInetPton(99, "1.2.3.4", &v4), ORBIS_NET_ERROR_EAFNOSUPPORT); + + char text[64]; + EXPECT_STREQ(sceNetInetNtop(ORBIS_NET_AF_INET, &v4, text, sizeof(text)), "192.168.1.20"); + EXPECT_EQ(sceNetInetNtop(ORBIS_NET_AF_INET, &v4, text, 4), nullptr); // too small + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_ENOSPC); + + u8 v6[16]; + EXPECT_EQ(sceNetInetPton(ORBIS_NET_AF_INET6, "2001:db8::8:800:200c:417a", v6), 1); + EXPECT_STREQ(sceNetInetNtop(ORBIS_NET_AF_INET6, v6, text, sizeof(text)), + "2001:db8::8:800:200c:417a"); +} + +TEST_F(NetLib, RegisterLibRuns) { + Core::Loader::SymbolsResolver symbols; + RegisterLib(&symbols); // every NID registers without touching emulator state +} + +// --------------------------------------------------------------------------------------------- +// Native sockets +// --------------------------------------------------------------------------------------------- + +TEST_F(NetLib, TcpThroughExports) { + OrbisNetId listener = 0; + const u16 port = Listen(&listener); + + OrbisNetId accepted = -1; + OrbisNetSockaddrIn peer{}; + u32 peer_len = sizeof(peer); + std::thread server([&] { accepted = sceNetAccept(listener, Guest(&peer), &peer_len); }); + + const OrbisNetId client = sceNetSocket("client", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + auto to = Loopback(port); + to.sin_len = 0; // games often leave sa_len unset + ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK); + server.join(); + ASSERT_GT(accepted, 0); + + // The accepted peer address comes back in guest layout, sa_len included. + EXPECT_EQ(peer_len, sizeof(OrbisNetSockaddrIn)); + EXPECT_EQ(peer.sin_len, sizeof(OrbisNetSockaddrIn)); + EXPECT_EQ(peer.sin_family, ORBIS_NET_AF_INET); + EXPECT_EQ(peer.sin_addr, sceNetHtonl(0x7F000001)); + + OrbisNetSockaddrIn remote{}; + u32 remote_len = sizeof(remote); + ASSERT_EQ(sceNetGetpeername(client, Guest(&remote), &remote_len), ORBIS_OK); + EXPECT_EQ(sceNetNtohs(remote.sin_port), port); + + EXPECT_EQ(sceNetSend(client, "hello", 5, 0), 5); + char buf[16]{}; + EXPECT_EQ(sceNetRecv(accepted, buf, sizeof(buf), 0), 5); + EXPECT_EQ(std::memcmp(buf, "hello", 5), 0); + + EXPECT_EQ(sceNetShutdown(client, ORBIS_NET_SHUT_WR), ORBIS_OK); + EXPECT_EQ(sceNetRecv(accepted, buf, sizeof(buf), 0), 0); // end of stream + EXPECT_EQ(sceNetShutdown(client, 7), ORBIS_NET_ERROR_EINVAL); + + sceNetSocketClose(client); + sceNetSocketClose(accepted); + sceNetSocketClose(listener); +} + +TEST_F(NetLib, UdpWithAddresses) { + const OrbisNetId a = sceNetSocket("a", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + const OrbisNetId b = sceNetSocket("b", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + auto any = Loopback(0); + sceNetBind(a, Guest(&any), sizeof(any)); + sceNetBind(b, Guest(&any), sizeof(any)); + OrbisNetSockaddrIn a_addr{}, b_addr{}; + u32 len = sizeof(a_addr); + sceNetGetsockname(a, Guest(&a_addr), &len); + len = sizeof(b_addr); + sceNetGetsockname(b, Guest(&b_addr), &len); + + EXPECT_EQ(sceNetSendto(a, "datagram", 8, 0, Guest(&b_addr), sizeof(b_addr)), 8); + char buf[32]{}; + OrbisNetSockaddrIn from{}; + u32 from_len = sizeof(from); + // PEEK leaves the datagram queued. + EXPECT_EQ(sceNetRecvfrom(b, buf, sizeof(buf), ORBIS_NET_MSG_PEEK, Guest(&from), &from_len), 8); + EXPECT_EQ(sceNetRecvfrom(b, buf, sizeof(buf), 0, Guest(&from), &from_len), 8); + EXPECT_EQ(from.sin_port, a_addr.sin_port); + EXPECT_EQ(from.sin_len, sizeof(OrbisNetSockaddrIn)); + + // Nothing left: MSG_DONTWAIT and SO_NBIO both give EWOULDBLOCK. + EXPECT_EQ(sceNetRecvfrom(b, buf, sizeof(buf), ORBIS_NET_MSG_DONTWAIT, nullptr, nullptr), + ORBIS_NET_ERROR_EWOULDBLOCK); + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EWOULDBLOCK); + EXPECT_EQ(SetInt(b, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, 1), ORBIS_OK); + EXPECT_EQ(sceNetRecv(b, buf, sizeof(buf), 0), ORBIS_NET_ERROR_EWOULDBLOCK); + sceNetSocketClose(a); + sceNetSocketClose(b); +} + +TEST_F(NetLib, SocketOptions) { + const OrbisNetId s = sceNetSocket("opts", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + + // Guest-owned options. + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_TYPE), + static_cast(ORBIS_NET_SOCK_STREAM)); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO), 0); + SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, 1); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO), 1); + SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO, 250000); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO), 250000); + SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, 1); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR), 1); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ERROR), 0); + EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_TYPE, 2), + ORBIS_NET_ERROR_BASE | ORBIS_NET_ENOPROTOOPT); + + // Host options, mapped by name (IP_TTL is 4 on the PS4, 2 on Linux). + EXPECT_EQ(SetInt(s, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_TTL, 42), ORBIS_OK); + EXPECT_EQ(GetInt(s, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_TTL), 42); + EXPECT_EQ(SetInt(s, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_NODELAY, 1), ORBIS_OK); + EXPECT_NE(GetInt(s, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_NODELAY), 0); + EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF, 32768), ORBIS_OK); + EXPECT_GE(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 32768); + + OrbisNetLinger linger{1, 5}; + EXPECT_EQ( + sceNetSetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &linger, sizeof(linger)), + ORBIS_OK); + OrbisNetLinger back{}; + u32 len = sizeof(back); + EXPECT_EQ(sceNetGetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &back, &len), + ORBIS_OK); + EXPECT_NE(back.l_onoff, 0); + EXPECT_EQ(back.l_linger, 5); + + // Every documented option is known: anything else is ENOPROTOOPT, at every level. + EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, 0x7777, 1), ORBIS_NET_ERROR_ENOPROTOOPT); + EXPECT_EQ(SetInt(s, ORBIS_NET_IPPROTO_TCP, 0x77, 1), ORBIS_NET_ERROR_ENOPROTOOPT); + EXPECT_EQ(SetInt(s, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_KEEPLISTEN, 1), ORBIS_OK); + EXPECT_EQ(GetInt(s, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_KEEPLISTEN), 1); + EXPECT_EQ(SetInt(s, 12345, 1, 1), ORBIS_NET_ERROR_ENOPROTOOPT); + EXPECT_EQ(sceNetSetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, nullptr, 4), + ORBIS_NET_ERROR_EFAULT); + sceNetSocketClose(s); +} + +TEST_F(NetLib, StoredOptionsReadBack) { + const OrbisNetId s = sceNetSocket("opts", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + const struct { + s32 level; + s32 name; + s32 value; + } options[] = { + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_CONNECTTIMEO, 5000000}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ACCEPTTIMEO, 3000000}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, 1}, + {ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ONESBCAST, 1}, + {ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_TTLCHK, 1}, + {ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_MSS_TO_ADVERTISE, 1200}, + }; + for (const auto& o : options) { + EXPECT_EQ(GetInt(s, o.level, o.name), 0) << o.name; // unset reads as 0 + EXPECT_EQ(SetInt(s, o.level, o.name, o.value), ORBIS_OK) << o.name; + EXPECT_EQ(GetInt(s, o.level, o.name), o.value) << o.name; + } + // Timeouts: <= 0 disables them. + EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_CONNECTTIMEO, -5), ORBIS_OK); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_CONNECTTIMEO), 0); + // MSS to advertise: 0 or at least 216. + EXPECT_EQ(SetInt(s, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_MSS_TO_ADVERTISE, 100), + ORBIS_NET_ERROR_EINVAL); + sceNetSocketClose(s); +} + +TEST_F(NetLib, OptionRulesFollowTheReference) { + const OrbisNetId tcp = sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + const OrbisNetId udp = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + s32 value = 0; + u32 len = sizeof(value); + // "get not possible" / "set not possible": ENOPROTOOPT. + EXPECT_EQ(sceNetGetsockopt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NAME, &value, &len), + ORBIS_NET_ERROR_ENOPROTOOPT); + EXPECT_EQ(SetInt(tcp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MAXTTL, 5), + ORBIS_NET_ERROR_ENOPROTOOPT); + EXPECT_EQ(SetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ERROR_EX, 0), + ORBIS_NET_ERROR_ENOPROTOOPT); + EXPECT_EQ(GetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ERROR_EX), 0); + // Options for other socket kinds: EPROCUNAVAIL ... + EXPECT_EQ(SetInt(udp, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_NODELAY, 1), + ORBIS_NET_ERROR_EPROCUNAVAIL); + EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_KEEPALIVE, 1), + ORBIS_NET_ERROR_EPROCUNAVAIL); + EXPECT_EQ(SetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO, 1), + ORBIS_NET_ERROR_EPROCUNAVAIL); // P2P only + // ... except SO_BROADCAST, which "other socket types are not affected" by. + EXPECT_EQ(SetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_BROADCAST, 1), ORBIS_OK); + // Priority: only 8 to 23 may be set. + EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_PRIORITY, 7), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_PRIORITY, 23), ORBIS_OK); + // Buffers: at most 512 KiB. + EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF, 4 << 20), ORBIS_OK); + EXPECT_EQ(GetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 512 * 1024); + // Multicast TTL and loop take an unsigned char or an int. + const u8 ttl = 4; + EXPECT_EQ(sceNetSetsockopt(udp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MULTICAST_TTL, &ttl, 1), + ORBIS_OK); + EXPECT_EQ(GetInt(udp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MULTICAST_TTL), 4); + u8 loop = 7; + len = 1; + EXPECT_EQ(sceNetGetsockopt(udp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MULTICAST_LOOP, &loop, &len), + ORBIS_OK); + EXPECT_EQ(loop, 1); // default: loopback + // BSD SO_RCVTIMEO (libkernel setsockopt): struct timeval. + const s64 tv[2] = {1, 500000}; + EXPECT_EQ(sceNetSetsockopt(udp, ORBIS_NET_SOL_SOCKET, 0x1006, tv, sizeof(tv)), ORBIS_OK); + EXPECT_EQ(GetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO), 1500000); + s64 back[2]{}; + len = sizeof(back); + EXPECT_EQ(sceNetGetsockopt(udp, ORBIS_NET_SOL_SOCKET, 0x1006, back, &len), ORBIS_OK); + EXPECT_EQ(back[0], 1); + EXPECT_EQ(back[1], 500000); + // IP_DONTFRAG reaches the host and reads back. + EXPECT_EQ(SetInt(udp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_DONTFRAG, 1), ORBIS_OK); + EXPECT_EQ(GetInt(udp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_DONTFRAG), 1); + // Policy: 0-15 or ORBIS_NET_SOCK_POLICY_NA (-1). + EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_POLICY, ORBIS_NET_SOCK_POLICY_NA), + ORBIS_OK); + EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_POLICY, 16), ORBIS_NET_ERROR_EINVAL); + + // UDP_SND_ON_SUSPEND: UDP only, and not together with connect. + OrbisNetSockaddrIn dest = Loopback(9); + char payload[] = "bye"; + OrbisNetUdpSndOnSuspend suspend{1, Guest(&dest), sizeof(dest), payload, 3}; + EXPECT_EQ(sceNetSetsockopt(tcp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, &suspend, + sizeof(suspend)), + ORBIS_NET_ERROR_EPROCUNAVAIL); + suspend.datalen = 513; + EXPECT_EQ(sceNetSetsockopt(udp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, &suspend, + sizeof(suspend)), + ORBIS_NET_ERROR_EINVAL); + suspend.datalen = 3; + ASSERT_EQ(sceNetSetsockopt(udp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, &suspend, + sizeof(suspend)), + ORBIS_OK); + OrbisNetUdpSndOnSuspend back_suspend{}; + len = sizeof(back_suspend); + EXPECT_EQ(sceNetGetsockopt(udp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, + &back_suspend, &len), + ORBIS_OK); + EXPECT_EQ(back_suspend.onoff, 1); + EXPECT_EQ(sceNetConnect(udp, Guest(&dest), sizeof(dest)), ORBIS_NET_ERROR_EPROCUNAVAIL); + suspend.onoff = 0; + sceNetSetsockopt(udp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, &suspend, + sizeof(suspend)); + ASSERT_EQ(sceNetConnect(udp, Guest(&dest), sizeof(dest)), ORBIS_OK); + suspend.onoff = 1; + EXPECT_EQ(sceNetSetsockopt(udp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, &suspend, + sizeof(suspend)), + ORBIS_NET_ERROR_EPROCUNAVAIL); // after connect + sceNetSocketClose(tcp); + sceNetSocketClose(udp); +} + +TEST_F(NetLib, ConnectAndAcceptTimeouts) { + // A listener that never accepts, with a backlog of one, so the second connect hangs. + // Instead, an address nothing answers (TEST-NET-1): the SYN goes unanswered. + const OrbisNetId s = sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_CONNECTTIMEO, 200000), ORBIS_OK); + OrbisNetSockaddrIn nowhere = Loopback(9); + nowhere.sin_addr = sceNetHtonl(0xC0000201); // 192.0.2.1 + auto start = std::chrono::steady_clock::now(); + const s32 r = sceNetConnect(s, Guest(&nowhere), sizeof(nowhere)); + EXPECT_LT(r, 0); + // Where the host has no route (or something answers for it), the connect fails at once; + // only an unanswered SYN shows the timeout. + if (Millis(start) >= 100.0) { + EXPECT_EQ(r, ORBIS_NET_ERROR_EWOULDBLOCK); + EXPECT_LT(Millis(start), 3000.0); + } + sceNetSocketClose(s); + + OrbisNetId listener = 0; + Listen(&listener); + EXPECT_EQ(SetInt(listener, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ACCEPTTIMEO, 150000), ORBIS_OK); + start = std::chrono::steady_clock::now(); + EXPECT_EQ(sceNetAccept(listener, nullptr, nullptr), ORBIS_NET_ERROR_EWOULDBLOCK); + EXPECT_GE(Millis(start), 140.0); + // Connecting a listening socket is EOPNOTSUPP. + auto self = Loopback(1); + EXPECT_EQ(sceNetConnect(listener, Guest(&self), sizeof(self)), ORBIS_NET_ERROR_EOPNOTSUPP); + // TCP to the broadcast address: EACCES. + const OrbisNetId t = sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + auto broadcast = Loopback(80); + broadcast.sin_addr = ORBIS_NET_INADDR_BROADCAST; + EXPECT_EQ(sceNetConnect(t, Guest(&broadcast), sizeof(broadcast)), ORBIS_NET_ERROR_EACCES); + sceNetSocketClose(t); + sceNetSocketClose(listener); +} + +TEST_F(NetLib, WaitallPeeklenAndConnectedSendto) { + OrbisNetId listener = 0; + const u16 port = Listen(&listener); + const OrbisNetId client = sceNetSocket("c", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + auto to = Loopback(port); + ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK); + const OrbisNetId server = sceNetAccept(listener, nullptr, nullptr); + ASSERT_GT(server, 0); + + // MSG_WAITALL waits for the whole length even when it arrives in pieces. + std::thread writer([&] { + for (const char* piece : {"ab", "cd", "ef"}) { + std::this_thread::sleep_for(30ms); + sceNetSend(client, piece, 2, 0); + } + }); + char buf[8]{}; + EXPECT_EQ(sceNetRecv(server, buf, 6, ORBIS_NET_MSG_WAITALL), 6); + writer.join(); + EXPECT_STREQ(buf, "abcdef"); + // ... and returns what it has at FIN. + sceNetSend(client, "xyz", 3, 0); + sceNetShutdown(client, ORBIS_NET_SHUT_WR); + std::memset(buf, 0, sizeof(buf)); + EXPECT_EQ(sceNetRecv(server, buf, 8, ORBIS_NET_MSG_WAITALL), 3); + + // MSG_PEEKLEN: the size waiting, with no buffer. + const OrbisNetId udp = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + auto any = Loopback(0); + sceNetBind(udp, Guest(&any), sizeof(any)); + OrbisNetSockaddrIn self{}; + u32 len = sizeof(self); + sceNetGetsockname(udp, Guest(&self), &len); + sceNetSendto(udp, "12345", 5, 0, Guest(&self), sizeof(self)); + std::this_thread::sleep_for(20ms); + EXPECT_EQ(sceNetRecv(udp, nullptr, 1500, ORBIS_NET_MSG_PEEKLEN), 5); + EXPECT_EQ(sceNetRecv(udp, buf, sizeof(buf), 0), 5); // still there + + // A destination for a connected socket: EISCONN. + ASSERT_EQ(sceNetConnect(udp, Guest(&self), sizeof(self)), ORBIS_OK); + EXPECT_EQ(sceNetSendto(udp, "x", 1, 0, Guest(&self), sizeof(self)), ORBIS_NET_ERROR_EISCONN); + EXPECT_EQ(sceNetSend(udp, "x", 1, 0), 1); + sceNetSocketClose(udp); + sceNetSocketClose(server); + sceNetSocketClose(client); + sceNetSocketClose(listener); +} + +TEST_F(NetLib, SendPreservationAgain) { + // An aborted send that already moved data returns the partial count; with + // SND_PRESERVATION_AGAIN the send after it fails with EINTR once. + OrbisNetId listener = 0; + const u16 port = Listen(&listener); + const OrbisNetId client = sceNetSocket("c", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + auto to = Loopback(port); + ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK); + const OrbisNetId server = sceNetAccept(listener, nullptr, nullptr); // never reads + std::vector big(64 << 20); + s32 sent = 0; + std::thread sender([&] { sent = sceNetSend(client, big.data(), big.size(), 0); }); + std::this_thread::sleep_for(300ms); // the buffers fill, the send blocks + EXPECT_EQ(sceNetSocketAbort(client, 0x4 /* SND_PRESERVATION_AGAIN */), ORBIS_OK); + sender.join(); + EXPECT_GT(sent, 0); + EXPECT_LT(sent, static_cast(big.size())); + EXPECT_EQ(sceNetSend(client, "x", 1, ORBIS_NET_MSG_DONTWAIT), ORBIS_NET_ERROR_EINTR); + EXPECT_NE(sceNetSend(client, "x", 1, ORBIS_NET_MSG_DONTWAIT), ORBIS_NET_ERROR_EINTR); // once + sceNetSocketClose(server); + sceNetSocketClose(client); + sceNetSocketClose(listener); +} + +TEST_F(NetLib, ReservedPortsCannotBeBound) { + const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + for (const u16 port : {80, 5353, 8545, 9300, 45000}) { + auto addr = Loopback(port); + EXPECT_EQ(sceNetBind(s, Guest(&addr), sizeof(addr)), ORBIS_NET_ERROR_EACCES) << port; + } + auto ephemeral = Loopback(0); + EXPECT_EQ(sceNetBind(s, Guest(&ephemeral), sizeof(ephemeral)), ORBIS_OK); + sceNetSocketClose(s); +} + +TEST_F(NetLib, PS4BufferSizesAndDatagramLimit) { + const OrbisNetId tcp = sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + EXPECT_EQ(GetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF), 32768); + EXPECT_EQ(GetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 65536); + EXPECT_EQ(SetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF, 20000), ORBIS_OK); + EXPECT_EQ(GetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 20000); // not the host's + EXPECT_EQ(SetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF, 0), ORBIS_NET_ERROR_EINVAL); + sceNetSocketClose(tcp); + + const OrbisNetId udp = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + EXPECT_EQ(GetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF), 9216); + EXPECT_EQ(GetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 40960); + auto any = Loopback(0); + sceNetBind(udp, Guest(&any), sizeof(any)); + OrbisNetSockaddrIn self{}; + u32 len = sizeof(self); + sceNetGetsockname(udp, Guest(&self), &len); + + // A datagram larger than the send buffer fails with EMSGSIZE, as on FreeBSD. + std::vector big(9217); + EXPECT_EQ(sceNetSendto(udp, big.data(), 9216, 0, Guest(&self), sizeof(self)), 9216); + EXPECT_EQ(sceNetSendto(udp, big.data(), big.size(), 0, Guest(&self), sizeof(self)), + ORBIS_NET_ERROR_EMSGSIZE); + EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF, 20000), ORBIS_OK); + EXPECT_EQ(sceNetSendto(udp, big.data(), big.size(), 0, Guest(&self), sizeof(self)), + static_cast(big.size())); + sceNetSocketClose(udp); +} + +TEST_F(NetLib, DebugNamePolicyAndPriority) { + const OrbisNetId s = sceNetSocket("echo_server", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + // SO_NAME cannot be read back; sceNetGetSockInfo shows it. + OrbisNetSockInfo info{}; + ASSERT_EQ(sceNetGetSockInfo(s, &info, 1, 0), 1); + EXPECT_STREQ(info.name, "echo_server"); + + // At most 31 characters: a longer name is ENAMETOOLONG and leaves the old one. + const char long_name[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + EXPECT_EQ( + sceNetSetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NAME, long_name, sizeof(long_name)), + ORBIS_NET_ERROR_ENAMETOOLONG); + const char name31[] = "0123456789abcdefghijklmnopqrstu"; + ASSERT_EQ(sceNetSetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NAME, name31, sizeof(name31)), + ORBIS_OK); + sceNetGetSockInfo(s, &info, 1, 0); + EXPECT_STREQ(info.name, name31); + + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_POLICY), 0); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_PRIORITY), 16); + SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_POLICY, 3); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_POLICY), 3); + sceNetSocketClose(s); +} + +TEST_F(NetLib, AcceptedSocketsInheritTheListenersOptions) { + OrbisNetId listener; + const u16 port = Listen(&listener); + ASSERT_EQ(SetInt(listener, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO, 250000), ORBIS_OK); + ASSERT_EQ(SetInt(listener, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDTIMEO, 125000), ORBIS_OK); + ASSERT_EQ(SetInt(listener, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, 1), ORBIS_OK); + const OrbisNetId client = sceNetSocket("client", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + auto to = Loopback(port); + ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK); + const OrbisNetId accepted = sceNetAccept(listener, nullptr, nullptr); + ASSERT_GT(accepted, 0); + // As FreeBSD's sonewconn: timeouts and socket-level flags come from the listener. + EXPECT_EQ(GetInt(accepted, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO), 250000); + EXPECT_EQ(GetInt(accepted, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDTIMEO), 125000); + EXPECT_EQ(GetInt(accepted, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR), 1); + sceNetSocketClose(accepted); + sceNetSocketClose(client); + sceNetSocketClose(listener); +} + +TEST_F(NetLib, ReusePortIsItsOwnOption) { + const OrbisNetId a = sceNetSocket("a", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + const OrbisNetId b = sceNetSocket("b", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + ASSERT_EQ(SetInt(a, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, 1), ORBIS_OK); + ASSERT_EQ(SetInt(b, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, 1), ORBIS_OK); + EXPECT_EQ(GetInt(a, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT), 1); + EXPECT_EQ(GetInt(a, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR), 0); // untouched + // Both bind the same port (an explicit one: the PS4 reserves 40000 and up, where hosts + // hand out ephemeral ports). + auto bound = Loopback(static_cast(20000 + std::random_device{}() % 10000)); + ASSERT_EQ(sceNetBind(a, Guest(&bound), sizeof(bound)), ORBIS_OK); + EXPECT_EQ(sceNetBind(b, Guest(&bound), sizeof(bound)), ORBIS_OK); + // Without it, the port is taken. + const OrbisNetId c = sceNetSocket("c", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + EXPECT_EQ(sceNetBind(c, Guest(&bound), sizeof(bound)), ORBIS_NET_ERROR_EADDRINUSE); + sceNetSocketClose(a); + sceNetSocketClose(b); + sceNetSocketClose(c); +} + +TEST_F(NetLib, LingerOnCloseFollowsTheGuestMode) { + // A connected pair whose receiver never reads, and a sender with unsent data queued. + const auto unsent_pair = [&](OrbisNetId* listener, OrbisNetId* sender, OrbisNetId* receiver) { + const u16 port = Listen(listener); + // A small, fixed receive buffer (no autotuning), so the data really stays unsent. + SetInt(*listener, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF, 4096); + *sender = sceNetSocket("sender", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + SetInt(*sender, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF, 16 * 1024); + auto to = Loopback(port); + ASSERT_EQ(sceNetConnect(*sender, Guest(&to), sizeof(to)), ORBIS_OK); + *receiver = sceNetAccept(*listener, nullptr, nullptr); + ASSERT_GT(*receiver, 0); + SetInt(*sender, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, 1); + // Fill until the socket stays full across a pause. One EWOULDBLOCK is not enough: + // macOS keeps growing the receive buffer and drains the send queue right after. + std::vector chunk(64 * 1024, 'x'); + bool stuck = false; + for (int round = 0; round < 200 && !stuck; ++round) { + bool progressed = false; + for (int i = 0; i < 4096; ++i) { + if (sceNetSend(*sender, chunk.data(), chunk.size(), 0) < 0) { + break; + } + progressed = true; + } + ASSERT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EWOULDBLOCK); + stuck = !progressed && round > 0; + std::this_thread::sleep_for(20ms); + } + ASSERT_TRUE(stuck) << "the receiver kept taking data"; + }; + const OrbisNetLinger linger{1, 1}; + + // Non-blocking guest socket: close returns at once and the data goes out in the background. + OrbisNetId listener, sender, receiver; + unsent_pair(&listener, &sender, &receiver); + ASSERT_EQ(sceNetSetsockopt(sender, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &linger, + sizeof(linger)), + ORBIS_OK); + auto start = std::chrono::steady_clock::now(); + EXPECT_EQ(sceNetSocketClose(sender), ORBIS_OK); + EXPECT_LT(std::chrono::steady_clock::now() - start, 500ms); + sceNetSocketClose(receiver); + sceNetSocketClose(listener); + + // Blocking guest socket: close waits up to the linger time. + unsent_pair(&listener, &sender, &receiver); + SetInt(sender, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, 0); + ASSERT_EQ(sceNetSetsockopt(sender, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &linger, + sizeof(linger)), + ORBIS_OK); + start = std::chrono::steady_clock::now(); + EXPECT_EQ(sceNetSocketClose(sender), ORBIS_OK); + const auto waited = std::chrono::steady_clock::now() - start; + EXPECT_GE(waited, 500ms); + EXPECT_LT(waited, 3s); + sceNetSocketClose(receiver); + sceNetSocketClose(listener); +} + +TEST_F(NetLib, PoolIdsAndRetailOnlyFeatures) { + // Pool errors are returned without touching sce_net_errno. + *sceNetErrnoLoc() = 0; + EXPECT_EQ(sceNetPoolCreate("small", 4095, 0), ORBIS_NET_ERROR_EINVAL); // at least 4 KiB + EXPECT_EQ(sceNetPoolCreate("flags", 4096, 1), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(sceNetPoolCreate("0123456789abcdefghijklmnopqrstuvwxyz", 4096, 0), + ORBIS_NET_ERROR_ENAMETOOLONG); + EXPECT_EQ(*sceNetErrnoLoc(), 0); + + std::vector pools; + for (int i = 0; i < 32; ++i) { + pools.push_back(sceNetPoolCreate("pool", 4096, 0)); + } + EXPECT_EQ(pools.front(), 3000); + EXPECT_EQ(pools[30], 3030); + EXPECT_EQ(pools.back(), ORBIS_NET_ERROR_ENFILE); // at most 31 pools + pools.pop_back(); + EXPECT_EQ(sceNetPoolDestroy(3005), ORBIS_OK); + EXPECT_EQ(sceNetPoolDestroy(3005), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(sceNetPoolCreate("again", 4096, 0), 3005); // lowest free id + + // A pool with a resolver created from it is in use until the resolver is destroyed. + const OrbisNetId rid = sceNetResolverCreate("user", 3005, 0); + ASSERT_GT(rid, 0); + EXPECT_EQ(sceNetPoolDestroy(3005), ORBIS_NET_ERROR_ENOTEMPTY); + EXPECT_EQ(sceNetResolverDestroy(rid), ORBIS_OK); + for (const s32 id : pools) { + EXPECT_EQ(sceNetPoolDestroy(id), ORBIS_OK); + } + + // Network emulation is not available on retail units. + EXPECT_EQ(sceNetEmulationSet(), ORBIS_NET_ERROR_EOPNOTSUPP); + EXPECT_EQ(sceNetEmulationGet(), ORBIS_NET_ERROR_EOPNOTSUPP); +} + +TEST_F(NetLib, RecvTimeoutAndAbort) { + const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + auto any = Loopback(0); + sceNetBind(s, Guest(&any), sizeof(any)); + char buf[4]; + + SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO, 200000); // microseconds + auto start = std::chrono::steady_clock::now(); + EXPECT_EQ(sceNetRecv(s, buf, sizeof(buf), 0), ORBIS_NET_ERROR_EWOULDBLOCK); + EXPECT_GE(Millis(start), 190.0); + EXPECT_LT(Millis(start), 2000.0); + + SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO, 0); + std::thread aborter([&] { + std::this_thread::sleep_for(100ms); + EXPECT_EQ(sceNetSocketAbort(s, 1 /* RCV_PRESERVATION */), ORBIS_OK); + }); + start = std::chrono::steady_clock::now(); + EXPECT_EQ(sceNetRecv(s, buf, sizeof(buf), 0), ORBIS_NET_ERROR_EINTR); + EXPECT_LT(Millis(start), 2000.0); + aborter.join(); + sceNetSocketClose(s); +} + +TEST_F(NetLib, EpollThroughExports) { + const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + auto any = Loopback(0); + sceNetBind(s, Guest(&any), sizeof(any)); + OrbisNetSockaddrIn self{}; + u32 len = sizeof(self); + sceNetGetsockname(s, Guest(&self), &len); + + const OrbisNetId ep = sceNetEpollCreate("ep", 0); + ASSERT_GT(ep, 0); + OrbisNetEpollEvent ev{}; + ev.events = ORBIS_NET_EPOLLIN; + ev.data.data_u64 = 0xC0FFEE; + ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_OK); + EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_NET_ERROR_EEXIST); + EXPECT_EQ(sceNetEpollControl(ep, 9, s, &ev), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_MOD, s, nullptr), ORBIS_NET_ERROR_EINVAL); + + OrbisNetEpollEvent out[4]{}; + EXPECT_EQ(sceNetEpollWait(ep, out, 4, 10000), 0); // 10 ms, nothing yet + sceNetSendto(s, "x", 1, 0, Guest(&self), sizeof(self)); + ASSERT_EQ(sceNetEpollWait(ep, out, 4, 1000000), 1); + EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLIN); + EXPECT_EQ(out[0].ident, static_cast(s)); + EXPECT_EQ(out[0].data.data_u64, 0xC0FFEEu); + EXPECT_EQ(sceNetEpollWait(ep, out, 0, 0), ORBIS_NET_ERROR_EINVAL); + + std::thread aborter([&] { + std::this_thread::sleep_for(100ms); + sceNetEpollAbort(ep, 1 /* PRESERVATION */); + }); + char buf[4]; + sceNetRecv(s, buf, sizeof(buf), 0); // drain, so the wait below would block forever + EXPECT_EQ(sceNetEpollWait(ep, out, 4, -1), ORBIS_NET_ERROR_EINTR); + aborter.join(); + + EXPECT_EQ(sceNetEpollDestroy(ep), ORBIS_OK); + EXPECT_EQ(sceNetEpollDestroy(ep), ORBIS_NET_ERROR_EBADF); + sceNetSocketClose(s); +} + +TEST_F(NetLib, EpollErrorCodesMatchThePS4) { + EXPECT_EQ(sceNetEpollCreate("flags", 1), ORBIS_NET_ERROR_EINVAL); // no flags defined + + const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + const OrbisNetId ep = sceNetEpollCreate("ep", 0); + ASSERT_GT(ep, 0); + OrbisNetEpollEvent ev{}; + ev.events = ORBIS_NET_EPOLLIN; + + // DEL of an unregistered id: EBADF, where Linux would say ENOENT. + EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_DEL, s, nullptr), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EBADF); + // MOD of an unregistered id registers it + EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_MOD, s, &ev), ORBIS_OK); + EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_DEL, s, nullptr), ORBIS_OK); + // ADD/MOD need event bits. + OrbisNetEpollEvent none{}; + EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &none), ORBIS_NET_ERROR_EINVAL); + + // Delete takes no event. + ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_OK); + EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_DEL, s, &ev), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_DEL, s, nullptr), ORBIS_OK); + + sceNetEpollDestroy(ep); + sceNetSocketClose(s); +} + +TEST_F(NetLib, EpollIdKindsNamesAndAbortFlags) { + const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + const OrbisNetId ep = sceNetEpollCreate("ep", 0); + const OrbisNetId other = sceNetEpollCreate("other", 0); + ASSERT_GT(ep, 0); + ASSERT_GT(other, 0); + EXPECT_EQ(sceNetEpollCreate("0123456789abcdefghijklmnopqrstuvwxyz", 0), + ORBIS_NET_ERROR_ENAMETOOLONG); + + OrbisNetEpollEvent ev{}; + ev.events = ORBIS_NET_EPOLLIN; + // Another epoll cannot be waited on; a socket is not an epoll; a free id is nothing. + EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, other, &ev), ORBIS_NET_ERROR_EPERM); + EXPECT_EQ(sceNetEpollControl(s, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, 12345, &ev), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(sceNetEpollDestroy(s), ORBIS_NET_ERROR_EPERM); + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EPERM); + OrbisNetEpollEvent out[2]{}; + EXPECT_EQ(sceNetEpollWait(s, out, 2, 0), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(sceNetEpollAbort(s, 0), ORBIS_NET_ERROR_EBADF); + + // Only ORBIS_NET_EPOLL_ABORT_FLAG_PRESERVATION; with it, the next wait fails even though an + // event is there. + EXPECT_EQ(sceNetEpollAbort(ep, 2), ORBIS_NET_ERROR_EINVAL); + ev.events = ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLOUT; + ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_OK); + auto any = Loopback(0); + sceNetBind(s, Guest(&any), sizeof(any)); + OrbisNetSockaddrIn self{}; + u32 len = sizeof(self); + sceNetGetsockname(s, Guest(&self), &len); + sceNetSendto(s, "x", 1, 0, Guest(&self), sizeof(self)); + std::this_thread::sleep_for(20ms); + EXPECT_EQ(sceNetEpollAbort(ep, 1), ORBIS_OK); + EXPECT_EQ(sceNetEpollWait(ep, out, 2, 0), ORBIS_NET_ERROR_EINTR); + // A UDP socket reports OUT too, it is always writable. + ASSERT_EQ(sceNetEpollWait(ep, out, 2, 1'000'000), 1); + EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLOUT); + + sceNetEpollDestroy(other); + sceNetEpollDestroy(ep); + sceNetSocketClose(s); +} + +TEST_F(NetLib, EpollReportsAbortedSocketsAsHangup) { + const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + auto any = Loopback(0); + sceNetBind(s, Guest(&any), sizeof(any)); + const OrbisNetId ep = sceNetEpollCreate("ep", 0); + OrbisNetEpollEvent ev{}; + ev.events = ORBIS_NET_EPOLLIN; + ev.data.data_u64 = 7; + ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_OK); + + // Nobody waiting anywhere: nothing to abort. + EXPECT_EQ(sceNetSocketAbort(s, 0), ORBIS_NET_ERROR_ENOTBLK); + + std::atomic abort_result{1}; + std::thread aborter([&] { + std::this_thread::sleep_for(100ms); + abort_result = sceNetSocketAbort(s, 0); + }); + OrbisNetEpollEvent out[2]{}; + ASSERT_EQ(sceNetEpollWait(ep, out, 2, 5'000'000), 1); + aborter.join(); + EXPECT_EQ(abort_result.load(), ORBIS_OK); // the epoll wait counts as waiting + EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLHUP); + EXPECT_EQ(out[0].ident, static_cast(s)); + EXPECT_EQ(out[0].data.data_u64, 7u); + // Reported once; the registration keeps working. + EXPECT_EQ(sceNetEpollWait(ep, out, 2, 10'000), 0); + + sceNetEpollDestroy(ep); + sceNetSocketClose(s); +} + +TEST_F(NetLib, ResolverNeedsEpollInToBeReported) { + ScopedPool pool; + const OrbisNetId rid = sceNetResolverCreate("resolver", pool.id, 0); + const OrbisNetId ep = sceNetEpollCreate("ep", 0); + OrbisNetEpollEvent ev{}; + ev.events = ORBIS_NET_EPOLLOUT; // ignored for a resolver: nothing is waited for + ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, rid, &ev), ORBIS_OK); + OrbisNetInAddr addr{}; + ASSERT_EQ(sceNetResolverStartNtoa(rid, "127.0.0.1", &addr, 0, 0, ORBIS_NET_RESOLVER_ASYNC), + ORBIS_OK); + OrbisNetEpollEvent out[2]{}; + EXPECT_EQ(sceNetEpollWait(ep, out, 2, 200'000), 0); + // Asking for ORBIS_NET_EPOLLIN afterwards reports the finished lookup. + ev.events = ORBIS_NET_EPOLLIN; + ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_MOD, rid, &ev), ORBIS_OK); + ASSERT_EQ(sceNetEpollWait(ep, out, 2, 5'000'000), 1); + EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLDESCID); + EXPECT_EQ(sceNetEpollWait(ep, out, 2, 10'000), 0); // once + sceNetEpollDestroy(ep); + sceNetResolverDestroy(rid); +} + +TEST_F(NetLib, SendmsgGathersAndRecvmsgScatters) { + const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + auto any = Loopback(0); + sceNetBind(s, Guest(&any), sizeof(any)); + OrbisNetSockaddrIn self{}; + u32 len = sizeof(self); + sceNetGetsockname(s, Guest(&self), &len); + + char a[] = "hello ", b[] = "world"; + OrbisNetIovec out_iov[] = {{a, 6}, {b, 5}}; + OrbisNetMsghdr out{}; + out.msg_name = &self; + out.msg_namelen = sizeof(self); + out.msg_iov = out_iov; + out.msg_iovlen = 2; + ASSERT_EQ(sceNetSendmsg(s, &out, 0), 11); // one datagram + + char x[4]{}, y[16]{}; + OrbisNetIovec in_iov[] = {{x, 4}, {y, sizeof(y)}}; + OrbisNetSockaddrIn from{}; + OrbisNetMsghdr in{}; + in.msg_name = &from; + in.msg_namelen = sizeof(from); + in.msg_iov = in_iov; + in.msg_iovlen = 2; + in.msg_controllen = 99; + ASSERT_EQ(sceNetRecvmsg(s, &in, 0), 11); + EXPECT_EQ(std::string(x, 4), "hell"); + EXPECT_STREQ(y, "o world"); + EXPECT_EQ(in.msg_namelen, sizeof(from)); + EXPECT_EQ(from.sin_port, self.sin_port); + EXPECT_EQ(in.msg_controllen, 99u); // "not used": left alone + + in.msg_iovlen = -1; + EXPECT_EQ(sceNetRecvmsg(s, &in, 0), ORBIS_NET_ERROR_EINVAL); + in.msg_iovlen = 1025; // at most 1024 elements + EXPECT_EQ(sceNetRecvmsg(s, &in, 0), ORBIS_NET_ERROR_EMSGSIZE); + in.msg_iovlen = 2; + EXPECT_EQ(sceNetRecvmsg(s, &in, ORBIS_NET_MSG_PEEKLEN), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(sceNetSendmsg(s, nullptr, 0), ORBIS_NET_ERROR_EFAULT); + sceNetSocketClose(s); +} + +TEST_F(NetLib, UnixDatagramSockets) { +#ifdef _WIN32 + GTEST_SKIP() << "Windows has no AF_UNIX datagram sockets"; +#else + const std::string path = "/tmp/shadps4_net_test_" + std::to_string(::getpid()); + ::unlink(path.c_str()); + OrbisNetSockaddrUn addr{}; + addr.sun_len = sizeof(addr); + addr.sun_family = ORBIS_NET_AF_UNIX; + std::strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1); + auto* guest = reinterpret_cast(&addr); + + const OrbisNetId server = sceNetSocket("unix", ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_DGRAM, 0); + ASSERT_GT(server, 0); + ASSERT_EQ(sceNetBind(server, guest, sizeof(addr)), ORBIS_OK); + const OrbisNetId client = sceNetSocket("unix", ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_DGRAM, 0); + EXPECT_EQ(sceNetSendto(client, "hi", 2, 0, guest, sizeof(addr)), 2); + char buf[8]{}; + EXPECT_EQ(sceNetRecv(server, buf, sizeof(buf), 0), 2); + EXPECT_STREQ(buf, "hi"); + + OrbisNetSockaddrUn name{}; + u32 len = sizeof(name); + ASSERT_EQ(sceNetGetsockname(server, reinterpret_cast(&name), &len), + ORBIS_OK); + EXPECT_EQ(name.sun_family, ORBIS_NET_AF_UNIX); + EXPECT_EQ(std::string(name.sun_path), path); + EXPECT_EQ(sceNetSocket("unix", ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_DGRAM_P2P, 0), + ORBIS_NET_ERROR_EPROTONOSUPPORT); + sceNetSocketClose(client); + sceNetSocketClose(server); + ::unlink(path.c_str()); +#endif +} + +TEST_F(NetLib, SockInfoDescribesSockets) { + // No buffer and s < 0: the number of sockets. + EXPECT_EQ(sceNetGetSockInfo(-1, nullptr, 0, 0), 0); + const OrbisNetId udp = sceNetSocket("sockinfo_udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + auto any = Loopback(0); + sceNetBind(udp, Guest(&any), sizeof(any)); + SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, 1); + const OrbisNetId tcp = + sceNetSocket("sockinfo_tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + EXPECT_EQ(sceNetGetSockInfo(-1, nullptr, 0, 0), 2); + + OrbisNetSockInfo info{}; + ASSERT_EQ(sceNetGetSockInfo(udp, &info, 1, 0), 1); + EXPECT_STREQ(info.name, "sockinfo_udp"); + EXPECT_EQ(info.s, udp); + EXPECT_EQ(info.socket_type, ORBIS_NET_SOCK_DGRAM); + EXPECT_EQ(info.local_adr.inaddr_addr, sceNetHtonl(0x7F000001)); + EXPECT_NE(info.local_port, 0); + EXPECT_EQ(info.flags, ORBIS_NET_SOCKINFO_F_SELF | ORBIS_NET_SOCKINFO_F_NONBLOCK); + EXPECT_EQ(info.recv_buffer_size, 40960); + EXPECT_EQ(info.state, ORBIS_NET_SOCKINFO_STATE_OPENED); + + OrbisNetSockInfo all[4]{}; + ASSERT_EQ(sceNetGetSockInfo(-1, all, 4, 0), 2); + EXPECT_EQ(all[1].s, tcp); + EXPECT_EQ(all[1].socket_type, ORBIS_NET_SOCK_STREAM); + EXPECT_EQ(sceNetGetSockInfo(-1, all, 1, 0), 1); // no more than asked for + + EXPECT_EQ(sceNetGetSockInfo(9999, &info, 1, 0), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(sceNetGetSockInfo(udp, &info, 0, 0), ORBIS_NET_ERROR_EINVAL); + sceNetSocketClose(udp); + sceNetSocketClose(tcp); +} + +TEST_F(NetLib, ResolverLooksUpNames) { + ScopedPool pool; + const OrbisNetId rid = sceNetResolverCreate("resolver", pool.id, 0); + ASSERT_GT(rid, 0); + EXPECT_EQ(sceNetResolverCreate("bad", pool.id, 1), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(sceNetResolverCreate("bad", 0, 0), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(sceNetResolverCreate("bad", 3099, 0), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(sceNetResolverCreate("0123456789abcdef0123456789abcdef", pool.id, 0), + ORBIS_NET_ERROR_ENAMETOOLONG); + EXPECT_EQ(sceNetShowRouteWithMemory(0), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(sceNetShowRouteWithMemory(3099), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(sceNetShowRouteWithMemory(pool.id), ORBIS_OK); + + // Synchronous: a numeric address needs no DNS. + OrbisNetInAddr addr{}; + ASSERT_EQ(sceNetResolverStartNtoa(rid, "127.0.0.1", &addr, 0, 0, 0), ORBIS_OK); + EXPECT_EQ(addr.inaddr_addr, sceNetHtonl(0x7F000001)); + OrbisNetResolverInfo info{}; + ASSERT_EQ(sceNetResolverStartNtoaMultipleRecords(rid, "127.0.0.2", &info, 0, 0, 0), ORBIS_OK); + EXPECT_EQ(info.records, 1u); + EXPECT_EQ(info.recordsv4, 1u); + EXPECT_EQ(info.addrs[0].af, static_cast(ORBIS_NET_AF_INET)); + EXPECT_EQ(info.addrs[0].u.addr.inaddr_addr, sceNetHtonl(0x7F000002)); + + // Asynchronous: completion shows up in an epoll as ORBIS_NET_EPOLLDESCID. + const OrbisNetId ep = sceNetEpollCreate("resolver", 0); + OrbisNetEpollEvent ev{}; + ev.events = ORBIS_NET_EPOLLIN; + ev.data.data_u64 = 42; + ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, rid, &ev), ORBIS_OK); + addr = {}; + ASSERT_EQ(sceNetResolverStartNtoa(rid, "127.0.0.3", &addr, 0, 0, ORBIS_NET_RESOLVER_ASYNC), + ORBIS_OK); + OrbisNetEpollEvent out[2]{}; + ASSERT_EQ(sceNetEpollWait(ep, out, 2, 5'000'000), 1); + EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLDESCID); + EXPECT_EQ(out[0].ident, static_cast(rid)); + EXPECT_EQ(out[0].data.data_u64, 42u); + s32 status = -1; + ASSERT_EQ(sceNetResolverGetError(rid, &status), ORBIS_OK); + EXPECT_EQ(status, 0); + EXPECT_EQ(addr.inaddr_addr, sceNetHtonl(0x7F000003)); + EXPECT_EQ(sceNetResolverGetError(rid, nullptr), ORBIS_NET_ERROR_EINVAL); + + // Offline, names fail with ENODNS and the status says so. Addresses, "localhost" and a + // trailing dot are handled locally, unless DISABLE_IPADDRESS sends an address to DNS. + SetSystemHooks({.is_online = [] { return false; }}); + EXPECT_EQ(sceNetResolverStartNtoa(rid, "example.com", &addr, 0, 0, 0), + ORBIS_NET_ERROR_RESOLVER_ENODNS); + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_RESOLVER_ENODNS); + sceNetResolverGetError(rid, &status); + EXPECT_EQ(status, ORBIS_NET_ERROR_RESOLVER_ENODNS); + ASSERT_EQ(sceNetResolverStartNtoa(rid, "10.1.2.3", &addr, 0, 0, 0), ORBIS_OK); + EXPECT_EQ(addr.inaddr_addr, sceNetHtonl(0x0A010203)); + ASSERT_EQ(sceNetResolverStartNtoa(rid, "localhost.", &addr, 0, 0, 0), ORBIS_OK); + EXPECT_EQ(addr.inaddr_addr, sceNetHtonl(ORBIS_NET_INADDR_LOOPBACK)); + EXPECT_EQ(sceNetResolverStartNtoa(rid, "10.1.2.3", &addr, 0, 0, + ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS), + ORBIS_NET_ERROR_RESOLVER_ENODNS); + EXPECT_EQ(sceNetResolverStartNtoa(rid, "", &addr, 0, 0, 0), ORBIS_NET_ERROR_EINVAL); + OrbisNetResolverInfo records{}; + EXPECT_EQ(sceNetResolverStartNtoaMultipleRecordsEx(rid, "10.1.2.3", &records, 0, 0, 0x2000000), + ORBIS_OK); + EXPECT_EQ(records.records, 1u); + EXPECT_EQ(sceNetResolverStartNtoaMultipleRecordsEx(rid, "10.1.2.3", &records, 0, 0, 0x1000000), + ORBIS_NET_ERROR_RESOLVER_ENORECORD); + EXPECT_EQ(sceNetResolverStartNtoaMultipleRecordsEx(rid, "10.1.2.3", &records, 0, 0, 0x4), + ORBIS_NET_ERROR_EINVAL); + SetSystemHooks({}); + + // Destroying removes it from the epoll and frees the id. + EXPECT_EQ(sceNetResolverDestroy(rid), ORBIS_OK); + EXPECT_EQ(sceNetResolverDestroy(rid), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(sceNetResolverStartNtoa(rid, "127.0.0.1", &addr, 0, 0, 0), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_DEL, rid, nullptr), ORBIS_NET_ERROR_EBADF); + sceNetEpollDestroy(ep); +} + +TEST_F(NetLib, EtherPoolsAndStatistics) { + OrbisNetEtherAddr mac{}; + ASSERT_EQ(sceNetEtherStrton("00:1A:2b:3c:4D:5e", &mac), ORBIS_OK); + EXPECT_EQ(mac.data[1], 0x1a); + EXPECT_EQ(mac.data[5], 0x5e); + char text[ORBIS_NET_ETHER_ADDRSTRLEN]{}; + ASSERT_EQ(sceNetEtherNtostr(&mac, text, sizeof(text)), ORBIS_OK); + EXPECT_STREQ(text, "00:1a:2b:3c:4d:5e"); + EXPECT_EQ(sceNetEtherNtostr(&mac, text, 17), ORBIS_NET_ERROR_ENOSPC); + EXPECT_EQ(sceNetEtherStrton("00:1a:2b", &mac), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(sceNetEtherStrton("00:1a:2b:3c:4d:5e:6f", &mac), ORBIS_NET_ERROR_EINVAL); + + const s32 pool = sceNetPoolCreate("pool", 64 * 1024, 0); + OrbisNetMemoryPoolStats stats{}; + ASSERT_EQ(sceNetGetMemoryPoolStats(pool, &stats), ORBIS_OK); + EXPECT_EQ(stats.pool_size, 64u * 1024u); + EXPECT_EQ(stats.current_inuse_size, 0u); + EXPECT_EQ(sceNetGetMemoryPoolStats(pool, nullptr), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EINVAL); + sceNetPoolDestroy(pool); + EXPECT_EQ(sceNetGetMemoryPoolStats(pool, &stats), ORBIS_NET_ERROR_EBADF); + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EBADF); + + OrbisNetStatisticsInfo info{}; + ASSERT_EQ(sceNetGetStatisticsInfo(&info, 0), ORBIS_OK); + EXPECT_GT(info.kernel_mem_free_size, 0); + EXPECT_GT(info.libnet_mem_free_size, 0); +} + +TEST_F(NetLib, EpollReportsPeerCloseAsReadable) { + OrbisNetId listener = 0; + const u16 port = Listen(&listener); + OrbisNetId accepted = -1; + std::thread server([&] { accepted = sceNetAccept(listener, nullptr, nullptr); }); + const OrbisNetId client = sceNetSocket("client", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + auto to = Loopback(port); + ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK); + server.join(); + ASSERT_GT(accepted, 0); + + const OrbisNetId ep = sceNetEpollCreate("ep", 0); + OrbisNetEpollEvent ev{}; + ev.events = ORBIS_NET_EPOLLIN; + ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, accepted, &ev), ORBIS_OK); + + // The PS4 epoll is kqueue read/write filters: the peer closing shows up as IN only, and + // the read returns the end of stream. + sceNetSocketClose(client); + OrbisNetEpollEvent out[2]{}; + ASSERT_EQ(sceNetEpollWait(ep, out, 2, 2'000'000), 1); + EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLIN); + char buf[4]; + EXPECT_EQ(sceNetRecv(accepted, buf, sizeof(buf), 0), 0); + + sceNetEpollDestroy(ep); + sceNetSocketClose(accepted); + sceNetSocketClose(listener); +} + +TEST_F(NetLib, SmallHelpers) { + u32 random = 0; + EXPECT_EQ(sceNetGetRandom(&random), ORBIS_OK); + EXPECT_EQ(sceNetGetRandom(nullptr), ORBIS_NET_ERROR_EINVAL); + + u64 t1 = 0; + u64 t2 = 0; + sceNetGetSystemTime(&t1); + std::this_thread::sleep_for(2ms); + sceNetGetSystemTime(&t2); + EXPECT_GT(t2, t1); + sceNetGetSystemTime(nullptr); // ignored + + EXPECT_STREQ(sceNetGetIfName(0), "lo0"); + EXPECT_STREQ(sceNetGetIfName(1), "eth0"); + EXPECT_STREQ(sceNetGetIfName(10), ""); + EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EINVAL); + + EXPECT_EQ(sceNetMemoryAllocate(0, 0), nullptr); + auto* zeroed = static_cast(sceNetMemoryAllocate(64, 2)); + ASSERT_NE(zeroed, nullptr); + EXPECT_EQ(std::count(zeroed, zeroed + 64, u8{0}), 64); + sceNetMemoryFree(zeroed); + + EXPECT_EQ(sceNetUsleep(-1), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(sceNetUsleep(10), ORBIS_OK); +} + +TEST_F(NetLib, IoctlAndAddressEdgeCasesOnNativeSockets) { + const OrbisNetId udp = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + auto any = Loopback(0); + ASSERT_EQ(sceNetBind(udp, Guest(&any), sizeof(any)), ORBIS_OK); + OrbisNetSockaddrIn self{}; + u32 len = sizeof(self); + sceNetGetsockname(udp, Guest(&self), &len); + + // FIONBIO and FIONREAD reach the socket like they would the kernel. + s32 on = 1; + ASSERT_EQ(sceNetIoctl(udp, 0x8004667e, &on), ORBIS_OK); + EXPECT_EQ(GetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO), 1); + s32 ready = -1; + ASSERT_EQ(sceNetIoctl(udp, 0x4004667f, &ready), ORBIS_OK); + EXPECT_EQ(ready, 0); + sceNetSendto(udp, "12345", 5, 0, Guest(&self), sizeof(self)); + std::this_thread::sleep_for(20ms); + ASSERT_EQ(sceNetIoctl(udp, 0x4004667f, &ready), ORBIS_OK); + EXPECT_GE(ready, 5); + EXPECT_EQ(sceNetIoctl(udp, 0x12345678, &ready), ORBIS_NET_ERROR_EINVAL); + + // An address buffer without a length pointer is ignored, not EFAULT. + char buf[8]; + OrbisNetSockaddrIn from{}; + EXPECT_EQ(sceNetRecvfrom(udp, buf, sizeof(buf), 0, Guest(&from), nullptr), 5); + sceNetSocketClose(udp); + + // A connected stream ignores a destination address. + OrbisNetId listener = 0; + const u16 port = Listen(&listener); + OrbisNetId accepted = -1; + std::thread server([&] { accepted = sceNetAccept(listener, nullptr, nullptr); }); + const OrbisNetId client = sceNetSocket("client", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0); + auto to = Loopback(port); + ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK); + server.join(); + ASSERT_GT(accepted, 0); + EXPECT_EQ(sceNetSendto(client, "hi", 2, 0, Guest(&to), sizeof(to)), 2); + EXPECT_EQ(sceNetRecv(accepted, buf, sizeof(buf), 0), 2); + sceNetSocketClose(client); + sceNetSocketClose(accepted); + sceNetSocketClose(listener); +} + +TEST_F(NetLib, DnsInfoOverride) { + std::array dns{}; + EXPECT_EQ(sceNetGetDnsInfo(nullptr, 0), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(sceNetGetDnsInfo(dns.data(), 1), ORBIS_NET_ERROR_EINVAL); + + const std::array mine{sceNetHtonl(0x0A000001), 0}; + EXPECT_EQ(sceNetSetDnsInfo(mine.data(), 0), ORBIS_OK); + EXPECT_EQ(sceNetGetDnsInfo(dns.data(), 0), 1); // the count + EXPECT_EQ(dns, mine); + const std::array no_primary{0, sceNetHtonl(0x0A000001)}; + EXPECT_EQ(sceNetSetDnsInfo(no_primary.data(), 0), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(sceNetSetDnsInfo(nullptr, 0), ORBIS_OK); // clears it + + std::array dns6{}; + std::array mine6{}; + mine6[0] = 0x20; + mine6[15] = 1; + EXPECT_EQ(sceNetGetDns6Info(dns6.data(), 0), 0); + EXPECT_EQ(sceNetSetDns6Info(mine6.data(), 0), ORBIS_OK); + EXPECT_EQ(sceNetGetDns6Info(dns6.data(), 0), 1); + EXPECT_EQ(dns6, mine6); + EXPECT_EQ(sceNetSetDns6Info(nullptr, 0), ORBIS_OK); +} + +TEST_F(NetLib, ResolverReverseLookupAndPreservedAbort) { + ScopedPool pool; + const OrbisNetId rid = sceNetResolverCreate("aton", pool.id, 0); + ASSERT_GT(rid, 0); + // A preserved abort fails the next lookup of its kind only. + EXPECT_EQ(sceNetResolverAbort(rid, ORBIS_NET_RESOLVER_ABORT_FLAG_ATON_PRESERVATION), ORBIS_OK); + OrbisNetInAddr addr{}; + EXPECT_EQ(sceNetResolverStartNtoa(rid, "127.0.0.1", &addr, 0, 0, 0), ORBIS_OK); + const OrbisNetInAddr loopback{sceNetHtonl(ORBIS_NET_INADDR_LOOPBACK)}; + char name[256]{}; + EXPECT_EQ(sceNetResolverStartAton(rid, &loopback, name, sizeof(name), 0, 0, 0), + ORBIS_NET_ERROR_EINTR); + // Then the reverse lookup works: 127.0.0.1 is "localhost", answered locally. + ASSERT_EQ(sceNetResolverStartAton(rid, &loopback, name, sizeof(name), 0, 0, 0), ORBIS_OK); + EXPECT_STREQ(name, "localhost"); + char tiny[2]{}; + EXPECT_EQ(sceNetResolverStartAton(rid, &loopback, tiny, sizeof(tiny), 0, 0, 0), + ORBIS_NET_ERROR_RESOLVER_ENOSPACE); + // Host names longer than 255 characters are refused. + const std::string long_name(256, 'a'); + EXPECT_EQ(sceNetResolverStartNtoa(rid, long_name.c_str(), &addr, 0, 0, 0), + ORBIS_NET_ERROR_EINVAL); + sceNetResolverDestroy(rid); +} + +TEST_F(NetLib, MacAddressComesFromTheHost) { + OrbisNetEtherAddr mac{}; + EXPECT_EQ(sceNetGetMacAddress(nullptr, 0), ORBIS_NET_ERROR_EINVAL); + SetSystemHooks({.mac_address = [](std::array* out) { + *out = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + return true; + }}); + ASSERT_EQ(sceNetGetMacAddress(&mac, 0), ORBIS_OK); + EXPECT_EQ(mac.data[5], 0x55); + SetSystemHooks({}); + ASSERT_EQ(sceNetGetMacAddress(&mac, 0), ORBIS_OK); + EXPECT_EQ(mac.data[5], 0x00); // no hook: zeros +} + +TEST_F(NetLib, P2PTransportLifecycleHooks) { + u16 started = 0; + u16 stopped = 0; + int starts = 0; + u16 port_setting = 0; // any free port + SetSystemHooks({ + .p2p_port = [&] { return port_setting; }, + .p2p_started = + [&](u16 port) { + started = port; + ++starts; + }, + .p2p_stopped = [&](u16 port) { stopped = port; }, + .public_addr = [] { return sceNetHtonl(0xC6336401); }, // 198.51.100.1 + }); + EXPECT_EQ(GetP2PConfiguredPort(), 0); // the setting wins over ConfigureP2P + EXPECT_EQ(GetP2PAdvertisedAddr(), sceNetHtonl(0xC6336401)); + EXPECT_FALSE(P2PTransportIsReady()); + + // First use starts it and tells the hook the port it got; later uses do not restart it. + ASSERT_TRUE(EnsureP2PTransport()); + EXPECT_NE(started, 0); + EXPECT_EQ(started, GetP2PBoundPort()); + EXPECT_EQ(GetP2PAdvertisedPort(), started); + EXPECT_TRUE(EnsureP2PTransport()); + EXPECT_EQ(starts, 1); + + StopP2P(); + EXPECT_EQ(stopped, started); + EXPECT_FALSE(P2PTransportIsReady()); + EXPECT_EQ(GetP2PAdvertisedPort(), 0); // not running: the configured port + + // A port that is taken, e.g. by another instance on the same PC: the transport moves to a + // nearby free port, and that is the one advertised and forwarded. + const OrbisNetId blocker = sceNetSocket("blocker", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + OrbisNetSockaddrIn at{}; + at.sin_len = sizeof(at); + at.sin_family = ORBIS_NET_AF_INET; + bool bound = false; + for (u16 port = 20000; port < 30000 && !bound; port += 97) { + at.sin_port = sceNetHtons(port); + bound = sceNetBind(blocker, Guest(&at), sizeof(at)) == ORBIS_OK; + } + ASSERT_TRUE(bound); + port_setting = sceNetNtohs(at.sin_port); + ASSERT_TRUE(EnsureP2PTransport()); + EXPECT_NE(GetP2PBoundPort(), port_setting); + EXPECT_GT(GetP2PBoundPort(), port_setting); + EXPECT_LE(GetP2PBoundPort(), port_setting + 16); + EXPECT_EQ(started, GetP2PBoundPort()); + EXPECT_EQ(GetP2PAdvertisedPort(), started); + EXPECT_EQ(starts, 2); + sceNetSocketClose(blocker); + + // An explicit StartP2P gets exactly the port it asks for, or nothing. + StopP2P(); + const OrbisNetId blocker2 = + sceNetSocket("blocker2", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + ASSERT_EQ(sceNetBind(blocker2, Guest(&at), sizeof(at)), ORBIS_OK); + EXPECT_EQ(StartP2P(port_setting), 0); + sceNetSocketClose(blocker2); + EXPECT_EQ(StartP2P(port_setting), port_setting); + + StopP2P(); + SetSystemHooks({}); +} + +// --------------------------------------------------------------------------------------------- +// P2P, over the real wire format +// --------------------------------------------------------------------------------------------- + +class NetLibP2P : public NetLib { +protected: + static void SetUpTestSuite() { + NetLib::SetUpTestSuite(); + // The transport starts with the first P2P socket, on the configured port. + ConfigureP2P(0, 0); // any free port for the test + EXPECT_FALSE(P2PTransportIsReady()); + const OrbisNetId first = + sceNetSocket("first", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0); + EXPECT_GT(first, 0); + EXPECT_TRUE(P2PTransportIsReady()); + sceNetSocketClose(first); + local_keys_ = GetP2PKeyring(); + local_port_ = GetP2PBoundPort(); + ASSERT_NE(local_port_, 0); + Host::Error error; + remote_keys_ = std::make_shared(); + remote_ = + P2P::Transport::Create(AF_INET, P2P::Endpoint::IPv4("127.0.0.1", 0), + std::make_unique(remote_keys_), {}, &error); + ASSERT_TRUE(remote_); + } + static void TearDownTestSuite() { + StopP2P(); + remote_.reset(); + NetLib::TearDownTestSuite(); + } + + static P2P::Endpoint LocalEndpoint() { + return P2P::Endpoint::IPv4("127.0.0.1", local_port_); + } + + static inline u16 local_port_ = 0; + static inline std::shared_ptr local_keys_; + static inline std::shared_ptr remote_keys_; + static inline std::shared_ptr remote_; +}; + +// shadNet's STUN server is a plain UDP socket. Signaling reaches it as a P2P datagram from and +// to vport 0xFFFF, and it answers the same way. +TEST_F(NetLibP2P, SignalingReachesShadNetStunServer) { + const OrbisNetId server = sceNetSocket("server", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + ASSERT_GT(server, 0); + OrbisNetSockaddrIn addr{}; + addr.sin_len = sizeof(addr); + addr.sin_family = ORBIS_NET_AF_INET; + addr.sin_addr = sceNetHtonl(0x7F000001); + ASSERT_EQ(sceNetBind(server, Guest(&addr), sizeof(addr)), ORBIS_OK); + u32 len = sizeof(addr); + ASSERT_EQ(sceNetGetsockname(server, Guest(&addr), &len), ORBIS_OK); + + const std::array header{0xFF, 0x80 | 3, 0xFF, 0xFF, 0xFF, 0xFF}; + const std::array ping{0x01, 'S', 't', 'e', 'p', 'h', 'e', 'n'}; + ASSERT_EQ(P2PSignalingSendTo(ping.data(), ping.size(), addr.sin_addr, addr.sin_port), + static_cast(ping.size())); + std::array got{}; + OrbisNetSockaddrIn from{}; + u32 from_len = sizeof(from); + ASSERT_EQ(sceNetRecvfrom(server, got.data(), got.size(), 0, Guest(&from), &from_len), + static_cast(header.size() + ping.size())); + EXPECT_TRUE(std::equal(header.begin(), header.end(), got.begin())); + EXPECT_TRUE(std::equal(ping.begin(), ping.end(), got.begin() + header.size())); + EXPECT_EQ(sceNetNtohs(from.sin_port), local_port_); + + // STUN echo: header, then [ext ip 4][ext port 2] + std::vector echo(header.begin(), header.end()); + const std::array body{ + 0x7F, 0, 0, 1, static_cast(local_port_ >> 8), static_cast(local_port_)}; + echo.insert(echo.end(), body.begin(), body.end()); + ASSERT_EQ(sceNetSendto(server, echo.data(), echo.size(), 0, Guest(&from), sizeof(from)), + static_cast(echo.size())); + std::array reply{}; + u32 reply_addr = 0; + u16 reply_port = 0; + int n = -1; + for (int i = 0; i < 200 && n < 0; ++i) { + n = P2PSignalingRecvFrom(reply.data(), reply.size(), &reply_addr, &reply_port); + if (n < 0) { + std::this_thread::sleep_for(5ms); + } + } + ASSERT_EQ(n, static_cast(body.size())); + EXPECT_TRUE(std::equal(body.begin(), body.end(), reply.begin())); + EXPECT_EQ(reply_port, addr.sin_port); + sceNetSocketClose(server); +} + +TEST_F(NetLibP2P, DatagramWakesEpollAndReportsVport) { + const OrbisNetId s = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0); + ASSERT_GT(s, 0); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_TYPE), + static_cast(ORBIS_NET_SOCK_DGRAM_P2P)); + auto bind_addr = Loopback(local_port_, 3001); + ASSERT_EQ(sceNetBind(s, Guest(&bind_addr), sizeof(bind_addr)), ORBIS_OK); + + OrbisNetSockaddrIn name{}; + u32 len = sizeof(name); + ASSERT_EQ(sceNetGetsockname(s, Guest(&name), &len), ORBIS_OK); + EXPECT_EQ(sceNetNtohs(name.sin_port), local_port_); + EXPECT_EQ(sceNetNtohs(name.sin_vport), 3001); + + const OrbisNetId ep = sceNetEpollCreate("p2p", 0); + OrbisNetEpollEvent ev{}; + ev.events = ORBIS_NET_EPOLLIN; + ev.data.data_u64 = 77; + sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev); + + auto remote = remote_->CreateDatagram(); + remote->Bind(4001); + std::thread sender([&] { + std::this_thread::sleep_for(150ms); + const u8 msg[] = "ping"; + const auto to = LocalEndpoint(); + remote->SendTo(msg, &to, 3001); + }); + OrbisNetEpollEvent out[2]{}; + EXPECT_EQ(sceNetEpollWait(ep, out, 2, -1), 1); // infinite wait wakes on P2P data + sender.join(); + EXPECT_EQ(out[0].data.data_u64, 77u); + + char buf[16]{}; + OrbisNetSockaddrIn from{}; + u32 from_len = sizeof(from); + EXPECT_EQ(sceNetRecvfrom(s, buf, sizeof(buf), 0, Guest(&from), &from_len), 5); + EXPECT_STREQ(buf, "ping"); + EXPECT_EQ(sceNetNtohs(from.sin_port), remote_->BoundPort()); // UDP port + EXPECT_EQ(sceNetNtohs(from.sin_vport), 4001); // virtual port + + // Reply through the guest API to the address it reported. + EXPECT_EQ(sceNetSendto(s, "pong", 4, 0, Guest(&from), from_len), 4); + u8 reply[8]{}; + size_t got = 0; + for (int i = 0; i < 200 && got == 0; ++i) { + got = remote->RecvFrom(reply, false, nullptr, nullptr).bytes; + std::this_thread::sleep_for(5ms); + } + EXPECT_EQ(got, 4u); + + sceNetEpollDestroy(ep); + sceNetSocketClose(s); +} + +TEST_F(NetLibP2P, PortRangesAndDefaults) { + const OrbisNetId dgram = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0); + // Reserved virtual ports cannot be bound. + for (const u16 vport : {5353, 32768, 60000}) { + auto addr = Loopback(local_port_, vport); + EXPECT_EQ(sceNetBind(dgram, Guest(&addr), sizeof(addr)), ORBIS_NET_ERROR_EACCES) << vport; + } + // Ephemeral virtual ports come from 32768-49999. + auto any = Loopback(local_port_, 0); + ASSERT_EQ(sceNetBind(dgram, Guest(&any), sizeof(any)), ORBIS_OK); + OrbisNetSockaddrIn name{}; + u32 len = sizeof(name); + sceNetGetsockname(dgram, Guest(&name), &len); + EXPECT_GE(sceNetNtohs(name.sin_vport), 32768); + EXPECT_LE(sceNetNtohs(name.sin_vport), 49999); + EXPECT_EQ(GetInt(dgram, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF), 9216); + EXPECT_EQ(GetInt(dgram, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 40960); + std::vector big(9217); + auto to = Loopback(remote_->BoundPort(), 4000); + EXPECT_EQ(sceNetSendto(dgram, big.data(), big.size(), 0, Guest(&to), sizeof(to)), + ORBIS_NET_ERROR_EMSGSIZE); + sceNetSocketClose(dgram); + + // Stream ports follow the TCP table. + const OrbisNetId stream = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0); + auto reserved = Loopback(1000, 0); + EXPECT_EQ(sceNetBind(stream, Guest(&reserved), sizeof(reserved)), ORBIS_NET_ERROR_EACCES); + auto allowed = Loopback(30000, 0); + EXPECT_EQ(sceNetBind(stream, Guest(&allowed), sizeof(allowed)), ORBIS_OK); + EXPECT_EQ(GetInt(stream, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF), 32768); + EXPECT_EQ(GetInt(stream, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 65536); + sceNetSocketClose(stream); +} + +TEST_F(NetLibP2P, AddressesAreCheckedStrictly) { + const OrbisNetId dgram = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0); + auto to = Loopback(remote_->BoundPort(), 4300); + // Connecting needs a bound socket. + EXPECT_EQ(sceNetConnect(dgram, Guest(&to), sizeof(to)), ORBIS_NET_ERROR_EINVAL); + + // Bind: exactly 16 bytes; the UDP port named; no multicast address. sa_len may be 0, as + // games leave it. + auto addr = Loopback(local_port_, 3300); + EXPECT_EQ(sceNetBind(dgram, Guest(&addr), 32), ORBIS_NET_ERROR_EINVAL); + addr.sin_family = ORBIS_NET_AF_INET6; + EXPECT_EQ(sceNetBind(dgram, Guest(&addr), sizeof(addr)), ORBIS_NET_ERROR_EAFNOSUPPORT); + addr.sin_family = ORBIS_NET_AF_INET; + auto no_udp_port = Loopback(0, 3300); + EXPECT_EQ(sceNetBind(dgram, Guest(&no_udp_port), sizeof(no_udp_port)), + ORBIS_NET_ERROR_EADDRNOTAVAIL); + auto multicast = Loopback(local_port_, 3300); + multicast.sin_addr = sceNetHtonl(0xE0000001); // 224.0.0.1 + EXPECT_EQ(sceNetBind(dgram, Guest(&multicast), sizeof(multicast)), + ORBIS_NET_ERROR_EADDRNOTAVAIL); + addr.sin_len = 0; + ASSERT_EQ(sceNetBind(dgram, Guest(&addr), sizeof(addr)), ORBIS_OK); + EXPECT_EQ(sceNetBind(dgram, Guest(&addr), sizeof(addr)), ORBIS_NET_ERROR_EINVAL); // once + + // Sendto and connect need an address and both ports. + auto no_vport = Loopback(remote_->BoundPort(), 0); + EXPECT_EQ(sceNetSendto(dgram, "x", 1, 0, Guest(&no_vport), sizeof(no_vport)), + ORBIS_NET_ERROR_EADDRNOTAVAIL); // "0 was set to ... sin_vport" + EXPECT_EQ(sceNetSendto(dgram, "x", 1, 0, Guest(&to), 20), ORBIS_NET_ERROR_EINVAL); + EXPECT_EQ(sceNetConnect(dgram, Guest(&no_vport), sizeof(no_vport)), + ORBIS_NET_ERROR_EADDRNOTAVAIL); + ASSERT_EQ(sceNetConnect(dgram, Guest(&to), sizeof(to)), ORBIS_OK); + EXPECT_EQ(sceNetConnect(dgram, Guest(&to), sizeof(to)), ORBIS_NET_ERROR_EISCONN); + sceNetSocketClose(dgram); + + // Streams: listen needs a bind (connect binds implicitly, see + // StreamConnectBindsImplicitly); the UDP port is 0 or the P2P port. + const OrbisNetId stream = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0); + EXPECT_EQ(sceNetListen(stream, 4), ORBIS_NET_ERROR_EINVAL); + auto peer = Loopback(5000, remote_->BoundPort()); + auto odd_udp_port = Loopback(30100, 1234); + EXPECT_EQ(sceNetBind(stream, Guest(&odd_udp_port), sizeof(odd_udp_port)), + ORBIS_NET_ERROR_EINVAL); + auto well_known = Loopback(30100, 3658); + ASSERT_EQ(sceNetBind(stream, Guest(&well_known), sizeof(well_known)), ORBIS_OK); + auto nowhere = Loopback(0, 0); + nowhere.sin_addr = 0; + EXPECT_EQ(sceNetConnect(stream, Guest(&nowhere), sizeof(nowhere)), + ORBIS_NET_ERROR_EADDRNOTAVAIL); + // Bound to a non-zero TCP port: a listener, which cannot connect (EPROTO). + EXPECT_EQ(sceNetConnect(stream, Guest(&peer), sizeof(peer)), + ORBIS_NET_ERROR_BASE | ORBIS_NET_EPROTO); + EXPECT_EQ(sceNetListen(stream, 4), ORBIS_OK); + EXPECT_EQ(sceNetConnect(stream, Guest(&peer), sizeof(peer)), ORBIS_NET_ERROR_EOPNOTSUPP); + sceNetSocketClose(stream); + // Datagram P2P sockets cannot listen or accept. + const OrbisNetId dgram2 = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0); + EXPECT_EQ(sceNetAccept(dgram2, nullptr, nullptr), ORBIS_NET_ERROR_EOPNOTSUPP); + sceNetSocketClose(dgram2); +} + +TEST_F(NetLibP2P, ConnectTimeout) { + // The peer is a plain UDP socket that drops everything: the SYN is never answered. + const OrbisNetId sink = sceNetSocket("sink", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + auto any = Loopback(0); + sceNetBind(sink, Guest(&any), sizeof(any)); + OrbisNetSockaddrIn sink_addr{}; + u32 len = sizeof(sink_addr); + sceNetGetsockname(sink, Guest(&sink_addr), &len); + + const OrbisNetId s = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0); + auto self = Loopback(0, 0); + ASSERT_EQ(sceNetBind(s, Guest(&self), sizeof(self)), ORBIS_OK); + ASSERT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_CONNECTTIMEO, 200000), ORBIS_OK); + auto to = Loopback(5000, sceNetNtohs(sink_addr.sin_port)); + const auto start = std::chrono::steady_clock::now(); + EXPECT_EQ(sceNetConnect(s, Guest(&to), sizeof(to)), ORBIS_NET_ERROR_EWOULDBLOCK); + EXPECT_GE(Millis(start), 190.0); + EXPECT_LT(Millis(start), 2000.0); + sceNetSocketClose(s); + sceNetSocketClose(sink); +} + +TEST_F(NetLibP2P, IoctlInstallsKeysAndCommunicationId) { + const OrbisNetId p2p = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0); + std::array record{}; + for (u8 i = 0; i < 16; ++i) { + record[i] = i; + } + ASSERT_EQ(sceNetIoctl(p2p, 0x801050d8, record.data()), ORBIS_OK); + EXPECT_EQ(GetP2PKeyring()->GetCommunicationId(), + (P2P::CommunicationId{0x54, 0x33, 0x12, 0x2f})); + ASSERT_EQ(sceNetIoctl(p2p, 0x200050d9, nullptr), ORBIS_OK); // clear it again + EXPECT_FALSE(GetP2PKeyring()->GetCommunicationId()); + + u16 np_port = 0; + ASSERT_EQ(sceNetIoctl(p2p, 0xc00250cf, &np_port), ORBIS_OK); + EXPECT_EQ(sceNetNtohs(np_port), local_port_); + + EXPECT_EQ(sceNetIoctl(p2p, 0x802450ff, record.data()), ORBIS_NET_ERROR_EINVAL); + const OrbisNetId udp = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0); + EXPECT_EQ(sceNetIoctl(udp, 0x801050d8, record.data()), ORBIS_NET_ERROR_EINVAL); + sceNetSocketClose(udp); + sceNetSocketClose(p2p); +} + +TEST_F(NetLibP2P, PerMessageEncryption) { + const OrbisNetId s = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0); + auto bind_addr = Loopback(local_port_, 3200); + sceNetBind(s, Guest(&bind_addr), sizeof(bind_addr)); + auto remote = remote_->CreateDatagram(); + remote->Bind(4200); + auto to = Loopback(remote_->BoundPort(), 4200); + const auto received = [&] { + u8 buf[16]{}; + for (int i = 0; i < 40; ++i) { + if (const auto r = remote->RecvFrom(buf, false, nullptr, nullptr); r.bytes != 0) { + return std::string(reinterpret_cast(buf), r.bytes); + } + std::this_thread::sleep_for(5ms); + } + return std::string(); + }; + + P2P::P2PKey key; + key.value.fill(0x6b); + local_keys_->SetWildcardKey(key); + // MSG_USECRYPTO on this message only: a receiver without the key drops it ... + EXPECT_EQ(sceNetSendto(s, "secret", 6, ORBIS_NET_MSG_USECRYPTO, Guest(&to), sizeof(to)), 6); + EXPECT_EQ(received(), ""); + // ... one with the key decrypts it. + remote_keys_->SetWildcardKey(key); + EXPECT_EQ(sceNetSendto(s, "secret", 6, ORBIS_NET_MSG_USECRYPTO, Guest(&to), sizeof(to)), 6); + EXPECT_EQ(received(), "secret"); + // Without the flag the same socket sends in the clear, which the receiver also accepts. + EXPECT_EQ(sceNetSendto(s, "plain", 5, 0, Guest(&to), sizeof(to)), 5); + EXPECT_EQ(received(), "plain"); + + local_keys_->SetWildcardKey(std::nullopt); + remote_keys_->SetWildcardKey(std::nullopt); + sceNetSocketClose(s); +} + +TEST_F(NetLibP2P, NpChannelsAreKeptApart) { + const u32 loopback = sceNetHtonl(0x7F000001); + const u16 port = sceNetHtons(local_port_); + const auto packet = [](u8 type) { return std::array{'S', 'H', 'A', 'D', type, 0x42}; }; + const auto control = packet(0x10); + const auto matching2 = packet(0x21); + const auto handshake = packet(0x07); + const std::array other{'x', 'y', 'z'}; + ASSERT_EQ(P2PControlSendTo(control.data(), 6, loopback, port), 6); + ASSERT_EQ(P2PMatching2SendTo(matching2.data(), 6, loopback, port), 6); + ASSERT_EQ(P2PSignalingSendTo(handshake.data(), 6, loopback, port), 6); + ASSERT_EQ(P2PSignalingSendTo(other.data(), 3, loopback, port), 3); + std::this_thread::sleep_for(100ms); + + u8 buf[16]{}; + u32 from_addr = 0; + u16 from_port = 0; + ASSERT_EQ(P2PControlRecvFrom(buf, sizeof(buf), &from_addr, &from_port), 6); + EXPECT_EQ(buf[4], 0x10); + EXPECT_EQ(from_addr, loopback); + EXPECT_EQ(from_port, port); + EXPECT_EQ(P2PControlRecvFrom(buf, sizeof(buf), nullptr, nullptr), -1); // nothing else + ASSERT_EQ(P2PMatching2RecvFrom(buf, sizeof(buf), nullptr, nullptr), 6); + EXPECT_EQ(buf[4], 0x21); + ASSERT_EQ(P2PSignalingRecvFrom(buf, sizeof(buf), nullptr, nullptr), 6); + EXPECT_EQ(buf[4], 0x07); + ASSERT_EQ(P2PSignalingRecvFrom(buf, sizeof(buf), nullptr, nullptr), 3); + EXPECT_EQ(P2PSignalingRecvFrom(buf, sizeof(buf), nullptr, nullptr), -1); + + EXPECT_TRUE(EnsureP2PTransport()); + EXPECT_EQ(GetP2PConfiguredPort(), 0); // what the test configured + EXPECT_EQ(GetP2PAdvertisedAddr(), 0u); +} + +// JoJo: Eyes of Heaven connects STREAM_P2P sockets without binding them, which works on the +// PS4. Connect binds them to a free port, as BSD TCP does. +TEST_F(NetLibP2P, StreamConnectBindsImplicitly) { + auto listener = remote_->CreateStream(); + ASSERT_EQ(listener->Bind(5100), Host::Error::Ok); + ASSERT_EQ(listener->Listen(4), Host::Error::Ok); + + const OrbisNetId s = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0); + ASSERT_GT(s, 0); + auto to = Loopback(5100, remote_->BoundPort()); + ASSERT_EQ(sceNetConnect(s, Guest(&to), sizeof(to)), ORBIS_OK); // no bind first + + OrbisNetSockaddrIn self{}; + u32 len = sizeof(self); + ASSERT_EQ(sceNetGetsockname(s, Guest(&self), &len), ORBIS_OK); + EXPECT_GE(sceNetNtohs(self.sin_port), 49152); // ephemeral, as with an explicit bind to 0 + EXPECT_EQ(sceNetNtohs(self.sin_vport), local_port_); + + std::shared_ptr server; + Host::Error error; + for (int i = 0; i < 400 && !server; ++i) { + server = listener->Accept(nullptr, nullptr, &error); + std::this_thread::sleep_for(5ms); + } + ASSERT_TRUE(server); + ASSERT_EQ(sceNetSend(s, "jojo", 4, 0), 4); + std::array got{}; + size_t n = 0; + for (int i = 0; i < 400 && n == 0; ++i) { + n = server->Recv(std::span(got), false).bytes; + if (n == 0) { + std::this_thread::sleep_for(5ms); + } + } + EXPECT_EQ(n, 4u); + EXPECT_EQ(std::memcmp(got.data(), "jojo", 4), 0); + sceNetSocketClose(s); + + // Datagram P2P sockets still have to be bound first. + const OrbisNetId dgram = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0); + auto dgram_to = Loopback(remote_->BoundPort(), 4300); + EXPECT_EQ(sceNetConnect(dgram, Guest(&dgram_to), sizeof(dgram_to)), ORBIS_NET_ERROR_EINVAL); + sceNetSocketClose(dgram); +} + +TEST_F(NetLibP2P, LingerIsKeptWholeOnP2PStreams) { + const OrbisNetId s = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0); + ASSERT_GT(s, 0); + const OrbisNetLinger set{1, 7}; + ASSERT_EQ(sceNetSetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &set, sizeof(set)), + ORBIS_OK); + OrbisNetLinger got{}; + u32 len = sizeof(got); + ASSERT_EQ(sceNetGetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &got, &len), ORBIS_OK); + EXPECT_EQ(len, sizeof(got)); // the struct, not an int + EXPECT_EQ(got.l_onoff, 1); + EXPECT_EQ(got.l_linger, 7); + sceNetSocketClose(s); +} + +TEST_F(NetLibP2P, StreamConnectSendRecv) { + auto listener = remote_->CreateStream(); + ASSERT_EQ(listener->Bind(5000), Host::Error::Ok); + ASSERT_EQ(listener->Listen(4), Host::Error::Ok); + + const OrbisNetId s = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_TYPE), + static_cast(ORBIS_NET_SOCK_STREAM_P2P)); + auto self_addr = Loopback(0, 0); // any TCP port, the P2P UDP port + ASSERT_EQ(sceNetBind(s, Guest(&self_addr), sizeof(self_addr)), ORBIS_OK); + // Stream layout: sin_port = the TCP port, sin_vport = the peer's UDP port. + auto to = Loopback(5000, remote_->BoundPort()); + ASSERT_EQ(sceNetConnect(s, Guest(&to), sizeof(to)), ORBIS_OK); // blocking handshake + + OrbisNetSockaddrIn peer{}; + u32 len = sizeof(peer); + ASSERT_EQ(sceNetGetpeername(s, Guest(&peer), &len), ORBIS_OK); + EXPECT_EQ(sceNetNtohs(peer.sin_port), 5000); + EXPECT_EQ(sceNetNtohs(peer.sin_vport), remote_->BoundPort()); + EXPECT_EQ(peer.sin_addr, sceNetHtonl(0x7F000001)); + + OrbisNetSockaddrIn self{}; + len = sizeof(self); + ASSERT_EQ(sceNetGetsockname(s, Guest(&self), &len), ORBIS_OK); + EXPECT_GE(sceNetNtohs(self.sin_port), 49152); // ephemeral TCP-over-UDPP2P port + EXPECT_EQ(sceNetNtohs(self.sin_vport), local_port_); + + std::shared_ptr server; + Host::Error error; + for (int i = 0; i < 400 && !server; ++i) { + server = listener->Accept(nullptr, nullptr, &error); + std::this_thread::sleep_for(5ms); + } + ASSERT_TRUE(server); + + std::vector data(200 * 1024); + for (size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast(i * 31); + } + // A blocking send larger than both buffers completes only while the peer reads, so it + // runs on its own thread, as a game's network thread would. + s32 sent = 0; + std::thread sender([&] { sent = sceNetSend(s, data.data(), data.size(), 0); }); + std::vector got; + u8 buf[8192]; + for (int i = 0; i < 2000 && got.size() < data.size(); ++i) { + const auto r = server->Recv(buf, false); + got.insert(got.end(), buf, buf + r.bytes); + if (r.bytes == 0) { + std::this_thread::sleep_for(1ms); + } + } + sender.join(); + EXPECT_EQ(sent, static_cast(data.size())); + EXPECT_TRUE(got == data) << "received " << got.size(); + + server->Send(std::span(reinterpret_cast("reply"), 5)); + char reply[8]{}; + EXPECT_EQ(sceNetRecv(s, reply, sizeof(reply), 0), 5); // blocking recv + EXPECT_STREQ(reply, "reply"); + + sceNetSocketClose(s); + listener->Close(); +} + +TEST_F(NetLibP2P, ProtectionOptions) { + const OrbisNetId s = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO), 0); + EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO, 1), ORBIS_OK); + EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USESIGNATURE, 1), ORBIS_OK); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO), 1); + SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO, 0); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO), 0); + EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USESIGNATURE), 1); // independent + + // Encrypted + signed end to end once both sides hold the key. + P2P::P2PKey key; + key.value.fill(0x33); + local_keys_->SetWildcardKey(key); + remote_keys_->SetWildcardKey(key); + SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO, 1); + auto bind_addr = Loopback(local_port_, 3100); + sceNetBind(s, Guest(&bind_addr), sizeof(bind_addr)); + auto remote = remote_->CreateDatagram(); + remote->Bind(4100); + auto to = Loopback(remote_->BoundPort(), 4100); + EXPECT_EQ(sceNetSendto(s, "secret", 6, 0, Guest(&to), sizeof(to)), 6); + u8 buf[16]{}; + size_t got = 0; + for (int i = 0; i < 200 && got == 0; ++i) { + got = remote->RecvFrom(buf, false, nullptr, nullptr).bytes; + std::this_thread::sleep_for(5ms); + } + EXPECT_EQ(got, 6u); + EXPECT_EQ(std::memcmp(buf, "secret", 6), 0); + local_keys_->SetWildcardKey(std::nullopt); + remote_keys_->SetWildcardKey(std::nullopt); + sceNetSocketClose(s); +} + +} // namespace diff --git a/tests/network/test_net_lib_p2p_ioctl.cpp b/tests/network/test_net_lib_p2p_ioctl.cpp new file mode 100644 index 000000000..dbefb5dc3 --- /dev/null +++ b/tests/network/test_net_lib_p2p_ioctl.cpp @@ -0,0 +1,165 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// The P2P key ioctl (sceNetIoctl on P2P sockets): how the NP libraries install keys and the +// communication ID. Tested on a Keyring directly, without sockets. + +#include +#include + +#include + +#include "core/libraries/net/net_error.h" +#include "core/libraries/net/net_p2p_ioctl.h" +#include "core/libraries/net/net_types.h" +#include "core/net/p2p_codec.h" + +using namespace Libraries::Net; +namespace P2P = Core::Net::P2P; + +namespace { + +constexpr u16 NpPort = 0x4a0e; // 3658 in network byte order + +// ioctl numbers: group 'P' (0x50), 36-byte argument written by the caller (IOC_IN). +constexpr u64 SetDefault = 0x802450c9; +constexpr u64 GetDefault = 0xc02450ca; +constexpr u64 AddKey = 0x802450cb; +constexpr u64 ReleaseKey = 0x802450cc; +constexpr u64 SetWildcard = 0x802450cd; +constexpr u64 GetWildcard = 0xc02450ce; +constexpr u64 GetNpPort = 0xc00250cf; +constexpr u64 RemoveById = 0x802450c8; +constexpr u64 SetCommunicationIdCmd = 0x801050d8; +constexpr u64 ClearCommunicationId = 0x200050d9; + +/// The 36-byte argument: IPv4 P2P sockaddr, key value, local port, flags, id. +struct Record { + std::array bytes{}; + + Record(const char* ip, u16 port, u8 key_fill, u8 id, u16 local_port = 0) { + OrbisNetSockaddrIn in{}; + in.sin_len = sizeof(in); + in.sin_family = ORBIS_NET_AF_INET; + in.sin_port = htons(port); + inet_pton(AF_INET, ip, &in.sin_addr); + std::memcpy(bytes.data(), &in, sizeof(in)); + std::memset(bytes.data() + 16, key_fill, 16); + std::memcpy(bytes.data() + 32, &local_port, 2); + bytes[34] = 0x01; // flags + bytes[35] = id; + } + void* data() { + return bytes.data(); + } +}; + +P2P::P2PKey Key(u8 fill) { + P2P::P2PKey key; + key.value.fill(fill); + return key; +} + +const P2P::Endpoint Peer = P2P::Endpoint::IPv4("198.51.100.4", 3658); + +TEST(NetLibP2PIoctl, CommunicationIdIsDerived) { + P2P::Keyring keys; + std::array value; + for (u8 i = 0; i < 16; ++i) { + value[i] = i; + } + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, SetCommunicationIdCmd, value.data()), 0); + // HMAC-SHA1(key = value, message = "")[0..4], from an independent implementation (Python). + EXPECT_EQ(keys.GetCommunicationId(), (P2P::CommunicationId{0x54, 0x33, 0x12, 0x2f})); + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, ClearCommunicationId, nullptr), 0); + EXPECT_FALSE(keys.GetCommunicationId()); +} + +TEST(NetLibP2PIoctl, PeerKeysAreReferenceCounted) { + P2P::Keyring keys; + Record record("198.51.100.4", 3658, 0x11, 7); + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, record.data()), 0); + const auto entry = keys.FindPeerEntry(Peer); + ASSERT_TRUE(entry); + EXPECT_EQ(entry->key, Key(0x11)); + EXPECT_EQ(entry->id, 7); + EXPECT_EQ(entry->flags, 1); + EXPECT_EQ(entry->local_port, NpPort); // 0 in the record means the NP port + + // Installed twice, so it takes two releases to go away. + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, record.data()), 0); + EXPECT_EQ(P2PKeyIoctl(keys, NpPort, ReleaseKey, record.data()), 0); + EXPECT_TRUE(keys.FindPeerEntry(Peer)); + EXPECT_EQ(P2PKeyIoctl(keys, NpPort, ReleaseKey, record.data()), 0); + EXPECT_FALSE(keys.FindPeerEntry(Peer)); + EXPECT_EQ(P2PKeyIoctl(keys, NpPort, ReleaseKey, record.data()), ORBIS_NET_ENOENT); + + // A release must name the same key value and id. + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, record.data()), 0); + Record wrong_id("198.51.100.4", 3658, 0x11, 8); + Record wrong_key("198.51.100.4", 3658, 0x22, 7); + EXPECT_EQ(P2PKeyIoctl(keys, NpPort, ReleaseKey, wrong_id.data()), ORBIS_NET_ENOENT); + EXPECT_EQ(P2PKeyIoctl(keys, NpPort, ReleaseKey, wrong_key.data()), ORBIS_NET_ENOENT); + + // A different value replaces the key; an explicit local port is kept. + Record other("198.51.100.4", 3658, 0x33, 7, 0x1234); + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, other.data()), 0); + EXPECT_EQ(keys.FindPeerEntry(Peer)->key, Key(0x33)); + EXPECT_EQ(keys.FindPeerEntry(Peer)->local_port, 0x1234); +} + +TEST(NetLibP2PIoctl, RemoveKeysById) { + P2P::Keyring keys; + Record a("198.51.100.4", 3658, 0x11, 5); + Record b("198.51.100.5", 3658, 0x22, 5); + Record c("198.51.100.6", 3658, 0x33, 6); + for (auto* r : {&a, &b, &c}) { + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, r->data()), 0); + } + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, RemoveById, a.data()), 0); // id 5 + EXPECT_FALSE(keys.FindPeerEntry(P2P::Endpoint::IPv4("198.51.100.4", 3658))); + EXPECT_FALSE(keys.FindPeerEntry(P2P::Endpoint::IPv4("198.51.100.5", 3658))); + EXPECT_TRUE(keys.FindPeerEntry(P2P::Endpoint::IPv4("198.51.100.6", 3658))); +} + +TEST(NetLibP2PIoctl, DefaultAndWildcardKeys) { + P2P::Keyring keys; + Record default_key("0.0.0.0", 0, 0x44, 1); + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, SetDefault, default_key.data()), 0); + EXPECT_EQ(keys.Find(Peer), Key(0x44)); // falls back to the default key + EXPECT_EQ(keys.FindPeerEntry(P2P::Endpoint::IPv4("127.0.0.1", 3658))->key, Key(0x44)); + + Record out("0.0.0.0", 0, 0x00, 0); + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, GetDefault, out.data()), 0); + EXPECT_EQ(out.bytes[16], 0x44); + EXPECT_EQ(out.bytes[31], 0x44); + + // The wildcard key wins over the default key; reading it back works too. + Record out_wild("0.0.0.0", 0, 0xee, 0); + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, GetWildcard, out_wild.data()), 0); + EXPECT_EQ(out_wild.bytes[16], 0x00); // none yet: zeros + Record wildcard("0.0.0.0", 0, 0x55, 1); + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, SetWildcard, wildcard.data()), 0); + EXPECT_EQ(keys.Find(Peer), Key(0x55)); + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, GetWildcard, out_wild.data()), 0); + EXPECT_EQ(out_wild.bytes[16], 0x55); +} + +TEST(NetLibP2PIoctl, NpPortAndErrors) { + P2P::Keyring keys; + u16 port = 0; + ASSERT_EQ(P2PKeyIoctl(keys, NpPort, GetNpPort, &port), 0); + EXPECT_EQ(port, NpPort); + + EXPECT_EQ(P2PKeyIoctl(keys, NpPort, 0x8004667e /* FIONBIO */, &port), ORBIS_NET_EINVAL); + EXPECT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, nullptr), ORBIS_NET_EFAULT); + EXPECT_EQ(P2PKeyIoctl(keys, NpPort, 0x200050c8, nullptr), 0); // the no-op command + EXPECT_EQ(P2PKeyIoctl(keys, NpPort, 0x802450ff, &port), ORBIS_NET_EINVAL); + + // Adding a key needs an IPv4 P2P sockaddr. + Record bad_family("198.51.100.4", 3658, 0x11, 7); + bad_family.bytes[1] = ORBIS_NET_AF_INET6; + EXPECT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, bad_family.data()), ORBIS_NET_EINVAL); +} + +} // namespace diff --git a/tests/network/test_net_lib_translate.cpp b/tests/network/test_net_lib_translate.cpp new file mode 100644 index 000000000..7bf477685 --- /dev/null +++ b/tests/network/test_net_lib_translate.cpp @@ -0,0 +1,360 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// The guest <-> host translation used by the new libSceNet (src/core/libraries/net): pure +// functions, tested without sockets. + +#include +#include + +#include + +#ifndef _WIN32 +#include +#else +#include +#include +#endif + +#include "core/libraries/net/net_error.h" +#include "core/libraries/net/net_translate.h" + +#ifndef _WIN32 +#include +#endif + +using namespace Libraries::Net; + +namespace { + +OrbisNetSockaddrIn GuestIn(const char* ip, u16 port, u16 vport = 0, u8 sa_len = 16) { + OrbisNetSockaddrIn in{}; + in.sin_len = sa_len; + in.sin_family = ORBIS_NET_AF_INET; + in.sin_port = htons(port); + inet_pton(AF_INET, ip, &in.sin_addr); + in.sin_vport = htons(vport); + return in; +} + +const OrbisNetSockaddr* AsGuest(const void* p) { + return static_cast(p); +} + +TEST(NetLibTranslate, SocketKinds) { + int error = 0; + auto k = ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0, &error); + ASSERT_TRUE(k); + EXPECT_EQ(k->family, AF_INET); + EXPECT_EQ(k->type, SOCK_STREAM); + EXPECT_FALSE(k->p2p); + + k = ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, ORBIS_NET_IPPROTO_UDP, &error); + ASSERT_TRUE(k); + EXPECT_EQ(k->type, SOCK_DGRAM); + EXPECT_EQ(k->protocol, IPPROTO_UDP); + + // IPv4-only stack: no IPv6 sockets, P2P included. + for (const int type : {ORBIS_NET_SOCK_STREAM, ORBIS_NET_SOCK_DGRAM, ORBIS_NET_SOCK_DGRAM_P2P}) { + error = 0; + EXPECT_FALSE(ToHostSocketKind(ORBIS_NET_AF_INET6, type, 0, &error)); + EXPECT_EQ(error, ORBIS_NET_EPROTONOSUPPORT); // sceNetSocket's code for a bad family + } + + k = ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0, &error); + ASSERT_TRUE(k); + EXPECT_TRUE(k->p2p); + EXPECT_EQ(k->type, SOCK_STREAM); + k = ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0, &error); + ASSERT_TRUE(k); + EXPECT_TRUE(k->p2p); + EXPECT_EQ(k->type, SOCK_DGRAM); + + // AF_UNIX: stream and datagram, protocol 0, never P2P. + k = ToHostSocketKind(ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_DGRAM, 0, &error); + ASSERT_TRUE(k); + EXPECT_EQ(k->family, AF_UNIX); + EXPECT_EQ(k->type, SOCK_DGRAM); + EXPECT_EQ(k->protocol, 0); + EXPECT_FALSE(ToHostSocketKind(ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_DGRAM_P2P, 0, &error)); + EXPECT_EQ(error, ORBIS_NET_EPROTONOSUPPORT); + + EXPECT_FALSE(ToHostSocketKind(99, ORBIS_NET_SOCK_STREAM, 0, &error)); + EXPECT_EQ(error, ORBIS_NET_EPROTONOSUPPORT); + // Non-RAW types take 0 or their own protocol; anything else is EPROTOTYPE. + EXPECT_FALSE( + ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, ORBIS_NET_IPPROTO_TCP, &error)); + EXPECT_EQ(error, ORBIS_NET_EPROTOTYPE); + EXPECT_FALSE(ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, + ORBIS_NET_IPPROTO_UDP, &error)); + EXPECT_EQ(error, ORBIS_NET_EPROTOTYPE); + EXPECT_TRUE( + ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_RAW, ORBIS_NET_IPPROTO_ICMP, &error)); + EXPECT_FALSE(ToHostSocketKind(ORBIS_NET_AF_INET, 77, 0, &error)); + EXPECT_EQ(error, ORBIS_NET_EPROTONOSUPPORT); + + EXPECT_EQ(ToOrbisSocketType(SOCK_STREAM, false), static_cast(ORBIS_NET_SOCK_STREAM)); + EXPECT_EQ(ToOrbisSocketType(SOCK_DGRAM, true), static_cast(ORBIS_NET_SOCK_DGRAM_P2P)); + EXPECT_EQ(ToOrbisSocketType(SOCK_STREAM, true), static_cast(ORBIS_NET_SOCK_STREAM_P2P)); + EXPECT_EQ(ToOrbisFamily(AF_INET6), static_cast(ORBIS_NET_AF_INET6)); +} + +TEST(NetLibTranslate, Ipv4SockaddrBothWays) { + // sa_len is not trusted: games often leave it 0. + for (const u8 sa_len : {u8{16}, u8{0}}) { + const auto guest = GuestIn("192.0.2.7", 3658, 0, sa_len); + sockaddr_storage host{}; + socklen_t len = 0; + ASSERT_EQ(ToHostSockaddr(AsGuest(&guest), sizeof(guest), &host, &len), 0); + ASSERT_EQ(len, static_cast(sizeof(sockaddr_in))); + const auto* in = reinterpret_cast(&host); + EXPECT_EQ(in->sin_family, AF_INET); + EXPECT_EQ(ntohs(in->sin_port), 3658); + char text[32]; + inet_ntop(AF_INET, &in->sin_addr, text, sizeof(text)); + EXPECT_STREQ(text, "192.0.2.7"); + } + + sockaddr_in host{}; + host.sin_family = AF_INET; + host.sin_port = htons(80); + inet_pton(AF_INET, "10.1.2.3", &host.sin_addr); + OrbisNetSockaddrIn back{}; + u32 len = sizeof(back); + ToOrbisSockaddr(reinterpret_cast(&host), sizeof(host), + reinterpret_cast(&back), &len); + EXPECT_EQ(len, 16u); + EXPECT_EQ(back.sin_len, 16); + EXPECT_EQ(back.sin_family, ORBIS_NET_AF_INET); + EXPECT_EQ(back.sin_port, htons(80)); + EXPECT_EQ(back.sin_vport, 0); +} + +TEST(NetLibTranslate, Ipv6Sockaddr) { + OrbisNetSockaddrIn6 guest{}; + guest.sin6_len = sizeof(guest); + guest.sin6_family = ORBIS_NET_AF_INET6; + guest.sin6_port = htons(443); + guest.sin6_scope_id = 3; + inet_pton(AF_INET6, "2001:db8::1", guest.sin6_addr); + sockaddr_storage host{}; + socklen_t len = 0; + ASSERT_EQ(ToHostSockaddr(AsGuest(&guest), sizeof(guest), &host, &len), 0); + const auto* in6 = reinterpret_cast(&host); + EXPECT_EQ(in6->sin6_family, AF_INET6); + EXPECT_EQ(ntohs(in6->sin6_port), 443); + EXPECT_EQ(in6->sin6_scope_id, 3u); + + OrbisNetSockaddrIn6 back{}; + u32 back_len = sizeof(back); + ToOrbisSockaddr(reinterpret_cast(&host), len, + reinterpret_cast(&back), &back_len); + EXPECT_EQ(back_len, 28u); + EXPECT_EQ(back.sin6_len, 28); + EXPECT_EQ(back.sin6_family, ORBIS_NET_AF_INET6); + EXPECT_EQ(std::memcmp(back.sin6_addr, guest.sin6_addr, 16), 0); +} + +TEST(NetLibTranslate, SockaddrErrorsAndTruncation) { + sockaddr_storage host{}; + socklen_t len = 0; + EXPECT_EQ(ToHostSockaddr(nullptr, 16, &host, &len), ORBIS_NET_EFAULT); + const auto guest = GuestIn("1.2.3.4", 1); + EXPECT_EQ(ToHostSockaddr(AsGuest(&guest), 8, &host, &len), ORBIS_NET_EINVAL); // too short + auto unknown = guest; + unknown.sin_family = 99; + EXPECT_EQ(ToHostSockaddr(AsGuest(&unknown), 16, &host, &len), ORBIS_NET_EAFNOSUPPORT); + sockaddr_in in{}; + in.sin_family = AF_INET; + in.sin_port = htons(0x1234); + u8 buffer[4]{}; + u32 cap = sizeof(buffer); + ToOrbisSockaddr(reinterpret_cast(&in), sizeof(in), + reinterpret_cast(buffer), &cap); + EXPECT_EQ(cap, 16u); + EXPECT_EQ(buffer[0], 16); + EXPECT_EQ(buffer[1], ORBIS_NET_AF_INET); + EXPECT_EQ(buffer[2], 0x12); + EXPECT_EQ(buffer[3], 0x34); +} + +TEST(NetLibTranslate, UnixAddresses) { + OrbisNetSockaddrUn guest{}; + guest.sun_len = sizeof(guest); + guest.sun_family = ORBIS_NET_AF_UNIX; + std::strcpy(guest.sun_path, "/tmp/shadps4.sock"); + sockaddr_storage host{}; + socklen_t len = 0; + ASSERT_EQ( + ToHostSockaddr(reinterpret_cast(&guest), sizeof(guest), &host, &len), 0); + const auto* un = reinterpret_cast(&host); + EXPECT_EQ(un->sun_family, AF_UNIX); + EXPECT_STREQ(un->sun_path, "/tmp/shadps4.sock"); + EXPECT_EQ(len, offsetof(sockaddr_un, sun_path) + std::strlen("/tmp/shadps4.sock") + 1); + + OrbisNetSockaddrUn back{}; + u32 back_len = sizeof(back); + ToOrbisSockaddr(reinterpret_cast(&host), len, + reinterpret_cast(&back), &back_len); + EXPECT_EQ(back_len, sizeof(OrbisNetSockaddrUn)); + EXPECT_EQ(back.sun_family, ORBIS_NET_AF_UNIX); + EXPECT_STREQ(back.sun_path, "/tmp/shadps4.sock"); + + // A path that does not fit the host's sun_path. + std::memset(guest.sun_path, 'a', sizeof(guest.sun_path)); + EXPECT_EQ( + ToHostSockaddr(reinterpret_cast(&guest), sizeof(guest), &host, &len), + sizeof(guest.sun_path) >= sizeof(un->sun_path) ? ORBIS_NET_ENAMETOOLONG : 0); +} + +TEST(NetLibTranslate, P2PDatagramAddresses) { + // sin_port = the peer's UDP port, sin_vport = the virtual port. + const auto guest = GuestIn("198.51.100.4", 3658, 5000); + Core::Net::P2P::Endpoint endpoint; + u16 vport = 0; + ASSERT_EQ(ToP2PAddress(AsGuest(&guest), sizeof(guest), P2PKind::Datagram, &endpoint, &vport), + 0); + EXPECT_EQ(endpoint, Core::Net::P2P::Endpoint::IPv4("198.51.100.4", 3658)); + EXPECT_EQ(vport, 5000); // host byte order + + OrbisNetSockaddrIn back{}; + u32 len = sizeof(back); + ToOrbisP2PAddress(endpoint, vport, P2PKind::Datagram, + reinterpret_cast(&back), &len); + EXPECT_EQ(len, 16u); + EXPECT_EQ(std::memcmp(&back, &guest, sizeof(back)), 0); // exact round trip +} + +TEST(NetLibTranslate, P2PStreamAddresses) { + // sin_port = the TCP port inside the encapsulation, sin_vport = the peer's UDP port. + const auto guest = GuestIn("198.51.100.4", 49200, 3659); + Core::Net::P2P::Endpoint endpoint; + u16 tcp_port = 0; + ASSERT_EQ(ToP2PAddress(AsGuest(&guest), sizeof(guest), P2PKind::Stream, &endpoint, &tcp_port), + 0); + EXPECT_EQ(endpoint, Core::Net::P2P::Endpoint::IPv4("198.51.100.4", 3659)); + EXPECT_EQ(tcp_port, 49200); + + OrbisNetSockaddrIn back{}; + u32 len = sizeof(back); + ToOrbisP2PAddress(endpoint, tcp_port, P2PKind::Stream, + reinterpret_cast(&back), &len); + EXPECT_EQ(std::memcmp(&back, &guest, sizeof(back)), 0); + + // No UDP port given: the standard PlayStation P2P port. + const auto no_udp = GuestIn("198.51.100.4", 49200, 0); + ASSERT_EQ(ToP2PAddress(AsGuest(&no_udp), sizeof(no_udp), P2PKind::Stream, &endpoint, &tcp_port), + 0); + EXPECT_EQ(endpoint.Port(), DefaultP2PUdpPort); +} + +TEST(NetLibTranslate, P2PAddressesAreIPv4Only) { + auto guest = GuestIn("198.51.100.4", 3658, 5000); + Core::Net::P2P::Endpoint endpoint; + u16 port = 0; + // Exactly 16 bytes: not shorter, and not the 32-byte IPv6 P2P sockaddr either. + EXPECT_EQ(ToP2PAddress(AsGuest(&guest), 15, P2PKind::Datagram, &endpoint, &port), + ORBIS_NET_EINVAL); + EXPECT_EQ(ToP2PAddress(AsGuest(&guest), 32, P2PKind::Datagram, &endpoint, &port), + ORBIS_NET_EINVAL); + guest.sin_family = ORBIS_NET_AF_INET6; + EXPECT_EQ(ToP2PAddress(AsGuest(&guest), sizeof(guest), P2PKind::Stream, &endpoint, &port), + ORBIS_NET_EAFNOSUPPORT); + EXPECT_EQ(ToP2PAddress(nullptr, sizeof(guest), P2PKind::Stream, &endpoint, &port), + ORBIS_NET_EFAULT); +} + +TEST(NetLibTranslate, MessageFlags) { + auto f = ToHostMsgFlags(ORBIS_NET_MSG_PEEK | ORBIS_NET_MSG_DONTWAIT); + EXPECT_TRUE(f.peek); + EXPECT_TRUE(f.dontwait); + EXPECT_EQ(f.host, MSG_PEEK); // DONTWAIT is the guest layer's business + EXPECT_EQ(f.unsupported, 0); + + f = ToHostMsgFlags(ORBIS_NET_MSG_WAITALL | 0x20000 /* MSG_NOSIGNAL */); + EXPECT_EQ(f.host, MSG_WAITALL); // 0x40 on the PS4, 0x100 on Linux, 0x8 on Windows + EXPECT_EQ(f.unsupported, 0); + + f = ToHostMsgFlags(ORBIS_NET_MSG_USECRYPTO | ORBIS_NET_MSG_PEEKLEN); + EXPECT_TRUE(f.peek); + EXPECT_TRUE(f.peeklen); + EXPECT_TRUE(f.crypto); // per-message protection is understood now + EXPECT_FALSE(f.signature); + EXPECT_EQ(f.unsupported, 0); + EXPECT_TRUE(ToHostMsgFlags(ORBIS_NET_MSG_WAITALL).waitall); + EXPECT_EQ(ToHostMsgFlags(0x1 /* MSG_OOB */).unsupported, 0x1); +} + +TEST(NetLibTranslate, OutOfBandIsNotPassedThrough) { + // The PS4's TCP has no urgent data: MSG_OOB is reported, not handed to the host. + const auto f = ToHostMsgFlags(0x1 | ORBIS_NET_MSG_DONTWAIT); + EXPECT_EQ(f.host & MSG_OOB, 0); + EXPECT_EQ(f.unsupported, 0x1); + EXPECT_TRUE(f.dontwait); +} + +TEST(NetLibTranslate, ReservedPorts) { + // UDP, TCP and TCP over UDPP2P ports. + for (const u16 port : {1, 80, 1023, 5353, 8540, 8579, 9293, 9310, 40000, 49152, 65535}) { + EXPECT_TRUE(IsReservedPort(port)) << port; + } + for (const u16 port : {0, 1024, 3658, 5352, 5354, 8539, 8580, 9292, 9311, 39999}) { + EXPECT_FALSE(IsReservedPort(port)) << port; + } + // UDPP2P virtual ports. + for (const u16 vport : {5353, 32768, 49999, 65535}) { + EXPECT_TRUE(IsReservedP2PVport(vport)) << vport; + } + for (const u16 vport : {0, 1, 80, 3658, 32767}) { + EXPECT_FALSE(IsReservedP2PVport(vport)) << vport; + } +} + +TEST(NetLibTranslate, DefaultBufferSizes) { + EXPECT_EQ(DefaultSendBuffer(SOCK_STREAM), 32768); + EXPECT_EQ(DefaultReceiveBuffer(SOCK_STREAM), 65536); + EXPECT_EQ(DefaultSendBuffer(SOCK_DGRAM), 9216); + EXPECT_EQ(DefaultReceiveBuffer(SOCK_DGRAM), 40960); + EXPECT_EQ(DefaultReceiveBuffer(SOCK_RAW), 40960); +} + +TEST(NetLibTranslate, OptionsMapByName) { + // Host numbering differs (on Linux IP_TTL is 2, not 4): the mapping must use host names. + auto o = ToHostOption(ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_TTL); + ASSERT_TRUE(o); + EXPECT_EQ(o->level, IPPROTO_IP); + EXPECT_EQ(o->name, IP_TTL); + o = ToHostOption(ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_ADD_MEMBERSHIP); + ASSERT_TRUE(o); + EXPECT_EQ(o->name, IP_ADD_MEMBERSHIP); + EXPECT_EQ(o->value, OptionValue::Raw); + o = ToHostOption(ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF); + ASSERT_TRUE(o); + EXPECT_EQ(o->level, SOL_SOCKET); + EXPECT_EQ(o->name, SO_RCVBUF); + o = ToHostOption(ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER); + ASSERT_TRUE(o); + EXPECT_EQ(o->value, OptionValue::Linger); + o = ToHostOption(ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_NODELAY); + ASSERT_TRUE(o); + EXPECT_EQ(o->name, TCP_NODELAY); + + // Guest-owned options are not host options. + EXPECT_FALSE(ToHostOption(ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO)); + EXPECT_FALSE(ToHostOption(ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO)); + EXPECT_FALSE(ToHostOption(0x1234, 1)); +} + +TEST(NetLibTranslate, EpollEventsAndErrors) { + const u32 all = + ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLOUT | ORBIS_NET_EPOLLERR | ORBIS_NET_EPOLLHUP; + EXPECT_EQ(ToOrbisEpollEvents(ToHostEpollEvents(all)), all); + EXPECT_EQ(ToHostEpollEvents(ORBIS_NET_EPOLLHUP), static_cast(Host::EvHup)); + + EXPECT_EQ(ToOrbisErrno(Host::Error::WouldBlock), ORBIS_NET_EWOULDBLOCK); + EXPECT_EQ(ToOrbisErrno(Host::Error::ConnRefused), ORBIS_NET_ECONNREFUSED); + EXPECT_EQ(ToOrbisErrno(Host::Error::NotSock), ORBIS_NET_EBADF); + EXPECT_EQ(ToOrbisErrno(Host::Error::Intr), ORBIS_NET_EINTR); +} + +} // namespace diff --git a/tests/network/test_net_p2p_codec.cpp b/tests/network/test_net_p2p_codec.cpp new file mode 100644 index 000000000..380f1148b --- /dev/null +++ b/tests/network/test_net_p2p_codec.cpp @@ -0,0 +1,263 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// The P2P wire format: exact bytes (pinning the reverse-engineered layout), communication-ID +// filtering, and payload protection (AES-128-CFB + truncated HMAC-SHA1). + +#include + +#include "core/net/p2p_codec.h" + +using namespace Core::Net; +using namespace Core::Net::P2P; + +namespace { + +const Endpoint kPeer = Endpoint::IPv4("192.0.2.10", 3658); +const Endpoint kOther = Endpoint::IPv4("192.0.2.11", 3658); +constexpr CommunicationId kComId{'N', 'P', 'W', 'R'}; + +std::vector Bytes(std::string_view s) { + return {s.begin(), s.end()}; +} + +P2PKey Key(u8 fill) { + P2PKey k; + k.value.fill(fill); + return k; +} + +class NetP2PCodec : public ::testing::Test { +protected: + static void SetUpTestSuite() { + ASSERT_TRUE(Host::Initialize()); + } + + std::shared_ptr keys = std::make_shared(); + FramingCodec codec{keys}; +}; + +// --------------------------------------------------------------------------------------------- +// Exact bytes +// --------------------------------------------------------------------------------------------- + +TEST_F(NetP2PCodec, DatagramHeader) { + // Vports are always two bytes, network byte order, even when small (0x40 is + // P2P_BROADCAST, not a short-vport flag). + EXPECT_EQ(codec.EncodeDatagram(3, 4, Bytes("ab"), kPeer, {}), + (std::vector{0xFF, 0x80 | 3, 0x00, 0x03, 0x00, 0x04, 'a', 'b'})); + EXPECT_EQ(codec.EncodeDatagram(0x0E4A, 4, Bytes("ab"), kPeer, {}), + (std::vector{0xFF, 0x80 | 3, 0x0E, 0x4A, 0x00, 0x04, 'a', 'b'})); +} + +TEST_F(NetP2PCodec, DatagramWithDigest) { + keys->SetCommunicationId(kComId); + EXPECT_EQ(codec.EncodeDatagram(1, 2, Bytes("z"), kPeer, {}), + (std::vector{0xFF, 0x80 | 0x20 | 3, 0, 1, 0, 2, 'N', 'P', 'W', 'R', 'z'})); +} + +TEST_F(NetP2PCodec, StreamHeader) { + const auto segment = Bytes("tcp-segment"); + auto expected = std::vector{0xFF, 0x80 | 7}; + expected.insert(expected.end(), segment.begin(), segment.end()); + EXPECT_EQ(codec.EncodeStream(segment, kPeer, {}), expected); + + keys->SetCommunicationId(kComId); + expected = {0xFF, 0x80 | 0x20 | 7, 'N', 'P', 'W', 'R'}; + expected.insert(expected.end(), segment.begin(), segment.end()); + EXPECT_EQ(codec.EncodeStream(segment, kPeer, {}), expected); +} + +TEST_F(NetP2PCodec, SignalingHeader) { + EXPECT_EQ(codec.EncodeSignaling(Bytes("SHAD"), kPeer), + (std::vector{0xFF, 0x80 | 3, 0xFF, 0xFF, 0xFF, 0xFF, 'S', 'H', 'A', 'D'})); +} + +// --------------------------------------------------------------------------------------------- +// Decoding +// --------------------------------------------------------------------------------------------- + +TEST_F(NetP2PCodec, DecodeRoundTrips) { + for (const auto& [src, dst] : {std::pair{3, 4}, {3658, 49152}, {255, 256}}) { + const auto d = + codec.Decode(codec.EncodeDatagram(src, dst, Bytes("payload"), kPeer, {}), kPeer); + EXPECT_EQ(d.kind, Codec::Kind::Datagram); + EXPECT_EQ(d.src_vport, src); // host byte order + EXPECT_EQ(d.dst_vport, dst); + EXPECT_EQ(d.payload, Bytes("payload")); + } + const auto s = codec.Decode(codec.EncodeStream(Bytes("seg"), kPeer, {}), kPeer); + EXPECT_EQ(s.kind, Codec::Kind::Stream); + EXPECT_EQ(s.payload, Bytes("seg")); + const auto g = codec.Decode(codec.EncodeSignaling(Bytes("hi"), kPeer), kPeer); + EXPECT_EQ(g.kind, Codec::Kind::Signaling); + EXPECT_EQ(g.payload, Bytes("hi")); +} + +TEST_F(NetP2PCodec, DecodeRejectsMalformed) { + const auto invalid = [&](std::vector p) { + return codec.Decode(p, kPeer).kind == Codec::Kind::Invalid; + }; + EXPECT_TRUE(invalid({})); + EXPECT_TRUE(invalid({0xFF})); + EXPECT_TRUE(invalid({0xFE, 0x80 | 3, 0, 1, 0, 2})); // bad marker + EXPECT_TRUE(invalid({0xFF, 3, 0, 1, 0, 2})); // P2P_VALID missing + EXPECT_TRUE(invalid({0xFF, 0x80 | 2, 0, 1, 0, 2})); // kind below P2P_UDP_SIMPLE + EXPECT_TRUE(invalid({0xFF, 0x80 | 11})); // kind above P2P_TCP_CRYPTO_SIGNTR + EXPECT_TRUE(invalid({0xFF, 0x80 | 0x40 | 7, 0, 1})); // P2P_BROADCAST on TCP + EXPECT_TRUE(invalid({0xFF, 0x80 | 3, 0, 1, 0})); // truncated header + EXPECT_TRUE(invalid({0xFF, 0x80 | 0x20 | 7, 'N', 'P'})); // truncated communication ID +} + +TEST_F(NetP2PCodec, BroadcastPacketsAreDecodedAndReported) { + int reported = 0; + std::vector seen; + FramingCodec observed{keys, [&](std::span packet, const Endpoint& from) { + ++reported; + seen.assign(packet.begin(), packet.end()); + EXPECT_EQ(from, kPeer); + }}; + // [FF][valid|broadcast|digest|UDP_SIMPLE][port 0x0E4A][digest][payload] + const std::vector broadcast{ + 0xFF, 0x80 | 0x40 | 0x20 | 3, 0x0E, 0x4A, 'N', 'P', 'W', 'R', 'x', 'y'}; + for (int i = 0; i < FramingCodec::MaxReportedBroadcasts + 5; ++i) { + const auto d = observed.Decode(broadcast, kPeer); + ASSERT_EQ(d.kind, Codec::Kind::Datagram); + EXPECT_EQ(d.dst_vport, 0x0E4A); + EXPECT_EQ(d.src_vport, 0x0E4A); // no source vport on the wire: the same port + EXPECT_EQ(d.payload, Bytes("xy")); + } + EXPECT_EQ(reported, FramingCodec::MaxReportedBroadcasts); // only the first few + EXPECT_EQ(seen, broadcast); // the whole packet + + // Without a digest the payload follows the port. + const auto small = observed.Decode(std::vector{0xFF, 0x80 | 0x40 | 3, 0, 3, 'z'}, kPeer); + EXPECT_EQ(small.kind, Codec::Kind::Datagram); + EXPECT_EQ(small.dst_vport, 3); + EXPECT_EQ(small.payload, Bytes("z")); + + // The digest filters broadcasts like everything else; no broadcast TCP; truncated. + keys->SetCommunicationId(CommunicationId{'O', 'T', 'H', 'R'}); + EXPECT_EQ(observed.Decode(broadcast, kPeer).kind, Codec::Kind::Invalid); + keys->SetCommunicationId(std::nullopt); + EXPECT_EQ(observed.Decode(std::vector{0xFF, 0x80 | 0x40 | 7, 0, 3}, kPeer).kind, + Codec::Kind::Invalid); + EXPECT_EQ(observed.Decode(std::vector{0xFF, 0x80 | 0x40 | 3, 0}, kPeer).kind, + Codec::Kind::Invalid); + + // Ordinary packets are not reported. + const int before = reported; + observed.Decode(codec.EncodeDatagram(1, 2, Bytes("x"), kPeer, {}), kPeer); + EXPECT_EQ(reported, before); +} + +TEST_F(NetP2PCodec, CommunicationIdFilter) { + FramingCodec sender{std::make_shared()}; + const auto plain = sender.EncodeDatagram(1, 2, Bytes("x"), kPeer, {}); + auto sender_keys = std::make_shared(); + sender_keys->SetCommunicationId(CommunicationId{'O', 'T', 'H', 'R'}); + FramingCodec other_title{sender_keys}; + const auto foreign = other_title.EncodeDatagram(1, 2, Bytes("x"), kPeer, {}); + const auto signaling = other_title.EncodeSignaling(Bytes("s"), kPeer); + + // No ID configured: everything is accepted. + EXPECT_EQ(codec.Decode(plain, kPeer).kind, Codec::Kind::Datagram); + EXPECT_EQ(codec.Decode(foreign, kPeer).kind, Codec::Kind::Datagram); + + keys->SetCommunicationId(kComId); + EXPECT_EQ(codec.Decode(plain, kPeer).kind, Codec::Kind::Invalid); // missing ID + EXPECT_EQ(codec.Decode(foreign, kPeer).kind, Codec::Kind::Invalid); // other title + EXPECT_EQ(codec.Decode(codec.EncodeDatagram(1, 2, Bytes("x"), kPeer, {}), kPeer).kind, + Codec::Kind::Datagram); + EXPECT_EQ(codec.Decode(signaling, kPeer).kind, Codec::Kind::Signaling); // exempt +} + +// --------------------------------------------------------------------------------------------- +// Protection +// --------------------------------------------------------------------------------------------- + +TEST(NetP2PCrypto, LayoutAndRoundTrip) { + const auto data = Bytes("the quick brown fox"); + for (const bool encrypt : {false, true}) { + for (const bool sign : {false, true}) { + std::vector protected_data, restored; + ASSERT_TRUE(ProtectPayload(data, encrypt, sign, Key(7), protected_data)); + EXPECT_EQ(protected_data.size(), data.size() + (encrypt ? 4 : 0) + (sign ? 4 : 0)); + if (encrypt) { + EXPECT_NE(std::vector(protected_data.end() - data.size(), protected_data.end()), + data); + } + ASSERT_TRUE(UnprotectPayload(protected_data, encrypt, sign, Key(7), restored)); + EXPECT_EQ(restored, data); + } + } +} + +TEST(NetP2PCrypto, SignatureDetectsTamperingAndWrongKey) { + std::vector protected_data, restored; + ASSERT_TRUE(ProtectPayload(Bytes("payload"), true, true, Key(7), protected_data)); + EXPECT_FALSE(UnprotectPayload(protected_data, true, true, Key(8), restored)); + protected_data.back() ^= 0x01; + EXPECT_FALSE(UnprotectPayload(protected_data, true, true, Key(7), restored)); + EXPECT_FALSE(UnprotectPayload(std::vector(7, 0), true, true, Key(7), restored)); // short +} + +TEST(NetP2PCrypto, RandomIvPerPacket) { + std::vector a, b; + ProtectPayload(Bytes("same"), true, false, Key(1), a); + ProtectPayload(Bytes("same"), true, false, Key(1), b); + EXPECT_NE(a, b); +} + +TEST_F(NetP2PCodec, ProtectedTrafficNeedsMatchingKey) { + auto sender_keys = std::make_shared(); + FramingCodec sender{sender_keys}; + const Protection both{true, true}; + + // Sender without a key falls back to a plain packet (kind 3). + auto packet = sender.EncodeDatagram(1, 2, Bytes("secret"), kPeer, both); + EXPECT_EQ(packet[1] & 0x0F, 3); + + sender_keys->SetPeerKey(kPeer, Key(9)); + packet = sender.EncodeDatagram(1, 2, Bytes("secret"), kPeer, both); + EXPECT_EQ(packet[1] & 0x0F, 3 + 1 + 2); + const auto stream = sender.EncodeStream(Bytes("segment"), kPeer, both); + EXPECT_EQ(stream[1] & 0x0F, 7 + 1 + 2); + + // The receiver looks up the key of whoever sent it (the sender is kPeer from its view). + EXPECT_EQ(codec.Decode(packet, kPeer).kind, Codec::Kind::Invalid); // no key: dropped + keys->SetPeerKey(kOther, Key(9)); + EXPECT_EQ(codec.Decode(packet, kPeer).kind, Codec::Kind::Invalid); // key for someone else + keys->SetWildcardKey(Key(1)); + EXPECT_EQ(codec.Decode(packet, kPeer).kind, Codec::Kind::Invalid); // wrong wildcard key + keys->SetPeerKey(kPeer, Key(9)); + const auto d = codec.Decode(packet, kPeer); + EXPECT_EQ(d.kind, Codec::Kind::Datagram); + EXPECT_EQ(d.payload, Bytes("secret")); + EXPECT_EQ(codec.Decode(stream, kPeer).payload, Bytes("segment")); +} + +TEST(NetP2PKeyring, LookupOrder) { + Keyring keys; + EXPECT_FALSE(keys.Find(kPeer)); + keys.SetDefaultKey(Key(1)); + EXPECT_EQ(keys.Find(kPeer), Key(1)); + keys.SetWildcardKey(Key(2)); + EXPECT_EQ(keys.Find(kPeer), Key(2)); + keys.SetPeerKey(kPeer, Key(3)); + EXPECT_EQ(keys.Find(kPeer), Key(3)); + EXPECT_EQ(keys.Find(kOther), Key(2)); + keys.RemovePeerKey(kPeer); + EXPECT_EQ(keys.Find(kPeer), Key(2)); +} + +TEST_F(NetP2PCodec, PseudoHeaderUsesInterfaceAddress) { + const auto p = codec.StreamPseudoHeader(Endpoint::IPv4("127.0.0.1", 9)); + EXPECT_EQ(p.family, AF_INET); + EXPECT_EQ((std::array{p.local[0], p.local[1], p.local[2], p.local[3]}), + (std::array{127, 0, 0, 1})); + EXPECT_EQ((std::array{p.remote[0], p.remote[1], p.remote[2], p.remote[3]}), + (std::array{127, 0, 0, 1})); +} + +} // namespace diff --git a/tests/network/test_net_p2p_guest.cpp b/tests/network/test_net_p2p_guest.cpp new file mode 100644 index 000000000..a0db0c31a --- /dev/null +++ b/tests/network/test_net_p2p_guest.cpp @@ -0,0 +1,515 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include + +#include + +#include "core/net/guest_net.h" +#include "core/net/p2p_codec.h" +#include "core/net/p2p_transport.h" + +using namespace Core::Net; +using namespace Core::Net::P2P; +using namespace std::chrono_literals; +using Host::Error; + +namespace { + +class TestCodec final : public Codec { +public: + Decoded Decode(std::span packet, const Endpoint&) override { + Decoded d; + if (packet.size() < 5) { + return d; + } + d.kind = packet[0] == 1 ? Kind::Stream + : packet[0] == 2 ? Kind::Datagram + : packet[0] == 3 ? Kind::Signaling + : Kind::Invalid; + d.src_vport = static_cast((packet[1] << 8) | packet[2]); + d.dst_vport = static_cast((packet[3] << 8) | packet[4]); + d.payload.assign(packet.begin() + 5, packet.end()); + return d; + } + std::vector EncodeStream(std::span seg, const Endpoint&, Protection) override { + return Frame(1, 0, 0, seg); + } + std::vector EncodeDatagram(u16 src, u16 dst, std::span payload, const Endpoint&, + Protection) override { + return Frame(2, src, dst, payload); + } + std::vector EncodeSignaling(std::span data, const Endpoint&) override { + return Frame(3, 0, 0, data); + } + PseudoHeader StreamPseudoHeader(const Endpoint&) override { + PseudoHeader p; + p.local = p.remote = {127, 0, 0, 1}; + return p; + } + +private: + static std::vector Frame(u8 kind, u16 src, u16 dst, std::span payload) { + std::vector v{kind, static_cast(src >> 8), static_cast(src), + static_cast(dst >> 8), static_cast(dst)}; + v.insert(v.end(), payload.begin(), payload.end()); + return v; + } +}; + +std::shared_ptr MakeTransport(std::unique_ptr codec) { + Error e; + auto t = Transport::Create(AF_INET, Endpoint::IPv4("127.0.0.1", 0), std::move(codec), {}, &e); + EXPECT_TRUE(t) << "transport creation failed: " << static_cast(e); + return t; +} + +std::vector RandomBytes(size_t n, u32 seed) { + std::mt19937 rng(seed); + std::vector v(n); + for (auto& b : v) { + b = static_cast(rng()); + } + return v; +} + +double Millis(Clock::time_point since) { + return std::chrono::duration(Clock::now() - since).count(); +} + +/// Drives a remote stream socket: send everything, read until `expect_in` bytes arrived. +bool RemotePump(StreamSocket& s, const std::vector& out, std::vector& in, size_t expect_in, + std::chrono::seconds limit) { + size_t off = 0; + const auto end = Clock::now() + limit; + u8 buf[8192]; + while (Clock::now() < end) { + if (off < out.size()) { + off += s.Send(std::span(out).subspan(off)).bytes; + } + for (;;) { + const auto r = s.Recv(buf, false); + if (r.bytes == 0) { + break; + } + in.insert(in.end(), buf, buf + r.bytes); + } + if (off == out.size() && in.size() >= expect_in) { + return true; + } + std::this_thread::sleep_for(1ms); + } + return false; +} + +class NetP2PGuest : public ::testing::Test { +protected: + static void SetUpTestSuite() { + ASSERT_TRUE(Host::Initialize()); + local_ = MakeTransport(std::make_unique()); + remote_ = MakeTransport(std::make_unique()); + SetP2PTransport(AF_INET, local_); + } + static void TearDownTestSuite() { + SetP2PTransport(AF_INET, nullptr); + local_.reset(); + remote_.reset(); + } + void TearDown() override { + local_->SetOutgoingLossForTesting(0); + remote_->SetOutgoingLossForTesting(0); + } + + static Endpoint LocalAddress() { + return Endpoint::IPv4("127.0.0.1", local_->BoundPort()); + } + static Endpoint RemoteAddress() { + return Endpoint::IPv4("127.0.0.1", remote_->BoundPort()); + } + static s32 NewP2PSocket(bool stream) { + const auto r = P2PSocketCreate(AF_INET, stream); + EXPECT_EQ(r.error, Error::Ok); + return static_cast(r.value); + } + + static inline std::shared_ptr local_; + static inline std::shared_ptr remote_; +}; + +TEST_F(NetP2PGuest, EpollInfiniteWaitWakesOnDatagram) { + const s32 sock = NewP2PSocket(false); + ASSERT_EQ(P2PSocketBind(sock, 3001).error, Error::Ok); + const s32 ep = static_cast(EpollCreate().value); + ASSERT_EQ(EpollControl(ep, EpollOp::Add, sock, Host::EvIn, 0, 0xfeed).error, Error::Ok); + + auto remote = remote_->CreateDatagram(); + remote->Bind(4001); + std::thread sender([&] { + std::this_thread::sleep_for(200ms); + const u8 msg[] = "ping"; + const auto to = LocalAddress(); + remote->SendTo(msg, &to, 3001); + }); + const auto start = Clock::now(); + GuestEpollEvent events[4]; + const auto r = EpollWait(ep, events, -1); // infinite timeout + const double waited = Millis(start); + sender.join(); + + ASSERT_EQ(r.value, 1); + EXPECT_EQ(events[0].data, 0xfeedu); + EXPECT_TRUE(events[0].events & Host::EvIn); + EXPECT_LT(waited, 2000.0); + + u8 buf[16]; + Endpoint from; + u16 from_vport = 0; + EXPECT_EQ(P2PSocketRecvFrom(sock, buf, sizeof(buf), false, false, &from, &from_vport).value, 5); + EXPECT_EQ(from_vport, 4001); + EXPECT_EQ(from, RemoteAddress()); + SocketClose(sock); + EpollDestroy(ep); +} + +TEST_F(NetP2PGuest, MixedNativeAndP2PEpoll) { + const s32 native = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + ASSERT_EQ(SocketBind(native, reinterpret_cast(&addr), sizeof(addr)).error, + Error::Ok); + socklen_t len = sizeof(addr); + SocketGetName(native, reinterpret_cast(&addr), &len); + const s32 p2p = NewP2PSocket(false); + P2PSocketBind(p2p, 3002); + const s32 ep = static_cast(EpollCreate().value); + EpollControl(ep, EpollOp::Add, native, Host::EvIn, 0, 1); + EpollControl(ep, EpollOp::Add, p2p, Host::EvIn, 0, 2); + GuestEpollEvent events[4]; + + auto remote = remote_->CreateDatagram(); + remote->Bind(4002); + const u8 msg[] = "x"; + const auto to = LocalAddress(); + remote->SendTo(msg, &to, 3002); + auto r = EpollWait(ep, events, 2'000'000); + ASSERT_EQ(r.value, 1); + EXPECT_EQ(events[0].data, 2u); + EXPECT_EQ(EpollWait(ep, events, 0).value, 1); // level-triggered until read + u8 buf[8]; + P2PSocketRecvFrom(p2p, buf, sizeof(buf), false, false, nullptr, nullptr); + EXPECT_EQ(EpollWait(ep, events, 50'000).value, 0); + + Error e; + const auto helper = Host::CreateSocket(AF_INET, SOCK_DGRAM, 0, &e); + Host::SendTo(helper, "y", 1, 0, reinterpret_cast(&addr), sizeof(addr)); + r = EpollWait(ep, events, 2'000'000); + ASSERT_EQ(r.value, 1); + EXPECT_EQ(events[0].data, 1u); + + Host::CloseSocket(helper); + SocketClose(native); + SocketClose(p2p); + EpollDestroy(ep); +} + +TEST_F(NetP2PGuest, SelectWorksOnP2PSockets) { + // select() needs no host socket: a P2P socket is waited on like a native one. + const s32 p2p = NewP2PSocket(false); + ASSERT_EQ(P2PSocketBind(p2p, 3009).error, Error::Ok); + const s32 native = static_cast(SocketCreate(AF_INET, SOCK_DGRAM, 0).value); + SelectEntry entries[] = {{p2p, Host::EvIn, 0}, {native, Host::EvIn, 0}}; + EXPECT_EQ(SocketSelect(entries, 20'000).value, 0); + + auto remote = remote_->CreateDatagram(); + remote->Bind(4009); + std::thread sender([&] { + std::this_thread::sleep_for(100ms); + const u8 msg[] = "hi"; + const auto to = LocalAddress(); + remote->SendTo(msg, &to, 3009); + }); + const auto r = SocketSelect(entries, 2'000'000); + sender.join(); + ASSERT_EQ(r.error, Error::Ok); + EXPECT_EQ(r.value, 1); + EXPECT_EQ(entries[0].ready, Host::EvIn); + EXPECT_EQ(entries[1].ready, 0u); + + // Selecting leaves nothing behind: an epoll on the same socket still works afterwards. + const s32 ep = static_cast(EpollCreate().value); + ASSERT_EQ(EpollControl(ep, EpollOp::Add, p2p, Host::EvIn, 0, 7).error, Error::Ok); + GuestEpollEvent events[2]; + EXPECT_EQ(EpollWait(ep, events, 0).value, 1); + EpollDestroy(ep); + SocketClose(p2p); + SocketClose(native); +} + +TEST_F(NetP2PGuest, EpollWritableForDatagram) { + const s32 sock = NewP2PSocket(false); + P2PSocketBind(sock, 3003); + const s32 ep = static_cast(EpollCreate().value); + EpollControl(ep, EpollOp::Add, sock, Host::EvIn | Host::EvOut, 0, 7); + GuestEpollEvent events[4]; + ASSERT_EQ(EpollWait(ep, events, 0).value, 1); // reported once, not per handle + EXPECT_EQ(events[0].events, static_cast(Host::EvOut)); + EpollControl(ep, EpollOp::Modify, sock, Host::EvIn, 0, 7); + EXPECT_EQ(EpollWait(ep, events, 0).value, 0); + EXPECT_EQ(EpollControl(ep, EpollOp::Add, sock, Host::EvIn, 0, 0).error, Error::Exist); + // One-shot/edge-triggered are not emulated for P2P sockets yet (DISCUSS in guest_net). + EpollControl(ep, EpollOp::Delete, sock, 0, 0, 0); + EXPECT_EQ(EpollControl(ep, EpollOp::Add, sock, Host::EvIn, Host::OneShot, 0).error, + Error::Inval); + SocketClose(sock); + EpollDestroy(ep); +} + +TEST_F(NetP2PGuest, RecvTimeoutAndAbort) { + const s32 sock = NewP2PSocket(false); + P2PSocketBind(sock, 3004); + u8 buf[8]; + + SocketSetRecvTimeout(sock, 200ms); + auto start = Clock::now(); + EXPECT_EQ(P2PSocketRecvFrom(sock, buf, sizeof(buf), false, false, nullptr, nullptr).error, + Error::WouldBlock); + EXPECT_GE(Millis(start), 190.0); + EXPECT_LT(Millis(start), 2000.0); + + // Abort wakes a recv blocked without timeout (the old P2P abort only flushed queues). + SocketSetRecvTimeout(sock, 0us); + std::thread aborter([&] { + std::this_thread::sleep_for(150ms); + SocketAbort(sock, kSocketAbortPreserveRecv); + }); + start = Clock::now(); + EXPECT_EQ(P2PSocketRecvFrom(sock, buf, sizeof(buf), false, false, nullptr, nullptr).error, + Error::Intr); + EXPECT_LT(Millis(start), 2000.0); + aborter.join(); + SocketClose(sock); +} + +TEST_F(NetP2PGuest, EpollAbortWakesInfiniteWait) { + const s32 ep = static_cast(EpollCreate().value); + std::thread aborter([&] { + std::this_thread::sleep_for(100ms); + EpollAbort(ep, kEpollAbortPreserve); + }); + GuestEpollEvent events[1]; + EXPECT_EQ(EpollWait(ep, events, -1).error, Error::Intr); + aborter.join(); + EpollDestroy(ep); +} + +TEST_F(NetP2PGuest, StreamBlockingConnectAndRefused) { + auto listener = remote_->CreateStream(); + ASSERT_EQ(listener->Bind(5000), Error::Ok); + ASSERT_EQ(listener->Listen(4), Error::Ok); + + const s32 sock = NewP2PSocket(true); + ASSERT_EQ(P2PSocketConnect(sock, RemoteAddress(), 5000).error, Error::Ok); // blocks + Error e = Error::WouldBlock; + std::shared_ptr accepted; + for (int i = 0; i < 200 && !accepted; ++i) { + accepted = listener->Accept(nullptr, nullptr, &e); + std::this_thread::sleep_for(5ms); + } + ASSERT_TRUE(accepted); + const char msg[] = "hello from the guest"; + EXPECT_EQ(P2PSocketSendTo(sock, msg, sizeof(msg), false, nullptr, 0).value, + static_cast(sizeof(msg))); + u8 buf[64]{}; + size_t got = 0; + for (int i = 0; i < 200 && got < sizeof(msg); ++i) { + got += accepted->Recv(std::span(buf).subspan(got), false).bytes; + std::this_thread::sleep_for(5ms); + } + EXPECT_EQ(std::memcmp(buf, msg, sizeof(msg)), 0); + + // Nothing listens on vport 5999: the remote transport answers with RST. + const s32 refused = NewP2PSocket(true); + EXPECT_EQ(P2PSocketConnect(refused, RemoteAddress(), 5999).error, Error::ConnRefused); + + // Non-blocking connect reports progress, then completion via writability. + const s32 async = NewP2PSocket(true); + SocketSetNonBlocking(async, true); + EXPECT_EQ(P2PSocketConnect(async, RemoteAddress(), 5000).error, Error::InProgress); + const s32 ep = static_cast(EpollCreate().value); + EpollControl(ep, EpollOp::Add, async, Host::EvOut, 0, 0); + GuestEpollEvent events[1]; + EXPECT_EQ(EpollWait(ep, events, 2'000'000).value, 1); + EXPECT_EQ(P2PSocketConnect(async, RemoteAddress(), 5000).error, Error::IsConn); + + EpollDestroy(ep); + SocketClose(async); + SocketClose(sock); + SocketClose(refused); + listener->Close(); +} + +TEST_F(NetP2PGuest, CloseWakesBlockedAccept) { + const s32 listener = NewP2PSocket(true); + P2PSocketBind(listener, 3700); + SocketListen(listener, 1); + NetResult r{}; + std::thread t([&] { r = P2PSocketAccept(listener, nullptr, nullptr); }); + std::this_thread::sleep_for(100ms); + SocketClose(listener); + t.join(); + EXPECT_EQ(r.error, Error::Intr); +} + +TEST_F(NetP2PGuest, DatagramQueueBoundedAndUnboundDropped) { + const s32 sock = NewP2PSocket(false); + P2PSocketBind(sock, 3800); + EXPECT_EQ(P2PSocketBind(NewP2PSocket(false), 3800).error, Error::AddrInUse); + auto remote = remote_->CreateDatagram(); + remote->Bind(4800); + const auto to = LocalAddress(); + const std::vector big(1200, 0xab); + for (int i = 0; i < 400; ++i) { // 480 KB into a 256 KB budget + remote->SendTo(big, &to, 3800); + remote->SendTo(big, &to, 3801); // nobody bound here + if (i % 50 == 0) { + std::this_thread::sleep_for(1ms); // don't overflow the host socket buffer + } + } + std::this_thread::sleep_for(300ms); + size_t received = 0; + u8 buf[2048]; + SocketSetNonBlocking(sock, true); + while (P2PSocketRecvFrom(sock, buf, sizeof(buf), false, false, nullptr, nullptr).value > 0) { + received += 1200; + } + EXPECT_GT(received, 0u); + EXPECT_LE(received, TransportConfig{}.datagram_queue_bytes); + SocketClose(sock); +} + +class NetP2PGuestStream : public NetP2PGuest, public ::testing::WithParamInterface {}; + +TEST_P(NetP2PGuestStream, BlockingAcceptSendRecv) { + const double loss = GetParam(); + const size_t bytes = loss > 0 ? (1u << 20) : (4u << 20); + local_->SetOutgoingLossForTesting(loss); + remote_->SetOutgoingLossForTesting(loss); + const s32 listener = NewP2PSocket(true); + ASSERT_EQ(P2PSocketBind(listener, 3658).error, Error::Ok); + ASSERT_EQ(SocketListen(listener, 4).error, Error::Ok); + + const auto guest_data = RandomBytes(bytes, 11); + const auto remote_data = RandomBytes(bytes, 12); + std::vector guest_got; + std::atomic guest_done{false}; + + // Guest side: blocking accept, blocking send of everything, blocking recv to EOF. + std::thread guest([&] { + const auto a = P2PSocketAccept(listener, nullptr, nullptr); + if (a.error != Error::Ok) { + return; + } + const s32 conn = static_cast(a.value); + std::thread writer([&] { + EXPECT_EQ(P2PSocketSendTo(conn, guest_data.data(), guest_data.size(), false, nullptr, 0) + .value, + static_cast(guest_data.size())); + SocketShutdown(conn, 1); + }); + u8 buf[16384]; + for (;;) { + const auto r = + P2PSocketRecvFrom(conn, buf, sizeof(buf), false, false, nullptr, nullptr); + if (r.error != Error::Ok || r.value == 0) { + break; + } + guest_got.insert(guest_got.end(), buf, buf + r.value); + } + writer.join(); + SocketClose(conn); + guest_done = true; + }); + + auto remote = remote_->CreateStream(); + EXPECT_EQ(remote->Connect(LocalAddress(), 3658), Error::InProgress); + std::vector remote_got; + const bool pumped = RemotePump(*remote, remote_data, remote_got, bytes, 120s); + remote->Shutdown(1); + if (!pumped) { + SocketAbort(listener, kSocketAbortPreserveRecv); // unblock the guest thread + } + guest.join(); + remote->Close(); + SocketClose(listener); + + EXPECT_TRUE(pumped); + EXPECT_TRUE(guest_done); + EXPECT_TRUE(guest_got == remote_data) << "guest received " << guest_got.size(); + EXPECT_TRUE(remote_got == guest_data) << "remote received " << remote_got.size(); +} + +INSTANTIATE_TEST_SUITE_P(Loss, NetP2PGuestStream, ::testing::Values(0.0, 0.05), + [](const auto& info) { + return info.param > 0 ? std::string("Loss5") : std::string("Clean"); + }); + +// --------------------------------------------------------------------------------------------- +// The real wire format end to end +// --------------------------------------------------------------------------------------------- + +TEST(NetP2PFraming, ProtectedStreamWithCommunicationId) { + ASSERT_TRUE(Host::Initialize()); + auto keys_a = std::make_shared(); + auto keys_b = std::make_shared(); + const auto a = MakeTransport(std::make_unique(keys_a)); + const auto b = MakeTransport(std::make_unique(keys_b)); + const auto a_addr = Endpoint::IPv4("127.0.0.1", a->BoundPort()); + const auto b_addr = Endpoint::IPv4("127.0.0.1", b->BoundPort()); + P2PKey key; + key.value.fill(0x5a); + for (auto* keys : {keys_a.get(), keys_b.get()}) { + keys->SetCommunicationId(CommunicationId{'N', 'P', 'W', 'R'}); + } + keys_a->SetPeerKey(b_addr, key); + keys_b->SetPeerKey(a_addr, key); + + auto listener = b->CreateStream(); + listener->SetProtection({true, true}); + ASSERT_EQ(listener->Bind(3658), Error::Ok); + ASSERT_EQ(listener->Listen(1), Error::Ok); + auto client = a->CreateStream(); + client->SetProtection({true, true}); + ASSERT_EQ(client->Connect(b_addr, 3658), Error::InProgress); + + std::shared_ptr server; + Error e; + for (int i = 0; i < 400 && !server; ++i) { + server = listener->Accept(nullptr, nullptr, &e); + std::this_thread::sleep_for(5ms); + } + ASSERT_TRUE(server); + const auto data = RandomBytes(256 * 1024, 5); + std::vector got; + std::vector nothing; + std::thread reader([&] { RemotePump(*server, {}, got, data.size(), 30s); }); + EXPECT_TRUE(RemotePump(*client, data, nothing, 0, 30s)); + reader.join(); + EXPECT_TRUE(got == data) << "received " << got.size(); + + // A transport of another title (different communication ID) cannot even connect. + auto keys_c = std::make_shared(); + keys_c->SetCommunicationId(CommunicationId{'O', 'T', 'H', 'R'}); + const auto c = MakeTransport(std::make_unique(keys_c)); + auto stranger = c->CreateStream(); + stranger->Connect(b_addr, 3658); + std::this_thread::sleep_for(300ms); + EXPECT_EQ(stranger->ConnectResult(), Error::InProgress); // SYNs silently dropped + stranger->Abort(); +} + +} // namespace diff --git a/tests/network/test_net_p2p_tcp.cpp b/tests/network/test_net_p2p_tcp.cpp new file mode 100644 index 000000000..1ce60ad92 --- /dev/null +++ b/tests/network/test_net_p2p_tcp.cpp @@ -0,0 +1,395 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// The STREAM_P2P TCP state machine: wire format, and two connections over a simulated link with +// loss, duplication, reordering and jitter on a virtual clock (so the tests are fast and +// deterministic). + +#include +#include +#include +#include +#include + +#include + +#include "core/net/p2p_tcp.h" + +using namespace Core::Net; +using namespace Core::Net::P2P; +using namespace std::chrono_literals; + +namespace { + +PseudoHeader Pseudo(u8 local, u8 remote) { + PseudoHeader p; + p.family = AF_INET; + p.local = {10, 0, 0, local}; + p.remote = {10, 0, 0, remote}; + return p; +} + +std::vector RandomBytes(size_t n, u64 seed) { + std::mt19937_64 rng(seed); + std::vector v(n); + for (auto& b : v) { + b = static_cast(rng()); + } + return v; +} + +// --------------------------------------------------------------------------------------------- +// Wire format +// --------------------------------------------------------------------------------------------- + +TEST(NetP2PTcpWire, SequenceArithmeticWraps) { + EXPECT_TRUE(SeqLt(0xFFFFFFF0u, 0x10u)); + EXPECT_TRUE(SeqGt(0x10u, 0xFFFFFFF0u)); + EXPECT_TRUE(SeqLe(5u, 5u)); + EXPECT_FALSE(SeqLt(5u, 5u)); +} + +TEST(NetP2PTcpWire, BuildParseRoundTripWithChecksum) { + const u8 payload[] = {1, 2, 3, 4, 5}; + const auto seg = BuildTcpSegment(Pseudo(1, 2), 1234, 80, 1000, 2000, + TcpFlag::Ack | TcpFlag::Psh, 4096, u16{1200}, payload); + const auto receiver = Pseudo(2, 1); + const auto parsed = ParseTcpSegment(seg, &receiver); + ASSERT_TRUE(parsed); + EXPECT_EQ(parsed->src_port, 1234); + EXPECT_EQ(parsed->dst_port, 80); + EXPECT_EQ(parsed->seq, 1000u); + EXPECT_EQ(parsed->ack, 2000u); + EXPECT_EQ(parsed->flags, TcpFlag::Ack | TcpFlag::Psh); + EXPECT_EQ(parsed->window, 4096); + EXPECT_EQ(parsed->mss, 1200); + EXPECT_EQ(std::vector(parsed->payload.begin(), parsed->payload.end()), + std::vector(std::begin(payload), std::end(payload))); + + // A flipped bit, or the wrong pseudo-header, fails verification. + auto corrupt = seg; + corrupt.back() ^= 1; + EXPECT_FALSE(ParseTcpSegment(corrupt, &receiver)); + const auto wrong = Pseudo(2, 9); + EXPECT_FALSE(ParseTcpSegment(seg, &wrong)); + EXPECT_TRUE(ParseTcpSegment(corrupt)); // no pseudo-header: no verification +} + +TEST(NetP2PTcpWire, KnownChecksum) { + // SYN 10.0.0.1:1234 -> 10.0.0.2:80, seq 1, window 1024, no options; checksum computed with + // an independent implementation (RFC 1071 one's complement sum, Python). + const auto seg = + BuildTcpSegment(Pseudo(1, 2), 1234, 80, 1, 0, TcpFlag::Syn, 1024, std::nullopt, {}); + EXPECT_EQ((seg[16] << 8) | seg[17], 0x92BD); +} + +TEST(NetP2PTcpWire, SkipsUnknownOptions) { + // SYN carrying MSS, SACK-permitted, timestamps, NOP and window scale, as Linux sends. + std::vector seg(40, 0); + seg[12] = 10 << 4; + seg[13] = TcpFlag::Syn; + const u8 options[] = {2, 4, 0x05, 0xB4, 4, 2, 8, 10, 0, 0, 0, 1, 0, 0, 0, 0, 1, 3, 3, 7}; + std::copy(std::begin(options), std::end(options), seg.begin() + 20); + const auto parsed = ParseTcpSegment(seg); + ASSERT_TRUE(parsed); + EXPECT_EQ(parsed->mss, 1460); + EXPECT_TRUE(parsed->payload.empty()); +} + +TEST(NetP2PTcpWire, RejectsMalformed) { + std::vector seg(20, 0); + seg[12] = 4 << 4; // data offset below 20 bytes + EXPECT_FALSE(ParseTcpSegment(seg)); + seg[12] = 15 << 4; // data offset beyond the segment + EXPECT_FALSE(ParseTcpSegment(seg)); + EXPECT_FALSE(ParseTcpSegment(std::vector(19, 0))); +} + +TEST(NetP2PTcpWire, ResetForUnknownPort) { + const auto p = Pseudo(1, 2); + const u8 data[10]{}; + // With ACK: RST takes its sequence number from the ACK. + TcpSegment with_ack{5000, 80, 100, 777, TcpFlag::Ack, 100, std::nullopt, data}; + auto rst = ParseTcpSegment(*BuildResetFor(p, with_ack)); + EXPECT_EQ(rst->flags, TcpFlag::Rst); + EXPECT_EQ(rst->seq, 777u); + EXPECT_EQ(rst->src_port, 80); + EXPECT_EQ(rst->dst_port, 5000); + // Without ACK (a SYN): RST|ACK acknowledging what the segment occupied. + TcpSegment syn{5000, 80, 100, 0, TcpFlag::Syn, 100, std::nullopt, {}}; + rst = ParseTcpSegment(*BuildResetFor(p, syn)); + EXPECT_EQ(rst->flags, TcpFlag::Rst | TcpFlag::Ack); + EXPECT_EQ(rst->ack, 101u); + // Never answer a RST with a RST. + TcpSegment reset{5000, 80, 100, 0, TcpFlag::Rst, 0, std::nullopt, {}}; + EXPECT_FALSE(BuildResetFor(p, reset)); +} + +// --------------------------------------------------------------------------------------------- +// Simulated link +// --------------------------------------------------------------------------------------------- + +struct LinkParams { + double loss = 0.0; + double duplicate = 0.0; + std::chrono::milliseconds delay{20}; + std::chrono::milliseconds jitter{0}; // jitter larger than packet spacing reorders +}; + +class Sim { +public: + Sim(u64 seed, LinkParams link) : rng_(seed), link_(link) {} + + Clock::time_point now{}; + + void Transmit(int to, std::span bytes) { + if (std::getenv("SHADPS4_NET_SIMLOG")) { // keep a trace, printed on failure + const auto seg = ParseTcpSegment(bytes); + char line[160]; + std::snprintf(line, sizeof(line), "%8.3f %s flags=%02x seq=%u ack=%u len=%zu win=%u", + std::chrono::duration(now.time_since_epoch()).count(), + to ? "A->B" : "B->A", seg->flags, seg->seq, seg->ack, seg->payload.size(), + seg->window); + log_.push_back(line); + if (log_.size() > 60) { + log_.pop_front(); + } + } + std::uniform_real_distribution u(0, 1); + const int copies = u(rng_) < link_.loss ? 0 : (u(rng_) < link_.duplicate ? 2 : 1); + for (int i = 0; i < copies; ++i) { + const auto jitter = link_.jitter.count() + ? std::chrono::milliseconds(rng_() % link_.jitter.count()) + : 0ms; + wire_.push({now + link_.delay + jitter, to, {bytes.begin(), bytes.end()}, order_++}); + } + } + + std::optional NextDelivery() const { + return wire_.empty() ? std::nullopt : std::optional{wire_.top().deliver_at}; + } + + /// Pops the next packet due by `now`. + std::optional>> Pop() { + if (wire_.empty() || wire_.top().deliver_at > now) { + return std::nullopt; + } + auto p = wire_.top(); + wire_.pop(); + return std::pair{p.to, std::move(p.bytes)}; + } + + std::string Log() const { + std::string out; + for (const auto& line : log_) { + out += " " + line + "\n"; + } + return out; + } + +private: + struct Packet { + Clock::time_point deliver_at; + int to; // 0 = A, 1 = B + std::vector bytes; + u64 order; + bool operator>(const Packet& o) const { + return deliver_at != o.deliver_at ? deliver_at > o.deliver_at : order > o.order; + } + }; + + std::mt19937_64 rng_; + LinkParams link_; + std::priority_queue, std::greater<>> wire_; + u64 order_ = 0; + std::deque log_; +}; + +struct TransferResult { + std::vector a_sent, b_sent, a_got, b_got; + TcpState a_state, b_state; + Error a_error, b_error; + bool b_created; +}; + +TransferResult RunTransfer(u64 seed, LinkParams link, size_t bytes, Clock::duration reader_stall, + std::string* log) { + Sim sim(seed, link); + TcpConfig config; + const auto a_pseudo = Pseudo(1, 2); + const auto b_pseudo = Pseudo(2, 1); + std::optional b; + // A's initial sequence number sits just below 2^32 so the transfer crosses the wrap. + TcpConnection a(config, a_pseudo, 5000, 6000, 0xfffff000u, + [&](std::span s) { sim.Transmit(1, s); }); + a.Connect(sim.now); + + TransferResult r; + r.a_sent = RandomBytes(bytes, seed * 2 + 1); + r.b_sent = RandomBytes(bytes, seed * 2 + 2); + size_t a_off = 0, b_off = 0; + bool a_shut = false, b_shut = false; + const auto start = sim.now; + + auto pump = [&](TcpConnection& c, const std::vector& out, size_t& off, std::vector& in, + bool& shut, bool may_read) { + while (off < out.size()) { + const auto w = c.Send(std::span(out).subspan(off), sim.now); + if (w.bytes == 0) { + break; + } + off += w.bytes; + } + if (off == out.size() && !shut && + (c.State() == TcpState::Established || c.State() == TcpState::CloseWait)) { + c.Shutdown(sim.now); + shut = true; + } + if (may_read) { + u8 buf[4096]; + for (;;) { + const auto got = c.Recv(buf, false, sim.now); + if (got.bytes == 0) { + break; + } + in.insert(in.end(), buf, buf + got.bytes); + } + } + }; + const auto done = [](const TcpConnection& c) { + return c.State() == TcpState::Closed || c.State() == TcpState::TimeWait; + }; + + while (sim.now < start + 600s) { + pump(a, r.a_sent, a_off, r.a_got, a_shut, true); + if (b) { + pump(*b, r.b_sent, b_off, r.b_got, b_shut, sim.now - start >= reader_stall); + } + if (done(a) && b && done(*b) && r.a_got.size() == bytes && r.b_got.size() == bytes) { + break; + } + + // Advance the virtual clock to the next packet or timer. + std::optional next; + const auto consider = [&](std::optional t) { + if (t && (!next || *t < *next)) { + next = t; + } + }; + consider(sim.NextDelivery()); + consider(a.NextDeadline()); + if (b) { + consider(b->NextDeadline()); + if (sim.now - start < reader_stall) { + consider(start + reader_stall); + } + } + if (!next) { + break; + } + sim.now = std::max(sim.now, *next); + + while (auto packet = sim.Pop()) { + const auto [to, bytes_in] = std::move(*packet); + const auto& pseudo = to == 0 ? a_pseudo : b_pseudo; + const auto seg = ParseTcpSegment(bytes_in, &pseudo); + if (!seg) { + ADD_FAILURE() << "segment failed checksum verification"; + continue; + } + TcpConnection* target = to == 0 ? &a : (b ? &*b : nullptr); + if (target && target->State() == TcpState::Closed) { + if (auto rst = BuildResetFor(pseudo, *seg)) { + sim.Transmit(to ^ 1, *rst); + } + continue; + } + if (target) { + target->OnSegment(*seg, sim.now); + } else if ((seg->flags & (TcpFlag::Syn | TcpFlag::Ack)) == TcpFlag::Syn) { + b.emplace(config, b_pseudo, 6000, 5000, 0x12345678u, + [&](std::span s) { sim.Transmit(0, s); }); + b->AcceptSyn(*seg, sim.now); + } + } + a.OnTimer(sim.now); + if (b) { + b->OnTimer(sim.now); + } + } + + r.a_state = a.State(); + r.a_error = a.PendingError(); + r.b_created = b.has_value(); + r.b_state = b ? b->State() : TcpState::Closed; + r.b_error = b ? b->PendingError() : Error::Ok; + if (log) { + *log = sim.Log(); + } + return r; +} + +struct Scenario { + const char* name; + LinkParams link; + Clock::duration reader_stall; +}; + +class NetP2PTcpSim : public ::testing::TestWithParam {}; + +TEST_P(NetP2PTcpSim, BidirectionalTransferAndClose) { + constexpr size_t kBytes = 300 * 1024; + for (u64 seed = 1; seed <= 20; ++seed) { + std::string log; + const auto r = RunTransfer(seed, GetParam().link, kBytes, GetParam().reader_stall, &log); + ASSERT_TRUE(r.b_created) << "seed " << seed; + ASSERT_TRUE(r.a_got == r.b_sent && r.b_got == r.a_sent) + << "seed " << seed << ": a got " << r.a_got.size() << ", b got " << r.b_got.size() + << " of " << kBytes << "; a " << ToString(r.a_state) << " b " << ToString(r.b_state) + << "\n" + << log << "(set SHADPS4_NET_SIMLOG=1 for a packet trace)"; + EXPECT_EQ(r.a_error, Error::Ok) << "seed " << seed; + EXPECT_EQ(r.b_error, Error::Ok) << "seed " << seed; + } +} + +INSTANTIATE_TEST_SUITE_P( + Links, NetP2PTcpSim, + ::testing::Values( + Scenario{"Clean", {}, 0s}, Scenario{"Loss5", {.loss = 0.05}, 0s}, + Scenario{"Loss20", {.loss = 0.20}, 0s}, + Scenario{"ReorderDuplicate", {.duplicate = 0.05, .jitter = 30ms}, 0s}, + Scenario{"Loss10ReorderDuplicate", {.loss = 0.10, .duplicate = 0.05, .jitter = 30ms}, 0s}, + Scenario{"ZeroWindow", {.loss = 0.02}, 3s}), + [](const auto& info) { return std::string(info.param.name); }); + +TEST(NetP2PTcp, ConnectTimesOutOnDeadLink) { + Clock::time_point now{}; + TcpConfig config; + int syns = 0; + TcpConnection a(config, Pseudo(1, 2), 5000, 6000, 1, [&](std::span) { ++syns; }); + a.Connect(now); + while (const auto next = a.NextDeadline()) { + now = *next; + a.OnTimer(now); + } + EXPECT_EQ(a.State(), TcpState::Closed); + EXPECT_EQ(a.PendingError(), Error::TimedOut); + EXPECT_EQ(syns, 1 + config.syn_retries); + EXPECT_GE(std::chrono::duration(now.time_since_epoch()).count(), 60.0); +} + +TEST(NetP2PTcp, AppIoBeforeAndAfterConnection) { + Clock::time_point now{}; + TcpConnection a(TcpConfig{}, Pseudo(1, 2), 5000, 6000, 1, [](std::span) {}); + a.Connect(now); + const u8 byte = 1; + u8 buf[4]; + EXPECT_EQ(a.Send({&byte, 1}, now).error, Error::WouldBlock); // still connecting + EXPECT_EQ(a.Recv(buf, false, now).error, Error::WouldBlock); + a.Abort(); + EXPECT_EQ(a.State(), TcpState::Closed); + EXPECT_EQ(a.Send({&byte, 1}, now).error, Error::NotConn); +} + +} // namespace diff --git a/tests/network/test_net_p2p_tcp_interop.cpp b/tests/network/test_net_p2p_tcp_interop.cpp new file mode 100644 index 000000000..b2234363b --- /dev/null +++ b/tests/network/test_net_p2p_tcp_interop.cpp @@ -0,0 +1,475 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Linux only needs root or CAP_NET_ADMIN, otherwise every test is skipped. + +#include + +#ifdef __linux__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "core/net/p2p_tcp.h" + +using namespace Core::Net; +using namespace Core::Net::P2P; +using namespace std::chrono_literals; + +namespace { + +constexpr const char* KernelIp = "10.77.0.1"; // the TUN interface address (kernel TCP) +constexpr const char* OurIp = "10.77.0.2"; // the address our TCP answers for + +std::vector RandomBytes(size_t n, u32 seed) { + std::mt19937 rng(seed); + std::vector v(n); + for (auto& b : v) { + b = static_cast(rng()); + } + return v; +} + +u16 IpChecksum(const u8* header, size_t size) { + u32 sum = 0; + for (size_t i = 0; i < size; i += 2) { + sum += (header[i] << 8) | header[i + 1]; + } + while (sum >> 16) { + sum = (sum & 0xffff) + (sum >> 16); + } + return static_cast(~sum); +} + +/// Kernel socket helpers with a generous timeout so a broken test fails instead of hanging. +int KernelSocket() { + const int s = socket(AF_INET, SOCK_STREAM, 0); + timeval tv{120, 0}; + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + return s; +} + +sockaddr_in Address(const char* ip, u16 port) { + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, ip, &addr.sin_addr); + return addr; +} + +/// Our side of the network: owns TcpConnections keyed by (local port, remote port), accepts +/// SYNs on listening ports and resets everything else, like the P2P transport does. +class TunHost { +public: + explicit TunHost(int tun) : tun_(tun) { + inet_pton(AF_INET, OurIp, pseudo_.local.data()); + inet_pton(AF_INET, KernelIp, pseudo_.remote.data()); + } + + double loss = 0.0; + std::map, std::unique_ptr> connections; + std::map listeners; + + TcpConnection& Connect(u16 local_port, u16 remote_port) { + auto& c = connections[{local_port, remote_port}]; + c = std::make_unique(config_, pseudo_, local_port, remote_port, NextIss(), + [this](std::span s) { EmitToKernel(s); }); + c->Connect(Clock::now()); + return *c; + } + + TcpConnection* First() { + return connections.empty() ? nullptr : connections.begin()->second.get(); + } + + /// Runs the bridge until `step()` returns true or the time limit passes. + bool Run(const std::function& step, std::chrono::seconds limit) { + const auto end = Clock::now() + limit; + u8 buf[65536]; + while (Clock::now() < end) { + if (step()) { + return true; + } + auto next = Clock::now() + 5ms; + for (auto& [_, c] : connections) { + if (const auto d = c->NextDeadline(); d && *d < next) { + next = *d; + } + } + const auto wait = std::chrono::ceil( + std::max(next - Clock::now(), 0ms)); + pollfd pfd{tun_, POLLIN, 0}; + poll(&pfd, 1, static_cast(wait.count())); + for (;;) { + const auto n = read(tun_, buf, sizeof(buf)); + if (n <= 0) { + break; + } + OnPacket({buf, static_cast(n)}); + } + const auto now = Clock::now(); + for (auto& [_, c] : connections) { + if (const auto d = c->NextDeadline(); d && *d <= now) { + c->OnTimer(now); + } + } + } + return false; + } + + int checksum_failures = 0; + +private: + bool Drop() { + return loss > 0 && std::uniform_real_distribution(0, 1)(rng_) < loss; + } + + u32 NextIss() { + const u32 iss = next_iss_; + next_iss_ += 0x01000193; + return iss; + } + + /// Wraps one of our TCP segments in IPv4 and hands it to the kernel. + void EmitToKernel(std::span segment) { + if (Drop()) { + return; + } + std::vector packet(20 + segment.size()); + packet[0] = 0x45; + packet[2] = static_cast(packet.size() >> 8); + packet[3] = static_cast(packet.size()); + packet[6] = 0x40; // don't fragment + packet[8] = 64; + packet[9] = IPPROTO_TCP; + inet_pton(AF_INET, OurIp, packet.data() + 12); + inet_pton(AF_INET, KernelIp, packet.data() + 16); + const u16 checksum = IpChecksum(packet.data(), 20); + packet[10] = static_cast(checksum >> 8); + packet[11] = static_cast(checksum); + std::copy(segment.begin(), segment.end(), packet.begin() + 20); + [[maybe_unused]] const auto r = write(tun_, packet.data(), packet.size()); + } + + void OnPacket(std::span packet) { + if (packet.size() < 20 || (packet[0] >> 4) != 4 || packet[9] != IPPROTO_TCP) { + return; + } + const size_t ihl = (packet[0] & 0x0f) * 4; + const size_t total = (packet[2] << 8) | packet[3]; + if (total > packet.size() || ihl > total || Drop()) { + return; + } + // Verifying the kernel's checksum against our pseudo-header also proves ours agree. + const auto seg = ParseTcpSegment(packet.subspan(ihl, total - ihl), &pseudo_); + if (!seg) { + ++checksum_failures; + return; + } + const auto key = std::pair{seg->dst_port, seg->src_port}; + if (const auto it = connections.find(key); + it != connections.end() && it->second->State() != TcpState::Closed) { + it->second->OnSegment(*seg, Clock::now()); + return; + } + if ((seg->flags & (TcpFlag::Syn | TcpFlag::Ack | TcpFlag::Rst)) == TcpFlag::Syn && + listeners.contains(seg->dst_port)) { + auto& c = connections[key]; + c = std::make_unique(config_, pseudo_, seg->dst_port, seg->src_port, + NextIss(), + [this](std::span s) { EmitToKernel(s); }); + c->AcceptSyn(*seg, Clock::now()); + return; + } + if (const auto rst = BuildResetFor(pseudo_, *seg)) { + EmitToKernel(*rst); + } + } + + int tun_; + TcpConfig config_; + PseudoHeader pseudo_; + std::mt19937 rng_{42}; + u32 next_iss_ = 0xfffffc00u; // exercise sequence wraparound +}; + +class NetP2PTcpKernelInterop : public ::testing::Test { +protected: + void SetUp() override { + tun_ = open("/dev/net/tun", O_RDWR | O_NONBLOCK); + if (tun_ < 0) { + GTEST_SKIP() << "no /dev/net/tun access (needs root or CAP_NET_ADMIN)"; + } + ifreq ifr{}; + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + std::strncpy(ifr.ifr_name, "shadps4tun0", IFNAMSIZ - 1); + if (ioctl(tun_, TUNSETIFF, &ifr) < 0) { + GTEST_SKIP() << "cannot create TUN device: " << std::strerror(errno); + } + const int s = socket(AF_INET, SOCK_DGRAM, 0); + auto* addr = reinterpret_cast(&ifr.ifr_addr); + addr->sin_family = AF_INET; + inet_pton(AF_INET, KernelIp, &addr->sin_addr); + const bool ok = ioctl(s, SIOCSIFADDR, &ifr) == 0 && + (inet_pton(AF_INET, "255.255.255.0", &addr->sin_addr), + ioctl(s, SIOCSIFNETMASK, &ifr) == 0) && + ioctl(s, SIOCGIFFLAGS, &ifr) == 0 && + (ifr.ifr_flags |= IFF_UP | IFF_RUNNING, ioctl(s, SIOCSIFFLAGS, &ifr) == 0); + close(s); + if (!ok) { + GTEST_SKIP() << "cannot configure TUN device: " << std::strerror(errno); + } + std::this_thread::sleep_for(100ms); + host_ = std::make_unique(tun_); + } + + void TearDown() override { + host_.reset(); + if (tun_ >= 0) { + close(tun_); // the non-persistent interface disappears with its fd + } + } + + static bool Finished(const TcpConnection* c) { + return c && (c->State() == TcpState::Closed || c->State() == TcpState::TimeWait); + } + + /// The kernel connects to us; we echo; the kernel verifies and half-closes. + void KernelClientToOurEcho(double loss, size_t bytes) { + host_->loss = loss; + host_->listeners[7000] = true; + const auto data = RandomBytes(bytes, 1); + std::atomic kernel_done{false}; + std::vector kernel_got; + std::string kernel_error; + + std::thread kernel([&] { + const int s = KernelSocket(); + const auto addr = Address(OurIp, 7000); + if (connect(s, reinterpret_cast(&addr), sizeof(addr)) != 0) { + kernel_error = std::string("connect: ") + std::strerror(errno); + kernel_done = true; + return; + } + std::thread writer([&] { + for (size_t off = 0; off < data.size();) { + const auto n = send(s, data.data() + off, data.size() - off, 0); + if (n <= 0) { + break; + } + off += static_cast(n); + } + shutdown(s, SHUT_WR); + }); + u8 buf[16384]; + for (;;) { + const auto n = recv(s, buf, sizeof(buf), 0); + if (n < 0) { + kernel_error = std::string("recv: ") + std::strerror(errno); + } + if (n <= 0) { + break; + } + kernel_got.insert(kernel_got.end(), buf, buf + n); + } + writer.join(); + close(s); + kernel_done = true; + }); + + // Our echo: send back whatever arrives, close after the kernel's FIN. + std::vector pending; + bool shut = false; + host_->Run( + [&] { + if (auto* c = host_->First()) { + u8 buf[8192]; + for (;;) { + const auto r = c->Recv(buf, false, Clock::now()); + if (r.bytes == 0) { + break; + } + pending.insert(pending.end(), buf, buf + r.bytes); + } + while (!pending.empty()) { + const auto w = c->Send(pending, Clock::now()); + if (w.bytes == 0) { + break; + } + pending.erase(pending.begin(), pending.begin() + w.bytes); + } + if (c->ReceivedFin() && pending.empty() && !shut) { + c->Shutdown(Clock::now()); + shut = true; + } + } + return kernel_done.load(); + }, + 300s); + kernel.join(); + host_->Run([&] { return Finished(host_->First()); }, 20s); // final ACK exchange + + const auto* c = host_->First(); + EXPECT_TRUE(kernel_error.empty()) << kernel_error; + EXPECT_TRUE(kernel_got == data) + << "kernel received " << kernel_got.size() << " of " << data.size(); + ASSERT_TRUE(c); + EXPECT_EQ(c->PendingError(), Error::Ok); + EXPECT_TRUE(Finished(c)) << ToString(c->State()); + EXPECT_EQ(host_->checksum_failures, 0); + } + + /// We connect to a kernel echo server, send, verify the echo and close first. + void OurClientToKernelEcho(double loss, size_t bytes, int kernel_rcvbuf, + std::chrono::milliseconds kernel_stall) { + host_->loss = loss; + const int ls = socket(AF_INET, SOCK_STREAM, 0); + int one = 1; + setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (kernel_rcvbuf) { + setsockopt(ls, SOL_SOCKET, SO_RCVBUF, &kernel_rcvbuf, sizeof(kernel_rcvbuf)); + } + const auto addr = Address(KernelIp, 7001); + ASSERT_EQ(bind(ls, reinterpret_cast(&addr), sizeof(addr)), 0) + << std::strerror(errno); + listen(ls, 4); + + std::atomic kernel_done{false}; + std::thread kernel([&] { + const int s = accept(ls, nullptr, nullptr); + timeval tv{120, 0}; + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + std::this_thread::sleep_for(kernel_stall); // let our sender hit a zero window + u8 buf[16384]; + for (;;) { + const auto n = recv(s, buf, sizeof(buf), 0); + if (n <= 0) { + break; + } + for (ssize_t off = 0; off < n;) { + const auto w = send(s, buf + off, n - off, 0); + if (w <= 0) { + break; + } + off += w; + } + } + close(s); + kernel_done = true; + }); + + auto& c = host_->Connect(40001, 7001); + const auto data = RandomBytes(bytes, 2); + std::vector got; + size_t off = 0; + bool shut = false; + host_->Run( + [&] { + while (off < data.size()) { + const auto w = c.Send(std::span(data).subspan(off), Clock::now()); + if (w.bytes == 0) { + break; + } + off += w.bytes; + } + if (off == data.size() && !shut && c.State() == TcpState::Established) { + c.Shutdown(Clock::now()); + shut = true; + } + u8 buf[8192]; + for (;;) { + const auto r = c.Recv(buf, false, Clock::now()); + if (r.bytes == 0) { + break; + } + got.insert(got.end(), buf, buf + r.bytes); + } + return (c.ReceivedFin() && kernel_done && Finished(&c)) || + c.PendingError() != Error::Ok; + }, + 300s); + if (!kernel_done) { + shutdown(ls, SHUT_RDWR); + c.Abort(); + } + kernel.join(); + close(ls); + + EXPECT_TRUE(got == data) << "received " << got.size() << " of " << data.size(); + EXPECT_EQ(c.PendingError(), Error::Ok); + EXPECT_EQ(host_->checksum_failures, 0); + } + + int tun_ = -1; + std::unique_ptr host_; +}; + +TEST_F(NetP2PTcpKernelInterop, KernelClientToOurServer) { + KernelClientToOurEcho(0.0, 1 << 20); +} + +TEST_F(NetP2PTcpKernelInterop, KernelClientToOurServerWithLoss) { + KernelClientToOurEcho(0.05, 256 << 10); +} + +TEST_F(NetP2PTcpKernelInterop, OurClientToKernelServer) { + OurClientToKernelEcho(0.0, 1 << 20, 0, 0ms); +} + +TEST_F(NetP2PTcpKernelInterop, OurClientToKernelServerWithLoss) { + OurClientToKernelEcho(0.05, 256 << 10, 0, 0ms); +} + +TEST_F(NetP2PTcpKernelInterop, ZeroWindowProbing) { + // The kernel reads nothing for 3 s with a tiny receive buffer: our persist timer has to + // probe the closed window until it reopens. + OurClientToKernelEcho(0.0, 256 << 10, 4096, 3000ms); +} + +TEST_F(NetP2PTcpKernelInterop, OurConnectToClosedPortIsRefused) { + auto& c = host_->Connect(40002, 7009); // nothing listens on 7009 + host_->Run([&] { return c.State() == TcpState::Closed; }, 20s); + EXPECT_EQ(c.PendingError(), Error::ConnRefused); +} + +TEST_F(NetP2PTcpKernelInterop, KernelConnectToOurClosedPortIsRefused) { + std::atomic result{0}; + std::atomic done{false}; + std::thread kernel([&] { + const int s = KernelSocket(); + const auto addr = Address(OurIp, 7010); + result = + connect(s, reinterpret_cast(&addr), sizeof(addr)) == 0 ? 0 : errno; + close(s); + done = true; + }); + host_->Run([&] { return done.load(); }, 20s); + kernel.join(); + EXPECT_EQ(result, ECONNREFUSED); // we answered the SYN with a RST +} + +} // namespace + +#else + +TEST(NetP2PTcpKernelInterop, Unsupported) { + GTEST_SKIP() << "the TUN interop tests run on Linux only"; +} + +#endif diff --git a/tests/network/test_net_sample_sockets.cpp b/tests/network/test_net_sample_sockets.cpp new file mode 100644 index 000000000..8368c4d7e --- /dev/null +++ b/tests/network/test_net_sample_sockets.cpp @@ -0,0 +1,714 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "core/libraries/error_codes.h" +#include "core/libraries/net/net.h" +#include "core/libraries/net/net_error.h" + +using namespace Libraries::Net; +using namespace std::chrono_literals; + +namespace { + +// The homebrew's ports. +constexpr u16 TestPort = 8080; +constexpr u16 LogPort = 8181; +constexpr s32 MaxClients = 10; + +OrbisNetSockaddr* Sa(OrbisNetSockaddrIn* in) { + return reinterpret_cast(in); +} + +void Usleep(u32 micros) { + std::this_thread::sleep_for(std::chrono::microseconds{micros}); +} + +std::string Format(const char* fmt, u64 value) { + char buf[256]; + std::snprintf(buf, sizeof(buf), fmt, static_cast(value)); + return buf; +} + +class Logger { +public: + virtual ~Logger() = default; + virtual void LogMessage(const char* fmt, u64 value) = 0; + + std::vector Sent() { + std::scoped_lock lock{mutex_}; + return sent_; + } + std::vector Received() { + std::scoped_lock lock{mutex_}; + return received_; + } + std::vector Errors() { + std::scoped_lock lock{mutex_}; + return errors_; + } + /// Waits (up to 10 s) until every sent line has been received. + bool WaitForDelivery() { + for (int i = 0; i < 1000; ++i) { + { + std::scoped_lock lock{mutex_}; + if (received_.size() >= sent_.size()) { + return true; + } + } + Usleep(10000); + } + return false; + } + +protected: + void NoteSent(std::string line) { + std::scoped_lock lock{mutex_}; + sent_.push_back(std::move(line)); + } + void NoteReceived(std::string text) { + std::scoped_lock lock{mutex_}; + received_.push_back(std::move(text)); + } + void Error(const char* fmt, s32 value) { + std::scoped_lock lock{mutex_}; + errors_.push_back(Format(fmt, static_cast(value))); + } + +private: + std::mutex mutex_; + std::vector sent_; + std::vector received_; + std::vector errors_; +}; + +/// Blocking stream sockets: a server thread accepting one connection per message. +class BIOStreamLogger final : public Logger { +public: + BIOStreamLogger() { + server_ = std::thread([this] { ServerThread(); }); + while (!ready_) { + Usleep(10000); + } + } + ~BIOStreamLogger() override { + { + std::scoped_lock lock{log_mutex_}; + ready_ = false; + } + // Frees the server from its blocking accept; not expected to arrive. + Send("Closing BIOLoggerServer%lx\n", 0); + server_.join(); + } + + void LogMessage(const char* fmt, u64 value) override { + std::scoped_lock lock{log_mutex_}; + NoteSent(Format(fmt, value)); + std::thread client([&] { Send(fmt, value); }); + client.join(); + } + +private: + void ServerThread() { + const s32 stream_sock = sceNetSocket("BIOStreamLoggerServerSocket", ORBIS_NET_AF_INET, + ORBIS_NET_SOCK_STREAM, 0); + if (stream_sock <= 0) { + return Error("socket creation failed with 0x%08x", stream_sock); + } + s32 opt = 1; + EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, &opt, + sizeof(opt)), + ORBIS_OK); + EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, &opt, + sizeof(opt)), + ORBIS_OK); + OrbisNetSockaddrIn in_addr{}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_addr = ORBIS_NET_INADDR_ANY; + in_addr.sin_port = sceNetHtons(LogPort); + s32 result = sceNetBind(stream_sock, Sa(&in_addr), sizeof(in_addr)); + if (result < 0) { + sceNetSocketClose(stream_sock); + return Error("bind failed with 0x%08x", result); + } + result = sceNetListen(stream_sock, 1); + if (result < 0) { + sceNetSocketClose(stream_sock); + return Error("listen failed with 0x%08x", result); + } + ready_ = true; + + while (ready_) { + u32 addr_len = sizeof(in_addr); + const s32 log_sock = sceNetAccept(stream_sock, Sa(&in_addr), &addr_len); + if (!ready_) { + if (log_sock > 0) { + sceNetSocketClose(log_sock); + } + break; + } + if (log_sock <= 0) { + Error("Unexpected error 0x%08x while waiting for client", log_sock); + continue; + } + char log_buf[0x1000]{}; + result = sceNetRecv(log_sock, log_buf, sizeof(log_buf), 0); + if (result <= 0) { + Error("Unexpected error 0x%08x while reading from socket", result); + } else { + NoteReceived(log_buf); + } + if (sceNetSocketClose(log_sock) < 0) { + Error("Failed to close connection, error 0x%08x", result); + } + } + EXPECT_EQ(sceNetSocketClose(stream_sock), ORBIS_OK); + } + + void Send(const char* fmt, u64 value) { + const s32 stream_sock = sceNetSocket("BIOStreamLoggerClientSocket", ORBIS_NET_AF_INET, + ORBIS_NET_SOCK_STREAM, 0); + if (stream_sock <= 0) { + return Error("Failed to create client socket, error 0x%08x", stream_sock); + } + OrbisNetSockaddrIn in_addr{}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_port = sceNetHtons(LogPort); + EXPECT_EQ(sceNetInetPton(ORBIS_NET_AF_INET, "127.0.0.1", &in_addr.sin_addr), 1); + const s32 result = sceNetConnect(stream_sock, Sa(&in_addr), sizeof(in_addr)); + if (!ready_) { + sceNetSocketClose(stream_sock); + return; + } + if (result < 0) { + sceNetSocketClose(stream_sock); + return Error("Unexpected error while trying to connect to server, error 0x%08x", + result); + } + const std::string text = Format(fmt, value); + if (sceNetSend(stream_sock, text.data(), text.size(), 0) <= 0) { + Error("Unexpected error while sending data to logger server, error 0x%08x", + *sceNetErrnoLoc()); + } + EXPECT_EQ(sceNetSocketClose(stream_sock), ORBIS_OK); + } + + std::mutex log_mutex_; + std::atomic ready_{false}; + std::thread server_; +}; + +/// Non-blocking stream sockets and epolls; with `async`, up to MaxClients messages in flight. +class NBIOStreamLogger final : public Logger { +public: + explicit NBIOStreamLogger(bool async) : async_(async) { + server_ = std::thread([this] { ServerThread(); }); + while (!ready_) { + Usleep(10000); + } + } + ~NBIOStreamLogger() override { + std::scoped_lock lock{log_mutex_}; + for (auto& t : clients_) { + t.join(); + } + clients_.clear(); + ready_ = false; + server_.join(); + } + + void LogMessage(const char* fmt, u64 value) override { + std::scoped_lock lock{log_mutex_}; + NoteSent(Format(fmt, value)); + if (async_ && clients_.size() == static_cast(MaxClients)) { + for (auto& t : clients_) { + t.join(); + } + clients_.clear(); + } + // The thread copies what it needs; nothing here outlives this call. + std::thread client([this, text = Format(fmt, value)] { Send(text); }); + if (async_) { + clients_.push_back(std::move(client)); + } else { + client.join(); + } + } + +private: + void ServerThread() { + const s32 stream_sock = sceNetSocket("NBIOStreamLoggerServerSocket", ORBIS_NET_AF_INET, + ORBIS_NET_SOCK_STREAM, 0); + if (stream_sock <= 0) { + return Error("socket creation failed with 0x%08x", stream_sock); + } + s32 opt = 1; + EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, &opt, + sizeof(opt)), + ORBIS_OK); + EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, &opt, + sizeof(opt)), + ORBIS_OK); + OrbisNetSockaddrIn in_addr{}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_addr = ORBIS_NET_INADDR_ANY; + in_addr.sin_port = sceNetHtons(LogPort); + s32 result = sceNetBind(stream_sock, Sa(&in_addr), sizeof(in_addr)); + if (result < 0) { + sceNetSocketClose(stream_sock); + return Error("bind failed with 0x%08x", result); + } + EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, &opt, + sizeof(opt)), + ORBIS_OK); + result = sceNetListen(stream_sock, MaxClients); + if (result < 0) { + sceNetSocketClose(stream_sock); + return Error("listen failed with 0x%08x", result); + } + ready_ = true; + + while (ready_) { + u32 addr_len = sizeof(in_addr); + const s32 log_sock = sceNetAccept(stream_sock, Sa(&in_addr), &addr_len); + if (!ready_) { + if (log_sock > 0) { + sceNetSocketClose(log_sock); + } + break; + } + if (log_sock == ORBIS_NET_ERROR_EAGAIN) { + Usleep(10000); // nobody waiting yet + continue; + } + if (log_sock <= 0) { + Error("Unexpected error 0x%08x while waiting for client", log_sock); + continue; + } + + const s32 log_epoll = sceNetEpollCreate("NBIOStreamLoggerServerEpoll", 0); + if (log_epoll <= 0) { + Error("Unexpected error 0x%08x while creating epoll", log_epoll); + sceNetSocketClose(log_sock); + continue; + } + OrbisNetEpollEvent epoll_event{}; + epoll_event.events = ORBIS_NET_EPOLLIN; + epoll_event.data.fd = log_sock; + result = sceNetEpollControl(log_epoll, ORBIS_NET_EPOLL_CTL_ADD, log_sock, &epoll_event); + if (result < 0) { + Error("Unexpected error 0x%08x while preparing epoll", result); + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(log_sock); + continue; + } + OrbisNetEpollEvent epoll_ev_out{}; + result = sceNetEpollWait(log_epoll, &epoll_ev_out, 1, -1); + if (result <= 0) { + Error("Unexpected result 0x%08x while waiting for socket", result); + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(log_sock); + continue; + } + EXPECT_EQ(epoll_ev_out.data.fd, log_sock); + + std::string text; + char log_buf[0x1000]; + for (;;) { + std::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) { + break; + } + if (result < 0) { + Error("Unexpected error 0x%08x while reading from socket", result); + break; + } + text.append(log_buf, static_cast(result)); + } + if (!text.empty()) { + NoteReceived(std::move(text)); + } + if (sceNetEpollDestroy(log_epoll) < 0) { + Error("Failed to destroy epoll, error 0x%08x", *sceNetErrnoLoc()); + } + if (sceNetSocketClose(log_sock) < 0) { + Error("Failed to close connection, error 0x%08x", *sceNetErrnoLoc()); + } + } + EXPECT_EQ(sceNetSocketClose(stream_sock), ORBIS_OK); + } + + void Send(const std::string& text) { + const s32 stream_sock = sceNetSocket("NBIOStreamLoggerClientSocket", ORBIS_NET_AF_INET, + ORBIS_NET_SOCK_STREAM, 0); + if (stream_sock <= 0) { + return Error("Failed to create client socket, error 0x%08x", stream_sock); + } + OrbisNetSockaddrIn in_addr{}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_port = sceNetHtons(LogPort); + EXPECT_EQ(sceNetInetPton(ORBIS_NET_AF_INET, "127.0.0.1", &in_addr.sin_addr), 1); + s32 opt = 1; + EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, &opt, + sizeof(opt)), + ORBIS_OK); + + // A non-blocking connect: EINPROGRESS, then retried until EISCONN. The homebrew treats + // anything else as an error (no EALREADY on the PS4 over loopback). + s32 result = sceNetConnect(stream_sock, Sa(&in_addr), sizeof(in_addr)); + while (result != 0) { + if (!ready_) { + sceNetSocketClose(stream_sock); + return; + } + if (result == ORBIS_NET_ERROR_EINPROGRESS) { + Usleep(100000); + result = sceNetConnect(stream_sock, Sa(&in_addr), sizeof(in_addr)); + continue; + } + if (result == ORBIS_NET_ERROR_EISCONN) { + break; + } + Error("Unexpected error while trying to connect to server, error 0x%08x", result); + sceNetSocketClose(stream_sock); + return; + } + + const s32 log_epoll = sceNetEpollCreate("NBIOStreamLoggerClientEpoll", 0); + if (log_epoll <= 0) { + Error("Unexpected error while creating epoll, error 0x%08x", log_epoll); + sceNetSocketClose(stream_sock); + return; + } + OrbisNetEpollEvent epoll_event{}; + epoll_event.events = ORBIS_NET_EPOLLOUT; + epoll_event.data.fd = stream_sock; + result = sceNetEpollControl(log_epoll, ORBIS_NET_EPOLL_CTL_ADD, stream_sock, &epoll_event); + if (result < 0) { + Error("Unexpected error while preparing epoll, error 0x%08x", result); + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(stream_sock); + return; + } + OrbisNetEpollEvent epoll_ev_out{}; + result = sceNetEpollWait(log_epoll, &epoll_ev_out, 1, -1); + if (result <= 0) { + Error("Unexpected result while waiting for socket, 0x%08x", result); + sceNetEpollDestroy(log_epoll); + sceNetSocketClose(stream_sock); + return; + } + EXPECT_TRUE(epoll_ev_out.events & ORBIS_NET_EPOLLOUT); + + size_t sent = 0; + while (sent < text.size()) { + result = sceNetSend(stream_sock, text.data() + sent, text.size() - sent, 0); + if (result < 0) { + Error("Unexpected error while sending data to logger server, error 0x%08x", result); + break; + } + sent += static_cast(result); + } + EXPECT_EQ(sceNetEpollDestroy(log_epoll), ORBIS_OK); + EXPECT_EQ(sceNetSocketClose(stream_sock), ORBIS_OK); + } + + const bool async_; + std::mutex log_mutex_; + std::atomic ready_{false}; + std::thread server_; + std::vector clients_; +}; + +std::atomic g_server_ready{false}; +std::atomic g_cond{false}; +Logger* g_active_logger = nullptr; + +void LogMessage(const char* msg, u64 value) { + if (g_active_logger != nullptr) { + g_active_logger->LogMessage(msg, value); + } +} + +constexpr const char* ServerMessage = "This is a test message coming from the server"; +constexpr const char* ClientMessage = "This is a test message coming from the client"; +constexpr u32 MessageLength = 0x2d; // both messages, as the PS4 log shows + +void ServerThread() { + const 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); + ASSERT_GT(stream_sock, 0); + + 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); + EXPECT_EQ(result, 0); + 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); + EXPECT_EQ(result, 0); + + OrbisNetSockaddrIn in_addr{}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_addr = ORBIS_NET_INADDR_ANY; + in_addr.sin_port = sceNetHtons(TestPort); + result = sceNetBind(stream_sock, Sa(&in_addr), sizeof(in_addr)); + LogMessage("Server: bind returns 0x%08x\n", result); + EXPECT_EQ(result, 0); + + result = sceNetListen(stream_sock, 3); + LogMessage("Server: listen returns 0x%08x\n", result); + EXPECT_EQ(result, 0); + + g_server_ready = true; + + u32 addr_len = sizeof(in_addr); + const s32 connected_sock = sceNetAccept(stream_sock, Sa(&in_addr), &addr_len); + LogMessage("Server: accept returns 0x%08x\n", connected_sock); + ASSERT_GT(connected_sock, 0); + + result = sceNetSend(connected_sock, ServerMessage, std::strlen(ServerMessage), 0); + LogMessage("Server: send returns 0x%08x\n", result); + EXPECT_EQ(static_cast(result), MessageLength); + + char buffer[1024]{}; + result = sceNetRecv(connected_sock, buffer, sizeof(buffer), 0); + LogMessage("Server: recv returns 0x%08x\n", result); + EXPECT_EQ(static_cast(result), MessageLength); + EXPECT_STREQ(buffer, ClientMessage); + + const s32 epoll = sceNetEpollCreate("ServerEpoll", 0); + LogMessage("Server: epoll_create returns 0x%08x\n", epoll); + EXPECT_GT(epoll, 0); + + OrbisNetEpollEvent epoll_event{}; + epoll_event.events = ORBIS_NET_EPOLLIN; + epoll_event.data.fd = connected_sock; + result = sceNetEpollControl(epoll, ORBIS_NET_EPOLL_CTL_ADD, connected_sock, &epoll_event); + LogMessage("Server: epoll_ctl returns 0x%08x\n", result); + EXPECT_EQ(result, 0); + + // Nothing sent yet: a zero timeout finds nothing. + OrbisNetEpollEvent epoll_ev_out{}; + result = sceNetEpollWait(epoll, &epoll_ev_out, 1, 0); + LogMessage("Server: epoll_wait returns 0x%08x\n", result); + EXPECT_EQ(result, 0); + + g_cond = true; + + // No timeout: returns once the client's second message is there. + result = sceNetEpollWait(epoll, &epoll_ev_out, 1, -1); + LogMessage("Server: epoll_wait returns 0x%08x\n", result); + EXPECT_EQ(result, 1); + EXPECT_EQ(epoll_ev_out.data.fd, connected_sock); + EXPECT_EQ(epoll_ev_out.ident, static_cast(connected_sock)); + EXPECT_TRUE(epoll_ev_out.events & ORBIS_NET_EPOLLIN); + + std::memset(buffer, 0, sizeof(buffer)); + result = sceNetRecv(connected_sock, buffer, sizeof(buffer), 0); + LogMessage("Server: recv returns 0x%08x\n", result); + EXPECT_EQ(static_cast(result), MessageLength); + EXPECT_STREQ(buffer, ClientMessage); + + result = sceNetEpollDestroy(epoll); + LogMessage("Server: epoll_destroy returns 0x%08x\n", result); + EXPECT_EQ(result, 0); + + result = sceNetSocketClose(connected_sock); + LogMessage("Server: socket_close returns 0x%08x\n", result); + EXPECT_EQ(result, 0); + result = sceNetSocketClose(stream_sock); + LogMessage("Server: socket_close returns 0x%08x\n", result); + EXPECT_EQ(result, 0); + + g_server_ready = false; + g_cond = false; +} + +void ClientThread() { + while (!g_server_ready) { + Usleep(10000); + } + const 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); + ASSERT_GT(stream_sock, 0); + + OrbisNetSockaddrIn in_addr{}; + in_addr.sin_family = ORBIS_NET_AF_INET; + in_addr.sin_port = sceNetHtons(TestPort); + 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); + EXPECT_EQ(result, 1); + result = sceNetConnect(stream_sock, Sa(&in_addr), sizeof(in_addr)); + LogMessage("Client: connect returns 0x%08x\n", result); + EXPECT_EQ(result, 0); + + char buffer[1024]{}; + result = sceNetRecv(stream_sock, buffer, sizeof(buffer), 0); + LogMessage("Client: recv returns 0x%08x\n", result); + EXPECT_EQ(static_cast(result), MessageLength); + EXPECT_STREQ(buffer, ServerMessage); + + result = sceNetSend(stream_sock, ClientMessage, std::strlen(ClientMessage), 0); + LogMessage("Client: send returns 0x%08x\n", result); + EXPECT_EQ(static_cast(result), MessageLength); + + while (!g_cond) { + Usleep(10000); + } + g_cond = false; + + result = sceNetSend(stream_sock, ClientMessage, std::strlen(ClientMessage), 0); + LogMessage("Client: send returns 0x%08x\n", result); + EXPECT_EQ(static_cast(result), MessageLength); + + result = sceNetSocketClose(stream_sock); + LogMessage("Client: socket_close returns 0x%08x\n", result); + EXPECT_EQ(result, 0); +} + +void RunRound() { + std::thread server(ServerThread); + std::thread client(ClientThread); + server.join(); + client.join(); +} + +constexpr const char* Ps4Round[] = { + "Server: socket(AF_INET, SOCK_STREAM, 0) returns \n", + "Server: setsockopt(SOL_SOCKET, SO_REUSEADDR) returns 0x00000000\n", + "Server: setsockopt(SOL_SOCKET, SO_REUSEPORT) returns 0x00000000\n", + "Server: bind returns 0x00000000\n", + "Server: listen returns 0x00000000\n", + "Client: socket(AF_INET, SOCK_STREAM, 0) returns \n", + "Client: inet_pton(ORBIS_NET_AF_INET) returns 0x00000001\n", + "Client: connect returns 0x00000000\n", + "Server: accept returns \n", + "Server: send returns 0x0000002d\n", + "Client: recv returns 0x0000002d\n", + "Client: send returns 0x0000002d\n", + "Server: recv returns 0x0000002d\n", + "Server: epoll_create returns \n", + "Server: epoll_ctl returns 0x00000000\n", + "Server: epoll_wait returns 0x00000000\n", + "Client: send returns 0x0000002d\n", + "Client: socket_close returns 0x00000000\n", + "Server: epoll_wait returns 0x00000001\n", + "Server: recv returns 0x0000002d\n", + "Server: epoll_destroy returns 0x00000000\n", + "Server: socket_close returns 0x00000000\n", + "Server: socket_close returns 0x00000000\n", +}; + +std::string MaskId(const std::string& line) { + static constexpr const char* IdLines[] = {"socket(AF_INET", "accept returns", + "epoll_create returns"}; + const bool is_id = std::any_of(std::begin(IdLines), std::end(IdLines), [&](const char* k) { + return line.find(k) != std::string::npos; + }); + const size_t at = line.rfind("0x"); + if (!is_id || at == std::string::npos) { + return line; + } + const auto value = static_cast(std::stoul(line.substr(at + 2), nullptr, 16)); + EXPECT_GT(value, 0) << line; + return line.substr(0, at) + "\n"; +} + +/// The lines of one thread ("Server:" or "Client:"), in order. +std::vector Thread(const std::vector& lines, const char* prefix) { + std::vector out; + for (const auto& line : lines) { + if (line.starts_with(prefix)) { + out.push_back(line); + } + } + return out; +} + +void CheckLogger(Logger& logger, const char* name, bool ordered) { + SCOPED_TRACE(name); + ASSERT_TRUE(logger.WaitForDelivery()) << "not every message reached the logger server"; + EXPECT_TRUE(logger.Errors().empty()) << "logger error: " << logger.Errors().front(); + + std::vector sent, received; + for (const auto& line : logger.Sent()) { + sent.push_back(MaskId(line)); + } + for (const auto& text : logger.Received()) { + received.push_back(MaskId(text)); + } + const std::vector ps4(std::begin(Ps4Round), std::end(Ps4Round)); + + for (const char* prefix : {"Server:", "Client:"}) { + EXPECT_EQ(Thread(sent, prefix), Thread(ps4, prefix)) << prefix; + if (ordered) { + EXPECT_EQ(Thread(received, prefix), Thread(ps4, prefix)) << prefix; + } + } + std::sort(received.begin(), received.end()); + auto expected = ps4; + std::sort(expected.begin(), expected.end()); + EXPECT_EQ(received, expected); +} + +class NetSampleSockets : public ::testing::Test { +protected: + static void SetUpTestSuite() { + ASSERT_EQ(sceNetInit(), ORBIS_OK); + } + static void TearDownTestSuite() { + EXPECT_EQ(sceNetTerm(), ORBIS_OK); + } +}; + +} // namespace + +TEST_F(NetSampleSockets, IntegrationTestsPr14) { + // First round: no logging; basic blocking stream socket behaviour on its own. + RunRound(); + + // Second round: a logger built on blocking stream sockets. + { + BIOStreamLogger logger; + g_active_logger = &logger; + RunRound(); + g_active_logger = nullptr; + CheckLogger(logger, "BIOStreamLogger", true); + } + + // Third round: non-blocking stream sockets, one message at a time. + { + NBIOStreamLogger logger(false); + g_active_logger = &logger; + RunRound(); + g_active_logger = nullptr; + CheckLogger(logger, "NBIOStreamLogger (sync)", true); + } + + // Fourth round: non-blocking stream sockets, several messages in flight. + { + NBIOStreamLogger logger(true); + g_active_logger = &logger; + RunRound(); + g_active_logger = nullptr; + CheckLogger(logger, "NBIOStreamLogger (async)", false); + } +} diff --git a/tests/stubs/net_kernel_stub.cpp b/tests/stubs/net_kernel_stub.cpp new file mode 100644 index 000000000..dcd608fa4 --- /dev/null +++ b/tests/stubs/net_kernel_stub.cpp @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// What the net kernel glue (src/core/libraries/net/net_kernel.cpp) needs from the kernel, +// without the kernel: its errno and its file table. The table follows fs.cpp (lowest free +// slot first, entries deleted on release); the rest of fs.cpp needs the whole file system. + +#include + +#include "core/file_sys/fs.h" +#include "core/libraries/kernel/kernel.h" + +namespace Libraries::Kernel { + +s32* PS4_SYSV_ABI __Error() { + thread_local s32 error = 0; + return &error; +} + +} // namespace Libraries::Kernel + +namespace Core::FileSys { + +int HandleTable::CreateHandle() { + std::scoped_lock lock{m_mutex}; + auto* file = new File{}; + for (size_t index = 0; index < m_files.size(); ++index) { + if (m_files[index] == nullptr) { + m_files[index] = file; + return static_cast(index); + } + } + m_files.push_back(file); + return static_cast(m_files.size() - 1); +} + +void HandleTable::DeleteHandle(int d) { + std::scoped_lock lock{m_mutex}; + delete m_files.at(d); + m_files[d] = nullptr; +} + +File* HandleTable::GetFile(int d) { + std::scoped_lock lock{m_mutex}; + if (d < 0 || static_cast(d) >= m_files.size()) { + return nullptr; + } + return m_files[d]; +} + +void HandleTable::CreateStdHandles() { + // Descriptors 0-2, so net objects start at 3 as in the emulator. + for (int i = 0; i < 3; ++i) { + const int fd = CreateHandle(); + auto* file = GetFile(fd); + file->type = FileType::Device; + file->is_opened = true; + } +} + +} // namespace Core::FileSys diff --git a/tests/stubs/net_lib_kernel_stub.cpp b/tests/stubs/net_lib_kernel_stub.cpp new file mode 100644 index 000000000..e31744a06 --- /dev/null +++ b/tests/stubs/net_lib_kernel_stub.cpp @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// The kernel calls libSceNet forwards to (threads, sleep, process time), on host threads. + +#include +#include +#include + +#include "core/libraries/kernel/threads.h" +#include "core/libraries/kernel/threads/pthread.h" +#include "core/libraries/kernel/time.h" + +namespace Libraries::Kernel { + +u64 PS4_SYSV_ABI sceKernelGetProcessTime() { + static const auto start = std::chrono::steady_clock::now(); + return static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now() - start) + .count()); +} + +s32 PS4_SYSV_ABI sceKernelUsleep(u32 microseconds) { + std::this_thread::sleep_for(std::chrono::microseconds{microseconds}); + return 0; +} + +int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAttrT*, + PthreadEntryFunc start_routine, void* arg, + const char*) { + auto* t = new std::thread([start_routine, arg] { start_routine(arg); }); + *thread = reinterpret_cast(t); + return 0; +} + +int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return) { + auto* t = reinterpret_cast(pthread); + t->join(); + delete t; + if (thread_return != nullptr) { + *thread_return = nullptr; + } + return 0; +} + +void PS4_SYSV_ABI posix_pthread_exit(void*) { + std::abort(); // no test exits a thread this way +} + +} // namespace Libraries::Kernel