mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-10-11 21:49:59 +00:00
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 <[email protected]>
This commit is contained in:
+42
-19
@@ -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")
|
||||
|
||||
@@ -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<NetworkSettings>();
|
||||
}
|
||||
}
|
||||
|
||||
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<std::string> 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());
|
||||
|
||||
@@ -189,23 +189,16 @@ struct GeneralSettings {
|
||||
Setting<bool> dev_kit_mode{false};
|
||||
Setting<int> extra_dmem_in_mbytes{0};
|
||||
Setting<int> extra_fmem_in_mbytes{0};
|
||||
Setting<bool> shad_net_enabled{false};
|
||||
Setting<bool> trophy_popup_disabled{false};
|
||||
Setting<double> trophy_notification_duration{6.0};
|
||||
Setting<std::string> trophy_notification_side{"right"};
|
||||
Setting<bool> show_splash{false};
|
||||
Setting<bool> connected_to_network{false};
|
||||
Setting<bool> discord_rpc_enabled{false};
|
||||
Setting<bool> show_fps_counter{false};
|
||||
Setting<int> console_language{1};
|
||||
Setting<int> big_picture_scale{1000};
|
||||
Setting<int> big_picture_folder_depth{2};
|
||||
Setting<std::string> shadnet_server{"srv.shadps4.net:31313"};
|
||||
Setting<std::string> shadnet_webapi_server{"http://srv.shadps4.net:31315"};
|
||||
Setting<std::string> signaling_info{};
|
||||
Setting<bool> enable_upnp{true};
|
||||
Setting<bool> redzone_patches{false};
|
||||
|
||||
// return a vector of override descriptors (runtime, but tiny)
|
||||
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||
return std::vector<OverrideItem>{
|
||||
@@ -216,7 +209,6 @@ struct GeneralSettings {
|
||||
&GeneralSettings::extra_dmem_in_mbytes),
|
||||
make_override<GeneralSettings>("extra_fmem_in_mbytes",
|
||||
&GeneralSettings::extra_fmem_in_mbytes),
|
||||
make_override<GeneralSettings>("shad_net_enabled", &GeneralSettings::shad_net_enabled),
|
||||
make_override<GeneralSettings>("trophy_popup_disabled",
|
||||
&GeneralSettings::trophy_popup_disabled),
|
||||
make_override<GeneralSettings>("trophy_notification_duration",
|
||||
@@ -224,28 +216,52 @@ struct GeneralSettings {
|
||||
make_override<GeneralSettings>("show_splash", &GeneralSettings::show_splash),
|
||||
make_override<GeneralSettings>("trophy_notification_side",
|
||||
&GeneralSettings::trophy_notification_side),
|
||||
make_override<GeneralSettings>("connected_to_network",
|
||||
&GeneralSettings::connected_to_network),
|
||||
make_override<GeneralSettings>("console_language", &GeneralSettings::console_language),
|
||||
make_override<GeneralSettings>("shadnet_server", &GeneralSettings::shadnet_server),
|
||||
make_override<GeneralSettings>("shadnet_webapi_server",
|
||||
&GeneralSettings::shadnet_webapi_server),
|
||||
make_override<GeneralSettings>("signaling_info", &GeneralSettings::signaling_info),
|
||||
make_override<GeneralSettings>("enable_upnp", &GeneralSettings::enable_upnp),
|
||||
make_override<GeneralSettings>("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<bool> connected_to_network{false};
|
||||
Setting<bool> shad_net_enabled{false};
|
||||
Setting<std::string> shadnet_server{"srv.shadps4.net:31313"};
|
||||
Setting<std::string> shadnet_webapi_server{"http://srv.shadps4.net:31315"};
|
||||
Setting<std::string> signaling_info{};
|
||||
Setting<bool> enable_upnp{true};
|
||||
Setting<int> p2p_port{3658}; // 0 is any free port, 3658 is the default PS4 port. With UPnP
|
||||
// enabled it is forwarded on the router.
|
||||
Setting<bool> disable_https{false};
|
||||
|
||||
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||
return std::vector<OverrideItem>{
|
||||
make_override<NetworkSettings>("connected_to_network",
|
||||
&NetworkSettings::connected_to_network),
|
||||
make_override<NetworkSettings>("shad_net_enabled", &NetworkSettings::shad_net_enabled),
|
||||
make_override<NetworkSettings>("shadnet_server", &NetworkSettings::shadnet_server),
|
||||
make_override<NetworkSettings>("shadnet_webapi_server",
|
||||
&NetworkSettings::shadnet_webapi_server),
|
||||
make_override<NetworkSettings>("signaling_info", &NetworkSettings::signaling_info),
|
||||
make_override<NetworkSettings>("enable_upnp", &NetworkSettings::enable_upnp),
|
||||
make_override<NetworkSettings>("p2p_port", &NetworkSettings::p2p_port),
|
||||
make_override<NetworkSettings>("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<std::string>& 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<OverrideItem> GetGeneralOverrideableFields() const {
|
||||
return m_general.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> GetNetworkOverrideableFields() const {
|
||||
return m_network.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> 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)
|
||||
|
||||
@@ -602,30 +602,6 @@ std::vector<int> 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) {
|
||||
|
||||
@@ -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<Directories::BaseDirectory> directory; // only valid for type == Directory
|
||||
std::shared_ptr<Devices::BaseDevice> device; // only valid for type == Device
|
||||
std::shared_ptr<Libraries::Net::Socket> socket; // only valid for type == Socket
|
||||
std::shared_ptr<Libraries::Net::Epoll> epoll; // only valid for type == Epoll
|
||||
std::shared_ptr<Libraries::Net::Resolver> 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<int> GetSocketHandles();
|
||||
File* GetEpoll(int d);
|
||||
File* GetResolver(int d);
|
||||
File* GetFile(const std::filesystem::path& host_name);
|
||||
int GetFileDescriptor(File* file);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <ranges>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
#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<Core::FileSys::HandleTable>::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<s32, s32> host_to_guest;
|
||||
s32 socket_max_fd = -1;
|
||||
|
||||
GuestFdSet read_ready{}, write_ready{};
|
||||
s32 ready = 0;
|
||||
std::vector<Core::Net::SelectEntry> 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<s32>(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<s32>(*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<s32>(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<s32>(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<s32>(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<s32>(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<Core::FileSys::HandleTable>::Instance();
|
||||
fd_set read_host, write_host, except_host;
|
||||
FD_ZERO(&read_host);
|
||||
FD_ZERO(&write_host);
|
||||
FD_ZERO(&except_host);
|
||||
|
||||
std::map<s32, s32> 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<s32>(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<s32>(*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);
|
||||
|
||||
@@ -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 <Rpc.h>
|
||||
@@ -35,8 +36,6 @@
|
||||
#include <uuid/uuid.h>
|
||||
#endif
|
||||
#include <common/singleton.h>
|
||||
#include <core/libraries/network/net_error.h>
|
||||
#include <core/libraries/network/sockets.h>
|
||||
#include <core/linker.h>
|
||||
#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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,255 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#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<bool()> is_online;
|
||||
std::function<bool(std::array<u8, 6>* mac)> mac_address;
|
||||
std::function<u16()> p2p_port;
|
||||
std::function<void(u16 bound_port)> p2p_started;
|
||||
std::function<void(u16 bound_port)> p2p_stopped;
|
||||
std::function<u32()> public_addr;
|
||||
};
|
||||
void SetSystemHooks(SystemHooks hooks);
|
||||
SystemHooks GetSystemHooks();
|
||||
void SetKernelErrnoHook(void (*hook)(int orbis_errno));
|
||||
void ReleaseResolverPool(OrbisNetId rid);
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -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;
|
||||
@@ -0,0 +1,339 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <source_location>
|
||||
#include <vector>
|
||||
|
||||
#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<Core::FileSys::HandleTable>;
|
||||
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<u8, 6>* mac) {
|
||||
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::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<u16>(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<NetUtil::NetUtilInternal>::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<Core::Net::SelectEntry> entries, s64 timeout_us) {
|
||||
std::vector<Core::Net::SelectEntry> 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<int>(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<int>(Posix(sceNetConnect(s, addr, addrlen)));
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
const KeepNetErrno keep;
|
||||
return static_cast<int>(Posix(sceNetBind(s, addr, addrlen)));
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
|
||||
const KeepNetErrno keep;
|
||||
return static_cast<int>(Posix(sceNetAccept(s, addr, paddrlen)));
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_getpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
|
||||
const KeepNetErrno keep;
|
||||
return static_cast<int>(Posix(sceNetGetpeername(s, addr, paddrlen)));
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_getsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
|
||||
const KeepNetErrno keep;
|
||||
return static_cast<int>(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<int>(Posix(sceNetGetsockopt(s, level, optname, optval, optlen)));
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_listen(OrbisNetId s, int backlog) {
|
||||
const KeepNetErrno keep;
|
||||
return static_cast<int>(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<int>(Posix(sceNetSetsockopt(s, level, optname, optval, optlen)));
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_shutdown(OrbisNetId s, int how) {
|
||||
const KeepNetErrno keep;
|
||||
return static_cast<int>(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<int>(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<int>(Posix(sceNetSocketAbort(s, flags)));
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_socketclose(OrbisNetId s) {
|
||||
const KeepNetErrno keep;
|
||||
return static_cast<int>(Posix(sceNetSocketClose(s)));
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_send(OrbisNetId s, const void* buf, u64 len, int flags) {
|
||||
const KeepNetErrno keep;
|
||||
return static_cast<int>(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<int>(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<int>(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<int>(sceNetHtonl(v));
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -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 <span>
|
||||
|
||||
#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<Core::Net::SelectEntry> 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);
|
||||
@@ -0,0 +1,526 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#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_<name> 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 <name>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<std::pair<std::string, int>, 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<u32>(code);
|
||||
if ((u & 0xffffff00u) == static_cast<u32>(ORBIS_NET_ERROR_BASE)) {
|
||||
return ErrnoName(static_cast<int>(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<std::string> peers;
|
||||
};
|
||||
|
||||
std::mutex g_traffic_mutex;
|
||||
std::map<OrbisNetId, Traffic> 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<u64>(bytes);
|
||||
++t.sent_calls;
|
||||
} else {
|
||||
t.received_bytes += static_cast<u64>(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<const char*>(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<u32, const char*> 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
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,314 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#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<const u8> 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<P2P::Transport> transport;
|
||||
std::shared_ptr<P2P::Keyring> keyring = std::make_shared<P2P::Keyring>();
|
||||
u16 configured_port = 3658; // host order
|
||||
u32 advertised_addr = 0; // network order
|
||||
std::chrono::steady_clock::time_point retry_after{};
|
||||
std::pair<u16, Error> last_failure{0, Error::Ok};
|
||||
};
|
||||
|
||||
P2PState& P2PStateInstance() {
|
||||
static P2PState state;
|
||||
return state;
|
||||
}
|
||||
|
||||
void LogBroadcast(std::span<const u8> 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<const sockaddr_in*>(sa)->sin_addr, address,
|
||||
sizeof(address));
|
||||
} else if (sa->sa_family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &reinterpret_cast<const sockaddr_in6*>(sa)->sin6_addr, address,
|
||||
sizeof(address));
|
||||
}
|
||||
std::string hex;
|
||||
const size_t shown = std::min<size_t>(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<P2P::FramingCodec>(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<u16>(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<int>(error)));
|
||||
} else {
|
||||
LOG_DEBUG(Lib_Net, "cannot start P2P on UDP port {}: {}", udp_port,
|
||||
ErrnoName(static_cast<int>(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<P2P::Transport> RunningTransport() {
|
||||
auto& state = P2PStateInstance();
|
||||
const auto hooks = GetSystemHooks();
|
||||
std::shared_ptr<P2P::Transport> 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<const sockaddr*>(&to), sizeof(to));
|
||||
const Error e = transport->SendSignaling(
|
||||
{static_cast<const u8*>(data), static_cast<size_t>(len)}, endpoint);
|
||||
return e == Error::Ok ? static_cast<int>(len) : -1;
|
||||
}
|
||||
|
||||
int ChannelRecvFrom(size_t channel, void* buf, u32 len, u32* from_addr, u16* from_port) {
|
||||
const auto transport = RunningTransport();
|
||||
std::vector<u8> packet;
|
||||
P2P::Endpoint from;
|
||||
if (!transport || !transport->RecvSignaling(channel, &packet, &from)) {
|
||||
return -1;
|
||||
}
|
||||
const u32 n = std::min<u32>(len, static_cast<u32>(packet.size()));
|
||||
if (buf != nullptr) {
|
||||
std::memcpy(buf, packet.data(), n);
|
||||
}
|
||||
const auto* in = reinterpret_cast<const sockaddr_in*>(&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<int>(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<P2P::Keyring> 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
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#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<Core::Net::P2P::Keyring> 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
|
||||
@@ -0,0 +1,145 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#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<const sockaddr*>(&host), sizeof(host));
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteKey(u8* bytes, const std::optional<P2P::P2PKey>& 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<u32>(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<u8*>(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<const OrbisNetSockaddr*>(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<const u8, 16>(bytes, 16)));
|
||||
return 0;
|
||||
default:
|
||||
LOG_WARNING(Lib_Net, "P2P ioctl {:#x}: unknown command", control);
|
||||
return ORBIS_NET_EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -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
|
||||
@@ -0,0 +1,350 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#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 <netdb.h>
|
||||
#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<bool, 2> pending_abort{}; // indexed by LookupKind
|
||||
};
|
||||
|
||||
std::mutex g_mutex;
|
||||
std::unordered_map<s32, std::shared_ptr<Resolver>> g_resolvers;
|
||||
std::function<bool()> g_is_online;
|
||||
|
||||
std::shared_ptr<Resolver> 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<bool()> 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<u32>* 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<const sockaddr_in*>(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<u32>& 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<u32>(addrs.size());
|
||||
output.info->recordsv4 = static_cast<u32>(addrs.size());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
s32 CreateResolver(std::string name) {
|
||||
const auto r = Core::Net::ReserveExternalId();
|
||||
if (r.error != Error::Ok) {
|
||||
return -static_cast<s32>(r.error);
|
||||
}
|
||||
const auto id = static_cast<s32>(r.value);
|
||||
auto resolver = std::make_shared<Resolver>();
|
||||
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> 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<size_t>(LookupKind::Ntoa)] = true;
|
||||
}
|
||||
if (flags & ORBIS_NET_RESOLVER_ABORT_FLAG_ATON_PRESERVATION) {
|
||||
resolver->pending_abort[static_cast<size_t>(LookupKind::Aton)] = true;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename Work, typename Deliver>
|
||||
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<size_t>(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<const sockaddr*>(&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<size_t>(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<u32> 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<u32>& 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<size_t>(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<bool()> is_online) {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
g_is_online = std::move(is_online);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#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<bool()> is_online);
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,392 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "core/libraries/net/net_error.h"
|
||||
#include "core/libraries/net/net_translate.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <afunix.h>
|
||||
#else
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/un.h>
|
||||
#endif
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
std::optional<int> 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<HostSocketKind> 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<sockaddr_in*>(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<sockaddr_in6*>(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<u32>(addrlen, sizeof(OrbisNetSockaddrUn));
|
||||
const char* path = reinterpret_cast<const char*>(addr) + 2;
|
||||
const size_t path_len = strnlen(path, size - 2);
|
||||
auto* host = reinterpret_cast<sockaddr_un*>(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<socklen_t>(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<socklen_t>(sizeof(sockaddr_in))) {
|
||||
const auto* host = reinterpret_cast<const sockaddr_in*>(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<socklen_t>(sizeof(sockaddr_in6))) {
|
||||
const auto* host = reinterpret_cast<const sockaddr_in6*>(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<const sockaddr_un*>(addr);
|
||||
OrbisNetSockaddrUn un{};
|
||||
un.sun_len = sizeof(un);
|
||||
un.sun_family = ORBIS_NET_AF_UNIX;
|
||||
const auto offset = static_cast<socklen_t>(offsetof(sockaddr_un, sun_path));
|
||||
if (len > offset) {
|
||||
const size_t max = std::min<size_t>(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<const sockaddr*>(&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<const sockaddr_in*>(&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<HostOption> 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<int>(error); // already FreeBSD errno values
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,73 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#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<int> ToHostFamily(int orbis_family);
|
||||
int ToOrbisFamily(int host_family);
|
||||
|
||||
struct HostSocketKind {
|
||||
int family;
|
||||
int type;
|
||||
int protocol;
|
||||
bool p2p;
|
||||
};
|
||||
|
||||
std::optional<HostSocketKind> 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<HostOption> 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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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<bool> m_available{false};
|
||||
std::atomic<u32> m_external_ip_nbo{0};
|
||||
std::atomic<u16> m_external_port{0};
|
||||
std::atomic<u16> 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
|
||||
@@ -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 {
|
||||
@@ -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<std::mutex> lock(g_state.m_mutex);
|
||||
if (!g_state.inited) {
|
||||
LOG_ERROR(Lib_Http, "Not initialized");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -1,79 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#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
|
||||
@@ -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 <deque>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <wepoll.h>
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
||||
// ADD libepoll-shim if using freebsd!
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
#ifdef _WIN32
|
||||
using epoll_handle = HANDLE;
|
||||
#else
|
||||
using epoll_handle = int;
|
||||
#endif
|
||||
|
||||
struct Epoll {
|
||||
std::vector<std::pair<u32 /*netId*/, OrbisNetEpollEvent>> events{};
|
||||
std::string name;
|
||||
epoll_handle epoll_fd;
|
||||
std::deque<u32> 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<Epoll> epolls;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -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<NetUtil::NetUtilInternal>::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
|
||||
@@ -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 <mutex>
|
||||
#include <vector>
|
||||
|
||||
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<AsyncResolution> async_resolution{};
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <common/assert.h>
|
||||
#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
|
||||
@@ -1,716 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <vector>
|
||||
#include <common/assert.h>
|
||||
#include "common/error.h"
|
||||
#include "core/libraries/kernel/file_system.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "net.h"
|
||||
#ifndef _WIN32
|
||||
#include <sys/stat.h>
|
||||
#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<unsigned char>(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<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(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<const msghdr*>(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<LPWSAMSG>(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<msghdr*>(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<PosixSocket>(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<char> 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<const OrbisNetLinger*>(optval)->l_onoff;
|
||||
native_linger.l_linger = reinterpret_cast<const OrbisNetLinger*>(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
|
||||
@@ -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 <Ws2tcpip.h>
|
||||
#include <afunix.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <mstcpip.h>
|
||||
#include <winsock2.h>
|
||||
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 <cerrno>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
typedef int net_socket;
|
||||
#endif
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include "net.h"
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
struct OrbisKernelStat;
|
||||
}
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
struct Socket;
|
||||
|
||||
typedef std::shared_ptr<Socket> 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<net_socket> 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<net_socket> 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<net_socket> 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<net_socket> 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
|
||||
@@ -1,452 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <common/assert.h>
|
||||
#include <common/logging/log.h>
|
||||
#include <core/libraries/kernel/kernel.h>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#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<Core::FileSys::HandleTable>;
|
||||
|
||||
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<s32*>(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<UnixSocket>(family, type, protocol);
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case ORBIS_NET_SOCK_RAW:
|
||||
socket = std::make_shared<PosixSocket>(family, type, protocol);
|
||||
break;
|
||||
case ORBIS_NET_SOCK_DGRAM_P2P:
|
||||
case ORBIS_NET_SOCK_STREAM_P2P:
|
||||
socket = std::make_shared<P2PSocket>(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<UnixSocket>(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<UnixSocket>(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
|
||||
@@ -1,397 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <common/assert.h>
|
||||
#include "common/error.h"
|
||||
#include "core/libraries/kernel/file_system.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "net.h"
|
||||
#ifndef _WIN32
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#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<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), flags,
|
||||
&bytesSent, nullptr, nullptr);
|
||||
|
||||
if (res == SOCKET_ERROR) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
return static_cast<int>(bytesSent);
|
||||
#else
|
||||
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(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<LPWSAMSG>(msg), &bytesReceived, nullptr, nullptr);
|
||||
|
||||
if (res == SOCKET_ERROR) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
return static_cast<int>(bytesReceived);
|
||||
#else
|
||||
int res = recvmsg(sock, reinterpret_cast<msghdr*>(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<UnixSocket>(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
|
||||
@@ -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<NpMatching2::ContextManager>(new NpMatching2::ContextManager())),
|
||||
m_matching2_state(std::make_unique<NpMatching2::NpMatching2State>()) {}
|
||||
|
||||
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<NpMatching2::Matching2ContextCache> cache;
|
||||
{
|
||||
std::lock_guard lock(m_mutex_matching2_cache);
|
||||
auto& entry = m_matching2_cache[ctx_id];
|
||||
if (!entry) {
|
||||
entry = std::make_shared<NpMatching2::Matching2ContextCache>();
|
||||
}
|
||||
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<std::string, u16> 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<std::string>& 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<size_t>(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<NpScore::ScoreRequestCtx> 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<int>(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<int>(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<int>(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<int>(cmd), static_cast<int>(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<u32>(body[0]) | (static_cast<u32>(body[1]) << 8) |
|
||||
(static_cast<u32>(body[2]) << 16) | (static_cast<u32>(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<u32>(body[2]) << 16) |
|
||||
(static_cast<u32>(body[3]) << 24);
|
||||
if (static_cast<size_t>(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<u64>(static_cast<u64>(stored_size), pending.recvSize);
|
||||
std::memcpy(pending.dataOut, body.data() + 4, static_cast<size_t>(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<u32>(body[2]) << 16) |
|
||||
(static_cast<u32>(body[3]) << 24);
|
||||
if (static_cast<size_t>(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<int>(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<u32>(body[2]) << 16) |
|
||||
(static_cast<u32>(body[3]) << 24);
|
||||
if (static_cast<size_t>(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<int>(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<s32>(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<u32>(body[2]) << 16) |
|
||||
(static_cast<u32>(body[3]) << 24);
|
||||
if (static_cast<size_t>(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<int>(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<s32>(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<u32>(body[2]) << 16) |
|
||||
(static_cast<u32>(body[3]) << 24);
|
||||
if (static_cast<size_t>(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<int>(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<s32>(found));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LOG_WARNING(NpHandler, "OnScoreReply: unexpected cmd={} pkt_id={}", static_cast<int>(cmd),
|
||||
pkt_id);
|
||||
LOG_WARNING(NpHandler, "unexpected cmd={} pkt_id={}", static_cast<int>(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<int>(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<int>(cmd), static_cast<int>(error),
|
||||
static_cast<u32>(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<int>(cmd));
|
||||
LOG_DEBUG(NpHandler, "cmd={} ok (no payload)", static_cast<int>(cmd));
|
||||
break;
|
||||
}
|
||||
req->SetResult(ORBIS_OK);
|
||||
|
||||
@@ -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<NpMatching2::Matching2ContextCache> cache)
|
||||
: m_cache(std::move(cache)), m_lock(m_cache->mutex) {}
|
||||
|
||||
std::shared_ptr<NpMatching2::Matching2ContextCache> m_cache;
|
||||
std::unique_lock<std::recursive_mutex> 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<s32, FriendListSnapshot> m_friend_state;
|
||||
|
||||
mutable std::mutex m_mutex_matching2_cache;
|
||||
std::map<NpMatching2::OrbisNpMatching2ContextId,
|
||||
std::shared_ptr<NpMatching2::Matching2ContextCache>>
|
||||
m_matching2_cache;
|
||||
std::unique_ptr<NpMatching2::ContextManager> m_matching2_contexts;
|
||||
std::unique_ptr<NpMatching2::NpMatching2State> m_matching2_state;
|
||||
NpSignaling::NpSignalingState m_signaling_state;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Np
|
||||
} // namespace Libraries::Np
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <fmt/format.h>
|
||||
@@ -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<std::uintptr_t>(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<CallbackPayload>();
|
||||
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<CallbackPayload>();
|
||||
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<CallbackPayload>();
|
||||
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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#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<u8> 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<OrbisNpMatching2AttributeId, MemberBinCache> 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<OrbisNpMatching2RoomBinAttrInternal> bin_attrs_internal;
|
||||
std::vector<std::vector<u8>> bin_buffers;
|
||||
std::map<OrbisNpMatching2RoomGroupId, OrbisNpMatching2RoomGroup> groups;
|
||||
std::map<OrbisNpMatching2RoomMemberId, MemberCache> members;
|
||||
bool owner = false;
|
||||
};
|
||||
|
||||
struct Matching2ContextCache {
|
||||
mutable std::recursive_mutex mutex;
|
||||
std::map<OrbisNpMatching2RoomMemberId, PeerInfo> peers;
|
||||
std::map<OrbisNpMatching2RoomId, RoomCache> rooms;
|
||||
|
||||
void Reset() {
|
||||
peers.clear();
|
||||
rooms.clear();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Libraries::Np::NpMatching2
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<u8> 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<OrbisNpMatching2AttributeId, MemberBinCache> 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<OrbisNpMatching2RoomBinAttrInternal> bin_attrs_internal;
|
||||
std::vector<std::vector<u8>> bin_buffers;
|
||||
std::map<OrbisNpMatching2RoomGroupId, OrbisNpMatching2RoomGroup> groups;
|
||||
std::map<OrbisNpMatching2RoomMemberId, MemberCache> members;
|
||||
bool owner = false;
|
||||
};
|
||||
|
||||
struct CallbackPayload {
|
||||
std::unique_ptr<OrbisNpMatching2RoomDataInternal> room_data;
|
||||
std::unique_ptr<OrbisNpMatching2CreateJoinRoomResponse> create_join_response;
|
||||
@@ -106,6 +50,10 @@ struct CallbackPayload {
|
||||
room_member_data_external_list_response;
|
||||
std::unique_ptr<OrbisNpMatching2GetRoomMemberDataExternalListResponseA>
|
||||
room_member_data_external_list_response_a;
|
||||
std::unique_ptr<OrbisNpMatching2GetRoomMemberDataInternalResponse>
|
||||
room_member_data_internal_response;
|
||||
std::unique_ptr<OrbisNpMatching2GetRoomMemberDataInternalResponseA>
|
||||
room_member_data_internal_response_a;
|
||||
std::unique_ptr<OrbisNpMatching2GetUserInfoListResponse> user_info_list_response;
|
||||
std::unique_ptr<OrbisNpMatching2GetUserInfoListResponseA> user_info_list_response_a;
|
||||
std::unique_ptr<OrbisNpMatching2GetWorldInfoListResponse> world_info_response;
|
||||
@@ -120,6 +68,7 @@ struct CallbackPayload {
|
||||
std::vector<OrbisNpMatching2RoomBinAttrInternal> room_bin_attrs;
|
||||
std::vector<OrbisNpMatching2RoomBinAttrInternal*> room_bin_attr_ptrs;
|
||||
std::vector<OrbisNpMatching2RoomMemberBinAttrInternal> member_bin_attrs;
|
||||
std::vector<OrbisNpMatching2RoomMemberBinAttrInternal*> member_bin_attr_ptrs;
|
||||
std::vector<OrbisNpMatching2RoomDataExternal> room_data_external;
|
||||
std::vector<OrbisNpMatching2RoomDataExternalA> room_data_external_a;
|
||||
std::vector<OrbisNpMatching2UserInfo> user_info;
|
||||
@@ -136,18 +85,20 @@ struct CallbackPayload {
|
||||
std::unique_ptr<OrbisNpMatching2RoomMessageInfoA> room_message_info_a;
|
||||
std::unique_ptr<OrbisNpMatching2RoomMessageDestination> room_message_dst;
|
||||
std::unique_ptr<Libraries::Np::OrbisNpId> room_message_src_npid;
|
||||
std::unique_ptr<Libraries::Np::OrbisNpPeerAddressA> room_message_src_addr;
|
||||
std::unique_ptr<Libraries::Np::OrbisNpOnlineId> room_message_src_online_id;
|
||||
std::vector<OrbisNpMatching2RoomMemberId> room_message_multicast_members;
|
||||
std::vector<u8> room_message_data;
|
||||
void* room_message_callback_data = nullptr;
|
||||
|
||||
std::unique_ptr<OrbisNpMatching2RoomMemberUpdate> room_member_update;
|
||||
std::unique_ptr<OrbisNpMatching2RoomMemberUpdateA> room_member_update_a;
|
||||
std::unique_ptr<OrbisNpMatching2RoomMemberDataInternalUpdate> room_member_data_internal_update;
|
||||
std::unique_ptr<OrbisNpMatching2RoomMemberDataInternalUpdateA>
|
||||
room_member_data_internal_update_a;
|
||||
std::unique_ptr<OrbisNpMatching2RoomUpdate> room_update;
|
||||
std::unique_ptr<OrbisNpMatching2RoomDataInternalUpdate> room_data_internal_update;
|
||||
std::unique_ptr<OrbisNpMatching2FlagAttr> event_chg_flag_attr;
|
||||
std::unique_ptr<OrbisNpMatching2FlagAttr> event_prev_flag_attr;
|
||||
std::unique_ptr<OrbisNpMatching2TeamId> event_chg_team_id;
|
||||
std::unique_ptr<OrbisNpMatching2RoomPasswordSlotMask> event_chg_passwd_slot_mask;
|
||||
std::unique_ptr<OrbisNpMatching2RoomPasswordSlotMask> event_prev_passwd_slot_mask;
|
||||
std::unique_ptr<OrbisNpMatching2RoomMemberDataInternal> 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<ContextObject> {
|
||||
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<OrbisNpMatching2RoomMemberId, PeerInfo> peers;
|
||||
|
||||
std::map<OrbisNpMatching2RoomId, RoomCache> 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<ContextObject> Get(OrbisNpMatching2ContextId ctx_id);
|
||||
bool Destroy(OrbisNpMatching2ContextId ctx_id);
|
||||
void CompleteStop(OrbisNpMatching2ContextId ctx_id);
|
||||
void CompleteStop(OrbisNpMatching2ContextId ctx_id,
|
||||
const std::shared_ptr<ContextObject>& 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<ContextObject> GetLocked(OrbisNpMatching2ContextId ctx_id);
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::array<ContextObject, kMaxContexts + 1> m_contexts{};
|
||||
std::array<bool, kMaxContexts + 1> m_used{};
|
||||
std::array<std::shared_ptr<ContextObject>, 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<ContextObject> 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<CallbackPayload> request_payload_owner;
|
||||
std::shared_ptr<CallbackPayload> payload_owner;
|
||||
|
||||
OrbisNpMatching2RoomId room_id = 0;
|
||||
OrbisNpMatching2RoomMemberId member_id = 0;
|
||||
@@ -402,32 +306,45 @@ struct NpMatching2State {
|
||||
std::atomic<bool> 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<ContextObject>& ctx,
|
||||
const OrbisNpMatching2RequestOptParam* requestOpt);
|
||||
RequestCallbackInfo ConsumeRequestCallback(const std::shared_ptr<ContextObject>& 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<OrbisNpMatching2RoomMemberId>& dstMembers,
|
||||
const MemberCache* srcMember, const std::vector<u8>& msg);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,7 @@ enum class MmCommand : u16 {
|
||||
GetUserInfoList = 113,
|
||||
GetRoomMemberDataExternalList = 114,
|
||||
SendRoomMessage = 115,
|
||||
SetRoomMemberDataInternal = 116,
|
||||
};
|
||||
|
||||
void SetMmShadNetClient(std::shared_ptr<ShadNet::ShadNetClient> 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,
|
||||
|
||||
@@ -2,63 +2,31 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
|
||||
#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<bool> 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<u8>(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<u8>(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<OrbisNpMatching2ContextId>(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<OrbisNpMatching2RoomId>(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<OrbisNpMatching2RoomId>(pkt.room_id);
|
||||
const auto member_id = static_cast<OrbisNpMatching2RoomMemberId>(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<const char*>(pkt.online_id_from),
|
||||
ORBIS_NP_ONLINEID_MAX_LENGTH));
|
||||
|
||||
const auto kind = static_cast<Matching2HandshakeKind>(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::microseconds>(
|
||||
std::chrono::steady_clock::now() - peer.last_check_send);
|
||||
peer.ping_us =
|
||||
static_cast<u32>(std::min<u64>(static_cast<u64>(std::max<s64>(0, rtt.count())),
|
||||
std::numeric_limits<u32>::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<OrbisNpMatching2ContextId>(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<u16>(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<u64>(std::chrono::steady_clock::now().time_since_epoch().count() ^
|
||||
(static_cast<u64>(ctx.ctx_id) << 48) ^
|
||||
(static_cast<u64>(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<OrbisNpMatching2RoomMemberId> 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<u32>(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<u32>(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<OrbisNpMatching2SignalingGetPingInfoResponse>();
|
||||
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<NetUtil::NetUtilInternal>::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<OrbisNpMatching2SignalingConnectionInfoA*>(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<OrbisNpMatching2SignalingConnectionInfoA*>(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<NpSignaling::OrbisNpSignalingNetInfo*>(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<NetUtil::NetUtilInternal>::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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#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<u64>(threadStackSize), threadPriority,
|
||||
static_cast<u64>(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<OrbisNpSignalingHandler>(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<OrbisNpSignalingHandler>(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<s32> 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='<unresolved>' "
|
||||
"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='<unresolved>' "
|
||||
"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='<invalid>' "
|
||||
"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<NetUtil::NetUtilInternal>::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
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <fmt/format.h>
|
||||
#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<char>(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<uintptr_t>(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<const u8*>(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<intptr_t>(out_conn_id) - reinterpret_cast<intptr_t>(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<u32>(sock));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
const s32 ioctl_rc = Libraries::Net::sceNetIoctl();
|
||||
std::array<u8, 36> 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<u64>(-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<NetUtil::NetUtilInternal>::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<size_t>(rc);
|
||||
if (nbytes == sizeof(SignalingControl) && HasSignalingMagic(buf, nbytes) &&
|
||||
buf[4] == static_cast<u8>(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<size_t>(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<u8>(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<u8>(SignalingPacketType::EchoPong)) {
|
||||
SignalingEchoPong pong{};
|
||||
std::memcpy(&pong, buf, sizeof(pong));
|
||||
const s64 now = NowUs();
|
||||
s32 rtt_us = static_cast<s32>(now - static_cast<s64>(pong.orig_ts_us));
|
||||
if (rtt_us < 0) {
|
||||
rtt_us = 0;
|
||||
}
|
||||
{
|
||||
SignalingMutexGuard lock;
|
||||
RecordRttSampleLocked(static_cast<s32>(pong.conn_id), rtt_us);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (nbytes == sizeof(SignalingHandshake) &&
|
||||
type == static_cast<u8>(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<CtxSnapshot> 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<std::mutex> lock(g_dispatch_mutex);
|
||||
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(g_dispatch_mutex);
|
||||
g_dispatch_stop = true;
|
||||
g_dispatch_queue.clear();
|
||||
std::lock_guard<std::mutex> 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
|
||||
|
||||
@@ -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();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
@@ -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<s32, NpSignalingContext> g_contexts;
|
||||
extern std::unordered_map<s32, ConnectionInfo> g_connections;
|
||||
extern std::unordered_map<CtxNpIdKey, s32, CtxNpIdKeyHash> g_npid_to_conn;
|
||||
extern std::unordered_map<s32, PeerNetInfoResult> g_peer_netinfo_results;
|
||||
struct PendingActivation {
|
||||
OrbisNpSignalingConnectionId conn_id = 0;
|
||||
std::string peer_online_id;
|
||||
bool start_handshake = true;
|
||||
};
|
||||
extern std::vector<PendingActivation> 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<s32, NpSignalingContext> contexts;
|
||||
std::unordered_map<s32, ConnectionInfo> connections;
|
||||
std::unordered_map<s32, PeerTransport> peer_transports;
|
||||
std::unordered_map<CtxNpIdKey, s32, CtxNpIdKeyHash> npid_to_conn;
|
||||
std::unordered_map<s32, PeerNetInfoResult> peer_netinfo_results;
|
||||
std::vector<PendingActivation> 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<std::chrono::steady_clock::time_point, QueuedDispatch> dispatch_queue;
|
||||
std::mutex dispatch_mutex;
|
||||
std::condition_variable dispatch_cv;
|
||||
Libraries::Kernel::PthreadT dispatch_thread = nullptr;
|
||||
bool dispatch_stop = false;
|
||||
|
||||
std::atomic<bool> 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<std::recursive_mutex> lock;
|
||||
};
|
||||
|
||||
void InitSignalingMutex();
|
||||
void DestroySignalingMutex();
|
||||
|
||||
extern std::multimap<std::chrono::steady_clock::time_point, QueuedDispatch> 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
|
||||
|
||||
+3
-3
@@ -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
|
||||
+3
-2
@@ -4,9 +4,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#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
|
||||
@@ -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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,154 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#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<s32()> allocate; // -1 when out of ids
|
||||
std::function<void(s32)> release;
|
||||
};
|
||||
void SetIdAllocator(IdAllocator allocator);
|
||||
|
||||
enum class NetObjectKind { Socket, Epoll, External };
|
||||
|
||||
std::optional<NetObjectKind> GetObjectKind(s32 id);
|
||||
NetResult CloseObject(s32 id);
|
||||
NetResult ReserveExternalId();
|
||||
NetResult SignalExternal(s32 id, bool hangup = false);
|
||||
NetResult ReleaseExternalId(s32 id);
|
||||
std::vector<s32> 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<char, 32> 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<std::pair<s32, s32>, 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<std::array<u8, 8>> pending_memberships;
|
||||
};
|
||||
NetResult SocketGetAttributes(s32 id, SocketAttributes* out);
|
||||
NetResult SocketUpdateAttributes(s32 id, const std::function<void(SocketAttributes&)>& 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<P2P::Transport> 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<GuestEpollEvent> 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<SelectEntry> entries, s64 timeout_us);
|
||||
|
||||
} // namespace Core::Net
|
||||
@@ -0,0 +1,920 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
|
||||
#include "core/net/host_net.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <mstcpip.h>
|
||||
#include <wepoll.h>
|
||||
#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 <cerrno>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#ifdef __linux__
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#define HOST_NET_EPOLL 1
|
||||
#else
|
||||
#include <sys/event.h>
|
||||
#include <sys/time.h>
|
||||
#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<std::chrono::milliseconds>(left).count();
|
||||
return static_cast<int>(std::min<s64>(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<sockaddr*>(&addr), sizeof(addr)) != 0 ||
|
||||
listen(listener, 1) != 0 ||
|
||||
getsockname(listener, reinterpret_cast<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&client_name), &client_len);
|
||||
getpeername(server, reinterpret_cast<sockaddr*>(&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<const char*>(&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<char*>(&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<const char*>(&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<char*>(&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<s64>(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<int>(std::min<size_t>(len, INT_MAX));
|
||||
const char* p = static_cast<const char*>(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<s64>(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<int>(std::min<size_t>(len, INT_MAX));
|
||||
char* p = static_cast<char*>(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<s64>(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<sockaddr*>(&addr), sizeof(addr)) == 0 &&
|
||||
getsockname(read_, reinterpret_cast<sockaddr*>(&addr), &addr_len) == 0 &&
|
||||
connect(write_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == 0 &&
|
||||
getsockname(write_, reinterpret_cast<sockaddr*>(&peer), &peer_len) == 0 &&
|
||||
// Only accept datagrams from our writer.
|
||||
connect(read_, reinterpret_cast<sockaddr*>(&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<short>(((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<EpollEvent> out, Deadline deadline) {
|
||||
std::array<epoll_event, 64> buf;
|
||||
const int cap = static_cast<int>(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<void*>(static_cast<uintptr_t>(tag));
|
||||
}
|
||||
|
||||
u64 UdataToTag(void* udata) {
|
||||
return static_cast<u64>(reinterpret_cast<uintptr_t>(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<int>(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<EpollEvent> out, Deadline deadline) {
|
||||
std::array<struct kevent, 64> buf;
|
||||
const int cap = static_cast<int>(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<std::chrono::nanoseconds>(left).count();
|
||||
ts.tv_sec = static_cast<time_t>(ns / 1'000'000'000);
|
||||
ts.tv_nsec = static_cast<long>(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
|
||||
@@ -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 <chrono>
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#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<Clock::time_point>;
|
||||
|
||||
// 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<EpollEvent> 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
|
||||
@@ -0,0 +1,266 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#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<P2PKey> key) {
|
||||
std::scoped_lock lock{mutex};
|
||||
wildcard_key = key;
|
||||
}
|
||||
|
||||
std::optional<P2PKey> Keyring::GetWildcardKey() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return wildcard_key;
|
||||
}
|
||||
|
||||
void Keyring::SetDefaultKey(std::optional<P2PKey> key) {
|
||||
std::scoped_lock lock{mutex};
|
||||
default_key = key;
|
||||
}
|
||||
|
||||
std::optional<P2PKey> Keyring::GetDefaultKey() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return default_key;
|
||||
}
|
||||
|
||||
std::optional<P2PKey> 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<KeyEntry> 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<CommunicationId> id) {
|
||||
std::scoped_lock lock{mutex};
|
||||
communication_id = id;
|
||||
}
|
||||
|
||||
std::optional<CommunicationId> Keyring::GetCommunicationId() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return communication_id;
|
||||
}
|
||||
|
||||
FramingCodec::FramingCodec(std::shared_ptr<Keyring> keyring, BroadcastObserver broadcast_observer)
|
||||
: keyring(std::move(keyring)), broadcast_observer(std::move(broadcast_observer)) {}
|
||||
|
||||
Codec::Decoded FramingCodec::Decode(std::span<const u8> 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<u16>((packet[2] << 8) | packet[3]);
|
||||
src_vport = dst_vport;
|
||||
} else if (!stream) {
|
||||
offset = 6;
|
||||
if (packet.size() < offset) {
|
||||
return out;
|
||||
}
|
||||
src_vport = static_cast<u16>((packet[2] << 8) | packet[3]);
|
||||
dst_vport = static_cast<u16>((packet[4] << 8) | packet[5]);
|
||||
}
|
||||
|
||||
std::optional<CommunicationId> 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<u8> FramingCodec::Protect(std::span<const u8> 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<u8> out;
|
||||
if (ProtectPayload(data, protection.crypto, protection.signature, *key, out)) {
|
||||
mode =
|
||||
static_cast<u8>((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<u8> FramingCodec::EncodeStream(std::span<const u8> 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<u8> packet{Marker, static_cast<u8>(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<u8> FrameDatagram(u16 src_vport, u16 dst_vport, u8 kind,
|
||||
const std::optional<CommunicationId>& communication_id,
|
||||
std::span<const u8> body) {
|
||||
std::vector<u8> packet{Marker,
|
||||
static_cast<u8>(FlagValid | kind | (communication_id ? FlagDigest : 0)),
|
||||
static_cast<u8>(src_vport >> 8),
|
||||
static_cast<u8>(src_vport),
|
||||
static_cast<u8>(dst_vport >> 8),
|
||||
static_cast<u8>(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<u8> FramingCodec::EncodeDatagram(u16 src_vport, u16 dst_vport,
|
||||
std::span<const u8> 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<u8> FramingCodec::EncodeSignaling(std::span<const u8> 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<const void*>(&reinterpret_cast<const sockaddr_in6*>(&ss)->sin6_addr)
|
||||
: static_cast<const void*>(&reinterpret_cast<const sockaddr_in*>(&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<sockaddr*>(&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
|
||||
@@ -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 <map>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
#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<u8, 4>;
|
||||
|
||||
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<P2PKey> key);
|
||||
std::optional<P2PKey> GetWildcardKey() const;
|
||||
void SetDefaultKey(std::optional<P2PKey> key);
|
||||
std::optional<P2PKey> GetDefaultKey() const;
|
||||
std::optional<P2PKey> Find(const Endpoint& peer) const;
|
||||
std::optional<KeyEntry> FindPeerEntry(const Endpoint& peer) const;
|
||||
void SetCommunicationId(std::optional<CommunicationId> id);
|
||||
std::optional<CommunicationId> GetCommunicationId() const;
|
||||
|
||||
private:
|
||||
struct Installed {
|
||||
KeyEntry entry;
|
||||
int references = 1;
|
||||
};
|
||||
|
||||
mutable std::mutex mutex;
|
||||
std::map<Endpoint, Installed> peer_keys;
|
||||
std::optional<P2PKey> wildcard_key;
|
||||
std::optional<P2PKey> default_key;
|
||||
std::optional<CommunicationId> communication_id;
|
||||
};
|
||||
|
||||
class FramingCodec final : public Codec {
|
||||
public:
|
||||
// Gets dropped broadcast packets, for debugging.
|
||||
using BroadcastObserver = std::function<void(std::span<const u8>, const Endpoint&)>;
|
||||
static constexpr int MaxReportedBroadcasts = 16; // per session
|
||||
|
||||
explicit FramingCodec(std::shared_ptr<Keyring> keyring,
|
||||
BroadcastObserver broadcast_observer = {});
|
||||
|
||||
Decoded Decode(std::span<const u8> packet, const Endpoint& from) override;
|
||||
std::vector<u8> EncodeStream(std::span<const u8> tcp_segment, const Endpoint& to,
|
||||
Protection protection) override;
|
||||
std::vector<u8> EncodeDatagram(u16 src_vport, u16 dst_vport, std::span<const u8> payload,
|
||||
const Endpoint& to, Protection protection) override;
|
||||
std::vector<u8> EncodeSignaling(std::span<const u8> 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<u8> Protect(std::span<const u8> data, const Endpoint& to, Protection protection,
|
||||
u8& mode) const;
|
||||
|
||||
std::shared_ptr<Keyring> keyring;
|
||||
BroadcastObserver broadcast_observer;
|
||||
std::atomic<int> reported_broadcasts{0};
|
||||
std::mutex pseudo_mutex;
|
||||
std::map<Endpoint, PseudoHeader> pseudo_cache;
|
||||
};
|
||||
|
||||
} // namespace Core::Net::P2P
|
||||
@@ -0,0 +1,108 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include "core/net/p2p_crypto.h"
|
||||
|
||||
namespace Core::Net::P2P {
|
||||
|
||||
constexpr size_t IvSeedSize = 4;
|
||||
constexpr size_t SignatureSize = 4;
|
||||
|
||||
bool Crypt(std::vector<u8>& payload, const P2PKey& key, const std::array<u8, 4>& iv_seed,
|
||||
bool encrypt) {
|
||||
std::array<u8, 16> 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<int>(payload.size())) == 1 &&
|
||||
EVP_CipherFinal_ex(context, payload.data() + output_size, &final_size) == 1;
|
||||
EVP_CIPHER_CTX_free(context);
|
||||
return ok && static_cast<size_t>(output_size + final_size) == payload.size();
|
||||
}
|
||||
|
||||
std::array<u8, SignatureSize> Sign(std::span<const u8> body, const P2PKey& key) {
|
||||
std::array<u8, EVP_MAX_MD_SIZE> digest{};
|
||||
unsigned int digest_size = 0;
|
||||
HMAC(EVP_sha1(), key.value.data(), static_cast<int>(key.value.size()), body.data(), body.size(),
|
||||
digest.data(), &digest_size);
|
||||
std::array<u8, SignatureSize> signature{};
|
||||
std::copy_n(digest.begin(), signature.size(), signature.begin());
|
||||
return signature;
|
||||
}
|
||||
|
||||
std::array<u8, 4> DeriveCommunicationId(std::span<const u8, 16> value) {
|
||||
std::array<u8, EVP_MAX_MD_SIZE> digest{};
|
||||
unsigned int digest_size = 0;
|
||||
const u8 empty = 0;
|
||||
HMAC(EVP_sha1(), value.data(), static_cast<int>(value.size()), &empty, 0, digest.data(),
|
||||
&digest_size);
|
||||
std::array<u8, 4> id{};
|
||||
std::copy_n(digest.begin(), id.size(), id.begin());
|
||||
return id;
|
||||
}
|
||||
|
||||
bool ProtectPayload(std::span<const u8> data, bool encrypt, bool sign, const P2PKey& key,
|
||||
std::vector<u8>& output) {
|
||||
std::vector<u8> body(data.begin(), data.end());
|
||||
std::array<u8, IvSeedSize> iv{};
|
||||
if (encrypt &&
|
||||
(RAND_bytes(iv.data(), static_cast<int>(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<const u8> data, bool encrypt, bool sign, const P2PKey& key,
|
||||
std::vector<u8>& output) {
|
||||
const size_t prefix = (encrypt ? IvSeedSize : 0) + (sign ? SignatureSize : 0);
|
||||
if (data.size() < prefix) {
|
||||
return false;
|
||||
}
|
||||
std::array<u8, IvSeedSize> iv{};
|
||||
std::array<u8, SignatureSize> 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
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Net::P2P {
|
||||
|
||||
struct P2PKey {
|
||||
std::array<u8, 16> value{};
|
||||
bool operator==(const P2PKey&) const = default;
|
||||
};
|
||||
|
||||
bool ProtectPayload(std::span<const u8> data, bool encrypt, bool sign, const P2PKey& key,
|
||||
std::vector<u8>& output);
|
||||
|
||||
// Fails on short packet, bad signature or cipher error.
|
||||
bool UnprotectPayload(std::span<const u8> data, bool encrypt, bool sign, const P2PKey& key,
|
||||
std::vector<u8>& output);
|
||||
|
||||
// First 4 bytes of HMAC-SHA1 over an empty message, keyed with the title's 16-byte value.
|
||||
std::array<u8, 4> DeriveCommunicationId(std::span<const u8, 16> value);
|
||||
|
||||
} // namespace Core::Net::P2P
|
||||
@@ -0,0 +1,878 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "core/net/p2p_tcp.h"
|
||||
|
||||
namespace Core::Net::P2P {
|
||||
|
||||
using namespace TcpFlag;
|
||||
|
||||
namespace {
|
||||
|
||||
u16 Read16(const u8* p) {
|
||||
return static_cast<u16>((p[0] << 8) | p[1]);
|
||||
}
|
||||
|
||||
u32 Read32(const u8* p) {
|
||||
return (static_cast<u32>(p[0]) << 24) | (static_cast<u32>(p[1]) << 16) |
|
||||
(static_cast<u32>(p[2]) << 8) | p[3];
|
||||
}
|
||||
|
||||
void Write16(u8* p, u16 v) {
|
||||
p[0] = static_cast<u8>(v >> 8);
|
||||
p[1] = static_cast<u8>(v);
|
||||
}
|
||||
|
||||
void Write32(u8* p, u32 v) {
|
||||
p[0] = static_cast<u8>(v >> 24);
|
||||
p[1] = static_cast<u8>(v >> 16);
|
||||
p[2] = static_cast<u8>(v >> 8);
|
||||
p[3] = static_cast<u8>(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<u64>(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<const u8> 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<u16>(~sum);
|
||||
}
|
||||
|
||||
std::optional<TcpSegment> ParseTcpSegment(std::span<const u8> bytes, const PseudoHeader* pseudo) {
|
||||
if (bytes.size() < 20) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const u8* b = bytes.data();
|
||||
const size_t header_size = static_cast<size_t>(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<u8> BuildTcpSegment(const PseudoHeader& pseudo, u16 src_port, u16 dst_port, u32 seq,
|
||||
u32 ack, u8 flags, u16 window, std::optional<u16> mss,
|
||||
std::span<const u8> payload) {
|
||||
const size_t header_size = mss ? 24 : 20;
|
||||
std::vector<u8> 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<u8>((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<std::vector<u8>> 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<u32>(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<u16>(std::min<size_t>(config_.recv_buffer - used, 65535));
|
||||
}
|
||||
|
||||
u32 TcpConnection::FinSeq() const {
|
||||
return buf_seq_ + static_cast<u32>(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<const u8> 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<u16>{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<u8> payload(len);
|
||||
const auto begin = send_buf_.begin() + static_cast<std::ptrdiff_t>(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<u32>(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<u32>(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<u32>(static_cast<u32>(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<u16>(syn.mss.value_or(536), config_.mss);
|
||||
cwnd_ = std::min<u32>(4u * snd_mss_, std::max<u32>(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<u16>(seg.mss.value_or(536), config_.mss);
|
||||
cwnd_ = std::min<u32>(4u * snd_mss_, std::max<u32>(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<u32>(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<u32>(ack - buf_seq_, static_cast<u32>(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<u32>(acked, snd_mss_); // slow start
|
||||
} else {
|
||||
cwnd_ += std::max<u32>(1, snd_mss_ * snd_mss_ / std::max<u32>(cwnd_, 1));
|
||||
}
|
||||
cwnd_ = std::min<u32>(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<u32>(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<const u8> data = seg.payload;
|
||||
if (seg.flags & Fin) {
|
||||
peer_fin_seq_ = seg.seq + static_cast<u32>(seg.payload.size());
|
||||
}
|
||||
if (SeqLt(seq, rcv_nxt_)) {
|
||||
const u32 skip = rcv_nxt_ - seq;
|
||||
data = skip >= data.size() ? std::span<const u8>{} : 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<u32>(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<std::ptrdiff_t>(rcv_offset_ - it->first);
|
||||
recv_buf_.insert(recv_buf_.end(), it->second.begin() + skip, it->second.end());
|
||||
const u32 added = static_cast<u32>(end - rcv_offset_);
|
||||
rcv_nxt_ += added;
|
||||
rcv_offset_ += added;
|
||||
}
|
||||
out_of_order_bytes_ -= it->second.size();
|
||||
out_of_order_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Clock::time_point> TcpConnection::NextDeadline() const {
|
||||
std::optional<Clock::time_point> 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<Clock::duration>(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<Clock::duration>(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<u32>(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<Clock::duration>(4 * rttvar_, std::chrono::milliseconds(10));
|
||||
rto_ = std::clamp<Clock::duration>(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<const u8> 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<u8> 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<u32>(config_.recv_buffer / 2, 2u * snd_mss_);
|
||||
if (static_cast<s32>(ReceiveWindow()) - static_cast<s32>(last_advertised_window_) >=
|
||||
static_cast<s32>(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
|
||||
@@ -0,0 +1,233 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#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<s32>(a - b) < 0;
|
||||
}
|
||||
constexpr bool SeqLe(u32 a, u32 b) {
|
||||
return static_cast<s32>(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<u8, 16> local{}; // IPv4 uses first 4 bytes
|
||||
std::array<u8, 16> remote{};
|
||||
};
|
||||
|
||||
struct TcpSegment {
|
||||
u16 src_port; // host byte order
|
||||
u16 dst_port;
|
||||
u32 seq;
|
||||
u32 ack;
|
||||
u8 flags;
|
||||
u16 window;
|
||||
std::optional<u16> mss;
|
||||
std::span<const u8> payload;
|
||||
};
|
||||
|
||||
u16 TcpChecksum(int family, const u8* src_addr, const u8* dst_addr, std::span<const u8> segment);
|
||||
std::optional<TcpSegment> ParseTcpSegment(std::span<const u8> bytes,
|
||||
const PseudoHeader* pseudo = nullptr);
|
||||
|
||||
std::vector<u8> BuildTcpSegment(const PseudoHeader& pseudo, u16 src_port, u16 dst_port, u32 seq,
|
||||
u32 ack, u8 flags, u16 window, std::optional<u16> mss,
|
||||
std::span<const u8> payload);
|
||||
std::optional<std::vector<u8>> 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<void(std::span<const u8> 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<Clock::time_point> NextDeadline() const;
|
||||
|
||||
IoResult Send(std::span<const u8> data, Clock::time_point now);
|
||||
IoResult Recv(std::span<u8> 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<const u8> 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<u8> 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<Clock::time_point> retransmit_deadline_;
|
||||
std::optional<Clock::time_point> persist_deadline_;
|
||||
std::optional<Clock::time_point> delayed_ack_deadline_;
|
||||
std::optional<Clock::time_point> time_wait_deadline_;
|
||||
u32 irs_ = 0;
|
||||
u32 rcv_nxt_ = 0;
|
||||
u64 rcv_offset_ = 0; // stream offset of rcv_nxt_
|
||||
std::deque<u8> recv_buf_;
|
||||
std::map<u64, std::vector<u8>> out_of_order_;
|
||||
size_t out_of_order_bytes_ = 0;
|
||||
std::optional<u32> peer_fin_seq_;
|
||||
bool fin_received_ = false;
|
||||
int unacked_segments_ = 0;
|
||||
u16 last_advertised_window_ = 0;
|
||||
|
||||
u32 retransmissions_ = 0;
|
||||
};
|
||||
|
||||
} // namespace Core::Net::P2P
|
||||
@@ -0,0 +1,866 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <tuple>
|
||||
|
||||
#include "core/net/p2p_transport.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
namespace Core::Net::P2P {
|
||||
|
||||
struct DatagramBinding {
|
||||
struct Packet {
|
||||
std::vector<u8> payload;
|
||||
Endpoint from;
|
||||
u16 src_vport;
|
||||
};
|
||||
u16 vport = 0;
|
||||
std::shared_ptr<Readiness> readiness;
|
||||
std::optional<std::pair<Endpoint, u16>> peer; // set by SetPeer
|
||||
std::deque<Packet> queue;
|
||||
size_t bytes = 0;
|
||||
};
|
||||
|
||||
struct Connection {
|
||||
ConnectionKey key;
|
||||
Protection protection;
|
||||
std::unique_ptr<TcpConnection> tcp;
|
||||
std::shared_ptr<Readiness> readiness;
|
||||
std::weak_ptr<ListenerState> 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> readiness;
|
||||
std::deque<std::shared_ptr<Connection>> accept_queue;
|
||||
int half_open = 0;
|
||||
bool closed = false;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
auto Canonical(const Endpoint& e) {
|
||||
std::array<u8, 16> address{};
|
||||
u16 port = 0;
|
||||
u32 scope = 0;
|
||||
if (e.Family() == AF_INET) {
|
||||
const auto* in = reinterpret_cast<const sockaddr_in*>(&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<const sockaddr_in6*>(&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<size_t>(static_cast<size_t>(len), sizeof(e.addr)));
|
||||
return e;
|
||||
}
|
||||
|
||||
Endpoint Endpoint::IPv4(const char* dotted, u16 port) {
|
||||
Endpoint e;
|
||||
auto* in = reinterpret_cast<sockaddr_in*>(&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> codec, const TransportConfig& config)
|
||||
: family_(family), codec_(std::move(codec)), config_(config),
|
||||
signaling_(std::max<size_t>(config.signaling_channels, 1)),
|
||||
iss_state_(static_cast<u64>(Clock::now().time_since_epoch().count()) ^
|
||||
reinterpret_cast<uintptr_t>(this)),
|
||||
receive_buffer_(65536) {}
|
||||
|
||||
std::shared_ptr<Transport> Transport::Create(int family, const Endpoint& bind_address,
|
||||
std::unique_ptr<Codec> codec,
|
||||
const TransportConfig& config, Error* error) {
|
||||
std::shared_ptr<Transport> 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<const char*>(&buffer),
|
||||
sizeof(buffer));
|
||||
sockaddr_storage actual{};
|
||||
socklen_t actual_len = sizeof(actual);
|
||||
getsockname(t->socket_, reinterpret_cast<sockaddr*>(&actual), &actual_len);
|
||||
t->bound_port_ =
|
||||
Endpoint::FromSockaddr(reinterpret_cast<sockaddr*>(&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<DatagramSocket> Transport::CreateDatagram() {
|
||||
return std::shared_ptr<DatagramSocket>(new DatagramSocket(shared_from_this()));
|
||||
}
|
||||
|
||||
std::shared_ptr<StreamSocket> Transport::CreateStream() {
|
||||
return std::shared_ptr<StreamSocket>(new StreamSocket(shared_from_this(), nullptr));
|
||||
}
|
||||
|
||||
Error Transport::SendSignaling(std::span<const u8> data, const Endpoint& to) {
|
||||
std::scoped_lock lock{mutex_};
|
||||
return SendPacket(codec_->EncodeSignaling(data, to), to);
|
||||
}
|
||||
|
||||
bool Transport::RecvSignaling(size_t channel, std::vector<u8>* 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<Clock::time_point> 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<sockaddr*>(&from), &from_len);
|
||||
if (r.error != Error::Ok) {
|
||||
break;
|
||||
}
|
||||
HandlePacket({receive_buffer_.data(), static_cast<size_t>(r.value)},
|
||||
Endpoint::FromSockaddr(reinterpret_cast<sockaddr*>(&from), from_len), now);
|
||||
}
|
||||
RunTimers(now);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Clock::time_point> Transport::EarliestDeadline() const {
|
||||
std::optional<Clock::time_point> 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<std::shared_ptr<Connection>> 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<const u8> 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<double>(loss_rng_ >> 11) / static_cast<double>(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<std::chrono::microseconds>(Clock::now().time_since_epoch())
|
||||
.count() /
|
||||
4;
|
||||
return static_cast<u32>(z) + static_cast<u32>(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<u16>(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<const u8> 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<const u8> bytes, const Endpoint& from,
|
||||
Clock::time_point now) {
|
||||
std::optional<PseudoHeader> 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<int>(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<Readiness>());
|
||||
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<Connection> Transport::NewConnection(const ConnectionKey& key,
|
||||
Protection protection,
|
||||
std::shared_ptr<Readiness> readiness) {
|
||||
auto connection = std::make_shared<Connection>();
|
||||
connection->key = key;
|
||||
connection->protection = protection;
|
||||
connection->readiness = std::move(readiness);
|
||||
const Endpoint peer = key.peer;
|
||||
connection->tcp = std::make_unique<TcpConnection>(
|
||||
config_.tcp, codec_->StreamPseudoHeader(peer), key.local_vport, key.peer_vport, NewIss(),
|
||||
[this, peer, protection](std::span<const u8> segment) {
|
||||
SendPacket(codec_->EncodeStream(segment, peer, protection), peer);
|
||||
});
|
||||
connections_[key] = connection;
|
||||
return connection;
|
||||
}
|
||||
|
||||
void Transport::Update(const std::shared_ptr<Connection>& 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)
|
||||
: transport_(std::move(transport)), readiness_(std::make_shared<Readiness>()) {
|
||||
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<DatagramBinding>();
|
||||
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<const u8> 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<u8> 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<u32>(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> transport,
|
||||
std::shared_ptr<Readiness> readiness)
|
||||
: transport_(std::move(transport)),
|
||||
readiness_(readiness ? std::move(readiness) : std::make_shared<Readiness>()) {}
|
||||
|
||||
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<ListenerState>();
|
||||
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> 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<StreamSocket> 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<const u8> 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<u8> 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<u32>(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<TcpState> 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
|
||||
@@ -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 <atomic>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#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<const sockaddr*>(&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<u8> payload;
|
||||
};
|
||||
|
||||
virtual ~Codec() = default;
|
||||
virtual Decoded Decode(std::span<const u8> packet, const Endpoint& from) = 0;
|
||||
virtual std::vector<u8> EncodeStream(std::span<const u8> tcp_segment, const Endpoint& to,
|
||||
Protection protection) = 0;
|
||||
virtual std::vector<u8> EncodeDatagram(u16 src_vport, u16 dst_vport,
|
||||
std::span<const u8> payload, const Endpoint& to,
|
||||
Protection protection) = 0;
|
||||
virtual std::vector<u8> EncodeSignaling(std::span<const u8> 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<size_t(std::span<const u8>)> 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<const u8> data, const Endpoint* to, u16 to_vport,
|
||||
Protection extra = {});
|
||||
IoResult RecvFrom(std::span<u8> 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> transport);
|
||||
Error BindLocked(u16 vport);
|
||||
|
||||
std::shared_ptr<Transport> transport_;
|
||||
std::shared_ptr<Readiness> readiness_;
|
||||
std::shared_ptr<DatagramBinding> binding_;
|
||||
Protection protection_;
|
||||
std::optional<std::pair<Endpoint, u16>> 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<StreamSocket> Accept(Endpoint* peer, u16* peer_vport, Error* error);
|
||||
IoResult Send(std::span<const u8> data);
|
||||
IoResult Recv(std::span<u8> 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<TcpState> 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> transport, std::shared_ptr<Readiness> readiness);
|
||||
Error BindLocked(u16 vport); // transport mutex held
|
||||
|
||||
std::shared_ptr<Transport> transport_;
|
||||
std::shared_ptr<Readiness> readiness_;
|
||||
Protection protection_;
|
||||
u16 bound_vport_ = 0;
|
||||
bool owns_vport_ = false;
|
||||
bool read_shutdown_ = false;
|
||||
bool closed_ = false;
|
||||
std::shared_ptr<ListenerState> listener_;
|
||||
std::shared_ptr<Connection> connection_;
|
||||
};
|
||||
|
||||
class Transport : public std::enable_shared_from_this<Transport> {
|
||||
public:
|
||||
static std::shared_ptr<Transport> Create(int family, const Endpoint& bind_address,
|
||||
std::unique_ptr<Codec> codec,
|
||||
const TransportConfig& config, Error* error);
|
||||
~Transport();
|
||||
|
||||
u16 BoundPort() const {
|
||||
return bound_port_;
|
||||
}
|
||||
int Family() const {
|
||||
return family_;
|
||||
}
|
||||
|
||||
std::shared_ptr<DatagramSocket> CreateDatagram();
|
||||
std::shared_ptr<StreamSocket> CreateStream();
|
||||
|
||||
Error SendSignaling(std::span<const u8> data, const Endpoint& to);
|
||||
bool RecvSignaling(size_t channel, std::vector<u8>* data, Endpoint* from);
|
||||
|
||||
void SetOutgoingLossForTesting(double probability);
|
||||
|
||||
private:
|
||||
friend class DatagramSocket;
|
||||
friend class StreamSocket;
|
||||
|
||||
Transport(int family, std::unique_ptr<Codec> codec, const TransportConfig& config);
|
||||
|
||||
void ThreadMain();
|
||||
void HandlePacket(std::span<const u8> packet, const Endpoint& from, Clock::time_point now);
|
||||
void HandleStreamSegment(std::span<const u8> bytes, const Endpoint& from,
|
||||
Clock::time_point now);
|
||||
void HandleDatagram(Codec::Decoded& decoded, const Endpoint& from);
|
||||
void RunTimers(Clock::time_point now);
|
||||
std::optional<Clock::time_point> EarliestDeadline() const;
|
||||
|
||||
std::shared_ptr<Connection> NewConnection(const ConnectionKey& key, Protection protection,
|
||||
std::shared_ptr<Readiness> readiness);
|
||||
void Update(const std::shared_ptr<Connection>& connection);
|
||||
void Reschedule(const Connection& connection);
|
||||
Error SendPacket(std::span<const u8> packet, const Endpoint& to);
|
||||
u16 AllocateVport(bool stream);
|
||||
u32 NewIss();
|
||||
|
||||
const int family_;
|
||||
std::unique_ptr<Codec> codec_;
|
||||
const TransportConfig config_;
|
||||
Host::NativeSocket socket_ = Host::InvalidSocket;
|
||||
u16 bound_port_ = 0;
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
std::map<u16, std::shared_ptr<DatagramBinding>> datagram_bindings_;
|
||||
std::map<u16, std::shared_ptr<ListenerState>> listeners_;
|
||||
std::map<ConnectionKey, std::shared_ptr<Connection>> connections_;
|
||||
std::map<u16, int> stream_vports_in_use_;
|
||||
std::vector<std::deque<std::pair<std::vector<u8>, 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<bool> stop_{false};
|
||||
Host::WakeHandle kick_;
|
||||
Clock::time_point sleeping_until_ = Clock::time_point::min();
|
||||
std::vector<u8> receive_buffer_;
|
||||
};
|
||||
|
||||
} // namespace Core::Net::P2P
|
||||
@@ -479,6 +479,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> 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
|
||||
|
||||
@@ -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<u8>& 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<u8>& 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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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 $<IF:$<BOOL:${MSVC}>,/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
|
||||
)
|
||||
|
||||
@@ -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 <chrono>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
|
||||
EXPECT_GT(listener, 0);
|
||||
auto addr = Loopback();
|
||||
EXPECT_EQ(SocketBind(listener, reinterpret_cast<sockaddr*>(&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<sockaddr*>(bound), &len).error,
|
||||
Error::Ok);
|
||||
return listener;
|
||||
}
|
||||
|
||||
static double Millis(std::chrono::steady_clock::time_point since) {
|
||||
return std::chrono::duration<double, std::milli>(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<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
|
||||
ASSERT_EQ(SocketConnect(client, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)).error,
|
||||
Error::Ok);
|
||||
server.join();
|
||||
ASSERT_EQ(accepted.error, Error::Ok);
|
||||
const s32 conn = static_cast<s32>(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<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
|
||||
ASSERT_EQ(SocketConnect(client, reinterpret_cast<sockaddr*>(&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<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
|
||||
ASSERT_EQ(SocketConnect(client, reinterpret_cast<sockaddr*>(&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<s32>(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<sockaddr*>(&p.addr), sizeof(p.addr));
|
||||
socklen_t len = sizeof(p.addr);
|
||||
SocketGetName(p.sock, reinterpret_cast<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&addr), sizeof(addr))
|
||||
.error,
|
||||
Error::Intr);
|
||||
EXPECT_EQ(SocketSendTo(sock, "y", 1, 0, false, reinterpret_cast<sockaddr*>(&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<s32>(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<sockaddr*>(&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<s32>(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<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
|
||||
EXPECT_EQ(SocketConnect(client, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)).error,
|
||||
Error::ConnRefused);
|
||||
SocketClose(client);
|
||||
}
|
||||
|
||||
TEST_F(NetHostTest, NonBlockingRecvWouldBlock) {
|
||||
const s32 sock = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
|
||||
auto addr = Loopback();
|
||||
SocketBind(sock, reinterpret_cast<sockaddr*>(&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<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
|
||||
auto addr = Loopback();
|
||||
SocketBind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
|
||||
socklen_t len = sizeof(addr);
|
||||
SocketGetName(sock, reinterpret_cast<sockaddr*>(&addr), &len);
|
||||
|
||||
const s32 ep = static_cast<s32>(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<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
|
||||
SocketSendTo(sender, "x", 1, 0, false, reinterpret_cast<sockaddr*>(&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<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
|
||||
const s32 ep = static_cast<s32>(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<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
|
||||
const s32 ep = static_cast<s32>(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<s32>(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<s32> 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<s32>(r.value));
|
||||
}
|
||||
EXPECT_EQ(sockets.size(), static_cast<size_t>(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<s32>(again.value));
|
||||
EXPECT_EQ(SocketCreate(AF_INET, SOCK_DGRAM, 0).error, Error::MFile);
|
||||
|
||||
for (const s32 id : sockets) {
|
||||
SocketClose(id);
|
||||
}
|
||||
EpollDestroy(static_cast<s32>(ep.value));
|
||||
}
|
||||
|
||||
TEST_F(NetHostTest, IdAllocatorHook) {
|
||||
// Stands in for the kernel's file table.
|
||||
std::mutex mutex;
|
||||
s32 next = 1000;
|
||||
std::vector<s32> 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<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
|
||||
const s32 ep = static_cast<s32>(EpollCreate().value);
|
||||
EXPECT_EQ(sock, 1000);
|
||||
EXPECT_EQ(ep, 1001);
|
||||
CloseObject(sock);
|
||||
CloseObject(ep);
|
||||
EXPECT_EQ(released, (std::vector<s32>{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<s32>(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<s32>(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<sockaddr*>(&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<int>(Error::OpNotSupp), 45);
|
||||
EXPECT_EQ(static_cast<int>(Error::NetReset), 52);
|
||||
EXPECT_EQ(static_cast<int>(Error::NotEmpty), 66);
|
||||
// Anything unknown is the PS4's EINTERNAL.
|
||||
EXPECT_EQ(Host::TranslateNative(987654), Error::Internal);
|
||||
EXPECT_EQ(static_cast<int>(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<s32>(EpollCreate().value);
|
||||
const auto reserved = ReserveExternalId();
|
||||
ASSERT_EQ(reserved.error, Error::Ok);
|
||||
const s32 id = static_cast<s32>(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<s32> ids;
|
||||
for (int i = 0; i < MaxSockets; ++i) {
|
||||
ids.push_back(static_cast<s32>(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<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
|
||||
const s32 ep = static_cast<s32>(EpollCreate().value);
|
||||
const s32 b = static_cast<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
|
||||
EXPECT_EQ(ListSockets(), (std::vector<s32>{a, b})); // sockets only, ascending
|
||||
SocketClose(a);
|
||||
SocketClose(b);
|
||||
EpollDestroy(ep);
|
||||
EXPECT_TRUE(ListSockets().empty());
|
||||
}
|
||||
|
||||
TEST_F(NetHostTest, SelectOnNativeSockets) {
|
||||
const s32 udp = static_cast<s32>(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<sockaddr*>(&addr), sizeof(addr)).error, Error::Ok);
|
||||
socklen_t len = sizeof(addr);
|
||||
SocketGetName(udp, reinterpret_cast<sockaddr*>(&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<sockaddr*>(&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<s32>(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<s32> many;
|
||||
std::vector<SelectEntry> all;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
many.push_back(static_cast<s32>(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<u32>(ToOrbisReturn(NetResult::Fail(Error::WouldBlock))), 0x80410123u);
|
||||
EXPECT_EQ(static_cast<u32>(ToOrbisReturn(NetResult::Fail(Error::Intr))), 0x80410104u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -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 <cstring>
|
||||
|
||||
#include <gtest/gtest.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_p2p.h"
|
||||
#include "core/net/guest_net.h"
|
||||
|
||||
using namespace Libraries::Net;
|
||||
using FDTable = Common::Singleton<Core::FileSys::HandleTable>;
|
||||
|
||||
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<OrbisNetSockaddr*>(&addr), sizeof(addr)), 0);
|
||||
u32 len = sizeof(addr);
|
||||
ASSERT_EQ(sys_getsockname(fd, reinterpret_cast<OrbisNetSockaddr*>(&addr), &len), 0);
|
||||
ASSERT_EQ(sys_connect(fd, reinterpret_cast<OrbisNetSockaddr*>(&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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 <array>
|
||||
#include <cstring>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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<u8, 36> 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<u8, 16> 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
|
||||
@@ -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 <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/un.h>
|
||||
#else
|
||||
#include <winsock2.h>
|
||||
#include <afunix.h>
|
||||
#endif
|
||||
|
||||
#include "core/libraries/net/net_error.h"
|
||||
#include "core/libraries/net/net_translate.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <netinet/tcp.h>
|
||||
#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<const OrbisNetSockaddr*>(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<int>(ORBIS_NET_SOCK_STREAM));
|
||||
EXPECT_EQ(ToOrbisSocketType(SOCK_DGRAM, true), static_cast<int>(ORBIS_NET_SOCK_DGRAM_P2P));
|
||||
EXPECT_EQ(ToOrbisSocketType(SOCK_STREAM, true), static_cast<int>(ORBIS_NET_SOCK_STREAM_P2P));
|
||||
EXPECT_EQ(ToOrbisFamily(AF_INET6), static_cast<int>(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<socklen_t>(sizeof(sockaddr_in)));
|
||||
const auto* in = reinterpret_cast<const sockaddr_in*>(&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<sockaddr*>(&host), sizeof(host),
|
||||
reinterpret_cast<OrbisNetSockaddr*>(&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<const sockaddr_in6*>(&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<sockaddr*>(&host), len,
|
||||
reinterpret_cast<OrbisNetSockaddr*>(&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<sockaddr*>(&in), sizeof(in),
|
||||
reinterpret_cast<OrbisNetSockaddr*>(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<OrbisNetSockaddr*>(&guest), sizeof(guest), &host, &len), 0);
|
||||
const auto* un = reinterpret_cast<const sockaddr_un*>(&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<const sockaddr*>(&host), len,
|
||||
reinterpret_cast<OrbisNetSockaddr*>(&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<OrbisNetSockaddr*>(&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<OrbisNetSockaddr*>(&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<OrbisNetSockaddr*>(&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<u32>(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
|
||||
@@ -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 <gtest/gtest.h>
|
||||
|
||||
#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<u8> 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<Keyring> keys = std::make_shared<Keyring>();
|
||||
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<u8>{0xFF, 0x80 | 3, 0x00, 0x03, 0x00, 0x04, 'a', 'b'}));
|
||||
EXPECT_EQ(codec.EncodeDatagram(0x0E4A, 4, Bytes("ab"), kPeer, {}),
|
||||
(std::vector<u8>{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<u8>{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<u8>{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<u8>{0xFF, 0x80 | 3, 0xFF, 0xFF, 0xFF, 0xFF, 'S', 'H', 'A', 'D'}));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Decoding
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
TEST_F(NetP2PCodec, DecodeRoundTrips) {
|
||||
for (const auto& [src, dst] : {std::pair<u16, u16>{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<u8> 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<u8> seen;
|
||||
FramingCodec observed{keys, [&](std::span<const u8> 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<u8> 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<u8>{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<u8>{0xFF, 0x80 | 0x40 | 7, 0, 3}, kPeer).kind,
|
||||
Codec::Kind::Invalid);
|
||||
EXPECT_EQ(observed.Decode(std::vector<u8>{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<Keyring>()};
|
||||
const auto plain = sender.EncodeDatagram(1, 2, Bytes("x"), kPeer, {});
|
||||
auto sender_keys = std::make_shared<Keyring>();
|
||||
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<u8> 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<u8>(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<u8> 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<u8>(7, 0), true, true, Key(7), restored)); // short
|
||||
}
|
||||
|
||||
TEST(NetP2PCrypto, RandomIvPerPacket) {
|
||||
std::vector<u8> 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<Keyring>();
|
||||
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<u8, 4>{p.local[0], p.local[1], p.local[2], p.local[3]}),
|
||||
(std::array<u8, 4>{127, 0, 0, 1}));
|
||||
EXPECT_EQ((std::array<u8, 4>{p.remote[0], p.remote[1], p.remote[2], p.remote[3]}),
|
||||
(std::array<u8, 4>{127, 0, 0, 1}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,515 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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<const u8> 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<u16>((packet[1] << 8) | packet[2]);
|
||||
d.dst_vport = static_cast<u16>((packet[3] << 8) | packet[4]);
|
||||
d.payload.assign(packet.begin() + 5, packet.end());
|
||||
return d;
|
||||
}
|
||||
std::vector<u8> EncodeStream(std::span<const u8> seg, const Endpoint&, Protection) override {
|
||||
return Frame(1, 0, 0, seg);
|
||||
}
|
||||
std::vector<u8> EncodeDatagram(u16 src, u16 dst, std::span<const u8> payload, const Endpoint&,
|
||||
Protection) override {
|
||||
return Frame(2, src, dst, payload);
|
||||
}
|
||||
std::vector<u8> EncodeSignaling(std::span<const u8> 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<u8> Frame(u8 kind, u16 src, u16 dst, std::span<const u8> payload) {
|
||||
std::vector<u8> v{kind, static_cast<u8>(src >> 8), static_cast<u8>(src),
|
||||
static_cast<u8>(dst >> 8), static_cast<u8>(dst)};
|
||||
v.insert(v.end(), payload.begin(), payload.end());
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<Transport> MakeTransport(std::unique_ptr<Codec> 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<int>(e);
|
||||
return t;
|
||||
}
|
||||
|
||||
std::vector<u8> RandomBytes(size_t n, u32 seed) {
|
||||
std::mt19937 rng(seed);
|
||||
std::vector<u8> v(n);
|
||||
for (auto& b : v) {
|
||||
b = static_cast<u8>(rng());
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
double Millis(Clock::time_point since) {
|
||||
return std::chrono::duration<double, std::milli>(Clock::now() - since).count();
|
||||
}
|
||||
|
||||
/// Drives a remote stream socket: send everything, read until `expect_in` bytes arrived.
|
||||
bool RemotePump(StreamSocket& s, const std::vector<u8>& out, std::vector<u8>& 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<TestCodec>());
|
||||
remote_ = MakeTransport(std::make_unique<TestCodec>());
|
||||
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<s32>(r.value);
|
||||
}
|
||||
|
||||
static inline std::shared_ptr<Transport> local_;
|
||||
static inline std::shared_ptr<Transport> remote_;
|
||||
};
|
||||
|
||||
TEST_F(NetP2PGuest, EpollInfiniteWaitWakesOnDatagram) {
|
||||
const s32 sock = NewP2PSocket(false);
|
||||
ASSERT_EQ(P2PSocketBind(sock, 3001).error, Error::Ok);
|
||||
const s32 ep = static_cast<s32>(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<s32>(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<sockaddr*>(&addr), sizeof(addr)).error,
|
||||
Error::Ok);
|
||||
socklen_t len = sizeof(addr);
|
||||
SocketGetName(native, reinterpret_cast<sockaddr*>(&addr), &len);
|
||||
const s32 p2p = NewP2PSocket(false);
|
||||
P2PSocketBind(p2p, 3002);
|
||||
const s32 ep = static_cast<s32>(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<sockaddr*>(&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<s32>(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<s32>(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<s32>(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<u32>(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<s32>(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<StreamSocket> 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<s64>(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<s32>(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<u8> 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<double> {};
|
||||
|
||||
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<u8> guest_got;
|
||||
std::atomic<bool> 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<s32>(a.value);
|
||||
std::thread writer([&] {
|
||||
EXPECT_EQ(P2PSocketSendTo(conn, guest_data.data(), guest_data.size(), false, nullptr, 0)
|
||||
.value,
|
||||
static_cast<s64>(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<u8> 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<Keyring>();
|
||||
auto keys_b = std::make_shared<Keyring>();
|
||||
const auto a = MakeTransport(std::make_unique<FramingCodec>(keys_a));
|
||||
const auto b = MakeTransport(std::make_unique<FramingCodec>(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<StreamSocket> 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<u8> got;
|
||||
std::vector<u8> 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<Keyring>();
|
||||
keys_c->SetCommunicationId(CommunicationId{'O', 'T', 'H', 'R'});
|
||||
const auto c = MakeTransport(std::make_unique<FramingCodec>(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
|
||||
@@ -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 <cstdlib>
|
||||
#include <deque>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <queue>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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<u8> RandomBytes(size_t n, u64 seed) {
|
||||
std::mt19937_64 rng(seed);
|
||||
std::vector<u8> v(n);
|
||||
for (auto& b : v) {
|
||||
b = static_cast<u8>(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<u8>(parsed->payload.begin(), parsed->payload.end()),
|
||||
std::vector<u8>(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<u8> 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<u8> 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<u8>(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<const u8> 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<double>(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<double> 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<Clock::time_point> NextDelivery() const {
|
||||
return wire_.empty() ? std::nullopt : std::optional{wire_.top().deliver_at};
|
||||
}
|
||||
|
||||
/// Pops the next packet due by `now`.
|
||||
std::optional<std::pair<int, std::vector<u8>>> 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<u8> 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<Packet, std::vector<Packet>, std::greater<>> wire_;
|
||||
u64 order_ = 0;
|
||||
std::deque<std::string> log_;
|
||||
};
|
||||
|
||||
struct TransferResult {
|
||||
std::vector<u8> 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<TcpConnection> 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<const u8> 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<u8>& out, size_t& off, std::vector<u8>& 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<Clock::time_point> next;
|
||||
const auto consider = [&](std::optional<Clock::time_point> 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<const u8> 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<Scenario> {};
|
||||
|
||||
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<const u8>) { ++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<double>(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<const u8>) {});
|
||||
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
|
||||
@@ -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 <gtest/gtest.h>
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_tun.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
|
||||
#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<u8> RandomBytes(size_t n, u32 seed) {
|
||||
std::mt19937 rng(seed);
|
||||
std::vector<u8> v(n);
|
||||
for (auto& b : v) {
|
||||
b = static_cast<u8>(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<u16>(~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::pair<u16, u16>, std::unique_ptr<TcpConnection>> connections;
|
||||
std::map<u16, bool> listeners;
|
||||
|
||||
TcpConnection& Connect(u16 local_port, u16 remote_port) {
|
||||
auto& c = connections[{local_port, remote_port}];
|
||||
c = std::make_unique<TcpConnection>(config_, pseudo_, local_port, remote_port, NextIss(),
|
||||
[this](std::span<const u8> 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<bool()>& 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::chrono::milliseconds>(
|
||||
std::max<Clock::duration>(next - Clock::now(), 0ms));
|
||||
pollfd pfd{tun_, POLLIN, 0};
|
||||
poll(&pfd, 1, static_cast<int>(wait.count()));
|
||||
for (;;) {
|
||||
const auto n = read(tun_, buf, sizeof(buf));
|
||||
if (n <= 0) {
|
||||
break;
|
||||
}
|
||||
OnPacket({buf, static_cast<size_t>(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<double>(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<const u8> segment) {
|
||||
if (Drop()) {
|
||||
return;
|
||||
}
|
||||
std::vector<u8> packet(20 + segment.size());
|
||||
packet[0] = 0x45;
|
||||
packet[2] = static_cast<u8>(packet.size() >> 8);
|
||||
packet[3] = static_cast<u8>(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<u8>(checksum >> 8);
|
||||
packet[11] = static_cast<u8>(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<const u8> 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<TcpConnection>(config_, pseudo_, seg->dst_port, seg->src_port,
|
||||
NextIss(),
|
||||
[this](std::span<const u8> 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<sockaddr_in*>(&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<TunHost>(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<bool> kernel_done{false};
|
||||
std::vector<u8> kernel_got;
|
||||
std::string kernel_error;
|
||||
|
||||
std::thread kernel([&] {
|
||||
const int s = KernelSocket();
|
||||
const auto addr = Address(OurIp, 7000);
|
||||
if (connect(s, reinterpret_cast<const sockaddr*>(&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<size_t>(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<u8> 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<const sockaddr*>(&addr), sizeof(addr)), 0)
|
||||
<< std::strerror(errno);
|
||||
listen(ls, 4);
|
||||
|
||||
std::atomic<bool> 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<u8> 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<TunHost> 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<int> result{0};
|
||||
std::atomic<bool> done{false};
|
||||
std::thread kernel([&] {
|
||||
const int s = KernelSocket();
|
||||
const auto addr = Address(OurIp, 7010);
|
||||
result =
|
||||
connect(s, reinterpret_cast<const sockaddr*>(&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
|
||||
@@ -0,0 +1,714 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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<OrbisNetSockaddr*>(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<unsigned>(value));
|
||||
return buf;
|
||||
}
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
virtual ~Logger() = default;
|
||||
virtual void LogMessage(const char* fmt, u64 value) = 0;
|
||||
|
||||
std::vector<std::string> Sent() {
|
||||
std::scoped_lock lock{mutex_};
|
||||
return sent_;
|
||||
}
|
||||
std::vector<std::string> Received() {
|
||||
std::scoped_lock lock{mutex_};
|
||||
return received_;
|
||||
}
|
||||
std::vector<std::string> 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<u32>(value)));
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::vector<std::string> sent_;
|
||||
std::vector<std::string> received_;
|
||||
std::vector<std::string> 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<bool> 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<size_t>(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<size_t>(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<size_t>(result);
|
||||
}
|
||||
EXPECT_EQ(sceNetEpollDestroy(log_epoll), ORBIS_OK);
|
||||
EXPECT_EQ(sceNetSocketClose(stream_sock), ORBIS_OK);
|
||||
}
|
||||
|
||||
const bool async_;
|
||||
std::mutex log_mutex_;
|
||||
std::atomic<bool> ready_{false};
|
||||
std::thread server_;
|
||||
std::vector<std::thread> clients_;
|
||||
};
|
||||
|
||||
std::atomic<bool> g_server_ready{false};
|
||||
std::atomic<bool> 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<u32>(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<u32>(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<u64>(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<u32>(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<u32>(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<u32>(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<u32>(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 <id>\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 <id>\n",
|
||||
"Client: inet_pton(ORBIS_NET_AF_INET) returns 0x00000001\n",
|
||||
"Client: connect returns 0x00000000\n",
|
||||
"Server: accept returns <id>\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 <id>\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<s32>(std::stoul(line.substr(at + 2), nullptr, 16));
|
||||
EXPECT_GT(value, 0) << line;
|
||||
return line.substr(0, at) + "<id>\n";
|
||||
}
|
||||
|
||||
/// The lines of one thread ("Server:" or "Client:"), in order.
|
||||
std::vector<std::string> Thread(const std::vector<std::string>& lines, const char* prefix) {
|
||||
std::vector<std::string> 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<std::string> 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<std::string> 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);
|
||||
}
|
||||
}
|
||||
@@ -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 <mutex>
|
||||
|
||||
#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<int>(index);
|
||||
}
|
||||
}
|
||||
m_files.push_back(file);
|
||||
return static_cast<int>(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<size_t>(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
|
||||
@@ -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 <chrono>
|
||||
#include <cstdlib>
|
||||
#include <thread>
|
||||
|
||||
#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<u64>(std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
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<PthreadT>(t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return) {
|
||||
auto* t = reinterpret_cast<std::thread*>(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
|
||||
Reference in New Issue
Block a user