From 5f0f72bfda15be0c13c39ffd60df139f80b053ec Mon Sep 17 00:00:00 2001 From: libretroadmin Date: Thu, 8 Oct 2026 17:00:34 +0200 Subject: [PATCH] Remove sleeps and timed polling from networking Socket waits now block in select() on the socket plus a wake or cancel socket (Common/Net/Cancel), so a stop or cancel ends them at once. - HTTP: cancel flags become net::CancelToken; Connect waits once on all addresses, receives wait instead of spinning, no DNS retry sleep. - Adhoc: the friend finder and adhoc server block on their sockets with a wake socket and a deadline; matching runs on a CoreTiming tick instead of two threads (savestate section bumped). - sceNetInet select/poll check without blocking and park the HLE thread, re-checked from a CoreTiming event until ready or the emulated timeout. - Vendor the aemu_postoffice client (was a submodule) with no sleeps: waits on the socket, close via shutdown and an eventcount, cancelable connects, and lock-free session slot claims. - PortManager waits without a timeout; PSP_InitWait replaces the headless boot poll. - TimeUtil: sleep_us/sleep_ms only outside the core; sleep_precise, sleep_random, yield and the Windows timer period are gone. - Unit test for the socket waits. --- .gitmodules | 4 - CMakeLists.txt | 1 + Common/CMakeLists.txt | 2 + Common/File/PathBrowser.cpp | 26 +- Common/File/PathBrowser.h | 6 +- Common/GhidraClient.cpp | 5 +- Common/Net/Cancel.cpp | 141 +++ Common/Net/Cancel.h | 60 + Common/Net/HTTPClient.cpp | 104 +- Common/Net/HTTPClient.h | 6 +- Common/Net/HTTPNaettRequest.cpp | 4 +- Common/Net/HTTPRequest.cpp | 6 +- Common/Net/HTTPRequest.h | 9 +- Common/Net/NetBuffer.cpp | 35 +- Common/Net/NetBuffer.h | 7 +- Common/Net/Resolve.cpp | 1 - Common/TimeUtil.cpp | 109 +- Common/TimeUtil.h | 16 +- Core/FileLoaders/HTTPFileLoader.cpp | 1 - Core/FileLoaders/HTTPFileLoader.h | 5 +- Core/HLE/proAdhoc.cpp | 84 +- Core/HLE/proAdhoc.h | 20 +- Core/HLE/proAdhocServer.cpp | 47 +- Core/HLE/proAdhocServer.h | 5 + Core/HLE/sceHttp.cpp | 15 +- Core/HLE/sceHttp.h | 4 +- Core/HLE/sceNet.cpp | 5 +- Core/HLE/sceNetAdhoc.cpp | 40 +- Core/HLE/sceNetAdhocMatching.cpp | 725 +++++------ Core/HLE/sceNetAdhocMatching.h | 2 + Core/HLE/sceNetInet.cpp | 193 ++- Core/HLE/sceNetInet.h | 3 + Core/HLE/sceNp2.cpp | 3 +- Core/System.cpp | 7 + Core/System.h | 2 + Core/Util/PortManager.cpp | 19 +- Core/WebServer.cpp | 3 +- UI/RemoteISOScreen.cpp | 32 +- ext/CMakeLists.txt | 15 +- ext/aemu_postoffice | 1 - ext/aemu_postoffice_client/LICENSE.txt | 674 ++++++++++ ext/aemu_postoffice_client/README.md | 14 + .../aemu_postoffice_packets.h | 53 + ext/aemu_postoffice_client/log_impl.h | 19 + .../log_impl_ppsspp.cpp | 27 + ext/aemu_postoffice_client/mutex_impl.h | 16 + ext/aemu_postoffice_client/mutex_impl_cpp.cpp | 15 + ext/aemu_postoffice_client/postoffice.c | 1115 +++++++++++++++++ .../postoffice_client.h | 81 ++ ext/aemu_postoffice_client/postoffice_mem.h | 70 ++ .../postoffice_mem_stdc.c | 18 + ext/aemu_postoffice_client/sock_impl.h | 48 + ext/aemu_postoffice_client/sock_impl_linux.c | 285 +++++ .../sock_impl_windows.c | 278 ++++ headless/Headless.cpp | 4 +- libretro/Makefile.common | 14 +- unittest/TestNetWait.cpp | 79 ++ unittest/UnitTest.cpp | 2 + 58 files changed, 3822 insertions(+), 763 deletions(-) create mode 100644 Common/Net/Cancel.cpp create mode 100644 Common/Net/Cancel.h delete mode 160000 ext/aemu_postoffice create mode 100644 ext/aemu_postoffice_client/LICENSE.txt create mode 100644 ext/aemu_postoffice_client/README.md create mode 100644 ext/aemu_postoffice_client/aemu_postoffice_packets.h create mode 100644 ext/aemu_postoffice_client/log_impl.h create mode 100644 ext/aemu_postoffice_client/log_impl_ppsspp.cpp create mode 100644 ext/aemu_postoffice_client/mutex_impl.h create mode 100644 ext/aemu_postoffice_client/mutex_impl_cpp.cpp create mode 100644 ext/aemu_postoffice_client/postoffice.c create mode 100644 ext/aemu_postoffice_client/postoffice_client.h create mode 100644 ext/aemu_postoffice_client/postoffice_mem.h create mode 100644 ext/aemu_postoffice_client/postoffice_mem_stdc.c create mode 100644 ext/aemu_postoffice_client/sock_impl.h create mode 100644 ext/aemu_postoffice_client/sock_impl_linux.c create mode 100644 ext/aemu_postoffice_client/sock_impl_windows.c create mode 100644 unittest/TestNetWait.cpp diff --git a/.gitmodules b/.gitmodules index 8759dedce4..09b3ffdf10 100644 --- a/.gitmodules +++ b/.gitmodules @@ -53,10 +53,6 @@ [submodule "ext/nanosvg"] path = ext/nanosvg url = https://github.com/hrydgard/nanosvg.git -[submodule "ext/aemu_postoffice"] - path = ext/aemu_postoffice - url = https://github.com/Kethen/aemu_postoffice.git - branch = ppsspp [submodule "frametests"] path = frametests url = https://github.com/hrydgard/ppsspp-frametest-ci.git diff --git a/CMakeLists.txt b/CMakeLists.txt index dc240c732e..721b240148 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1155,6 +1155,7 @@ if(UNITTEST) unittest/TestLoongArch64Emitter.cpp unittest/TestSoftwareGPUJit.cpp unittest/TestThreadManager.cpp + unittest/TestNetWait.cpp unittest/JitHarness.cpp Core/MIPS/ARM/ArmRegCache.cpp Core/MIPS/ARM/ArmRegCacheFPU.cpp diff --git a/Common/CMakeLists.txt b/Common/CMakeLists.txt index f30b8197cf..91a0d68374 100644 --- a/Common/CMakeLists.txt +++ b/Common/CMakeLists.txt @@ -243,6 +243,8 @@ add_library(Common STATIC Net/HTTPNaettRequest.h Net/HTTPRequest.cpp Net/HTTPRequest.h + Net/Cancel.cpp + Net/Cancel.h Net/NetBuffer.cpp Net/NetBuffer.h Net/Resolve.cpp diff --git a/Common/File/PathBrowser.cpp b/Common/File/PathBrowser.cpp index 2928705514..16a4b31d7c 100644 --- a/Common/File/PathBrowser.cpp +++ b/Common/File/PathBrowser.cpp @@ -18,7 +18,7 @@ #include "android/jni/app-android.h" #endif -static bool LoadRemoteFileList(const Path &url, std::string_view userAgent, bool *cancel, std::vector &files) { +static bool LoadRemoteFileList(const Path &url, std::string_view userAgent, const net::CancelToken *cancel, std::vector &files) { _dbg_assert_(url.Type() == PathType::HTTP); http::Client http(nullptr); @@ -43,7 +43,7 @@ static bool LoadRemoteFileList(const Path &url, std::string_view userAgent, bool } } - if (code != 200 || (cancel && *cancel)) { + if (code != 200 || (cancel && cancel->IsCancelled())) { return false; } @@ -113,7 +113,7 @@ static bool LoadRemoteFileList(const Path &url, std::string_view userAgent, bool PathBrowser::~PathBrowser() { { std::unique_lock guard(pendingLock_); - pendingCancel_ = true; + CancelPending(); pendingStop_ = true; pendingCond_.notify_all(); } @@ -122,6 +122,13 @@ PathBrowser::~PathBrowser() { } } +// Under pendingLock_. +void PathBrowser::CancelPending() { + pendingCancel_ = true; + if (loadCancel_) + loadCancel_->Cancel(); +} + void PathBrowser::SetPath(const Path &path) { std::lock_guard guard(pendingLock_); path_ = path; @@ -138,7 +145,7 @@ void PathBrowser::RestrictToRoot(const Path &root) { void PathBrowser::HandlePath() { if (!path_.empty() && path_.ToString()[0] == '!') { if (pendingActive_) { - pendingCancel_ = true; + CancelPending(); pendingPath_.clear(); } ready_ = true; @@ -173,10 +180,15 @@ void PathBrowser::HandlePath() { lastPath = pendingPath_; if (lastPath.Type() == PathType::HTTP) { std::string userAgentCopy = userAgent_; + std::shared_ptr cancel = std::make_shared(); + if (pendingCancel_) + cancel->Cancel(); + loadCancel_ = cancel; guard.unlock(); results.clear(); - bool tempSuccess = LoadRemoteFileList(lastPath, userAgentCopy, &pendingCancel_, results); + bool tempSuccess = LoadRemoteFileList(lastPath, userAgentCopy, cancel.get(), results); guard.lock(); + loadCancel_.reset(); success_ = tempSuccess; } else if (lastPath.empty()) { results.clear(); @@ -204,9 +216,9 @@ void PathBrowser::HandlePath() { }); } -bool PathBrowser::GetListing(std::vector &fileInfo, const char *extensionFilter, bool *cancel) { +bool PathBrowser::GetListing(std::vector &fileInfo, const char *extensionFilter, const net::CancelToken *cancel) { std::unique_lock guard(pendingLock_); - while (!ready_ && (!cancel || !*cancel)) { + while (!ready_ && (!cancel || !cancel->IsCancelled())) { // In case cancel changes, just sleep. TODO: Replace with condition variable. guard.unlock(); sleep_ms(50, "pathbrowser-poll"); diff --git a/Common/File/PathBrowser.h b/Common/File/PathBrowser.h index 0fdee76888..e80aa284bd 100644 --- a/Common/File/PathBrowser.h +++ b/Common/File/PathBrowser.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -10,6 +11,7 @@ #include "Common/File/DirListing.h" #include "Common/File/Path.h" +#include "Common/Net/Cancel.h" // Abstraction above path that lets you navigate easily and get listings off-thread. // "/" is a special path that means the root of the file system. On Windows, @@ -29,7 +31,7 @@ public: } // If called before IsListingReady() returns true, will block (becomes synchronous). Don't do that. - bool GetListing(std::vector &fileInfo, const char *filter = nullptr, bool *cancel = nullptr); + bool GetListing(std::vector &fileInfo, const char *filter = nullptr, const net::CancelToken *cancel = nullptr); bool CanNavigateUp() const; void NavigateUp(); @@ -55,6 +57,7 @@ public: private: void HandlePath(); + void CancelPending(); void ApplyRestriction(); Path path_; @@ -67,6 +70,7 @@ private: std::thread pendingThread_; bool pendingActive_ = false; bool pendingCancel_ = false; + std::shared_ptr loadCancel_; bool pendingStop_ = false; bool ready_ = false; bool success_ = true; diff --git a/Common/GhidraClient.cpp b/Common/GhidraClient.cpp index 41e7ae72ed..a8065e5617 100644 --- a/Common/GhidraClient.cpp +++ b/Common/GhidraClient.cpp @@ -200,12 +200,11 @@ bool GhidraClient::FetchResource(const std::string &path, std::string &outResult pendingResult_.error = "can't resolve host"; return false; } - bool cancelled = false; - if (!http.Connect(1, 5.0, &cancelled)) { + if (!http.Connect(1, 5.0)) { pendingResult_.error = "can't connect to host"; return false; } - net::RequestProgress progress(&cancelled); + net::RequestProgress progress(nullptr); Buffer result; const int code = http.GET(http::RequestParams(path.c_str()), &result, &progress); http.Disconnect(); diff --git a/Common/Net/Cancel.cpp b/Common/Net/Cancel.cpp new file mode 100644 index 0000000000..90fd1db7b7 --- /dev/null +++ b/Common/Net/Cancel.cpp @@ -0,0 +1,141 @@ +#include "ppsspp_config.h" + +// First, so Windows gets its socket error codes instead of the CRT's. +#include "Common/Net/SocketCompat.h" + +#include + +#include "Common/Net/Cancel.h" +#include "Common/File/FileDescriptor.h" +#include "Common/TimeUtil.h" + +namespace net { + +WakeSocket::WakeSocket() { + intptr_t s = (intptr_t)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s == (intptr_t)INVALID_SOCKET) + return; +#if !PPSSPP_PLATFORM(WINDOWS) + if (s >= FD_SETSIZE) { + closesocket(s); + return; + } +#endif + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + socklen_t len = sizeof(addr); + if (bind(s, (sockaddr *)&addr, sizeof(addr)) != 0 || + getsockname(s, (sockaddr *)&addr, &len) != 0 || + connect(s, (sockaddr *)&addr, sizeof(addr)) != 0) { + closesocket(s); + return; + } + fd_util::SetNonBlocking((int)s, true); + fd_ = s; +} + +WakeSocket::~WakeSocket() { + if (fd_ != -1) + closesocket(fd_); +} + +void WakeSocket::Wake() { + if (fd_ != -1) { + // Non-blocking: with the buffer full, a wake is already pending. + const char b = 1; + send(fd_, &b, 1, MSG_NOSIGNAL); + } +} + +void WakeSocket::Drain() { + if (fd_ != -1) { + char buf[64]; + while (recv(fd_, buf, sizeof(buf), 0) > 0) { + } + } +} + +void CancelToken::Cancel() { + cancelled_.store(true, std::memory_order_release); + // Never drained, so the socket stays readable from here on. + wake_.Wake(); +} + +static WaitResult WaitInternal(const uintptr_t *socks, bool *ready, int count, bool forWrite, double timeout, intptr_t wake, const CancelToken *cancel) { + const double deadline = timeout >= 0.0 ? time_now_d() + timeout : 0.0; + for (;;) { + if (cancel && cancel->IsCancelled()) + return WaitResult::CANCELLED; + + fd_set rfds, wfds; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + intptr_t maxfd = -1; + for (int i = 0; i < count; i++) { + if ((intptr_t)socks[i] == -1) + continue; + FD_SET((SOCKET)socks[i], forWrite ? &wfds : &rfds); + if ((intptr_t)socks[i] > maxfd) + maxfd = (intptr_t)socks[i]; + } + if (wake != -1) { + FD_SET((SOCKET)wake, &rfds); + if (wake > maxfd) + maxfd = wake; + } + + timeval tv{}; + timeval *ptv = nullptr; + if (timeout >= 0.0) { + double left = deadline - time_now_d(); + if (left < 0.0) + left = 0.0; + tv.tv_sec = (long)floor(left); + tv.tv_usec = (long)((left - floor(left)) * 1000000.0); + ptv = &tv; + } + if (maxfd == -1) { + // Only the timeout could end this, and that would be a sleep. + return WaitResult::FAILED; + } + + const int r = select((int)maxfd + 1, &rfds, forWrite ? &wfds : nullptr, nullptr, ptv); + if (r < 0) { + if (socket_errno == EINTR) + continue; + return WaitResult::FAILED; + } + if (cancel && cancel->IsCancelled()) + return WaitResult::CANCELLED; + if (r == 0) + return WaitResult::TIMEOUT; + + bool any = false; + for (int i = 0; i < count; i++) { + const bool set = (intptr_t)socks[i] != -1 && FD_ISSET((SOCKET)socks[i], forWrite ? &wfds : &rfds) != 0; + if (ready) + ready[i] = set; + any = any || set; + } + if (any) + return WaitResult::READY; + if (!cancel && wake != -1 && FD_ISSET((SOCKET)wake, &rfds)) + return WaitResult::CANCELLED; + } +} + +WaitResult WaitSockets(const uintptr_t *socks, bool *ready, int count, bool forWrite, double timeout, const CancelToken *cancel) { + return WaitInternal(socks, ready, count, forWrite, timeout, cancel ? cancel->WakeFd() : -1, cancel); +} + +WaitResult WaitSocket(uintptr_t sock, bool forWrite, double timeout, const CancelToken *cancel) { + return WaitSockets(&sock, nullptr, 1, forWrite, timeout, cancel); +} + +WaitResult WaitSocketsOrWake(const uintptr_t *socks, bool *ready, int count, bool forWrite, double timeout, WakeSocket *wake) { + return WaitInternal(socks, ready, count, forWrite, timeout, wake ? wake->Fd() : -1, nullptr); +} + +} // namespace net diff --git a/Common/Net/Cancel.h b/Common/Net/Cancel.h new file mode 100644 index 0000000000..c7f71f64c6 --- /dev/null +++ b/Common/Net/Cancel.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +namespace net { + +// A socket that another thread can make readable, to wake a select() that +// includes it. It is a loopback UDP socket connected to itself. If one can't +// be made, Fd() is -1 and waits only see a wake when they return anyway. +class WakeSocket { +public: + WakeSocket(); + ~WakeSocket(); + WakeSocket(const WakeSocket &) = delete; + WakeSocket &operator=(const WakeSocket &) = delete; + + // Any thread. Wakes stack up into one readable state until drained. + void Wake(); + // Called by the waiting thread after it wakes, before it looks at its state. + void Drain(); + intptr_t Fd() const { return fd_; } + +private: + intptr_t fd_ = -1; +}; + +// A cancel flag that socket waits block on. Cancel() sets the flag and +// wakes its socket for good, so every wait that includes it returns at once. +class CancelToken { +public: + // Any thread. + void Cancel(); + bool IsCancelled() const { return cancelled_.load(std::memory_order_acquire); } + intptr_t WakeFd() const { return wake_.Fd(); } + +private: + std::atomic cancelled_{ false }; + WakeSocket wake_; +}; + +enum class WaitResult { + READY, + TIMEOUT, + CANCELLED, // or, for the WakeSocket form, woken + FAILED, +}; + +// Blocks until sock is readable (or writable), cancel is set, or timeout +// seconds pass. A negative timeout waits for the first two only. +WaitResult WaitSocket(uintptr_t sock, bool forWrite, double timeout, const CancelToken *cancel); + +// The same over several sockets. ready[i] says which ones are ready on READY. +WaitResult WaitSockets(const uintptr_t *socks, bool *ready, int count, bool forWrite, double timeout, const CancelToken *cancel); + +// For event loops: also returns CANCELLED when wake is woken. Any of socks +// may be -1 (skipped); count may be 0, to wait only for a wake or the timeout. +WaitResult WaitSocketsOrWake(const uintptr_t *socks, bool *ready, int count, bool forWrite, double timeout, WakeSocket *wake); + +} // namespace net diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index 46d4454e15..d5701e9fda 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "Common/Net/HTTPClient.h" @@ -105,7 +106,7 @@ static void FormatAddr(char *addrbuf, size_t bufsize, const addrinfo *info) { } } -bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) { +bool Connection::Connect(int maxTries, double timeout, const net::CancelToken *cancel) { if (port_ <= 0) { ERROR_LOG(Log::Net, "Bad port"); return false; @@ -114,9 +115,6 @@ bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) { for (int tries = maxTries; tries > 0; --tries) { std::vector sockets; - fd_set fds; - int maxfd = 1; - FD_ZERO(&fds); for (addrinfo *possible = resolved_; possible != nullptr; possible = possible->ai_next) { if (possible->ai_family != AF_INET && possible->ai_family != AF_INET6) continue; @@ -156,68 +154,44 @@ bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) { } } sockets.push_back(sock); - FD_SET(sock, &fds); - if (maxfd < sock + 1) { - maxfd = sock + 1; - } } + if (cancel && cancel->IsCancelled()) { + for (uintptr_t sock : sockets) + closesocket(sock); + WARN_LOG(Log::Net, "connect: cancelled: %s:%d", host_.c_str(), port_); + break; + } if (sockets.empty()) { - // No need to call 'select' if we don't have any plausible sockets. - if (cancelConnect && *cancelConnect) { - WARN_LOG(Log::Net, "connect: cancelled (2): %s:%d", host_.c_str(), port_); - break; - } - sleep_ms(1, "connect"); + // No plausible address this time round; try the next one straight away. continue; } - // There is at least 1 socket candidate. - int selectResult = 0; - long timeoutHalfSeconds = floor(2 * timeout); - while (timeoutHalfSeconds >= 0 && selectResult == 0) { - struct timeval tv{}; - tv.tv_sec = 0; - if (timeoutHalfSeconds > 0) { - // Wait up to 0.5 seconds between cancel checks. - tv.tv_usec = 500000; - } else { - // Wait the remaining <= 0.5 seconds. Possibly 0, but that's okay. - tv.tv_usec = (timeout - floor(2 * timeout) / 2) * 1000000.0; - } - --timeoutHalfSeconds; - - selectResult = select(maxfd, nullptr, &fds, nullptr, &tv); - if (cancelConnect && *cancelConnect) { - WARN_LOG(Log::HTTP, "connect: cancelled (1): %s:%d", host_.c_str(), port_); - break; - } - } - if (selectResult > 0) { + // Wait for the first to connect, the timeout, or a cancel - no slicing, a cancel wakes the wait. + std::unique_ptr ready(new bool[sockets.size()]()); + const net::WaitResult wait = net::WaitSockets(sockets.data(), ready.get(), (int)sockets.size(), true, timeout, cancel); + if (wait == net::WaitResult::READY) { // Something connected. Pick the first one that did (if multiple.) - for (int sock : sockets) { - if ((intptr_t)sock_ == -1 && FD_ISSET(sock, &fds)) { - sock_ = sock; + for (size_t i = 0; i < sockets.size(); i++) { + if ((intptr_t)sock_ == -1 && ready[i]) { + sock_ = sockets[i]; } else { - closesocket(sock); + closesocket(sockets[i]); } } // Great, now we're good to go. return true; - } else { - // Fail. Close all the sockets. - for (int sock : sockets) { - closesocket(sock); - } } - if (cancelConnect && *cancelConnect) { - WARN_LOG(Log::Net, "connect: cancelled (2): %s:%d", host_.c_str(), port_); + // Fail. Close all the sockets. + for (uintptr_t sock : sockets) { + closesocket(sock); + } + if (wait == net::WaitResult::CANCELLED) { + WARN_LOG(Log::HTTP, "connect: cancelled: %s:%d", host_.c_str(), port_); break; } - - sleep_ms(1, "connect"); } // Nothing connected, unfortunately. @@ -386,7 +360,7 @@ int Client::SendRequestWithData(const char *method, const RequestParams &req, st req.acceptMime, otherHeaders ? otherHeaders : ""); buffer.Append(data); - bool flushed = buffer.FlushSocket(sock(), headerTimeout_, progress ? progress->cancelled : nullptr); + bool flushed = buffer.FlushSocket(sock(), headerTimeout_, progress ? progress->cancel : nullptr); if (!flushed) { return -1; // TODO error code. } @@ -395,18 +369,13 @@ int Client::SendRequestWithData(const char *method, const RequestParams &req, st int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector &responseHeaders, net::RequestProgress *progress, std::string *statusLine) { // Snarf all the data we can into RAM. A little unsafe but hey. - static constexpr float CANCEL_INTERVAL = 0.25f; - bool ready = false; - double endTimeout = time_now_d() + headerTimeout_; - while (!ready) { - if (progress && progress->cancelled && *progress->cancelled) - return -1; - ready = fd_util::WaitUntilReady(sock(), CANCEL_INTERVAL, false); - if (!ready && time_now_d() > endTimeout) { - ERROR_LOG(Log::HTTP, "HTTP headers timed out"); - return -1; - } - }; + const net::WaitResult wait = net::WaitSocket(sock(), false, headerTimeout_, progress ? progress->cancel : nullptr); + if (wait == net::WaitResult::TIMEOUT) { + ERROR_LOG(Log::HTTP, "HTTP headers timed out"); + return -1; + } else if (wait != net::WaitResult::READY) { + return -1; + } // Let's hope all the headers are available in a single packet... if (readbuf->Read(sock(), 4096) < 0) { ERROR_LOG(Log::HTTP, "Failed to read HTTP headers :("); @@ -457,7 +426,6 @@ int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector & } int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector &responseHeaders, Buffer *output, net::RequestProgress *progress) { - _dbg_assert_(progress->cancelled); bool gzip = false; bool chunked = false; @@ -531,7 +499,7 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector(); // In case someone managed to cancel us between construction and here. - sink_->cancelled = cancelled_; + sink_->cancelled = IsCancelled(); sink_->postData = postData_; std::vector options; diff --git a/Common/Net/HTTPRequest.cpp b/Common/Net/HTTPRequest.cpp index 6f260b099b..3d66b13672 100644 --- a/Common/Net/HTTPRequest.cpp +++ b/Common/Net/HTTPRequest.cpp @@ -10,8 +10,8 @@ namespace http { -Request::Request(RequestMethod method, std::string_view url, std::string_view name, const Path &outFile, bool *cancelled, RequestFlags flags) - : method_(method), url_(url), name_(name), outfile_(outFile), progress_(cancelled), flags_(flags) { +Request::Request(RequestMethod method, std::string_view url, std::string_view name, const Path &outFile, RequestFlags flags) + : method_(method), url_(url), name_(name), outfile_(outFile), progress_(&cancel_), flags_(flags) { INFO_LOG(Log::HTTP, "HTTP %s request: %.*s (%.*s)", RequestMethodToString(method), (int)url.size(), url.data(), (int)name.size(), name.data()); progress_.callback = [this](int64_t bytes, int64_t contentLength, bool done) { @@ -98,7 +98,7 @@ std::shared_ptr RequestManager::StartDownload(std::string_view url, con if (File::ReadBinaryFileToString(cacheFile, &contents)) { INFO_LOG(Log::HTTP, "Returning cached file for %.*s: %s", STR_VIEW(url), cacheFile.c_str()); // All is well, but we've indented a bit much here. - std::shared_ptr dl(new CachedRequest(RequestMethod::GET, url, KeepAfterLast(url, '/'), nullptr, flags, contents)); + std::shared_ptr dl(new CachedRequest(RequestMethod::GET, url, KeepAfterLast(url, '/'), flags, contents)); newDownloads_.push_back(dl); dl->SetCallback(completionCallback); return dl; diff --git a/Common/Net/HTTPRequest.h b/Common/Net/HTTPRequest.h index 0cd8db3288..3485431e7f 100644 --- a/Common/Net/HTTPRequest.h +++ b/Common/Net/HTTPRequest.h @@ -31,7 +31,7 @@ using RequestCompletionCallback = std::function; // Abstract request. class Request { public: - Request(RequestMethod method, std::string_view url, std::string_view name, const Path &outFile, bool *cancelled, RequestFlags mode); + Request(RequestMethod method, std::string_view url, std::string_view name, const Path &outFile, RequestFlags mode); virtual ~Request() {} void SetAccept(const char *mime) { @@ -77,8 +77,8 @@ public: // Virtual so a backend can act on it. The HTTPS one has to tell naett, which is what actually // stops a transfer in progress - see HTTPSRequest::Cancel. - virtual void Cancel() { cancelled_ = true; } - bool IsCancelled() const { return cancelled_; } + virtual void Cancel() { cancel_.Cancel(); } + bool IsCancelled() const { return cancel_.IsCancelled(); } // If not downloading to a file, access this to get the result. Buffer &buffer() { return buffer_; } @@ -95,7 +95,8 @@ protected: std::string userAgent_; Path outfile_; Buffer buffer_; - bool cancelled_ = false; + // Cancel() also wakes this request's socket waits. + net::CancelToken cancel_; int resultCode_ = 0; bool hasRunCallback_ = false; std::vector responseHeaders_; diff --git a/Common/Net/NetBuffer.cpp b/Common/Net/NetBuffer.cpp index 24a649745a..92c9d87431 100644 --- a/Common/Net/NetBuffer.cpp +++ b/Common/Net/NetBuffer.cpp @@ -27,21 +27,16 @@ void RequestProgress::Update(int64_t downloaded, int64_t totalBytes, bool done) } } -bool Buffer::FlushSocket(uintptr_t sock, double timeout, bool *cancelled) { - static constexpr float CANCEL_INTERVAL = 0.25f; - +bool Buffer::FlushSocket(uintptr_t sock, double timeout, const CancelToken *cancel) { data_.iterate_blocks([&](const char *data, size_t size) { for (size_t pos = 0, end = size; pos < end; ) { - bool ready = false; - double endTimeout = time_now_d() + timeout; - while (!ready) { - if (cancelled && *cancelled) - return false; - ready = fd_util::WaitUntilReady(sock, CANCEL_INTERVAL, true); - if (!ready && time_now_d() > endTimeout) { - ERROR_LOG(Log::IO, "FlushSocket timed out"); - return false; - } + // The timeout applies to each send, not the whole flush. + const WaitResult wait = WaitSocket(sock, true, timeout, cancel); + if (wait == WaitResult::TIMEOUT) { + ERROR_LOG(Log::IO, "FlushSocket timed out"); + return false; + } else if (wait != WaitResult::READY) { + return false; } int sent = send(sock, &data[pos], end - pos, MSG_NOSIGNAL); // TODO: Do we need some retry logic here, instead of just giving up? @@ -59,7 +54,6 @@ bool Buffer::FlushSocket(uintptr_t sock, double timeout, bool *cancelled) { } bool Buffer::ReadAllWithProgress(int fd, int knownSize, RequestProgress *progress) { - static constexpr float CANCEL_INTERVAL = 0.25f; std::vector buf; // We're non-blocking and reading from an OS buffer, so try to read as much as we can at a time. if (knownSize >= 65536 * 16) { @@ -73,15 +67,10 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, RequestProgress *progres double st = time_now_d(); int total = 0; while (true) { - bool ready = false; - - // If we might need to cancel, check on a timer for it to be ready. - // After this, we'll block on reading so we do this while first if we have a cancel pointer. - while (!ready && progress && progress->cancelled) { - if (*progress->cancelled) - return false; - ready = fd_util::WaitUntilReady(fd, CANCEL_INTERVAL, false); - } + // Block until there's data, the end of the stream, or a cancel. + const WaitResult wait = WaitSocket(fd, false, -1.0, progress ? progress->cancel : nullptr); + if (wait != WaitResult::READY) + return false; int retval = recv(fd, &buf[0], buf.size(), MSG_NOSIGNAL); if (retval == 0) { diff --git a/Common/Net/NetBuffer.h b/Common/Net/NetBuffer.h index 7fe8dc6d5f..fef57ae7e6 100644 --- a/Common/Net/NetBuffer.h +++ b/Common/Net/NetBuffer.h @@ -4,24 +4,25 @@ #include #include "Common/Buffer.h" +#include "Common/Net/Cancel.h" namespace net { class RequestProgress { public: - explicit RequestProgress(bool *c) : cancelled(c) {} + explicit RequestProgress(const CancelToken *c) : cancel(c) {} void Update(int64_t downloaded, int64_t totalBytes, bool done); float progress = 0.0f; float kBps = 0.0f; - bool *cancelled = nullptr; + const CancelToken *cancel = nullptr; std::function callback; }; class Buffer : public ::Buffer { public: - bool FlushSocket(uintptr_t sock, double timeout, bool *cancelled = nullptr); + bool FlushSocket(uintptr_t sock, double timeout, const CancelToken *cancel = nullptr); // If you know the size of the file to read, pass it in knownSize, for best performance and for progress reporting. bool ReadAllWithProgress(int fd, int knownSize, RequestProgress *progress); diff --git a/Common/Net/Resolve.cpp b/Common/Net/Resolve.cpp index 60321e2420..efe70e02b4 100644 --- a/Common/Net/Resolve.cpp +++ b/Common/Net/Resolve.cpp @@ -251,7 +251,6 @@ bool DNSResolve(const std::string &host, const std::string &service, addrinfo ** int result = getaddrinfo(host.c_str(), servicep, &hints, res); if (result == EAI_AGAIN) { // Temporary failure. Since this already blocks, let's just try once more. - sleep_ms(1, "dns-resolve-poll"); result = getaddrinfo(host.c_str(), servicep, &hints, res); } diff --git a/Common/TimeUtil.cpp b/Common/TimeUtil.cpp index b508a30c92..0a3bfc2561 100644 --- a/Common/TimeUtil.cpp +++ b/Common/TimeUtil.cpp @@ -4,7 +4,6 @@ #include #include "Common/TimeUtil.h" -#include "Common/Data/Random/Rng.h" #include "Common/Log.h" #ifdef HAVE_LIBNX @@ -17,18 +16,12 @@ #ifdef _WIN32 #include "CommonWindows.h" -#include #include #else #include #include #endif -// for _mm_pause -#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) -#include -#endif - #include // TODO: https://github.com/floooh/sokol/blob/9a6237fcdf213e6da48e4f9201f144bcb2dcb46f/sokol_time.h#L229-L248 @@ -47,10 +40,6 @@ static double frequencyMult; static LARGE_INTEGER startTime; static LARGE_INTEGER startFileTime; -HANDLE Timer; -int SchedulerPeriodMs = 10; -INT64 QpcPerSecond; - void TimeInit() { FILETIME ft; GetSystemTimeAsFileTime(&ft); //returns ticks in UTC @@ -60,25 +49,11 @@ void TimeInit() { QueryPerformanceFrequency(&frequency); QueryPerformanceCounter(&startTime); - QpcPerSecond = frequency.QuadPart; frequencyMult = 1.0 / frequency.QuadPart; - - // The timer will be automatically deleted on process destruction. Don't need to CloseHandle. - Timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); - - // TODO: We probably don't need this anymore if we are using the high res waitable timers? -#if !PPSSPP_PLATFORM(UWP) - TIMECAPS caps; - timeGetDevCaps(&caps, sizeof caps); - timeBeginPeriod(caps.wPeriodMin); - SchedulerPeriodMs = (int)caps.wPeriodMin; -#endif + // No timeBeginPeriod: nothing sleeps, so the process's timer resolution is left alone. } void TimeShutdown() { -#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) - timeEndPeriod(1); -#endif } double time_now_d() { @@ -123,10 +98,6 @@ double time_to_unix_utc(double timestamp) { return (double)(li.QuadPart - UNIX_TIME_START + static_cast(timestamp * TICKS_PER_SECOND)) / TICKS_PER_SECOND; } -void yield() { - YieldProcessor(); -} - Instant::Instant() { _dbg_assert_(frequencyMult != 0.0); QueryPerformanceCounter(reinterpret_cast(&nativeStart_)); @@ -184,15 +155,6 @@ double time_now_unix_utc() { return (double)tp.tv_sec + (double)tp.tv_nsec / 1000000000.0; } -void yield() { -#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) - _mm_pause(); -#elif PPSSPP_ARCH(ARM64) - // Took this out for now. See issue #17877 - // __builtin_arm_isb(15); -#endif -} - Instant::Instant() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); @@ -254,8 +216,6 @@ double from_time_raw_relative(uint64_t raw_time) { return from_time_raw(raw_time); } -void yield() {} - double time_now_unix_utc() { // Not time_now_raw() - that's a monotonic clock with no relation to the epoch. struct timeval tv; @@ -297,8 +257,8 @@ double Instant::ElapsedSeconds() const { #endif -#define SLEEP_LOG_ENABLED 0 - +#ifndef __LIBRETRO__ +// The headless tools and tests only; see TimeUtil.h. void sleep_ms(int ms, const char *reason) { if (ms <= 0) { return; @@ -334,63 +294,7 @@ void sleep_us(int us, const char *reason) { usleep(us); #endif } - -// This can be a little more expensive in some circumstances, so only use when necessary. -void sleep_precise(double seconds, const char *reason) { - if (seconds <= 0.0) { - return; - } -#if SLEEP_LOG_ENABLED - INFO_LOG(Log::System, "Sleep precise %f s: %s", seconds, reason); #endif -#ifdef _WIN32 - // Precise Windows sleep function from: https://github.com/blat-blatnik/Snippets/blob/main/precise_sleep.c - // Described in: https://blog.bearcats.nl/perfect-sleep-function/ - LARGE_INTEGER qpc; - QueryPerformanceCounter(&qpc); - INT64 targetQpc = (INT64)(qpc.QuadPart + seconds * QpcPerSecond); - - if (Timer) { // Try using a high resolution timer first. - const double TOLERANCE = 0.001'02; - INT64 maxTicks = (INT64)SchedulerPeriodMs * 9'500; - for (;;) // Break sleep up into parts that are lower than scheduler period. - { - double remainingSeconds = (targetQpc - qpc.QuadPart) / (double)QpcPerSecond; - INT64 sleepTicks = (INT64)((remainingSeconds - TOLERANCE) * 10'000'000); - if (sleepTicks <= 0) - break; - LARGE_INTEGER due; - due.QuadPart = -(sleepTicks > maxTicks ? maxTicks : sleepTicks); - // Note: SetWaitableTimerEx is not available on Vista. - if (!SetWaitableTimer(Timer, &due, 0, NULL, NULL, FALSE)) { - _dbg_assert_(false); - break; - } - WaitForSingleObject(Timer, INFINITE); - QueryPerformanceCounter(&qpc); - } - } else { // Fallback to Sleep. - const double TOLERANCE = 0.000'02; - double sleepMs = (seconds - TOLERANCE) * 1000 - SchedulerPeriodMs; // Sleep for 1 scheduler period less than requested. - int sleepSlices = (int)(sleepMs / SchedulerPeriodMs); - if (sleepSlices > 0) - Sleep((DWORD)sleepSlices * SchedulerPeriodMs); - QueryPerformanceCounter(&qpc); - } - while (qpc.QuadPart < targetQpc) // Spin for any remaining time. - { - YieldProcessor(); - QueryPerformanceCounter(&qpc); - } - // On other platforms, we just do a conversion with more input precision than in sleep_ms which is restricted to whole milliseconds. -#elif defined(HAVE_LIBNX) - svcSleepThread((int64_t)(seconds * 1000000000.0)); -#elif defined(__EMSCRIPTEN__) - emscripten_sleep(seconds * 1000.0); -#else - usleep(seconds * 1000000.0); -#endif -} // Return the current time formatted as Minutes:Seconds:Milliseconds // in the form 00:00:000. @@ -408,10 +312,3 @@ void GetCurrentTimeFormatted(char formattedTime[13]) { #endif } -// We don't even bother synchronizing this, it's fine if threads stomp a bit. -static GMRng g_sleepRandom; - -void sleep_random(double minSeconds, double maxSeconds, const char *reason) { - const double waitSeconds = minSeconds + (maxSeconds - minSeconds) * g_sleepRandom.F(); - sleep_precise(waitSeconds, reason); -} diff --git a/Common/TimeUtil.h b/Common/TimeUtil.h index 95420e6e7f..32838fec5a 100644 --- a/Common/TimeUtil.h +++ b/Common/TimeUtil.h @@ -19,20 +19,12 @@ double from_time_raw_relative(uint64_t raw_time); double time_now_unix_utc(); double time_to_unix_utc(double timeNowSeconds); -// Sleep for milliseconds. Does not necessarily have millisecond granularity, especially on Windows. -// Requires a "reason" since sleeping generally should be very sparingly used. This -// can be logged if desired to figure out where we're wasting time. +#ifndef __LIBRETRO__ +// Sleeps, for the standalone frontends, headless tools and tests. The core never sleeps and +// doesn't have this, so a sleep can't creep back into it. void sleep_ms(int ms, const char *reason); -// Sleep for microseconds. Does not necessarily have microsecond granularity, especially on Windows. void sleep_us(int us, const char *reason); -// Precise sleep. Can consume a little bit of CPU on Windows at least. -void sleep_precise(double seconds, const char *reason); - -// Random sleep, used for debugging. -void sleep_random(double minSeconds, double maxSeconds, const char *reason); - -// Yield. Signals that this thread is busy-waiting but wants to allow other hyperthreads to run. -void yield(); +#endif void GetCurrentTimeFormatted(char formattedTime[13]); diff --git a/Core/FileLoaders/HTTPFileLoader.cpp b/Core/FileLoaders/HTTPFileLoader.cpp index 8830994ee6..882e500826 100644 --- a/Core/FileLoaders/HTTPFileLoader.cpp +++ b/Core/FileLoaders/HTTPFileLoader.cpp @@ -265,7 +265,6 @@ size_t HTTPFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags f void HTTPFileLoader::Connect(double timeout) { if (!connected_) { - cancel_ = false; connected_ = client_.Connect(3, timeout, &cancel_); } } diff --git a/Core/FileLoaders/HTTPFileLoader.h b/Core/FileLoaders/HTTPFileLoader.h index 303271467d..1be05f5fd1 100644 --- a/Core/FileLoaders/HTTPFileLoader.h +++ b/Core/FileLoaders/HTTPFileLoader.h @@ -21,6 +21,7 @@ #include #include "Common/File/Path.h" +#include "Common/Net/Cancel.h" #include "Common/Net/HTTPClient.h" #include "Common/Net/Resolve.h" #include "Common/Net/URL.h" @@ -47,7 +48,7 @@ public: size_t ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags = Flags::NONE) override; void Cancel() override { - cancel_ = true; + cancel_.Cancel(); } std::string LatestError() const override { @@ -74,7 +75,7 @@ private: net::RequestProgress progress_; ::Path filename_; bool connected_ = false; - bool cancel_ = false; + net::CancelToken cancel_; const char *latestError_ = ""; std::once_flag preparedFlag_; diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 1ed893dda8..ea75127114 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -54,10 +54,11 @@ #include "Core/HLE/sceNetAdhoc.h" #include "Core/Instance.h" #include "proAdhoc.h" +#include "Common/Net/Cancel.h" #include "Core/HLE/NetAdhocCommon.h" -#include "ext/aemu_postoffice/client/postoffice_client.h" +#include "ext/aemu_postoffice_client/postoffice_client.h" #ifdef _WIN32 #undef errno @@ -110,6 +111,32 @@ std::atomic metasocket((int)INVALID_SOCKET); SceNetAdhocctlParameter parameter; SceNetAdhocctlAdhocId product_code; std::thread friendFinderThread; +// Wakes the friend finder's wait when the game side changes something it acts on. +// Made once before the first start and kept, so a wake never races a free. +static net::WakeSocket *friendFinderWake; + +void FriendFinderPrepare() { + if (!friendFinderWake) + friendFinderWake = new net::WakeSocket(); +} + +void FriendFinderWake() { + net::WakeSocket *wake = friendFinderWake; + if (wake) + wake->Wake(); +} + +void JoinPostofficeConnect(AdhocSocket *sock, bool cancel) { + if (sock->connectThread != NULL) { + if (cancel && sock->connectCancel != NULL) + sock->connectCancel->Cancel(); + sock->connectThread->join(); + delete sock->connectThread; + sock->connectThread = NULL; + } + delete sock->connectCancel; + sock->connectCancel = NULL; +} std::recursive_mutex peerlock; AdhocSocket* adhocSockets[MAX_SOCKET]; bool isOriPort = false; @@ -435,10 +462,7 @@ void deleteAllAdhocSockets() { if (serverHasRelay) { if (sock->type == SOCK_PTP) { // sync - if (sock->connectThread != NULL) { - sock->connectThread->join(); - delete sock->connectThread; - } + JoinPostofficeConnect(sock, true); void *socket = sock->postofficeHandle; if (socket != NULL) { if (sock->data.ptp.state == ADHOC_PTP_STATE_LISTEN) { @@ -1403,6 +1427,9 @@ int friendFinder() { // Finder Loop. The flag was set by whoever started us, and cleared to stop us. while (friendFinderRunning) { + // Bytes buffered before this pass handled a packet. + int rxQueued = 0; + // Acquire Network Lock //_acquireNetworkLock(); @@ -1491,6 +1518,7 @@ int friendFinder() { } // Handle Packets + rxQueued = rxpos; if (rxpos > 0) { // BSSID Packet if (rx[0] == OPCODE_CONNECT_BSSID) { @@ -1767,12 +1795,31 @@ int friendFinder() { } } } - // This delay time should be 100ms when there is an event otherwise 500ms ? - sleep_ms(10, "pro-adhoc-poll-2"); // Using 1ms for faster response just like AdhocServer? + // A pass handles one packet; another whole one may already be buffered. + if (rxpos > 0 && rxpos < rxQueued) + continue; - // Don't do anything if it's paused, otherwise the log will be flooded - while (Core_IsStepping() && coreState != CORE_POWERDOWN && friendFinderRunning) - sleep_ms(10, "pro-adhoc-paused-poll-2"); + // Wait for the server, the game side (FriendFinderWake), or the next ping or game mode deadline. + uintptr_t sock = (uintptr_t)(intptr_t)-1; + bool forWrite = false; + double timeout = -1.0; + if (g_adhocServerConnected && metasocket != (int)INVALID_SOCKET) { + sock = (uintptr_t)(intptr_t)metasocket; + const u64 t = (u64)(time_now_d() * 1000000.0); + s64 left = PSP_ADHOCCTL_PING_TIMEOUT - static_cast(t - lastping); + // A ping that is due but couldn't go out waits for the socket to take it. + forWrite = left <= 0; + if (isAdhocctlBusy && adhocctlState == ADHOCCTL_STATE_DISCONNECTED && adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE && netAdhocGameModeEntered) + left = std::min(left, (s64)netAdhocEnterGameModeTimeout - static_cast(t - adhocctlStartTime) + 1); + timeout = forWrite ? -1.0 : std::max(left, (s64)0) / 1000000.0; + } + const net::WaitResult wait = net::WaitSocketsOrWake(&sock, nullptr, 1, forWrite, timeout, friendFinderWake); + if (wait == net::WaitResult::FAILED && sock == (uintptr_t)(intptr_t)-1) { + ERROR_LOG(Log::sceNet, "FriendFinder: nothing to wait on (no wake socket), stopping"); + break; + } + if (friendFinderWake) + friendFinderWake->Drain(); } // Groups/Networks should be deallocated isn't? @@ -2248,9 +2295,17 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ u64 startTime = (u64)(time_now_d() * 1000000.0); bool done = false; while (!done) { - if (coreState == CORE_POWERDOWN) + if (coreState == CORE_POWERDOWN || !friendFinderRunning) return iResult; + // Writable, the timeout, or a wake to recheck the above. + const u64 waited = (u64)(time_now_d() * 1000000.0) - startTime; + uintptr_t sock = (uintptr_t)(intptr_t)metasocket; + const double left = waited < (u64)adhocDefaultTimeout ? (adhocDefaultTimeout - waited) / 1000000.0 : 0.0; + if (net::WaitSocketsOrWake(&sock, nullptr, 1, true, left, friendFinderWake) == net::WaitResult::CANCELLED) { + friendFinderWake->Drain(); + continue; + } done = (IsSocketReady((int)metasocket, false, true) > 0); if (done) { // Writable can also mean the attempt failed (refused, say). Then there's no point @@ -2266,14 +2321,17 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ socklen_t sinlen = sizeof(sin); memset(&sin, 0, sinlen); // Ensure that the connection really established or not, since "select" alone can't accurately detects it + const bool writable = done; done &= (getpeername((int)metasocket, (struct sockaddr*)&sin, &sinlen) != SOCKET_ERROR); + // Writable but not connected is a failed attempt, and would stay writable. + if (writable && !done) + break; u64 now = (u64)(time_now_d() * 1000000.0); - if (static_cast(now - startTime) > adhocDefaultTimeout) { + if (static_cast(now - startTime) >= adhocDefaultTimeout) { if (connectInProgress(errorcode)) errorcode = ETIMEDOUT; break; } - sleep_ms(10, "pro-adhoc-socket-poll"); } if (!done) { ERROR_LOG(Log::sceNet, "Socket error (%i) when connecting to AdhocServer [%s/%s:%u]", errorcode, g_Config.sProAdhocServer.c_str(), ip2str(g_adhocServerIP.in.sin_addr).c_str(), ntohs(g_adhocServerIP.in.sin_port)); diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 969ba11afe..c9bfc7130b 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -39,6 +39,8 @@ #include "Core/HLE/sceKernelMutex.h" #include "Core/HLE/sceUtility.h" +namespace net { class CancelToken; } + #define IsMatch(buf1, buf2) (memcmp(&buf1, &buf2, sizeof(buf1)) == 0) // Server Listening Port @@ -331,10 +333,15 @@ typedef struct AdhocSocket { } data; void *postofficeHandle; // aemu_postoffice mode handle std::thread *connectThread; + // Cancelled before a join, so closing doesn't wait out a connect. + net::CancelToken *connectCancel; bool connectThreadDone; int connectThreadResult; } PACK AdhocSocket; +// Joins a postoffice socket's connect thread (cancelling it first if asked) and frees it. +void JoinPostofficeConnect(AdhocSocket *sock, bool cancel); + // Gamemode Optional Peer Buffer Data typedef struct SceNetAdhocGameModeOptData { u32_le size; @@ -496,16 +503,12 @@ typedef struct SceNetAdhocMatchingContext { //HLEHelperThread *matchingThread; int matching_thid; - // Event Caller Thread - std::thread eventThread; - //s32_le event_thid; + // Event and input loops, run by a CoreTiming tick on the emulator thread. bool eventRunning = false; bool IsMatchingInCB = false; - - // IO Handler Thread - std::thread inputThread; - //s32_le input_thid; bool inputRunning = false; + u64_le inputLastPing = 0; + u64_le inputLastHello = 0; // Event Caller Thread Message Stack std::recursive_mutex *eventlock; // s32_le event_stack_lock; @@ -772,6 +775,9 @@ extern std::atomic metasocket; extern SceNetAdhocctlParameter parameter; extern SceNetAdhocctlAdhocId product_code; extern std::thread friendFinderThread; +// Make the wake before starting the thread; wake it after changing what it acts on. +void FriendFinderPrepare(); +void FriendFinderWake(); extern std::recursive_mutex peerlock; extern AdhocSocket* adhocSockets[MAX_SOCKET]; diff --git a/Core/HLE/proAdhocServer.cpp b/Core/HLE/proAdhocServer.cpp index 6e5d53d5ce..e02422d310 100644 --- a/Core/HLE/proAdhocServer.cpp +++ b/Core/HLE/proAdhocServer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ #include "Common/File/FileUtil.h" #include "Common/TimeUtil.h" #include "Common/Net/Resolve.h" +#include "Common/Net/Cancel.h" #include "Core/Util/PortManager.h" #include "Core/Instance.h" #include "Core/Core.h" @@ -60,6 +62,19 @@ SceNetAdhocctlGameNode * _db_game = NULL; // Server Status std::atomic adhocServerRunning(false); std::thread adhocServerThread; +// Made once before the first start and kept, so a wake never races a free. +static net::WakeSocket *adhocServerWake; + +void AdhocServerPrepare() { + if (!adhocServerWake) + adhocServerWake = new net::WakeSocket(); +} + +void AdhocServerWake() { + net::WakeSocket *wake = adhocServerWake; + if (wake) + wake->Wake(); +} // Crosslink database for cross region Adhoc play std::vector crosslinks; @@ -2064,13 +2079,33 @@ int server_loop(int server) user = next; } - // Prevent needless CPU Overload (1ms Sleep) - // !!!! COMMENT NOT REFLECTING REALITY - sleep_ms(10, "pro-adhoc-poll"); + // A pass handles one packet per user; another whole one may already be buffered. + bool more = false; + std::vector socks; + socks.push_back((uintptr_t)(intptr_t)server); + time_t nextTimeout = 0; + const time_t now = time(NULL); + for (SceNetAdhocctlUserNode *u = _db_user; u != NULL; u = u->next) { + if (u->rxpos > 0 && u->rxpos != u->rxseen) + more = true; + u->rxseen = u->rxpos; + socks.push_back((uintptr_t)(intptr_t)u->stream); + const time_t left = u->last_recv + SERVER_USER_TIMEOUT - now; + if (nextTimeout == 0 || left < nextTimeout) + nextTimeout = left < 1 ? 1 : left; + } + if (more) + continue; - // Don't do anything if it's paused, otherwise the log will be flooded - while (adhocServerRunning && Core_IsStepping() && coreState != CORE_POWERDOWN) - sleep_ms(10, "pro-adhot-paused-poll"); + // Wait for a login, data from a user, the stop wake, or the first user timeout. + const double timeout = nextTimeout > 0 ? (double)nextTimeout : -1.0; + const net::WaitResult wait = net::WaitSocketsOrWake(socks.data(), nullptr, (int)socks.size(), false, timeout, adhocServerWake); + if (wait == net::WaitResult::FAILED) { + ERROR_LOG(Log::sceNet, "AdhocServer: wait failed, stopping"); + break; + } + if (adhocServerWake) + adhocServerWake->Drain(); } // Free User Database Memory diff --git a/Core/HLE/proAdhocServer.h b/Core/HLE/proAdhocServer.h index 63a2897652..7224c7fa24 100644 --- a/Core/HLE/proAdhocServer.h +++ b/Core/HLE/proAdhocServer.h @@ -220,6 +220,8 @@ typedef struct SceNetAdhocctlUserNode { // RX Buffer uint8_t rx[1024]; uint32_t rxpos; + // rxpos at the end of the last pass: a change means another packet may be buffered. + uint32_t rxseen; } SceNetAdhocctlUserNode; // Double-Linked Game List @@ -371,3 +373,6 @@ int proAdhocServerThread(int port); // (int argc, char * argv[]) //extern int _status; extern std::atomic adhocServerRunning; extern std::thread adhocServerThread; +// Make the wake before starting the thread; wake it after clearing adhocServerRunning. +void AdhocServerPrepare(); +void AdhocServerWake(); diff --git a/Core/HLE/sceHttp.cpp b/Core/HLE/sceHttp.cpp index cf6dda49db..9d15fa91da 100644 --- a/Core/HLE/sceHttp.cpp +++ b/Core/HLE/sceHttp.cpp @@ -92,7 +92,6 @@ HTTPRequest::HTTPRequest(int connectionID, int method, const char *url, u64 cont this->url = url ? url : ""; this->contentLength = contentLength; - //progress_.cancelled = &cancelled_; responseContent_.clear(); } @@ -121,7 +120,7 @@ int HTTPRequest::getResponseContentLength() { } int HTTPRequest::abortRequest() { - cancelled_ = true; + cancel_.Cancel(); // FIXME: Will sceHttpAbortRequest returns an error if the request was not sent yet? //if (progress_.progress == 0.0f) // return SCE_HTTP_ERROR_BEFORE_SEND; @@ -238,11 +237,11 @@ int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) { } // Establish Connection - if (!client.Connect(getResolveRetryCount(), getConnectTimeout() / 1000000.0, &cancelled_)) { + if (!client.Connect(getResolveRetryCount(), getConnectTimeout() / 1000000.0, &cancel_)) { ERROR_LOG(Log::sceNet, "Failed connecting to server or cancelled."); return -1; // SCE_HTTP_ERROR_ABORTED } - if (cancelled_) { + if (cancel_.IsCancelled()) { return SCE_HTTP_ERROR_ABORTED; } @@ -259,13 +258,13 @@ int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) { break; } net::Buffer buffer_; - net::RequestProgress progress_(&cancelled_); + net::RequestProgress progress_(&cancel_); http::RequestParams req(fileUrl.Resource(), "*/*"); const char* postData = Memory::GetCharPointer(postDataPtr); if (postDataSize > 0) NotifyMemInfo(MemBlockFlags::READ, postDataPtr, postDataSize, "HttpSendRequest"); int err = client.SendRequestWithData(methodstr.c_str(), req, std::string(postData ? postData : "", postData ? postDataSize : 0), extraHeaders.c_str(), &progress_); - if (cancelled_) { + if (cancel_.IsCancelled()) { return SCE_HTTP_ERROR_ABORTED; } if (err < 0) { @@ -274,7 +273,7 @@ int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) { // Retrieve Response's Status Code (and Headers too?) responseCode_ = client.ReadResponseHeaders(&buffer_, responseHeaders_, &progress_, &httpLine_); - if (cancelled_) { + if (cancel_.IsCancelled()) { return SCE_HTTP_ERROR_ABORTED; } @@ -285,7 +284,7 @@ int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) { ERROR_LOG(Log::sceNet, "Unable to read HTTP response entity: %d", res); } entity_.TakeAll(&responseContent_); - if (cancelled_) { + if (cancel_.IsCancelled()) { return SCE_HTTP_ERROR_ABORTED; } diff --git a/Core/HLE/sceHttp.h b/Core/HLE/sceHttp.h index 8c532bc2cc..4ba3ec2b18 100644 --- a/Core/HLE/sceHttp.h +++ b/Core/HLE/sceHttp.h @@ -257,12 +257,12 @@ private: u32 headerAddr_ = 0; u32 headerSize_ = 0; - bool cancelled_ = false; + // sceHttpAbortRequest sets it; it also wakes the socket waits. + net::CancelToken cancel_; int responseCode_ = -1; int entityLength_ = -1; http::Client client; - //net::RequestProgress progress_(&cancelled_); std::vector responseHeaders_; std::string httpLine_; std::string responseContent_; diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp index 2e6a22fee1..ae18d981a4 100644 --- a/Core/HLE/sceNet.cpp +++ b/Core/HLE/sceNet.cpp @@ -656,6 +656,7 @@ void __NetInit() { __ResetInitNetLib(); __NetApctlInit(); __NetCallbackInit(); + __NetInetInit(); } void __NetApctlShutdown() { @@ -708,7 +709,7 @@ void netValidateLoopMemory() { // This feels like a dubious proposition, mostly... void __NetDoState(PointerWrap &p) { - auto s = p.Section("sceNet", 1, 7); + auto s = p.Section("sceNet", 1, 8); if (!s) return; @@ -777,6 +778,8 @@ void __NetDoState(PointerWrap &p) { apctlProdCodeAddr = 0; } + __NetInetDoWaitEvent(p, s >= 8); + if (p.mode == p.MODE_READ) { // Let's not change "Inited" value when Loading SaveState in the middle of multiplayer to prevent memory & port leaks g_netApctlInited = cur_netApctlInited; diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 74e475c577..93cc3c73e7 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -59,7 +59,8 @@ #include "Core/HLE/KernelWaitHelpers.h" #include "Core/HLE/NetAdhocCommon.h" -#include "ext/aemu_postoffice/client/postoffice_client.h" +#include "Common/Net/Cancel.h" +#include "ext/aemu_postoffice_client/postoffice_client.h" #ifdef _WIN32 #undef errno @@ -314,6 +315,7 @@ void deleteMatchingEvents(const int matchingId = -1); void discardMatchingEvents(); void DoNetAdhocMatchingInited(PointerWrap &p); void DoNetAdhocMatchingThreads(PointerWrap &p); +void DoNetAdhocMatchingTick(PointerWrap &p, bool present); void ZeroNetAdhocMatchingThreads(); void SaveNetAdhocMatchingInited(); void RestoreNetAdhocMatchingInited(); @@ -325,6 +327,7 @@ bool __NetAdhocConnected() { void __NetAdhocShutdown() { // Kill AdhocServer Thread adhocServerRunning = false; + AdhocServerWake(); if (adhocServerThread.joinable()) { adhocServerThread.join(); } @@ -623,6 +626,7 @@ int WaitBlockingAdhocctlSocket(AdhocctlRequest request, int usec, const char* re u64 param = ((u64)__KernelGetCurThread()) << 32 | uid; adhocctlStartTime = (u64)(time_now_d() * 1000000.0); + FriendFinderWake(); adhocctlRequests[uid] = request; CoreTiming::ScheduleEvent(usToCycles(usec), adhocctlNotifyEvent, param); __KernelWaitCurThread(WAITTYPE_NET, uid, request.opcode, 0, false, reason); @@ -1318,6 +1322,7 @@ static int ptp_accept_postoffice(int idx, SceNetEtherAddr *saddr, uint16_t *spor internal->data.ptp.id = AEMU_POSTOFFICE_ID_BASE + idx; internal->flags = 0; internal->connectThread = NULL; + internal->connectCancel = NULL; AdhocSocket **slot = NULL; int i; @@ -1425,20 +1430,19 @@ static int ptp_connect_postoffice(int idx, const char *caller) { } if (internal->connectThreadDone) { - if (internal->connectThread != NULL) { - internal->connectThread->join(); - delete internal->connectThread; - internal->connectThread = NULL; - } + // The last attempt is over, so this join returns at once. + JoinPostofficeConnect(internal, false); internal->connectThreadDone = false; internal->connectThreadResult = 0; - internal->connectThread = new std::thread([internal, addr, idx] { + internal->connectCancel = new net::CancelToken(); + const int cancelFd = (int)internal->connectCancel->WakeFd(); + internal->connectThread = new std::thread([internal, addr, idx, cancelFd] { int state; SceNetEtherAddr fixed_daddr = internal->data.ptp.paddr; fixGameMac(&fixed_daddr); - void *ptp_socket = ptp_connect_v4(&addr, (const char *)&internal->data.ptp.laddr, offset_port_simple(internal->data.ptp.lport), (const char *)&fixed_daddr, offset_port_simple(internal->data.ptp.pport), &state); + void *ptp_socket = ptp_connect_v4(&addr, (const char *)&internal->data.ptp.laddr, offset_port_simple(internal->data.ptp.lport), (const char *)&fixed_daddr, offset_port_simple(internal->data.ptp.pport), &state, cancelFd); if (ptp_socket == NULL) { internal->connectThreadResult = SCE_NET_ADHOC_ERROR_CONNECTION_REFUSED; ERROR_LOG(Log::sceNet, "%s: failed connecting to ptp socket, %d", __func__, state); @@ -1786,7 +1790,7 @@ int WaitBlockingAdhocSocket(u64 threadSocketId, int type, int pspSocketId, void* } void __NetAdhocDoState(PointerWrap &p) { - auto s = p.Section("sceNetAdhoc", 1, 9); + auto s = p.Section("sceNetAdhoc", 1, 10); if (!s) return; @@ -1876,6 +1880,7 @@ void __NetAdhocDoState(PointerWrap &p) { } else if (p.mode == p.MODE_READ) { adhocctlRequests.clear(); } + DoNetAdhocMatchingTick(p, s >= 10); if (p.mode == p.MODE_READ) { // Discard leftover events @@ -1925,10 +1930,12 @@ void __NetAdhocInit() { // Create built-in AdhocServer Thread. The flag is set here rather than by the thread, so that a // shutdown that clears it before the thread gets going can't be undone (see friendFinder). adhocServerRunning = false; + AdhocServerWake(); if (adhocServerThread.joinable()) { adhocServerThread.join(); } if (g_Config.bEnableWlan && g_Config.bEnableAdhocServer) { + AdhocServerPrepare(); adhocServerRunning = true; adhocServerThread = std::thread(proAdhocServerThread, SERVER_PORT); } @@ -2004,6 +2011,7 @@ int sceNetAdhocctlInit(int stackSize, int prio, u32 productAddr) { netAdhocctlInited = true; //needed for cleanup during AdhocctlTerm even when it failed to connect to Adhoc Server (since it's being faked as success) g_adhocServerLoginFailed = false; isAdhocctlNeedLogin = true; + FriendFinderWake(); // Create fake PSP Thread for callback // TODO: Should use a separated threads for friendFinder, matchingEvent, and matchingInput and created on AdhocctlInit & AdhocMatchingStart instead of here @@ -2020,6 +2028,7 @@ int sceNetAdhocctlInit(int stackSize, int prio, u32 productAddr) { if (friendFinderThread.joinable()) { friendFinderThread.join(); } + FriendFinderPrepare(); friendFinderRunning = true; friendFinderThread = std::thread(friendFinder); } @@ -3195,6 +3204,7 @@ int sceNetAdhocctlScan() { isAdhocctlBusy = true; g_adhocServerLoginFailed = false; isAdhocctlNeedLogin = true; + FriendFinderWake(); adhocctlState = ADHOCCTL_STATE_SCANNING; adhocctlCurrentMode = ADHOCCTL_MODE_NORMAL; @@ -3490,6 +3500,7 @@ int NetAdhocctl_Term() { // Terminate Adhoc Threads friendFinderRunning = false; + FriendFinderWake(); if (friendFinderThread.joinable()) { friendFinderThread.join(); } @@ -3689,6 +3700,7 @@ int NetAdhocctl_Create(const char *groupName) { isAdhocctlBusy = true; g_adhocServerLoginFailed = false; isAdhocctlNeedLogin = true; + FriendFinderWake(); // Set Network Name if (groupName) { @@ -3709,6 +3721,7 @@ int NetAdhocctl_Create(const char *groupName) { //Faking success, to prevent Full Auto 2 from freezing while Initializing Network else { adhocctlStartTime = (u64)(time_now_d() * 1000000.0); + FriendFinderWake(); if (adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE) { adhocctlState = ADHOCCTL_STATE_GAMEMODE; notifyAdhocctlHandlers(ADHOCCTL_EVENT_GAME, 0); @@ -3842,6 +3855,7 @@ int NetAdhocctl_CreateEnterGameMode(const char* group_name, int game_type, int n adhocConnectionType = ADHOC_CREATE; netAdhocGameModeEntered = true; netAdhocEnterGameModeTimeout = timeout; + FriendFinderWake(); return NetAdhocctl_Create(group_name); } @@ -3904,6 +3918,7 @@ static int sceNetAdhocctlJoinEnterGameMode(const char * group_name, const char * adhocConnectionType = ADHOC_JOIN; netAdhocGameModeEntered = true; netAdhocEnterGameModeTimeout = timeout; + FriendFinderWake(); return hleLogDebug(Log::sceNet, NetAdhocctl_Create(group_name)); } @@ -4320,6 +4335,7 @@ static int ptp_open_postoffice(const SceNetEtherAddr *saddr, uint16_t sport, con internal->data.ptp.snd_sb_cc = 0; internal->flags = 0; internal->connectThread = NULL; + internal->connectCancel = NULL; internal->connectThreadDone = true; internal->lastAttempt = 0; internal->internalLastAttempt = 0; @@ -4918,10 +4934,7 @@ static int ptp_close_postoffice(int idx){ AdhocSocket *internal = adhocSockets[idx]; // sync - if (internal->connectThread != NULL) { - internal->connectThread->join(); - delete internal->connectThread; - } + JoinPostofficeConnect(internal, true); void *socket = internal->postofficeHandle; if (socket != NULL) { @@ -5015,6 +5028,7 @@ static int ptp_listen_postoffice(const SceNetEtherAddr *saddr, uint16_t sport, u internal->data.ptp.snd_sb_cc = 0; internal->flags = 0; internal->connectThread = NULL; + internal->connectCancel = NULL; internal->lastAttempt = 0; internal->internalLastAttempt = 0; diff --git a/Core/HLE/sceNetAdhocMatching.cpp b/Core/HLE/sceNetAdhocMatching.cpp index e1cee8dfd2..422d81c895 100644 --- a/Core/HLE/sceNetAdhocMatching.cpp +++ b/Core/HLE/sceNetAdhocMatching.cpp @@ -1151,413 +1151,361 @@ void actOnByePacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * send * @param argp SceNetAdhocMatchingContext * * @return Exit Point is never reached... */ -int matchingEventThread(int matchingId) { - SetCurrentThreadName("MatchingEvent"); - // Multithreading Lock - peerlock.lock(); - // Cast Context - SceNetAdhocMatchingContext * context = findMatchingContext(matchingId); - // Multithreading Unlock - peerlock.unlock(); +// The matching event and input loops run as one tick of emulated time on the +// emulator thread, instead of host threads polling the wall clock. +static constexpr int MATCHING_TICK_US = 10000; +static int matchingTickEvent = -1; - // Log Startup - INFO_LOG(Log::sceNet, "EventLoop: Begin of EventLoop[%i] Thread", matchingId); +// Hands every queued event to the game's handler. +static void MatchingEventPass(SceNetAdhocMatchingContext *context, int matchingId) { + u32 bufLen = context->rxbuflen; + u32 bufAddr = 0; + u32_le *args = context->handlerArgs; - // Run while needed... - if (context != NULL) { - u32 bufLen = context->rxbuflen; //0; - u32 bufAddr = 0; //= userMemory.Alloc(bufLen); //context->rxbuf; - u32_le * args = context->handlerArgs; //MatchingArgs + // Messages on Stack ready for processing + while (context != NULL && context->event_stack != NULL) { + // Claim Stack + context->eventlock->lock(); - while (contexts != NULL && context->eventRunning) { - // Multithreading Lock - peerlock.lock(); - // Cast Context - context = findMatchingContext(matchingId); - // Multithreading Unlock - peerlock.unlock(); + // Iterate Message List + ThreadMessage * msg = context->event_stack; + if (msg != NULL) { + // Default Optional Data + void* opt = NULL; - // Messages on Stack ready for processing - while (context != NULL && context->event_stack != NULL) { - // Claim Stack - context->eventlock->lock(); + // Grab Optional Data + if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); //&msg[1] - // Iterate Message List - ThreadMessage * msg = context->event_stack; - if (msg != NULL) { - // Default Optional Data - void* opt = NULL; + // Log Matching Events + INFO_LOG(Log::sceNet, "EventLoop[%d]: Matching Event [%d=%s][%s] OptSize=%d", matchingId, msg->opcode, getMatchingEventStr(msg->opcode), mac2str(&msg->mac).c_str(), msg->optlen); - // Grab Optional Data - if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); //&msg[1] + // Unlock to prevent race-condition with other threads due to recursive lock + //context->eventlock->unlock(); + // Call Event Handler + //context->handler(context->id, msg->opcode, &msg->mac, msg->optlen, opt); + // Notify Event Handlers + notifyMatchingHandler(context, msg, opt, bufAddr, bufLen, args); // If we're using shared Buffer & Args for All Events We should wait for the Mipscall to be fully executed before processing the next event. GTA VCS need this delay/sleep. - // Log Matching Events - INFO_LOG(Log::sceNet, "EventLoop[%d]: Matching Event [%d=%s][%s] OptSize=%d", matchingId, msg->opcode, getMatchingEventStr(msg->opcode), mac2str(&msg->mac).c_str(), msg->optlen); + // Give some time before executing the next mipscall to prevent event ACCEPT(6)->ESTABLISH(7) getting reversed After Action ESTABLISH(7)->ACCEPT(6) + // Must Not be delayed too long to prevent desync/disconnect. Not longer than the delays on callback's HLE? + //sleep_ms(10); //sceKernelDelayThread(10000); - // Unlock to prevent race-condition with other threads due to recursive lock - //context->eventlock->unlock(); - // Call Event Handler - //context->handler(context->id, msg->opcode, &msg->mac, msg->optlen, opt); - // Notify Event Handlers - notifyMatchingHandler(context, msg, opt, bufAddr, bufLen, args); // If we're using shared Buffer & Args for All Events We should wait for the Mipscall to be fully executed before processing the next event. GTA VCS need this delay/sleep. + // Lock again + //context->eventlock->lock(); - // Give some time before executing the next mipscall to prevent event ACCEPT(6)->ESTABLISH(7) getting reversed After Action ESTABLISH(7)->ACCEPT(6) - // Must Not be delayed too long to prevent desync/disconnect. Not longer than the delays on callback's HLE? - //sleep_ms(10); //sceKernelDelayThread(10000); - - // Lock again - //context->eventlock->lock(); - - // Pop event stack from front (this should be queue instead of stack?) - context->event_stack = msg->next; - free(msg); - msg = NULL; - } - - // Unlock Stack - context->eventlock->unlock(); - } - - // Share CPU Time - sleep_ms(10, "pro-adhoc-poll-3"); //1 //sceKernelDelayThread(10000); - - // Don't do anything if it's paused, otherwise the log will be flooded - while (Core_IsStepping() && coreState != CORE_POWERDOWN && contexts != NULL && context->eventRunning) - sleep_ms(10, "pro-adhoc-event-poll-3"); + // Pop event stack from front (this should be queue instead of stack?) + context->event_stack = msg->next; + free(msg); + msg = NULL; } - // Process Last Messages - if (contexts != NULL && context->event_stack != NULL) { + // Unlock Stack + context->eventlock->unlock(); + } +} + +// What the event loop did on its way out: the last queued events. +static void MatchingEventFinish(SceNetAdhocMatchingContext *context, int matchingId) { + u32 bufLen = context->rxbuflen; + u32 bufAddr = 0; + u32_le *args = context->handlerArgs; + + // Process Last Messages + if (contexts != NULL && context->event_stack != NULL) { + // Claim Stack + context->eventlock->lock(); + + // Iterate Message List + int msg_count = 0; + ThreadMessage * msg = context->event_stack; + for (; msg != NULL; msg = msg->next) { + // Default Optional Data + void * opt = NULL; + + // Grab Optional Data + if (msg->optlen > 0) opt = ((u8 *)msg) + sizeof(ThreadMessage); //&msg[1] + + INFO_LOG(Log::sceNet, "EventLoop[%d]: Matching Event [EVENT=%d]\n", matchingId, msg->opcode); + + //context->eventlock->unlock(); + // Original Call Event Handler + //context->handler(context->id, msg->opcode, &msg->mac, msg->optlen, opt); + // Notify Event Handlers + notifyMatchingHandler(context, msg, opt, bufAddr, bufLen, args); + //context->eventlock->lock(); + msg_count++; + } + + // Clear Event Message Stack + clearStack(context, PSP_ADHOC_MATCHING_EVENT_STACK); + + // Free Stack + context->eventlock->unlock(); + INFO_LOG(Log::sceNet, "EventLoop[%d]: Finished (%d msg)", matchingId, msg_count); + } +} + +// Hello and keepalive timers, queued sends, and every datagram that is waiting. +static void MatchingInputPass(SceNetAdhocMatchingContext *context, int matchingId) { + auto n = GetI18NCategory(I18NCat::NETWORKING); + u64_le &lastping = context->inputLastPing; + u64_le &lasthello = context->inputLastHello; + u64_le now; + SceNetEtherAddr sendermac; + u32_le senderport; + int rxbuflen; + + while (context->inputRunning) { + now = CoreTiming::GetGlobalTimeUsScaled(); //time_now_d()*1000000.0; + + // Hello Message Sending Context with unoccupied Slots + if ((context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && (countChildren(context) < (context->maxpeers - 1))) || (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && findP2P(context) == NULL)) { + // Hello Message Broadcast necessary because of Hello Interval + if (context->hello_int > 0) + if (static_cast(now - lasthello) >= static_cast(context->hello_int)) { + // Broadcast Hello Message + broadcastHelloMessage(context); + + // Update Hello Timer + lasthello = now; + } + } + + // Ping Required + if (context->keepalive_int > 0) { + if (static_cast(now - lastping) >= static_cast(context->keepalive_int)) { + // Handle Peer Timeouts + handleTimeout(context); + + // Broadcast Ping Message + broadcastPingMessage(context); + + // Update Ping Timer + lastping = now; + } + } + else { + // FIXME: Should we checks for Timeout too when the game doesn't set the keep alive interval? + handleTimeout(context); + } + + // Messages on Stack ready for processing + if (context->input_stack != NULL) { // Claim Stack - context->eventlock->lock(); + context->inputlock->lock(); + + // Iterate Message List + ThreadMessage* msg = context->input_stack; + while (msg != NULL) { + // Default Optional Data + void* opt = NULL; + + // Grab Optional Data + if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); + + //context->inputlock->unlock(); // Unlock to prevent race condition when locking peerlock + + // Send Accept Packet + if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_ACCEPT) sendAcceptPacket(context, &msg->mac, msg->optlen, opt); + + // Send Join Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_JOIN) sendJoinPacket(context, &msg->mac, msg->optlen, opt); + + // Send Cancel Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_CANCEL) sendCancelPacket(context, &msg->mac, msg->optlen, opt); + + // Send Bulk Data Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK) sendBulkDataPacket(context, &msg->mac, msg->optlen, opt); + + // Send Birth Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BIRTH) sendBirthPacket(context, &msg->mac); + + // Send Death Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_DEATH) sendDeathPacket(context, &msg->mac); + + // Cancel Bulk Data Transfer (does nothing as of now as we fire and forget anyway) // Do we need to check DeathPacket and ByePacket here? + //else if(msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) sendAbortBulkDataPacket(context, &msg->mac, msg->optlen, opt); + + //context->inputlock->lock(); // Lock again + + // Pop input stack from front (this should be queue instead of stack?) + context->input_stack = msg->next; + free(msg); + msg = context->input_stack; + } + + // Free Stack + context->inputlock->unlock(); + } + + // Receive PDP Datagram + // FIXME: When using JPCSP + prx files, the "SceNetAdhocMatchingInput" thread is using blocking PdpRecv with infinite(0) timeout, which can be stopped/aborted using SetSocketAlert, while "SceNetAdhocMatchingEvent" thread is using non-blocking for sending + rxbuflen = context->rxbuflen; + senderport = 0; + // Lock the peer first before locking the socket to avoid race condiion + peerlock.lock(); + context->socketlock->lock(); + int recvresult = sceNetAdhocPdpRecv(context->socket, &sendermac, &senderport, context->rxbuf, &rxbuflen, 0, ADHOC_F_NONBLOCK); + context->socketlock->unlock(); + peerlock.unlock(); + + // Received Data from a Sender that interests us + // Note: There are cases where the sender port might be re-mapped by router or ISP, so we shouldn't check the source port. + if (recvresult == 0 && rxbuflen > 0) { + // Log Receive Success + if (context->rxbuf[0] > 1) { + INFO_LOG(Log::sceNet, "InputLoop[%d]: Received %d Bytes (Opcode[%d]=%s)", matchingId, rxbuflen, context->rxbuf[0], getMatchingOpcodeStr(context->rxbuf[0])); + } + + // Update Peer Timestamp + peerlock.lock(); + SceNetAdhocctlPeerInfo* peer = findFriend(&sendermac); + if (peer != NULL) { + now = CoreTiming::GetGlobalTimeUsScaled(); + s64 delta = now - peer->last_recv; + DEBUG_LOG(Log::sceNet, "Timestamp LastRecv Delta: %lld (%llu - %llu) from %s", delta, now, peer->last_recv, mac2str(&sendermac).c_str()); + if (peer->last_recv != 0) peer->last_recv = std::max(peer->last_recv, now - defaultLastRecvDelta); + } + else { + WARN_LOG(Log::sceNet, "InputLoop[%d]: Unknown Peer[%s:%u] (Recved=%i, Length=%i)", matchingId, mac2str(&sendermac).c_str(), senderport, recvresult, rxbuflen); + } + + // Show a warning if other player is having their port being re-mapped, thus that other player may have issue with the communication. + // Note: That other player may need to switch side between host and join, or reboot their router to solve this issue. + if (context->port != senderport && senderport != (*context->peerPort)[sendermac]) { + char name[9] = {}; + if (peer != NULL) + truncate_cpy(name, sizeof(name), (const char*)peer->nickname.data); + WARN_LOG(Log::sceNet, "InputLoop[%d]: Unknown Source Port from [%s][%s:%u -> %u] (Recved=%i, Length=%i)", matchingId, name, mac2str(&sendermac).c_str(), senderport, context->port, recvresult, rxbuflen); + g_OSD.Show(OSDType::MESSAGE_WARNING, std::string(n->T("AM: Data from Unknown Port")) + std::string(" [") + std::string(name) + std::string("]:") + std::to_string(senderport) + std::string(" -> ") + std::to_string(context->port) + std::string(" (") + std::to_string(portOffset) + std::string(")"), 0.0f, "unknowndata"); + } + // Keep tracks of re-mapped peer's ports for further communication. + // Note: This will only works if this player were able to receives data on normal port from other players (ie. this player's port wasn't remapped) + (*context->peerPort)[sendermac] = senderport; + peerlock.unlock(); + + // Ping Packet + if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_PING) actOnPingPacket(context, &sendermac); + + // Hello Packet + else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_HELLO) actOnHelloPacket(context, &sendermac, rxbuflen); + + // Join Packet + else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_JOIN) actOnJoinPacket(context, &sendermac, rxbuflen); + + // Accept Packet + else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_ACCEPT) actOnAcceptPacket(context, &sendermac, rxbuflen); + + // Cancel Packet + else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_CANCEL) actOnCancelPacket(context, &sendermac, rxbuflen); + + // Bulk Data Packet + else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BULK) actOnBulkDataPacket(context, &sendermac, rxbuflen); + + // Abort Bulk Data Packet + //else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) actOnAbortBulkDataPacket(context, &sendermac, rxbuflen); + + // Birth Packet + else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BIRTH) actOnBirthPacket(context, &sendermac, rxbuflen); + + // Death Packet + else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_DEATH) actOnDeathPacket(context, &sendermac, rxbuflen); + + // Bye Packet + else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BYE) actOnByePacket(context, &sendermac); + + // Ignore Incoming Trash Data + } + else + break; + } +} + +// What the input loop did on its way out: last sends, bye, and the peer list. +static void MatchingInputFinish(SceNetAdhocMatchingContext *context, int matchingId) { + if (contexts != NULL) { + // Process Last Messages + if (context->input_stack != NULL) { + // Claim Stack + context->inputlock->lock(); // Iterate Message List int msg_count = 0; - ThreadMessage * msg = context->event_stack; - for (; msg != NULL; msg = msg->next) { + ThreadMessage* msg = context->input_stack; + while (msg != NULL) { // Default Optional Data - void * opt = NULL; + void* opt = NULL; // Grab Optional Data - if (msg->optlen > 0) opt = ((u8 *)msg) + sizeof(ThreadMessage); //&msg[1] + if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); - INFO_LOG(Log::sceNet, "EventLoop[%d]: Matching Event [EVENT=%d]\n", matchingId, msg->opcode); + // Send Accept Packet + if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_ACCEPT) sendAcceptPacket(context, &msg->mac, msg->optlen, opt); - //context->eventlock->unlock(); - // Original Call Event Handler - //context->handler(context->id, msg->opcode, &msg->mac, msg->optlen, opt); - // Notify Event Handlers - notifyMatchingHandler(context, msg, opt, bufAddr, bufLen, args); - //context->eventlock->lock(); + // Send Join Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_JOIN) sendJoinPacket(context, &msg->mac, msg->optlen, opt); + + // Send Cancel Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_CANCEL) sendCancelPacket(context, &msg->mac, msg->optlen, opt); + + // Send Bulk Data Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK) sendBulkDataPacket(context, &msg->mac, msg->optlen, opt); + + // Send Birth Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BIRTH) sendBirthPacket(context, &msg->mac); + + // Send Death Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_DEATH) sendDeathPacket(context, &msg->mac); + + // Cancel Bulk Data Transfer (does nothing as of now as we fire and forget anyway) // Do we need to check DeathPacket and ByePacket here? + //else if(msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) sendAbortBulkDataPacket(context, &msg->mac, msg->optlen, opt); + + // Pop input stack from front (this should be queue instead of stack?) + context->input_stack = msg->next; + free(msg); + msg = context->input_stack; msg_count++; } - // Clear Event Message Stack - clearStack(context, PSP_ADHOC_MATCHING_EVENT_STACK); - // Free Stack - context->eventlock->unlock(); - INFO_LOG(Log::sceNet, "EventLoop[%d]: Finished (%d msg)", matchingId, msg_count); + context->inputlock->unlock(); + INFO_LOG(Log::sceNet, "InputLoop[%d]: Finished (%d msg)", matchingId, msg_count); } - // Free memory - //if (Memory::IsValidAddress(bufAddr)) userMemory.Free(bufAddr); + // Clear IO Message Stack + clearStack(context, PSP_ADHOC_MATCHING_INPUT_STACK); + + // Send Bye Messages. FIXME: Official prx seems to be sending DEATH instead of BYE packet during MatchingStop? But DEATH packet doesn't works with DBZ Team Tag + sendByePacket(context); + + // Free Peer List Buffer + clearPeerList(context); //deleteAllMembers(context); // Delete Pointer Reference (and notify caller about finished cleanup) - //context->eventThread = NULL; + //context->inputThread = NULL; } - - // Log Shutdown - INFO_LOG(Log::sceNet, "EventLoop: End of EventLoop[%i] Thread", matchingId); - - // Return Zero to shut up Compiler - return 0; } -/** -* Matching IO Handler Thread -* @param args sizeof(SceNetAdhocMatchingContext *) -* @param argp SceNetAdhocMatchingContext * -* @return Exit Point is never reached... -*/ -int matchingInputThread(int matchingId) { // TODO: The MatchingInput thread is using sceNetAdhocPdpRecv & sceNetAdhocPdpSend functions so it might be better to run this on PSP thread instead of real thread - SetCurrentThreadName("MatchingInput"); - auto n = GetI18NCategory(I18NCat::NETWORKING); - // Multithreading Lock +static void __AdhocMatchingTick(u64 userdata, int cyclesLate) { + const int matchingId = (int)userdata; peerlock.lock(); - // Cast Context - SceNetAdhocMatchingContext* context = findMatchingContext(matchingId); - // Multithreading Unlock + SceNetAdhocMatchingContext *context = findMatchingContext(matchingId); peerlock.unlock(); + if (context == NULL || (!context->inputRunning && !context->eventRunning)) + return; + if (context->inputRunning) + MatchingInputPass(context, matchingId); + if (context->eventRunning) + MatchingEventPass(context, matchingId); + CoreTiming::ScheduleEvent(usToCycles(MATCHING_TICK_US) - cyclesLate, matchingTickEvent, userdata); +} - // Last Ping - u64_le lastping = 0; - - // Last Hello - u64_le lasthello = 0; - - u64_le now; - - static SceNetEtherAddr sendermac; - static u32_le senderport; - static int rxbuflen; - - // Log Startup - INFO_LOG(Log::sceNet, "InputLoop: Begin of InputLoop[%i] Thread", matchingId); - - // Run while needed... - if (context != NULL) { - while (contexts != NULL && context->inputRunning) { - // Multithreading Lock - peerlock.lock(); - // Cast Context - context = findMatchingContext(matchingId); - // Multithreading Unlock - peerlock.unlock(); - - while (context != NULL && context->inputRunning && !Core_IsStepping()) { - now = CoreTiming::GetGlobalTimeUsScaled(); //time_now_d()*1000000.0; - - // Hello Message Sending Context with unoccupied Slots - if ((context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && (countChildren(context) < (context->maxpeers - 1))) || (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && findP2P(context) == NULL)) { - // Hello Message Broadcast necessary because of Hello Interval - if (context->hello_int > 0) - if (static_cast(now - lasthello) >= static_cast(context->hello_int)) { - // Broadcast Hello Message - broadcastHelloMessage(context); - - // Update Hello Timer - lasthello = now; - } - } - - // Ping Required - if (context->keepalive_int > 0) { - if (static_cast(now - lastping) >= static_cast(context->keepalive_int)) { - // Handle Peer Timeouts - handleTimeout(context); - - // Broadcast Ping Message - broadcastPingMessage(context); - - // Update Ping Timer - lastping = now; - } - } - else { - // FIXME: Should we checks for Timeout too when the game doesn't set the keep alive interval? - handleTimeout(context); - } - - // Messages on Stack ready for processing - if (context->input_stack != NULL) { - // Claim Stack - context->inputlock->lock(); - - // Iterate Message List - ThreadMessage* msg = context->input_stack; - while (msg != NULL) { - // Default Optional Data - void* opt = NULL; - - // Grab Optional Data - if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); - - //context->inputlock->unlock(); // Unlock to prevent race condition when locking peerlock - - // Send Accept Packet - if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_ACCEPT) sendAcceptPacket(context, &msg->mac, msg->optlen, opt); - - // Send Join Packet - else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_JOIN) sendJoinPacket(context, &msg->mac, msg->optlen, opt); - - // Send Cancel Packet - else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_CANCEL) sendCancelPacket(context, &msg->mac, msg->optlen, opt); - - // Send Bulk Data Packet - else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK) sendBulkDataPacket(context, &msg->mac, msg->optlen, opt); - - // Send Birth Packet - else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BIRTH) sendBirthPacket(context, &msg->mac); - - // Send Death Packet - else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_DEATH) sendDeathPacket(context, &msg->mac); - - // Cancel Bulk Data Transfer (does nothing as of now as we fire and forget anyway) // Do we need to check DeathPacket and ByePacket here? - //else if(msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) sendAbortBulkDataPacket(context, &msg->mac, msg->optlen, opt); - - //context->inputlock->lock(); // Lock again - - // Pop input stack from front (this should be queue instead of stack?) - context->input_stack = msg->next; - free(msg); - msg = context->input_stack; - } - - // Free Stack - context->inputlock->unlock(); - } - - // Receive PDP Datagram - // FIXME: When using JPCSP + prx files, the "SceNetAdhocMatchingInput" thread is using blocking PdpRecv with infinite(0) timeout, which can be stopped/aborted using SetSocketAlert, while "SceNetAdhocMatchingEvent" thread is using non-blocking for sending - rxbuflen = context->rxbuflen; - senderport = 0; - // Lock the peer first before locking the socket to avoid race condiion - peerlock.lock(); - context->socketlock->lock(); - int recvresult = sceNetAdhocPdpRecv(context->socket, &sendermac, &senderport, context->rxbuf, &rxbuflen, 0, ADHOC_F_NONBLOCK); - context->socketlock->unlock(); - peerlock.unlock(); - - // Received Data from a Sender that interests us - // Note: There are cases where the sender port might be re-mapped by router or ISP, so we shouldn't check the source port. - if (recvresult == 0 && rxbuflen > 0) { - // Log Receive Success - if (context->rxbuf[0] > 1) { - INFO_LOG(Log::sceNet, "InputLoop[%d]: Received %d Bytes (Opcode[%d]=%s)", matchingId, rxbuflen, context->rxbuf[0], getMatchingOpcodeStr(context->rxbuf[0])); - } - - // Update Peer Timestamp - peerlock.lock(); - SceNetAdhocctlPeerInfo* peer = findFriend(&sendermac); - if (peer != NULL) { - now = CoreTiming::GetGlobalTimeUsScaled(); - s64 delta = now - peer->last_recv; - DEBUG_LOG(Log::sceNet, "Timestamp LastRecv Delta: %lld (%llu - %llu) from %s", delta, now, peer->last_recv, mac2str(&sendermac).c_str()); - if (peer->last_recv != 0) peer->last_recv = std::max(peer->last_recv, now - defaultLastRecvDelta); - } - else { - WARN_LOG(Log::sceNet, "InputLoop[%d]: Unknown Peer[%s:%u] (Recved=%i, Length=%i)", matchingId, mac2str(&sendermac).c_str(), senderport, recvresult, rxbuflen); - } - - // Show a warning if other player is having their port being re-mapped, thus that other player may have issue with the communication. - // Note: That other player may need to switch side between host and join, or reboot their router to solve this issue. - if (context->port != senderport && senderport != (*context->peerPort)[sendermac]) { - char name[9] = {}; - if (peer != NULL) - truncate_cpy(name, sizeof(name), (const char*)peer->nickname.data); - WARN_LOG(Log::sceNet, "InputLoop[%d]: Unknown Source Port from [%s][%s:%u -> %u] (Recved=%i, Length=%i)", matchingId, name, mac2str(&sendermac).c_str(), senderport, context->port, recvresult, rxbuflen); - g_OSD.Show(OSDType::MESSAGE_WARNING, std::string(n->T("AM: Data from Unknown Port")) + std::string(" [") + std::string(name) + std::string("]:") + std::to_string(senderport) + std::string(" -> ") + std::to_string(context->port) + std::string(" (") + std::to_string(portOffset) + std::string(")"), 0.0f, "unknowndata"); - } - // Keep tracks of re-mapped peer's ports for further communication. - // Note: This will only works if this player were able to receives data on normal port from other players (ie. this player's port wasn't remapped) - (*context->peerPort)[sendermac] = senderport; - peerlock.unlock(); - - // Ping Packet - if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_PING) actOnPingPacket(context, &sendermac); - - // Hello Packet - else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_HELLO) actOnHelloPacket(context, &sendermac, rxbuflen); - - // Join Packet - else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_JOIN) actOnJoinPacket(context, &sendermac, rxbuflen); - - // Accept Packet - else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_ACCEPT) actOnAcceptPacket(context, &sendermac, rxbuflen); - - // Cancel Packet - else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_CANCEL) actOnCancelPacket(context, &sendermac, rxbuflen); - - // Bulk Data Packet - else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BULK) actOnBulkDataPacket(context, &sendermac, rxbuflen); - - // Abort Bulk Data Packet - //else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) actOnAbortBulkDataPacket(context, &sendermac, rxbuflen); - - // Birth Packet - else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BIRTH) actOnBirthPacket(context, &sendermac, rxbuflen); - - // Death Packet - else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_DEATH) actOnDeathPacket(context, &sendermac, rxbuflen); - - // Bye Packet - else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BYE) actOnByePacket(context, &sendermac); - - // Ignore Incoming Trash Data - } - else - break; - } - // Share CPU Time - sleep_ms(10, "pro-adhoc-4"); //1 //sceKernelDelayThread(10000); - - // Don't do anything if it's paused, otherwise the log will be flooded - while (Core_IsStepping() && coreState != CORE_POWERDOWN && contexts != NULL && context->inputRunning) - sleep_ms(10, "pro-adhoc-input-4"); - } - - if (contexts != NULL) { - // Process Last Messages - if (context->input_stack != NULL) { - // Claim Stack - context->inputlock->lock(); - - // Iterate Message List - int msg_count = 0; - ThreadMessage* msg = context->input_stack; - while (msg != NULL) { - // Default Optional Data - void* opt = NULL; - - // Grab Optional Data - if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); - - // Send Accept Packet - if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_ACCEPT) sendAcceptPacket(context, &msg->mac, msg->optlen, opt); - - // Send Join Packet - else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_JOIN) sendJoinPacket(context, &msg->mac, msg->optlen, opt); - - // Send Cancel Packet - else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_CANCEL) sendCancelPacket(context, &msg->mac, msg->optlen, opt); - - // Send Bulk Data Packet - else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK) sendBulkDataPacket(context, &msg->mac, msg->optlen, opt); - - // Send Birth Packet - else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BIRTH) sendBirthPacket(context, &msg->mac); - - // Send Death Packet - else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_DEATH) sendDeathPacket(context, &msg->mac); - - // Cancel Bulk Data Transfer (does nothing as of now as we fire and forget anyway) // Do we need to check DeathPacket and ByePacket here? - //else if(msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) sendAbortBulkDataPacket(context, &msg->mac, msg->optlen, opt); - - // Pop input stack from front (this should be queue instead of stack?) - context->input_stack = msg->next; - free(msg); - msg = context->input_stack; - msg_count++; - } - - // Free Stack - context->inputlock->unlock(); - INFO_LOG(Log::sceNet, "InputLoop[%d]: Finished (%d msg)", matchingId, msg_count); - } - - // Clear IO Message Stack - clearStack(context, PSP_ADHOC_MATCHING_INPUT_STACK); - - // Send Bye Messages. FIXME: Official prx seems to be sending DEATH instead of BYE packet during MatchingStop? But DEATH packet doesn't works with DBZ Team Tag - sendByePacket(context); - - // Free Peer List Buffer - clearPeerList(context); //deleteAllMembers(context); - - // Delete Pointer Reference (and notify caller about finished cleanup) - //context->inputThread = NULL; - } - } - - // Log Shutdown - INFO_LOG(Log::sceNet, "InputLoop: End of InputLoop[%i] Thread", matchingId); - - // Terminate Thread - //sceKernelExitDeleteThread(0); - - // Return Zero to shut up Compiler - return 0; +void DoNetAdhocMatchingTick(PointerWrap &p, bool present) { + if (present) + Do(p, matchingTickEvent); + else + matchingTickEvent = -1; + CoreTiming::RestoreRegisterEvent(matchingTickEvent, "__AdhocMatchingTick", __AdhocMatchingTick); } @@ -1578,14 +1526,15 @@ int NetAdhocMatching_Stop(int matchingId) { // This will cause using PdpRecv on this socket to return ERROR_NET_ADHOC_SOCKET_ALERTED (Based on Ys vs. Sora no Kiseki when tested with JPCSP + prx files). Is this used to abort inprogress socket activity? NetAdhoc_SetSocketAlert(item->socket, ADHOC_F_ALERTRECV); - item->inputRunning = false; - if (item->inputThread.joinable()) { - item->inputThread.join(); + // The loops stop here, on the emulator thread, and do what their threads did on the way out. + CoreTiming::UnscheduleEvent(matchingTickEvent, (u64)matchingId); + if (item->inputRunning) { + item->inputRunning = false; + MatchingInputFinish(item, matchingId); } - - item->eventRunning = false; - if (item->eventThread.joinable()) { - item->eventThread.join(); + if (item->eventRunning) { + item->eventRunning = false; + MatchingEventFinish(item, matchingId); } // Stop fake PSP Thread. @@ -1898,15 +1847,14 @@ int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int //item->matchingThread->Start(matchingId, 0); } - //Create the threads - if (!item->eventRunning) { - item->eventRunning = true; - item->eventThread = std::thread(matchingEventThread, matchingId); - } - if (!item->inputRunning) { - item->inputRunning = true; - item->inputThread = std::thread(matchingInputThread, matchingId); + // Start the loops: one tick of emulated time drives both. + if (!item->eventRunning && !item->inputRunning) { + item->inputLastPing = 0; + item->inputLastHello = 0; + CoreTiming::ScheduleEvent(0, matchingTickEvent, (u64)matchingId); } + item->eventRunning = true; + item->inputRunning = true; item->running = 1; netAdhocMatchingStarted++; @@ -2687,6 +2635,7 @@ void Register_sceNetAdhocMatching() { void __NetAdhocMatchingInit() { netAdhocMatchingInited = false; + matchingTickEvent = CoreTiming::RegisterEvent("__AdhocMatchingTick", __AdhocMatchingTick); } void __NetAdhocMatchingShutdown() { diff --git a/Core/HLE/sceNetAdhocMatching.h b/Core/HLE/sceNetAdhocMatching.h index a262a66995..527fc00eb4 100644 --- a/Core/HLE/sceNetAdhocMatching.h +++ b/Core/HLE/sceNetAdhocMatching.h @@ -35,6 +35,8 @@ int NetAdhocMatching_Term(); void DoNetAdhocMatchingInited(PointerWrap &p); void DoNetAdhocMatchingThreads(PointerWrap &p); +// present: the state has the tick's event id (sceNetAdhoc section 10 and up). +void DoNetAdhocMatchingTick(PointerWrap &p, bool present); void ZeroNetAdhocMatchingThreads(); void SaveNetAdhocMatchingInited(); void RestoreNetAdhocMatchingInited(); diff --git a/Core/HLE/sceNetInet.cpp b/Core/HLE/sceNetInet.cpp index d522acb788..2f16bd145f 100644 --- a/Core/HLE/sceNetInet.cpp +++ b/Core/HLE/sceNetInet.cpp @@ -1,4 +1,5 @@ #include +#include #include "Common/StringUtils.h" #include "Common/Net/SocketCompat.h" #include "Common/Data/Text/Parsers.h" @@ -8,6 +9,8 @@ #include "Core/HLE/SocketManager.h" #include "Core/HLE/HLE.h" +#include "Core/HLE/sceKernelThread.h" +#include "Core/CoreTiming.h" #include "Core/HLE/ErrorCodes.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/sceNet.h" @@ -222,11 +225,8 @@ static int sceNetInetGetsockname(int socket, u32 namePtr, u32 namelenPtr) { } // FIXME: nfds is number of fd(s) as in posix poll, or was it maximum fd value as in posix select? Star Wars Battlefront Renegade seems to set the nfds to 64, while Coded Arms Contagion is using 256 -int sceNetInetSelect(int nfds, u32 readfdsPtr, u32 writefdsPtr, u32 exceptfdsPtr, u32 timeoutPtr) { - SceNetInetFdSet *readfds = readfdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(readfdsPtr) : nullptr; - SceNetInetFdSet *writefds = writefdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(writefdsPtr) : nullptr; - SceNetInetFdSet *exceptfds = exceptfdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(exceptfdsPtr) : nullptr; - SceNetInetTimeval *timeout = timeoutPtr ? (SceNetInetTimeval*)Memory::GetPointerWriteOrException(timeoutPtr) : nullptr; +// One look at the sockets, never blocking: reads the requested sets and writes the result over them. +static int InetSelectCheck(int nfds, SceNetInetFdSet *readfds, SceNetInetFdSet *writefds, SceNetInetFdSet *exceptfds, SceUID thread) { // First, translate the specified fd_sets to host sockets. @@ -310,15 +310,9 @@ int sceNetInetSelect(int nfds, u32 readfdsPtr, u32 writefdsPtr, u32 exceptfdsPtr _dbg_assert_(wrcnt < FD_SETSIZE); _dbg_assert_(excnt < FD_SETSIZE); - timeval tmout = { 5, 543210 }; // Workaround timeout value when timeout = NULL - if (timeout) { - tmout.tv_sec = timeout->tv_sec; - tmout.tv_usec = timeout->tv_usec; - } - DEBUG_LOG(Log::sceNet, "Select(host: %d): Read count: %d, Write count: %d, Except count: %d, TimeVal: %u.%u", maxHostSocket + 1, rdcnt, wrcnt, excnt, (int)tmout.tv_sec, (int)tmout.tv_usec); - // TODO: Simulate blocking behaviour when timeout = NULL to prevent PPSSPP from freezing - // Note: select can overwrite tmout. - int retval = select(maxHostSocket + 1, readfds ? &rdfds : nullptr, writefds ? &wrfds : nullptr, exceptfds ? &exfds : nullptr, /*(timeout == NULL) ? NULL :*/ &tmout); + timeval tmout = { 0, 0 }; + VERBOSE_LOG(Log::sceNet, "Select(host: %d): Read count: %d, Write count: %d, Except count: %d", maxHostSocket + 1, rdcnt, wrcnt, excnt); + int retval = select(maxHostSocket + 1, readfds ? &rdfds : nullptr, writefds ? &wrfds : nullptr, exceptfds ? &exfds : nullptr, &tmout); // Convert the results back to PSP fd_sets. if (readfds) @@ -347,14 +341,13 @@ int sceNetInetSelect(int nfds, u32 readfdsPtr, u32 writefdsPtr, u32 exceptfdsPtr } if (retval < 0) { - UpdateErrnoFromHost(__KernelGetCurThread(), socket_errno, __FUNCTION__); - return hleDelayResult(hleLogDebug(Log::sceNet, retval), "workaround until blocking-socket", 500); // Using hleDelayResult as a workaround for games that need blocking-socket to be implemented (ie. Coded Arms Contagion) + UpdateErrnoFromHost(thread, socket_errno, __FUNCTION__); } - return hleDelayResult(hleLogDebug(Log::sceNet, retval), "workaround until blocking-socket", 500); // Using hleDelayResult as a workaround for games that need blocking-socket to be implemented (ie. Coded Arms Contagion) + return retval; } -int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout) { // timeout in miliseconds just like posix poll? or in microseconds as other PSP timeout? - DEBUG_LOG(Log::sceNet, "UNTESTED sceNetInetPoll(%08x, %d, %i) at %08x", fdsPtr, nfds, timeout, currentMIPS->pc); +// One look at the sockets, never blocking: fills in revents. < 0 with errno set on a bad fd. +static int InetPollCheck(u32 fdsPtr, u32 nfds, SceUID thread) { int retval = -1; int maxHostFd = 0; SceNetInetPollfd *fdarray = (SceNetInetPollfd*)Memory::GetPointerOrException(fdsPtr); // SceNetInetPollfd/pollfd, sceNetInetPoll() have similarity to BSD poll() but pollfd have different size on 64bit @@ -367,8 +360,9 @@ int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout) { // timeout in milisecond for (int i = 0; i < (s32)nfds; i++) { if (fdarray[i].fd < 0) { // In Unix, this is OK and means it the fd should be ignored, except fdarray[i].revents should be zeroed. - UpdateErrnoFromHost(__KernelGetCurThread(), EINVAL, __FUNCTION__); - return hleLogError(Log::sceNet, -1, "invalid socket id"); + UpdateErrnoFromHost(thread, EINVAL, __FUNCTION__); + ERROR_LOG(Log::sceNet, "sceNetInetPoll: invalid socket id"); + return -1; } SOCKET hostSocket = g_socketManager.GetHostSocketFromInetSocket(fdarray[i].fd); if (hostSocket > maxHostFd) { @@ -381,16 +375,11 @@ int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout) { // timeout in milisecond fdarray[i].revents = 0; } - timeval tmout = { 5, 543210 }; // Workaround timeout value when timeout = NULL - if (timeout >= 0) { - tmout.tv_sec = timeout / 1000000; // seconds - tmout.tv_usec = (timeout % 1000000); // microseconds - } - // TODO: Simulate blocking behaviour when timeout is non-zero to prevent PPSSPP from freezing - retval = select(maxHostFd + 1, &readfds, &writefds, &exceptfds, /*(timeout<0)? NULL:*/&tmout); + timeval tmout = { 0, 0 }; + retval = select(maxHostFd + 1, &readfds, &writefds, &exceptfds, &tmout); if (retval < 0) { - UpdateErrnoFromHost(__KernelGetCurThread(), EINTR, __FUNCTION__); - return hleDelayResult(hleLogError(Log::sceNet, retval), "workaround until blocking-socket", 500); // Using hleDelayResult as a workaround for games that need blocking-socket to be implemented + UpdateErrnoFromHost(thread, EINTR, __FUNCTION__); + return -2; } retval = 0; @@ -407,8 +396,148 @@ int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout) { // timeout in milisecond retval++; VERBOSE_LOG(Log::sceNet, "Poll Socket#%d Fd: %d, events: %04x, revents: %04x, availToRecv: %d", i, fdarray[i].fd, fdarray[i].events, fdarray[i].revents, (int)getAvailToRecv(fdarray[i].fd)); } - //hleEatMicro(1000); - return hleDelayResult(hleLogDebug(Log::sceNet, retval), "workaround until blocking-socket", 1000); // Using hleDelayResult as a workaround for games that need blocking-socket to be implemented + return retval; +} + +// A select or poll that found nothing ready and was told to wait: its thread waits in +// emulated time, rechecked every INET_WAIT_POLL_US, until something is ready or the +// timeout passes. No host thread blocks inside the emulator. +static constexpr u64 INET_WAIT_POLL_US = 1000; +// What a wait without a timeout gets, as before: these calls never blocked forever. +static constexpr u64 INET_WAIT_DEFAULT_US = 5543210; + +struct InetWait { + bool isPoll; + int nfds; + u32 readfdsPtr, writefdsPtr, exceptfdsPtr; + SceNetInetFdSet readSet, writeSet, exceptSet; + u32 fdsPtr; + u64 deadlineUs; +}; +static std::map inetWaits; +static int inetWaitEvent = -1; + +static SceNetInetFdSet *InetRestoreSet(u32 ptr, const SceNetInetFdSet &set) { + if (!ptr) + return nullptr; + SceNetInetFdSet *dst = (SceNetInetFdSet *)Memory::GetPointerWriteOrException(ptr); + *dst = set; + return dst; +} + +static int InetWaitCheck(const InetWait &w, SceUID thread) { + if (w.isPoll) + return InetPollCheck(w.fdsPtr, (u32)w.nfds, thread); + // The last look wrote its result over the sets; ask for the same again. + return InetSelectCheck(w.nfds, InetRestoreSet(w.readfdsPtr, w.readSet), InetRestoreSet(w.writefdsPtr, w.writeSet), InetRestoreSet(w.exceptfdsPtr, w.exceptSet), thread); +} + +static void __InetWaitCheck(u64 userdata, int cyclesLate) { + const SceUID thread = (SceUID)userdata; + u32 error; + if (__KernelGetWaitID(thread, WAITTYPE_NET, error) != 1 || error != 0) { + inetWaits.erase(thread); + return; + } + auto it = inetWaits.find(thread); + if (it == inetWaits.end()) { + // Loaded from a state, which doesn't keep these: report a timeout and let the game ask again. + __KernelResumeThreadFromWait(thread, 0); + __KernelReSchedule("inet wait lost"); + return; + } + int result = InetWaitCheck(it->second, thread); + if (result == -2) + result = -1; + const u64 now = CoreTiming::GetGlobalTimeUs(); + if (result != 0 || now >= it->second.deadlineUs) { + inetWaits.erase(it); + __KernelResumeThreadFromWait(thread, result); + __KernelReSchedule("inet wait done"); + return; + } + const u64 left = it->second.deadlineUs - now; + CoreTiming::ScheduleEvent(usToCycles(std::min(left, INET_WAIT_POLL_US)) - cyclesLate, inetWaitEvent, userdata); +} + +static int InetWaitBegin(const InetWait &w, const char *reason) { + const SceUID thread = __KernelGetCurThread(); + inetWaits[thread] = w; + const u64 left = w.deadlineUs - CoreTiming::GetGlobalTimeUs(); + CoreTiming::ScheduleEvent(usToCycles(std::min(left, INET_WAIT_POLL_US)), inetWaitEvent, (u64)thread); + __KernelWaitCurThread(WAITTYPE_NET, 1, 0, 0, false, reason); + return 0; +} + +// FIXME: nfds is number of fd(s) as in posix poll, or was it maximum fd value as in posix select? Star Wars Battlefront Renegade seems to set the nfds to 64, while Coded Arms Contagion is using 256 +int sceNetInetSelect(int nfds, u32 readfdsPtr, u32 writefdsPtr, u32 exceptfdsPtr, u32 timeoutPtr) { + SceNetInetFdSet *readfds = readfdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(readfdsPtr) : nullptr; + SceNetInetFdSet *writefds = writefdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(writefdsPtr) : nullptr; + SceNetInetFdSet *exceptfds = exceptfdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(exceptfdsPtr) : nullptr; + SceNetInetTimeval *timeout = timeoutPtr ? (SceNetInetTimeval*)Memory::GetPointerWriteOrException(timeoutPtr) : nullptr; + + InetWait w{}; + w.nfds = nfds; + w.readfdsPtr = readfds ? readfdsPtr : 0; + w.writefdsPtr = writefds ? writefdsPtr : 0; + w.exceptfdsPtr = exceptfds ? exceptfdsPtr : 0; + if (readfds) + w.readSet = *readfds; + if (writefds) + w.writeSet = *writefds; + if (exceptfds) + w.exceptSet = *exceptfds; + + const SceUID thread = __KernelGetCurThread(); + const int retval = InetSelectCheck(nfds, readfds, writefds, exceptfds, thread); + const u64 waitUs = timeout ? (u64)timeout->tv_sec * 1000000 + (u64)timeout->tv_usec : INET_WAIT_DEFAULT_US; + if (retval != 0 || waitUs == 0) { + // Using hleDelayResult as a workaround for games that need blocking-socket to be implemented (ie. Coded Arms Contagion) + return hleDelayResult(hleLogDebug(Log::sceNet, retval), "workaround until blocking-socket", 500); + } + w.deadlineUs = CoreTiming::GetGlobalTimeUs() + waitUs; + return hleLogDebug(Log::sceNet, InetWaitBegin(w, "inet select"), "waiting"); +} + +int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout) { // timeout in miliseconds just like posix poll? or in microseconds as other PSP timeout? + DEBUG_LOG(Log::sceNet, "UNTESTED sceNetInetPoll(%08x, %d, %i) at %08x", fdsPtr, nfds, timeout, currentMIPS->pc); + if (nfds > FD_SETSIZE) + nfds = FD_SETSIZE; + + const SceUID thread = __KernelGetCurThread(); + const int retval = InetPollCheck(fdsPtr, nfds, thread); + if (retval == -1) { + return hleLogError(Log::sceNet, -1, "invalid socket id"); + } else if (retval < 0) { + // Using hleDelayResult as a workaround for games that need blocking-socket to be implemented + return hleDelayResult(hleLogError(Log::sceNet, -1), "workaround until blocking-socket", 500); + } + const u64 waitUs = timeout >= 0 ? (u64)timeout : INET_WAIT_DEFAULT_US; + if (retval != 0 || waitUs == 0) { + // Using hleDelayResult as a workaround for games that need blocking-socket to be implemented + return hleDelayResult(hleLogDebug(Log::sceNet, retval), "workaround until blocking-socket", 1000); + } + InetWait w{}; + w.isPoll = true; + w.nfds = (int)nfds; + w.fdsPtr = fdsPtr; + w.deadlineUs = CoreTiming::GetGlobalTimeUs() + waitUs; + return hleLogDebug(Log::sceNet, InetWaitBegin(w, "inet poll"), "waiting"); +} + +void __NetInetInit() { + inetWaits.clear(); + inetWaitEvent = CoreTiming::RegisterEvent("__InetWaitCheck", __InetWaitCheck); +} + +void __NetInetDoWaitEvent(PointerWrap &p, bool present) { + if (present) + Do(p, inetWaitEvent); + else + inetWaitEvent = -1; + CoreTiming::RestoreRegisterEvent(inetWaitEvent, "__InetWaitCheck", __InetWaitCheck); + if (p.mode == PointerWrap::MODE_READ) + inetWaits.clear(); } static int sceNetInetRecv(int socket, u32 bufPtr, u32 bufLen, u32 flags) { diff --git a/Core/HLE/sceNetInet.h b/Core/HLE/sceNetInet.h index f565776887..89ca125d05 100644 --- a/Core/HLE/sceNetInet.h +++ b/Core/HLE/sceNetInet.h @@ -131,4 +131,7 @@ int NetApctl_GetState(); int sceNetApctlConnect(int connIndex); int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout); +void __NetInetInit(); +// present: the state has the wait event's id (sceNet section 8 and up). +void __NetInetDoWaitEvent(PointerWrap &p, bool present); int sceNetApctlDisconnect(); diff --git a/Core/HLE/sceNp2.cpp b/Core/HLE/sceNp2.cpp index 48cc7d851e..d8881e859d 100644 --- a/Core/HLE/sceNp2.cpp +++ b/Core/HLE/sceNp2.cpp @@ -157,8 +157,7 @@ static int sceNpMatching2ContextStart(int ctxId) //npMatching2Ctx.started = true; Url url("http://static-resource.np.community.playstation.net/np/resource/psp-title/" + std::string(npTitleId.data) + "_00/matching/" + std::string(npTitleId.data) + "_00-matching.xml"); http::Client client(&ProcessHostnameWithInfraDNS); - bool cancelled = false; - net::RequestProgress progress(&cancelled); + net::RequestProgress progress(nullptr); if (!client.Resolve(url.Host().c_str(), url.Port())) { return hleLogError(Log::sceNet, SCE_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_TITLE, "HTTP failed to resolve %s", url.Resource().c_str()); } diff --git a/Core/System.cpp b/Core/System.cpp index 79300db3af..a81673d6a1 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -859,7 +859,14 @@ BootState PSP_Init(const CoreParameter &coreParam, std::string *error_string) { return BootState::Failed; } + return PSP_InitWait(error_string); +} + +BootState PSP_InitWait(std::string *error_string) { + if (g_bootState == BootState::Off) + return BootState::Off; // Every path out of the loader thread sets Complete or Failed. + _assert_msg_(g_loadingThread.joinable(), "bootstate: %d", (int)g_bootState.load()); g_loadingThread.join(); return PSP_InitFinish(error_string); } diff --git a/Core/System.h b/Core/System.h index 7019ebd8b3..751961c917 100644 --- a/Core/System.h +++ b/Core/System.h @@ -69,6 +69,8 @@ bool PSP_InitStart(const CoreParameter &coreParam); // Check the return value of this - if Booting, keep calling. // If Complete or Failed, handle as appropriate, and stop calling. BootState PSP_InitUpdate(std::string *error_string); +// After PSP_InitStart: blocks until the loader thread is done (it never sleeps), then finishes. +BootState PSP_InitWait(std::string *error_string); // Blocking wrapper around the two above functions, used for convenience in a couple of places. // Should be avoided/removed eventually. diff --git a/Core/Util/PortManager.cpp b/Core/Util/PortManager.cpp index 7a02b02690..0e495c56eb 100644 --- a/Core/Util/PortManager.cpp +++ b/Core/Util/PortManager.cpp @@ -506,8 +506,7 @@ static int upnpService(unsigned int timeout) { int failCount = 0; bool wasEnabled = false; - // Absolute time before which we won't try to (re)discover a router. This has to be a deadline - // rather than a sleep length: an incoming request wakes us early, and without it every single + // Absolute time before which we won't try to (re)discover a router: without it every single // request would trigger another full SSDP discovery while the router is unreachable. double nextInitTime = 0.0; @@ -526,10 +525,6 @@ static int upnpService(unsigned int timeout) { } } - // 0 means "sleep until someone wakes us" - which is what this thread does for the whole - // session for most users, rather than waking up periodically to find nothing to do. - double wakeAt = 0.0; - if (!g_Config.bEnableUPnP) { // Callers queue requests without checking the setting (see bind() in sceNetInet), so throw // them away here. Otherwise the queue grows without bound and, worse, the wait below never @@ -577,18 +572,12 @@ static int upnpService(unsigned int timeout) { nextInitTime = time_now_d() + MIN_RETRY_SECONDS; } } - if (g_PortManager.GetInitState() != UPNP_INITSTATE_DONE) - wakeAt = nextInitTime; } + // Sleep until someone wakes us, with no timer: a router that couldn't be reached is tried + // again on the next request (or setting change) once the backoff has passed. std::unique_lock lock(g_upnpLock); - auto shouldWake = [seq] { return g_upnpExit || g_upnpWakeSeq != seq; }; - if (wakeAt == 0.0) { - g_upnpCond.wait(lock, shouldWake); - } else { - const double delay = std::max(wakeAt - time_now_d(), 0.0); - g_upnpCond.wait_for(lock, std::chrono::duration(delay), shouldWake); - } + g_upnpCond.wait(lock, [seq] { return g_upnpExit || g_upnpWakeSeq != seq; }); } // Clean up regardless of g_Config.bEnableUPnP, to avoid leaving open ports on the router. diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index 9b511a09fb..85ee19aa5f 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -106,8 +106,7 @@ static ServerStatus RetrieveStatus() { static bool RegisterServer(int port) { bool success = false; http::Client http(nullptr); - bool cancelled = false; - net::RequestProgress progress(&cancelled); + net::RequestProgress progress(nullptr); Buffer theVoid = Buffer::Void(); http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION)); diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 5e4a1739a1..54a18054da 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -47,6 +47,7 @@ #include "Common/TimeUtil.h" #include "Core/Config.h" #include "Core/System.h" +#include "Common/Net/Cancel.h" #include "Core/WebServer.h" #include "UI/RemoteISOScreen.h" #include "UI/OnScreenDisplay.h" @@ -54,7 +55,12 @@ static const char * const REPORT_HOSTNAME = "report.ppsspp.org"; static const int REPORT_PORT = 80; -static bool scanCancelled = false; +// Made before each scan thread starts; a destroyed screen cancels it. +static std::shared_ptr scanCancel; + +static bool ScanCancelled() { + return scanCancel && scanCancel->IsCancelled(); +} static bool scanAborted = false; enum class ServerAllowStatus { @@ -136,14 +142,14 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort SetStatus("Connecting to [URL]...", host, port); // Don't wait as long for a connect - we need a good connection for smooth streaming anyway. // This way if it's down, we'll find the right one faster. - if (!http.Connect(1, 10.0, &scanCancelled)) { + if (!http.Connect(1, 10.0, scanCancel.get())) { hadTimeouts = true; SetStatus("Could not connect to [URL]", host, port); return false; } SetStatus("Loading game list from [URL]...", host, port); - net::RequestProgress progress(&scanCancelled); + net::RequestProgress progress(scanCancel.get()); code = http.GET(http::RequestParams(subdir.c_str()), &result, &progress); http.Disconnect(); @@ -194,22 +200,22 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort } // Don't scan if in manual mode. - if (g_Config.bRemoteISOManual || scanCancelled) { + if (g_Config.bRemoteISOManual || ScanCancelled()) { return false; } // Start by requesting a list of recent local ips for this network. SetStatus("Looking for peers...", "", 0); if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) { - if (http.Connect(2, 20.0, &scanCancelled)) { - net::RequestProgress progress(&scanCancelled); + if (http.Connect(2, 20.0, scanCancel.get())) { + net::RequestProgress progress(scanCancel.get()); code = http.GET(http::RequestParams("/match/list"), &result, &progress); http.Disconnect(); } } - if (code != 200 || scanCancelled) { - if (!scanCancelled) { + if (code != 200 || ScanCancelled()) { + if (!ScanCancelled()) { SetStatus("Could not load peers, retrying soon...", "", 0); } return false; @@ -234,7 +240,7 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort for (const auto pentry : entries) { JsonGet entry = pentry->value; - if (scanCancelled) + if (ScanCancelled()) return false; const char *host = entry.getStringOr("ip", ""); @@ -260,8 +266,8 @@ static bool LoadGameList(const Path &url, std::vector &games) { browser.SetPath(url); std::vector files; browser.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION)); - browser.GetListing(files, "iso:cso:chd:pbp:elf:prx:ppdmp:", &scanCancelled); - if (scanCancelled) { + browser.GetListing(files, "iso:cso:chd:pbp:elf:prx:ppdmp:", scanCancel.get()); + if (ScanCancelled()) { return false; } for (auto &file : files) { @@ -456,7 +462,7 @@ void RemoteISOScreen::HandleBrowse(UI::EventParams &e) { } RemoteISOConnectScreen::RemoteISOConnectScreen() { - scanCancelled = false; + scanCancel = std::make_shared(); scanAborted = false; scanThread_ = std::thread([](RemoteISOConnectScreen *thiz) { @@ -467,7 +473,7 @@ RemoteISOConnectScreen::RemoteISOConnectScreen() { RemoteISOConnectScreen::~RemoteISOConnectScreen() { int maxWait = 5000; - scanCancelled = true; + scanCancel->Cancel(); while (GetStatus() == ScanStatus::SCANNING || GetStatus() == ScanStatus::LOADING) { sleep_ms(1, "remote-iso-scan"); if (--maxWait < 0) { diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index 0c9d1e5ba1..af70416aa2 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -48,28 +48,29 @@ add_library(xxhash STATIC ) set(aemu_postoffice_client_obj - aemu_postoffice/client/postoffice.c - aemu_postoffice/client/postoffice_mem_stdc.c - aemu_postoffice/client/mutex_impl_cpp.cpp - aemu_postoffice/client/delay_impl_cpp.cpp - aemu_postoffice/client/log_impl_ppsspp.cpp + aemu_postoffice_client/postoffice.c + aemu_postoffice_client/postoffice_mem_stdc.c + aemu_postoffice_client/mutex_impl_cpp.cpp + aemu_postoffice_client/log_impl_ppsspp.cpp ) if(WIN32) list(APPEND aemu_postoffice_client_obj - aemu_postoffice/client/sock_impl_windows.c + aemu_postoffice_client/sock_impl_windows.c ) endif() if(LINUX OR ANDROID OR MACOSX OR IOS OR BSD) list(APPEND aemu_postoffice_client_obj - aemu_postoffice/client/sock_impl_linux.c + aemu_postoffice_client/sock_impl_linux.c ) endif() add_library(aemu_postoffice_client STATIC ${aemu_postoffice_client_obj} ) +# Its close handshake parks on libretro-common's eventcount. +target_link_libraries(aemu_postoffice_client libretro-common) set(FT_REQUIRE_ZLIB OFF CACHE BOOL "" FORCE) set(FT_REQUIRE_BZIP2 OFF CACHE BOOL "" FORCE) diff --git a/ext/aemu_postoffice b/ext/aemu_postoffice deleted file mode 160000 index 9e533637b9..0000000000 --- a/ext/aemu_postoffice +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9e533637b9a13bb0194c75cf43b0bf6d25d8101a diff --git a/ext/aemu_postoffice_client/LICENSE.txt b/ext/aemu_postoffice_client/LICENSE.txt new file mode 100644 index 0000000000..94a9ed024d --- /dev/null +++ b/ext/aemu_postoffice_client/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/ext/aemu_postoffice_client/README.md b/ext/aemu_postoffice_client/README.md new file mode 100644 index 0000000000..4957b661ee --- /dev/null +++ b/ext/aemu_postoffice_client/README.md @@ -0,0 +1,14 @@ +# aemu_postoffice client + +The client half of https://github.com/Kethen/aemu_postoffice (branch `ppsspp`, +commit 9e53363), as the core builds it. GPLv3, see LICENSE.txt. + +Changed from upstream: + +- Nothing sleeps or yields. Connects wait for the socket once with their + timeout, blocked sends and receives wait for the socket, and a close stops + new operations, wakes blocked ones with `shutdown()`, and parks on a + libretro-common eventcount until those in flight are done. +- `ptp_connect_v4`/`_v6` take a `cancel_fd`: a socket that becomes readable + to give the connect up, so a close doesn't wait one out. +- The PSP, stdc and test builds, and the server, are left out. diff --git a/ext/aemu_postoffice_client/aemu_postoffice_packets.h b/ext/aemu_postoffice_client/aemu_postoffice_packets.h new file mode 100644 index 0000000000..ebebe6ea6d --- /dev/null +++ b/ext/aemu_postoffice_client/aemu_postoffice_packets.h @@ -0,0 +1,53 @@ +#ifndef __AEMU_POSTOFFICE_PACKETS_H +#define __AEMU_POSTOFFICE_PACKETS_H + +#include + +// current limits on send/recv calls +#define AEMU_POSTOFFICE_PDP_BLOCK_MAX (10 * 1024) +#define AEMU_POSTOFFICE_PTP_BLOCK_MAX (50 * 1024) + +// structs here are gcc little endian + +typedef enum{ + AEMU_POSTOFFICE_INIT_PDP = 0, + AEMU_POSTOFFICE_INIT_PTP_LISTEN = 1, + AEMU_POSTOFFICE_INIT_PTP_CONNECT = 2, + AEMU_POSTOFFICE_INIT_PTP_ACCEPT = 3 +} aemu_postoffice_init_type; + +#pragma pack(push, 1) + +// client -> server +typedef struct aemu_postoffice_init{ + int32_t init_type; + // 2 bytes on padding on the end, only 6 bytes are used + char src_addr[8]; + uint16_t sport; + // 2 bytes on padding on the end + char dst_addr[8]; + uint16_t dport; +} aemu_postoffice_init; + +// client <-> server +typedef struct aemu_postoffice_pdp{ + // 2 bytes on padding on the end + char addr[8]; + uint16_t port; + uint32_t size; +} aemu_postoffice_pdp; + +// server -> client +typedef struct aemu_postoffice_ptp_connect{ + // 2 bytes on padding on the end + char addr[8]; + uint16_t port; +} aemu_postoffice_ptp_connect; + +typedef struct aemu_postoffice_ptp_data{ + uint32_t size; +} aemu_postoffice_ptp_data; + +#pragma pack(pop) + +#endif diff --git a/ext/aemu_postoffice_client/log_impl.h b/ext/aemu_postoffice_client/log_impl.h new file mode 100644 index 0000000000..8d6587cad4 --- /dev/null +++ b/ext/aemu_postoffice_client/log_impl.h @@ -0,0 +1,19 @@ +#ifndef __LOG_IMPL_H +#define __LOG_IMPL_H + +#ifdef __cplusplus +extern "C" { +#endif + +// on the psp, this points to the printk function provided by aemu +extern int (*log_func)(const char *format, ...); + +#define LOG(...){ \ + log_func(__VA_ARGS__); \ +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/aemu_postoffice_client/log_impl_ppsspp.cpp b/ext/aemu_postoffice_client/log_impl_ppsspp.cpp new file mode 100644 index 0000000000..8d6c222e84 --- /dev/null +++ b/ext/aemu_postoffice_client/log_impl_ppsspp.cpp @@ -0,0 +1,27 @@ +#include "Common/Log.h" + +#include +#include + +static int log_impl(const char *format, ...){ + va_list args; + char log_buf[2048] = {0}; + + va_start(args, format); + int len = vsnprintf(log_buf, sizeof(log_buf), format, args); + va_end(args); + + if (len > 0){ + // chew off the ending '\n' which is a standard in this project, but not in PPSSPP + log_buf[len - 1] = '\0'; + ERROR_LOG(Log::sceNet, "%s", log_buf); + } + + return 0; +} + +extern "C" { + +int (*log_func)(const char *format, ...) = log_impl; + +} diff --git a/ext/aemu_postoffice_client/mutex_impl.h b/ext/aemu_postoffice_client/mutex_impl.h new file mode 100644 index 0000000000..40b0b176ec --- /dev/null +++ b/ext/aemu_postoffice_client/mutex_impl.h @@ -0,0 +1,16 @@ +#ifndef __MUTEX_IMPL_H +#define __MUTEX_IMPL_H + +#ifdef __cplusplus +extern "C" { +#endif + +void init_drain_mutex(); +void lock_drain_mutex(); +void unlock_drain_mutex(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/aemu_postoffice_client/mutex_impl_cpp.cpp b/ext/aemu_postoffice_client/mutex_impl_cpp.cpp new file mode 100644 index 0000000000..1b925e68f3 --- /dev/null +++ b/ext/aemu_postoffice_client/mutex_impl_cpp.cpp @@ -0,0 +1,15 @@ +#include + +extern "C" { + +static std::mutex drain_mutex; +void init_drain_mutex(){ +} +void lock_drain_mutex(){ + drain_mutex.lock(); +} +void unlock_drain_mutex(){ + drain_mutex.unlock(); +} + +} diff --git a/ext/aemu_postoffice_client/postoffice.c b/ext/aemu_postoffice_client/postoffice.c new file mode 100644 index 0000000000..e8c3c7a734 --- /dev/null +++ b/ext/aemu_postoffice_client/postoffice.c @@ -0,0 +1,1115 @@ +#include +#include +#include + +#include "postoffice_client.h" + +#include "log_impl.h" +#include "sock_impl.h" +#include "mutex_impl.h" +#include "postoffice_mem.h" + +#include "aemu_postoffice_packets.h" + +#include +#include + +/* A close parks here until the operations in flight on its session are done. */ +static retro_eventcount_t close_ec; + +#define ABORTING(session) (retro_atomic_load_acquire_int(&(session)->abort) != 0) + +static void op_end(retro_atomic_int_t *inflight, retro_atomic_int_t *abort){ + if (retro_atomic_fetch_sub_int(inflight, 1) == 1){ + /* Paired with the fence in session_close: either it sees us gone, or we see it waiting. */ + retro_atomic_thread_fence_seq_cst(); + if (retro_atomic_load_acquire_int(abort)){ + retro_eventcount_notify(&close_ec); + } + } +} + +/* False if the session is closing; the operation must not touch it then. */ +static bool op_begin(retro_atomic_int_t *inflight, retro_atomic_int_t *abort){ + retro_atomic_fetch_add_int(inflight, 1); + retro_atomic_thread_fence_seq_cst(); + if (retro_atomic_load_acquire_int(abort)){ + op_end(inflight, abort); + return false; + } + return true; +} + +/* Stops new operations, wakes any that are blocked, and waits for those in flight. */ +static void session_close(retro_atomic_int_t *inflight, retro_atomic_int_t *abort, int sock, bool dead){ + retro_atomic_store_release_int(abort, 1); + retro_atomic_thread_fence_seq_cst(); + if (retro_atomic_load_acquire_int(inflight) == 0){ + return; + } + if (!dead){ + native_shutdown_tcp_sock(sock); + } + for (;;){ + int key = retro_eventcount_prepare_wait(&close_ec); + if (retro_atomic_load_acquire_int(inflight) == 0){ + retro_eventcount_cancel_wait(&close_ec); + return; + } + retro_eventcount_commit_wait(&close_ec, key); + } +} + +static void session_init_flags(retro_atomic_int_t *inflight, retro_atomic_int_t *abort){ + retro_atomic_int_init(inflight, 0); + retro_atomic_int_init(abort, 0); +} + +/* A blocking receive waiting for data. False if the session is closing or broke. */ +static bool session_wait_readable(retro_atomic_int_t *inflight, retro_atomic_int_t *abort, int sock){ + if (!op_begin(inflight, abort)){ + return false; + } + int wait = native_wait(sock, false, -1, -1); + op_end(inflight, abort); + return wait > 0 && !retro_atomic_load_acquire_int(abort); +} + +int aemu_post_office_init(){ + static bool first_run = true; + if (first_run){ + first_run = false; + init_postoffice_mem(); + + for (int i = 0;i < NUM_PDP_SESSIONS;i++){ + pdp_sessions[i].sock = -1; + retro_atomic_int_init(&pdp_sessions[i].claimed, 0); + } + for (int i = 0;i < NUM_PTP_LISTEN_SESSIONS;i++){ + ptp_listen_sessions[i].sock = -1; + retro_atomic_int_init(&ptp_listen_sessions[i].claimed, 0); + } + for (int i = 0;i < NUM_PTP_SESSIONS;i++){ + ptp_sessions[i].sock = -1; + retro_atomic_int_init(&ptp_sessions[i].claimed, 0); + } + + init_drain_mutex(); + retro_eventcount_init(&close_ec); + }else{ + // re-run, close all opened sessions + for (int i = 0;i < NUM_PDP_SESSIONS;i++){ + if(pdp_sessions[i].sock != -1){ + pdp_delete(&pdp_sessions[i]); + } + } + for (int i = 0;i < NUM_PTP_LISTEN_SESSIONS;i++){ + if(ptp_listen_sessions[i].sock != -1){ + ptp_listen_close(&ptp_listen_sessions[i]); + } + } + for (int i = 0;i < NUM_PTP_SESSIONS;i++){ + if(ptp_sessions[i].sock != -1){ + ptp_close(&ptp_sessions[i]); + } + } + } + return 0; +} + +static int create_and_init_socket(void *addr, int addrlen, const char *init_packet, int init_packet_len, const char *caller_name, int cancel_fd){ + int sock = native_connect_tcp_sock(addr, addrlen, cancel_fd); + if (sock < 0){ + LOG("%s: tcp connection failed\n", caller_name); + return sock; + } + + retro_atomic_int_t abort; + retro_atomic_int_init(&abort, 0); + int write_status = native_send_till_done(sock, (char *)init_packet, init_packet_len, false, &abort, cancel_fd); + if (write_status == -1){ + LOG("%s: failed sending init packet\n", caller_name); + native_close_tcp_sock(sock); + return AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK; + } + + return sock; +} + +static void *pdp_create(void *addr, int addrlen, const char *pdp_mac, int pdp_port, int *state){ + struct pdp_session* session = NULL; + for(int i = 0;i < NUM_PDP_SESSIONS;i++){ + if (retro_atomic_cas_int(&pdp_sessions[i].claimed, 0, 1)){ + session = &pdp_sessions[i]; + session->sock = 0; + break; + } + } + if (session == NULL){ + LOG("%s: failed allocating memory for pdp session\n", __func__); + *state = AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY; + return NULL; + } + + // Prepare init packet + struct aemu_postoffice_init init_packet = {0}; + init_packet.init_type = AEMU_POSTOFFICE_INIT_PDP; + memcpy(init_packet.src_addr, pdp_mac, 6); + init_packet.sport = pdp_port; + + int sock = create_and_init_socket(addr, addrlen, (char *)&init_packet, sizeof(init_packet), __func__, -1); + + if (sock < 0){ + *state = sock; + session->sock = -1; + retro_atomic_store_release_int(&session->claimed, 0); + return NULL; + } + + memcpy(session->pdp_mac, pdp_mac, 6); + session->pdp_port = pdp_port; + session->sock = sock; + session->dead = false; + session_init_flags(&session->inflight, &session->abort); + session->recv_ring_buf_start = 0; + session->recv_ring_buf_used = 0; + session->buffered_data = 0; + session->bytes_till_next_header = 0; + session->last_block_size = 0; + + *state = AEMU_POSTOFFICE_CLIENT_OK; + return session; +} + +void *pdp_create_v6(const struct aemu_post_office_sock6_addr *addr, const char *pdp_mac, int pdp_port, int *state){ + native_sock6_addr native_addr; + to_native_sock6_addr(&native_addr, addr); + + return pdp_create(&native_addr, sizeof(native_addr), pdp_mac, pdp_port, state); +} + +void *pdp_create_v4(const struct aemu_post_office_sock_addr *addr, const char *pdp_mac, int pdp_port, int *state){ + native_sock_addr native_addr; + to_native_sock_addr(&native_addr, addr); + + return pdp_create(&native_addr, sizeof(native_addr), pdp_mac, pdp_port, state); +} + +int pdp_send(void *pdp_handle, const char *pdp_mac, int pdp_port, const char *buf, int len, bool non_block){ + if (pdp_handle == NULL){ + return -1; + } + struct pdp_session *session = (struct pdp_session *)pdp_handle; + if (session->dead || ABORTING(session)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + if (len > AEMU_POSTOFFICE_PDP_BLOCK_MAX){ + LOG("%s: failed sending data, data too big, %d\n", __func__, len); + return AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY; + } + + // Write header + struct aemu_postoffice_pdp pdp_header = { + .port = pdp_port, + .size = len + }; + memcpy(pdp_header.addr, pdp_mac, 6); + + if (!op_begin(&session->inflight, &session->abort)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + int send_status = native_send_till_done(session->sock, (char *)&pdp_header, sizeof(pdp_header), non_block, &session->abort, -1); + op_end(&session->inflight, &session->abort); + if (send_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + if (send_status == NATIVE_SOCK_ABORTED){ + // getting aborted + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + if (send_status < 0){ + // Error + LOG("%s: failed sending header\n", __func__); + session->dead = true; + native_close_tcp_sock(session->sock); + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + if (!op_begin(&session->inflight, &session->abort)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + send_status = native_send_till_done(session->sock, buf, len, false, &session->abort, -1); + op_end(&session->inflight, &session->abort); + if (send_status == NATIVE_SOCK_ABORTED){ + // getting aborted + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + if (send_status < 0){ + // Error + LOG("%s: failed sending data\n", __func__); + session->dead = true; + native_close_tcp_sock(session->sock); + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + return AEMU_POSTOFFICE_CLIENT_OK; +} + +static int peek_ring_buf(uint8_t *dst, int dst_size, const uint8_t *ring_buf, int ring_buf_size, int ring_buf_begin, int ring_buf_used){ + int to_peek = dst_size > ring_buf_used ? ring_buf_used : dst_size; + for (int i = 0;i < to_peek;i++){ + int ring_buf_offset = (ring_buf_begin + i) % ring_buf_size; + dst[i] = ring_buf[ring_buf_offset]; + } + return to_peek; +} + +static int consume_ring_buf(uint8_t *dst, int dst_size, const uint8_t *ring_buf, int ring_buf_size, int *ring_buf_begin, int *ring_buf_used){ + int peeked = peek_ring_buf(dst, dst_size, ring_buf, ring_buf_size, *ring_buf_begin, *ring_buf_used); + *ring_buf_begin = (*ring_buf_begin + peeked) % ring_buf_size; + *ring_buf_used = *ring_buf_used - peeked; + return peeked; +} + +static int pdp_drain_blocks_to_ring_buf(struct pdp_session *session){ + while (true){ + int ring_buf_free = sizeof(session->recv_ring_buf) - session->recv_ring_buf_used; + if (session->bytes_till_next_header == 0){ + if (session->last_block_size != 0){ + session->buffered_data = session->buffered_data + session->last_block_size; + session->last_block_size = 0; + } + if (ring_buf_free < sizeof(aemu_postoffice_pdp)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + + aemu_postoffice_pdp header; + int peek_len = native_peek(session->sock, (char *)&header, sizeof(header)); + if (peek_len == 0){ + LOG("%s: remote closed the socket\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (peek_len == -1){ + LOG("%s: failed peeking header\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (peek_len != sizeof(header)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + + int recv_status = native_recv(session->sock, (char *)&header, sizeof(header)); + if (recv_status == 0){ + LOG("%s: remote closed the socket\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (recv_status == -1){ + LOG("%s: failed receiving header\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (header.size > AEMU_POSTOFFICE_PDP_BLOCK_MAX){ + LOG("%s: remote sent unexpected amount of data\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + session->bytes_till_next_header = header.size; + session->last_block_size = header.size; + uint8_t *header_bytes = (uint8_t *)&header; + int ring_buf_end = (session->recv_ring_buf_start + session->recv_ring_buf_used) % sizeof(session->recv_ring_buf); + for (int i = 0;i < sizeof(header);i++){ + int ring_buf_offset = (ring_buf_end + i) % sizeof(session->recv_ring_buf); + session->recv_ring_buf[ring_buf_offset] = header_bytes[i]; + } + session->recv_ring_buf_used = session->recv_ring_buf_used + sizeof(header); + continue; + } + + int ring_buf_end = (session->recv_ring_buf_start + session->recv_ring_buf_used) % sizeof(session->recv_ring_buf); + int linear_size_from_end = sizeof(session->recv_ring_buf) - ring_buf_end; + int to_consume = ring_buf_free; + if (to_consume > linear_size_from_end){ + to_consume = linear_size_from_end; + } + if (to_consume > session->bytes_till_next_header){ + to_consume = session->bytes_till_next_header; + } + + if (to_consume == 0){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + + int recv_status = native_recv(session->sock, &session->recv_ring_buf[ring_buf_end], to_consume); + if (recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + if (recv_status == 0){ + LOG("%s: remote closed the socket\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (recv_status == -1){ + LOG("%s: failed receiving data\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + session->recv_ring_buf_used = session->recv_ring_buf_used + recv_status; + session->bytes_till_next_header = session->bytes_till_next_header - recv_status; + } +} + +static int pdp_drain_blocks_to_ring_buf_locked(struct pdp_session *session){ + if (!op_begin(&session->inflight, &session->abort)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + lock_drain_mutex(); + int result = pdp_drain_blocks_to_ring_buf(session); + unlock_drain_mutex(); + op_end(&session->inflight, &session->abort); + return result; +} + +int pdp_recv(void *pdp_handle, char *pdp_mac, int *pdp_port, char *buf, int *len, bool non_block){ + if (pdp_handle == NULL){ + return -1; + } + struct pdp_session *session = (struct pdp_session *)pdp_handle; + if (session->dead || ABORTING(session)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + while (true){ + if (ABORTING(session)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + int drain_result = pdp_drain_blocks_to_ring_buf_locked(session); + if (drain_result == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + // AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK + + aemu_postoffice_pdp header; + int peeked = peek_ring_buf((uint8_t *)&header, sizeof(header), (uint8_t *)session->recv_ring_buf, sizeof(session->recv_ring_buf), session->recv_ring_buf_start, session->recv_ring_buf_used); + if (peeked != sizeof(header)){ + if (non_block){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + }else{ + /* Block until more arrives, or a close wakes us. */ + if (!session_wait_readable(&session->inflight, &session->abort, session->sock)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + continue; + } + } + + int total_size = sizeof(header) + header.size; + if (total_size > session->recv_ring_buf_used){ + if (non_block){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + }else{ + /* Block until more arrives, or a close wakes us. */ + if (!session_wait_readable(&session->inflight, &session->abort, session->sock)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + continue; + } + } + + break; + } + + aemu_postoffice_pdp header; + peek_ring_buf((uint8_t *)&header, sizeof(header), (uint8_t *)session->recv_ring_buf, sizeof(session->recv_ring_buf), session->recv_ring_buf_start, session->recv_ring_buf_used); + *pdp_port = header.port; + memcpy(pdp_mac, header.addr, 6); + if (header.size > *len){ + *len = header.size; + return AEMU_POSTOFFICE_CLIENT_SESSION_DATA_TRUNC; + } + consume_ring_buf((uint8_t *)&header, sizeof(header), (uint8_t *)session->recv_ring_buf, sizeof(session->recv_ring_buf), &session->recv_ring_buf_start, &session->recv_ring_buf_used); + consume_ring_buf((uint8_t *)buf, header.size, (uint8_t *)session->recv_ring_buf, sizeof(session->recv_ring_buf), &session->recv_ring_buf_start, &session->recv_ring_buf_used); + session->buffered_data = session->buffered_data - header.size; + *len = header.size; + + return AEMU_POSTOFFICE_CLIENT_OK; +} + +void pdp_delete(void *pdp_handle){ + if (pdp_handle == NULL){ + return; + } + struct pdp_session *session = (struct pdp_session *)pdp_handle; + + /* abort on-going ops, and wait for the ones in flight */ + session_close(&session->inflight, &session->abort, session->sock, session->dead); + + if (!session->dead) + native_close_tcp_sock(session->sock); + session->sock = -1; + retro_atomic_store_release_int(&session->claimed, 0); +} + +int pdp_peek_next_size(void *pdp_handle){ + struct pdp_session *session = pdp_handle; + + if (session->dead || ABORTING(session)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + int drain_result = pdp_drain_blocks_to_ring_buf_locked(session); + if (drain_result == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + if (session->recv_ring_buf_used < sizeof(aemu_postoffice_pdp)){ + return 0; + } + + aemu_postoffice_pdp header; + peek_ring_buf((uint8_t *)&header, sizeof(header), (uint8_t *)session->recv_ring_buf, sizeof(session->recv_ring_buf), session->recv_ring_buf_start, session->recv_ring_buf_used); + if (session->buffered_data >= header.size){ + return header.size; + } + return 0; +} + +int pdp_buffered_data_size(void *pdp_handle){ + struct pdp_session *session = pdp_handle; + + if (session->dead || ABORTING(session)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + int drain_result = pdp_drain_blocks_to_ring_buf_locked(session); + if (drain_result == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + return session->buffered_data; +} + +int pdp_is_dead(void *pdp_handle){ + if (pdp_handle == NULL){ + return 1; + } + + struct pdp_session *session = (struct pdp_session *)pdp_handle; + if (session->dead || ABORTING(session)){ + return 1; + } + + if (native_hung_up(session->sock)){ + LOG("%s: the other side closed the listen socket\n", __func__); + session->dead = true; + native_close_tcp_sock(session->sock); + return 1; + } + + return 0; +} + +int pdp_send_buf_not_full(void *pdp_handle){ + if (pdp_is_dead(pdp_handle)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + struct pdp_session *session = pdp_handle; + + return native_send_buf_not_full(session->sock); +} + +static void *ptp_listen(void *addr, int addrlen, const char *ptp_mac, int ptp_port, int *state){ + struct ptp_listen_session* session = NULL; + for(int i = 0;i < NUM_PTP_LISTEN_SESSIONS;i++){ + if (retro_atomic_cas_int(&ptp_listen_sessions[i].claimed, 0, 1)){ + session = &ptp_listen_sessions[i]; + session->sock = 0; + break; + } + } + if (session == NULL){ + LOG("%s: failed allocating memory for ptp listen session\n", __func__); + *state = AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY; + return NULL; + } + + // Prepare init packet + struct aemu_postoffice_init init_packet = {0}; + init_packet.init_type = AEMU_POSTOFFICE_INIT_PTP_LISTEN; + memcpy(init_packet.src_addr, ptp_mac, 6); + init_packet.sport = ptp_port; + + int sock = create_and_init_socket(addr, addrlen, (char *)&init_packet, sizeof(init_packet), __func__, -1); + + if (sock < 0){ + *state = sock; + session->sock = -1; + retro_atomic_store_release_int(&session->claimed, 0); + return NULL; + } + + memcpy(session->ptp_mac, ptp_mac, 6); + session->ptp_port = ptp_port; + session->sock = sock; + memcpy(session->addr, addr, addrlen); + session->addrlen = addrlen; + session->dead = false; + session_init_flags(&session->inflight, &session->abort); + + *state = AEMU_POSTOFFICE_CLIENT_OK; + return session; +} + +void *ptp_listen_v6(const struct aemu_post_office_sock6_addr *addr, const char *ptp_mac, int ptp_port, int *state){ + native_sock6_addr native_addr; + to_native_sock6_addr(&native_addr, addr); + + return ptp_listen(&native_addr, sizeof(native_addr), ptp_mac, ptp_port, state); +} + +void *ptp_listen_v4(const struct aemu_post_office_sock_addr *addr, const char *ptp_mac, int ptp_port, int *state){ + native_sock_addr native_addr; + to_native_sock_addr(&native_addr, addr); + + return ptp_listen(&native_addr, sizeof(native_addr), ptp_mac, ptp_port, state); +} + +void *ptp_accept(void *ptp_listen_handle, char *ptp_mac, int *ptp_port, bool nonblock, int *state){ + if (ptp_listen_handle == NULL){ + *state = AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + return NULL; + } + + struct ptp_listen_session *session = (struct ptp_listen_session *)ptp_listen_handle; + if (session->dead){ + *state = AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + return NULL; + } + + struct aemu_postoffice_ptp_connect connect_packet; + if (!op_begin(&session->inflight, &session->abort)){ + *state = AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + return NULL; + } + int recv_status = native_recv_till_done(session->sock, (char *)&connect_packet, sizeof(connect_packet), nonblock, &session->abort, -1); + op_end(&session->inflight, &session->abort); + if (recv_status == NATIVE_SOCK_ABORTED){ + // getting aborted + *state = AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + return NULL; + } + if (recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){ + *state = AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + return NULL; + } + if (recv_status == 0){ + LOG("%s: the other side closed the listen socket\n", __func__); + session->dead = true; + native_close_tcp_sock(session->sock); + *state = AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + return NULL; + } + if (recv_status <= 0){ + LOG("%s: socket error, %d\n", __func__, recv_status); + session->dead = true; + native_close_tcp_sock(session->sock); + *state = AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + return NULL; + } + + // Allocate memory + struct ptp_session *new_session = NULL; + for(int i = 0;i < NUM_PTP_SESSIONS;i++){ + if (retro_atomic_cas_int(&ptp_sessions[i].claimed, 0, 1)){ + new_session = &ptp_sessions[i]; + new_session->sock = 0; + break; + } + } + if (new_session == NULL){ + *state = AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY; + return NULL; + } + + // Prepare init packet + struct aemu_postoffice_init init_packet; + init_packet.init_type = AEMU_POSTOFFICE_INIT_PTP_ACCEPT; + memcpy(init_packet.src_addr, session->ptp_mac, 6); + init_packet.sport = session->ptp_port; + memcpy(init_packet.dst_addr, connect_packet.addr, 6); + init_packet.dport = connect_packet.port; + + int sock = create_and_init_socket(session->addr, session->addrlen, (char *)&init_packet, sizeof(init_packet), __func__, -1); + + if (sock < 0){ + *state = sock; + new_session->sock = -1; + retro_atomic_store_release_int(&new_session->claimed, 0); + return NULL; + } + + // Consume the ack packet + retro_atomic_int_t abort; + retro_atomic_int_init(&abort, 0); + int read_status = native_recv_till_done(sock, (char *)&connect_packet, sizeof(connect_packet), false, &abort, -1); + if (read_status == 0){ + LOG("%s: remote closed the socket during initial recv\n", __func__); + *state = AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK; + native_close_tcp_sock(sock); + new_session->sock = -1; + retro_atomic_store_release_int(&new_session->claimed, 0); + return NULL; + } + if (read_status == -1){ + LOG("%s: socket error receiving initial packet\n", __func__); + *state = AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK; + native_close_tcp_sock(sock); + new_session->sock = -1; + retro_atomic_store_release_int(&new_session->claimed, 0); + return NULL; + } + + // Now the session is ready + new_session->sock = sock; + new_session->dead = false; + session_init_flags(&new_session->inflight, &new_session->abort); + new_session->recv_ring_buf_start = 0; + new_session->recv_ring_buf_used = 0; + new_session->bytes_till_next_header = 0; + *state = AEMU_POSTOFFICE_CLIENT_OK; + *ptp_port = connect_packet.port; + memcpy(ptp_mac, connect_packet.addr, 6); + return new_session; +} + +static void *ptp_connect(void *addr, int addrlen, const char *src_ptp_mac, int ptp_sport, const char *dst_ptp_mac, int ptp_dport, int *state, int cancel_fd){ + // Allocate memory + struct ptp_session *new_session = NULL; + for(int i = 0;i < NUM_PTP_SESSIONS;i++){ + if (retro_atomic_cas_int(&ptp_sessions[i].claimed, 0, 1)){ + new_session = &ptp_sessions[i]; + new_session->sock = 0; + break; + } + } + if (new_session == NULL){ + *state = AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY; + return NULL; + } + + // Prepare init packet + struct aemu_postoffice_init init_packet; + init_packet.init_type = AEMU_POSTOFFICE_INIT_PTP_CONNECT; + memcpy(init_packet.src_addr, src_ptp_mac, 6); + init_packet.sport = ptp_sport; + memcpy(init_packet.dst_addr, dst_ptp_mac, 6); + init_packet.dport = ptp_dport; + + int sock = create_and_init_socket(addr, addrlen, (char *)&init_packet, sizeof(init_packet), __func__, cancel_fd); + + if (sock < 0){ + *state = sock; + new_session->sock = -1; + retro_atomic_store_release_int(&new_session->claimed, 0); + return NULL; + } + + // Consume the ack packet + struct aemu_postoffice_ptp_connect connect_packet; + retro_atomic_int_t abort; + retro_atomic_int_init(&abort, 0); + int read_status = native_recv_till_done(sock, (char *)&connect_packet, sizeof(connect_packet), false, &abort, cancel_fd); + if (read_status == 0){ + LOG("%s: remote closed the socket during initial recv\n", __func__); + *state = AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK; + native_close_tcp_sock(sock); + new_session->sock = -1; + retro_atomic_store_release_int(&new_session->claimed, 0); + return NULL; + } + if (read_status == -1){ + LOG("%s: socket error receiving initial packet\n", __func__); + *state = AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK; + native_close_tcp_sock(sock); + new_session->sock = -1; + retro_atomic_store_release_int(&new_session->claimed, 0); + return NULL; + } + + // Now the session is ready + new_session->sock = sock; + new_session->dead = false; + session_init_flags(&new_session->inflight, &new_session->abort); + new_session->recv_ring_buf_start = 0; + new_session->recv_ring_buf_used = 0; + new_session->bytes_till_next_header = 0; + *state = AEMU_POSTOFFICE_CLIENT_OK; + return new_session; +} + +void *ptp_connect_v6(const struct aemu_post_office_sock6_addr *addr, const char *src_ptp_mac, int ptp_sport, const char *dst_ptp_mac, int ptp_dport, int *state, int cancel_fd){ + native_sock6_addr native_addr; + to_native_sock6_addr(&native_addr, addr); + + return ptp_connect(&native_addr, sizeof(native_addr), src_ptp_mac, ptp_sport, dst_ptp_mac, ptp_dport, state, cancel_fd); +} + +void *ptp_connect_v4(const struct aemu_post_office_sock_addr *addr, const char *src_ptp_mac, int ptp_sport, const char *dst_ptp_mac, int ptp_dport, int *state, int cancel_fd){ + native_sock_addr native_addr; + to_native_sock_addr(&native_addr, addr); + + return ptp_connect(&native_addr, sizeof(native_addr), src_ptp_mac, ptp_sport, dst_ptp_mac, ptp_dport, state, cancel_fd); +} + +int ptp_send(void *ptp_handle, const char *buf, int len, bool non_block){ + if (ptp_handle == NULL){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + struct ptp_session *session = (struct ptp_session *)ptp_handle; + if (session->dead || ABORTING(session)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + if (len > AEMU_POSTOFFICE_PTP_BLOCK_MAX){ + LOG("%s: failed sending data, data too big, %d\n", __func__, len); + return AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY; + } + + struct aemu_postoffice_ptp_data header = { + .size = len + }; + + if (!op_begin(&session->inflight, &session->abort)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + int send_status = native_send_till_done(session->sock, (char *)&header, sizeof(header), non_block, &session->abort, -1); + op_end(&session->inflight, &session->abort); + if (send_status == NATIVE_SOCK_ABORTED){ + // getting aborted + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (send_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + + if (send_status < 0){ + LOG("%s: failed sending header\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + if (!op_begin(&session->inflight, &session->abort)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + send_status = native_send_till_done(session->sock, buf, len, false, &session->abort, -1); + op_end(&session->inflight, &session->abort); + if (send_status == NATIVE_SOCK_ABORTED){ + // getting aborted + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (send_status < 0){ + LOG("%s: failed sending data\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + return AEMU_POSTOFFICE_CLIENT_OK; +} + +static int ptp_drain_blocks_to_ring_buf(struct ptp_session *session){ + while(true){ + if (session->bytes_till_next_header == 0){ + struct aemu_postoffice_ptp_data header = {0}; + int peek_result = native_peek(session->sock, (char *)&header, sizeof(header)); + if (peek_result == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + if (peek_result == 0){ + LOG("%s: remote closed the socket\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (peek_result == -1){ + LOG("%s: failed peeking header\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (peek_result != sizeof(header)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + int recv_status = native_recv(session->sock, (char *)&header, sizeof(header)); + if (recv_status == 0){ + LOG("%s: remote closed the socket\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (recv_status == -1){ + LOG("%s: failed reading header\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + if (header.size > AEMU_POSTOFFICE_PTP_BLOCK_MAX){ + LOG("%s: remote sent unexpected amount of data\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + session->bytes_till_next_header = header.size; + } + + int ring_buf_free = sizeof(session->recv_ring_buf) - session->recv_ring_buf_used; + int ring_buf_end = (session->recv_ring_buf_start + session->recv_ring_buf_used) % sizeof(session->recv_ring_buf); + int linear_size_from_end = sizeof(session->recv_ring_buf) - ring_buf_end; + int to_append = ring_buf_free; + if (to_append > session->bytes_till_next_header){ + to_append = session->bytes_till_next_header; + } + if (to_append > linear_size_from_end){ + to_append = linear_size_from_end; + } + if (to_append == 0){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + + int recv_status = native_recv(session->sock, &session->recv_ring_buf[ring_buf_end], to_append); + if (recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + if (recv_status == 0){ + LOG("%s: remote closed the socket\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (recv_status == -1){ + LOG("%s: failed reading data\n", __func__); + native_close_tcp_sock(session->sock); + session->dead = true; + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + session->recv_ring_buf_used = session->recv_ring_buf_used + recv_status; + session->bytes_till_next_header = session->bytes_till_next_header - recv_status; + } +} + +static int ptp_drain_blocks_to_ring_buf_locked(struct ptp_session *session){ + if (!op_begin(&session->inflight, &session->abort)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + lock_drain_mutex(); + int result = ptp_drain_blocks_to_ring_buf(session); + unlock_drain_mutex(); + op_end(&session->inflight, &session->abort); + return result; +} + +int ptp_recv(void *ptp_handle, char *buf, int *len, bool non_block){ + if (ptp_handle == NULL){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + struct ptp_session *session = (struct ptp_session *)ptp_handle; + if (session->dead || ABORTING(session)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + while(true){ + if (ABORTING(session)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + int drain_result = ptp_drain_blocks_to_ring_buf_locked(session); + if (drain_result == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (drain_result == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){ + if (non_block){ + break; + } + if (session->recv_ring_buf_used != 0){ + break; + } + } + /* Block until more arrives, or a close wakes us. */ + if (!session_wait_readable(&session->inflight, &session->abort, session->sock)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + } + + if (session->recv_ring_buf_used == 0){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + + int ring_buffer_consumed = consume_ring_buf((uint8_t *)buf, *len, (uint8_t *)session->recv_ring_buf, sizeof(session->recv_ring_buf), &session->recv_ring_buf_start, &session->recv_ring_buf_used); + *len = ring_buffer_consumed; + + return AEMU_POSTOFFICE_CLIENT_OK; +} + +void ptp_close(void *ptp_handle){ + if (ptp_handle == NULL){ + return; + } + + struct ptp_session *session = (struct ptp_session *)ptp_handle; + + /* abort on-going ops, and wait for the ones in flight */ + session_close(&session->inflight, &session->abort, session->sock, session->dead); + + if (!session->dead) + native_close_tcp_sock(session->sock); + session->sock = -1; + retro_atomic_store_release_int(&session->claimed, 0); +} + +void ptp_listen_close(void *ptp_listen_handle){ + if (ptp_listen_handle == NULL){ + return; + } + + struct ptp_listen_session *session = (struct ptp_listen_session *)ptp_listen_handle; + + /* abort on-going ops, and wait for the ones in flight */ + session_close(&session->inflight, &session->abort, session->sock, session->dead); + + if (!session->dead) + native_close_tcp_sock(session->sock); + session->sock = -1; + retro_atomic_store_release_int(&session->claimed, 0); +} + +int pdp_get_native_sock(void *pdp_handle){ + struct pdp_session *session = pdp_handle; + return session->sock; +} + +int ptp_get_native_sock(void *ptp_handle){ + struct ptp_session *session = ptp_handle; + return session->sock; +} + +int ptp_listen_get_native_sock(void *ptp_listen_handle){ + struct ptp_listen_session *session = ptp_listen_handle; + return session->sock; +} + +int ptp_peek_next_size(void *ptp_handle){ + struct ptp_session *session = ptp_handle; + + if (session->dead || ABORTING(session)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + int drain_result = ptp_drain_blocks_to_ring_buf_locked(session); + if (drain_result == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + // AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK + + return session->recv_ring_buf_used; +} + +int ptp_is_dead(void *ptp_handle){ + if (ptp_handle == NULL){ + return 1; + } + + struct ptp_session *session = (struct ptp_session *)ptp_handle; + if (session->dead || ABORTING(session)){ + return 1; + } + + if (native_hung_up(session->sock)){ + LOG("%s: the other side closed the listen socket\n", __func__); + session->dead = true; + native_close_tcp_sock(session->sock); + return 1; + } + + return 0; +} + +int ptp_send_buf_not_full(void *ptp_handle){ + if (ptp_is_dead(ptp_handle)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + struct ptp_session *session = ptp_handle; + + return native_send_buf_not_full(session->sock); +} + +int ptp_listen_is_dead(void *ptp_listen_handle){ + if (ptp_listen_handle == NULL){ + return 1; + } + + struct ptp_listen_session *session = (struct ptp_listen_session *)ptp_listen_handle; + if (session->dead){ + return 1; + } + + if (native_hung_up(session->sock)){ + LOG("%s: the other side closed the listen socket\n", __func__); + session->dead = true; + native_close_tcp_sock(session->sock); + return 1; + } + + return 0; +} + +int ptp_listen_has_request(void *ptp_listen_handle){ + if (ptp_listen_handle == NULL){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + if (ptp_listen_is_dead(ptp_listen_handle)){ + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + + struct ptp_listen_session *session = (struct ptp_listen_session *)ptp_listen_handle; + + struct aemu_postoffice_ptp_connect connect_packet; + int peek_result = native_peek(session->sock, (char *)&connect_packet, sizeof(connect_packet)); + if (peek_result == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){ + return 0; + } + if (peek_result == 0){ + LOG("%s: the other side closed the listen socket\n", __func__); + session->dead = true; + native_close_tcp_sock(session->sock); + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (peek_result <= 0){ + LOG("%s: socket error\n", __func__); + session->dead = true; + native_close_tcp_sock(session->sock); + return AEMU_POSTOFFICE_CLIENT_SESSION_DEAD; + } + if (peek_result != sizeof(connect_packet)){ + return 0; + } + return 1; +} diff --git a/ext/aemu_postoffice_client/postoffice_client.h b/ext/aemu_postoffice_client/postoffice_client.h new file mode 100644 index 0000000000..d71a95137b --- /dev/null +++ b/ext/aemu_postoffice_client/postoffice_client.h @@ -0,0 +1,81 @@ +#ifndef __POSTOFFICE_CLIENT_H +#define __POSTOFFICE_CLIENT_H + +#include +#include + +// current limits on send/recv calls +#define AEMU_POSTOFFICE_PDP_BLOCK_MAX (10 * 1024) +#define AEMU_POSTOFFICE_PTP_BLOCK_MAX (50 * 1024) + +enum aemu_postoffice_client_errors { + AEMU_POSTOFFICE_CLIENT_OK = 0, + AEMU_POSTOFFICE_CLIENT_UNKNOWN = -1, + AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY = -2, + AEMU_POSTOFFICE_CLIENT_SESSION_DEAD = -3, + AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK = -4, + AEMU_POSTOFFICE_CLIENT_SESSION_DATA_TRUNC = -5, + AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK = -6 +}; + +struct aemu_post_office_sock_addr{ + uint32_t addr; // network order + uint16_t port; // network order +}; + +struct aemu_post_office_sock6_addr{ + uint8_t addr[16]; // network order + uint16_t port; // network order +}; + +#ifdef __cplusplus +extern "C" { +#endif + +int aemu_post_office_init(); + +/* + * Thread safety: + * multiple threads can perform create/listen/connect + * only one thread at a time can accept on a created socket + * only one thread at a time can send on a created socket + * only one thread at a time can recv/peek on a created socket + * only one thread at a time can close a created socket + * + * violating the above yields undefined results + */ + +void *pdp_create_v6(const struct aemu_post_office_sock6_addr *addr, const char *pdp_mac, int pdp_port, int *state); +void *pdp_create_v4(const struct aemu_post_office_sock_addr *addr, const char *pdp_mac, int pdp_port, int *state); +void pdp_delete(void *pdp_handle); +int pdp_send(void *pdp_handle, const char *pdp_mac, int pdp_port, const char *buf, int len, bool non_block); +int pdp_recv(void *pdp_handle, char *pdp_mac, int *pdp_port, char *buf, int *len, bool non_block); +int pdp_peek_next_size(void *pdp_handle); +int pdp_buffered_data_size(void *pdp_handle); +int pdp_send_buf_not_full(void *pdp_handle); +int pdp_is_dead(void *ptp_handle); +void *ptp_listen_v6(const struct aemu_post_office_sock6_addr *addr, const char *ptp_mac, int ptp_port, int *state); +void *ptp_listen_v4(const struct aemu_post_office_sock_addr *addr, const char *ptp_mac, int ptp_port, int *state); +void *ptp_accept(void *ptp_listen_handle, char *ptp_mac, int *ptp_port, bool nonblock, int *state); +/* cancel_fd: -1, or a socket that becomes readable to give the connect up. */ +void *ptp_connect_v6(const struct aemu_post_office_sock6_addr *addr, const char *src_ptp_mac, int ptp_sport, const char *dst_ptp_mac, int ptp_dport, int *state, int cancel_fd); +void *ptp_connect_v4(const struct aemu_post_office_sock_addr *addr, const char *src_ptp_mac, int ptp_sport, const char *dst_ptp_mac, int ptp_dport, int *state, int cancel_fd); +int ptp_send(void *ptp_handle, const char *buf, int len, bool non_block); +int ptp_recv(void *ptp_handle, char *buf, int *len, bool non_block); +void ptp_close(void *ptp_handle); +void ptp_listen_close(void *ptp_listen_handle); +int ptp_peek_next_size(void *ptp_handle); +int ptp_send_buf_not_full(void *pdp_handle); +int ptp_listen_has_request(void *pdp_listen_handler); +int ptp_is_dead(void *ptp_handle); + +int pdp_get_native_sock(void *pdp_handle); +int ptp_get_native_sock(void *ptp_handle); +int ptp_listen_get_native_sock(void *ptp_listen_handle); +int ptp_listen_is_dead(void *ptp_listen_handle); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/aemu_postoffice_client/postoffice_mem.h b/ext/aemu_postoffice_client/postoffice_mem.h new file mode 100644 index 0000000000..6a2780a8f7 --- /dev/null +++ b/ext/aemu_postoffice_client/postoffice_mem.h @@ -0,0 +1,70 @@ +#ifndef __POSTOFFICE_MEM_H +#define __POSTOFFICE_MEM_H + +#include +#include +#include "sock_impl.h" + +#include "postoffice_client.h" +#include "aemu_postoffice_packets.h" + +struct pdp_session{ + char *pdp_mac[6]; + int16_t pdp_port; + int sock; + bool dead; + /* Set by a close; operations in flight are counted so the close can wait them out. */ + retro_atomic_int_t abort; + retro_atomic_int_t inflight; + retro_atomic_int_t claimed; + char recv_ring_buf[AEMU_POSTOFFICE_PDP_BLOCK_MAX + sizeof(aemu_postoffice_pdp)]; + int recv_ring_buf_start; + int recv_ring_buf_used; + int buffered_data; + int bytes_till_next_header; + int last_block_size; +}; + +struct ptp_listen_session{ + char *ptp_mac[6]; + int16_t ptp_port; + int sock; + bool dead; + retro_atomic_int_t abort; + retro_atomic_int_t inflight; + retro_atomic_int_t claimed; + char addr[sizeof(native_sock6_addr) > sizeof(native_sock_addr) ? sizeof(native_sock6_addr) : sizeof(native_sock_addr)]; + int addrlen; +}; + +struct ptp_session{ + int sock; + bool dead; + retro_atomic_int_t abort; + retro_atomic_int_t inflight; + retro_atomic_int_t claimed; + char recv_ring_buf[AEMU_POSTOFFICE_PTP_BLOCK_MAX]; + int recv_ring_buf_start; + int recv_ring_buf_used; + int bytes_till_next_header; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +extern int NUM_PDP_SESSIONS; +extern int NUM_PTP_LISTEN_SESSIONS; +extern int NUM_PTP_SESSIONS; + +extern struct pdp_session *pdp_sessions; +extern struct ptp_listen_session *ptp_listen_sessions; +extern struct ptp_session *ptp_sessions; + +void init_postoffice_mem(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/aemu_postoffice_client/postoffice_mem_stdc.c b/ext/aemu_postoffice_client/postoffice_mem_stdc.c new file mode 100644 index 0000000000..165a16354a --- /dev/null +++ b/ext/aemu_postoffice_client/postoffice_mem_stdc.c @@ -0,0 +1,18 @@ +#include "postoffice_mem.h" + +#define SESSIONS_PER_TYPE 32 + +int NUM_PDP_SESSIONS = SESSIONS_PER_TYPE; +int NUM_PTP_LISTEN_SESSIONS = SESSIONS_PER_TYPE; +int NUM_PTP_SESSIONS = SESSIONS_PER_TYPE; + +struct pdp_session _pdp_sessions[SESSIONS_PER_TYPE]; +struct ptp_listen_session _ptp_listen_sessions[SESSIONS_PER_TYPE]; +struct ptp_session _ptp_sessions[SESSIONS_PER_TYPE]; + +struct pdp_session *pdp_sessions = _pdp_sessions; +struct ptp_listen_session *ptp_listen_sessions = _ptp_listen_sessions; +struct ptp_session *ptp_sessions = _ptp_sessions; + +void init_postoffice_mem(){ +} diff --git a/ext/aemu_postoffice_client/sock_impl.h b/ext/aemu_postoffice_client/sock_impl.h new file mode 100644 index 0000000000..f7e560ffea --- /dev/null +++ b/ext/aemu_postoffice_client/sock_impl.h @@ -0,0 +1,48 @@ +#ifndef __SOCK_IMPL_COMMON_H +#define __SOCK_IMPL_COMMON_H + +#include + +#if defined(__unix) || defined(__APPLE__) || defined(__PSP__) +#include +#endif + +#ifdef _WIN32 +#include +#include +#include +#endif + +#include "postoffice_client.h" + +typedef struct sockaddr_in native_sock_addr; + +#ifdef __PSP__ +typedef int native_sock6_addr; +#else +typedef struct sockaddr_in6 native_sock6_addr; +#endif + +#include + +#define NATIVE_SOCK_ABORTED -100 + +void to_native_sock_addr(native_sock_addr *dst, const struct aemu_post_office_sock_addr *src); +void to_native_sock6_addr(native_sock6_addr *dst, const struct aemu_post_office_sock6_addr *src); + +/* cancel_fd: -1, or a socket that becomes readable to give up a wait. */ +int native_connect_tcp_sock(void *addr, int addrlen, int cancel_fd); +int native_close_tcp_sock(int sock); +/* Ends any wait blocked on sock, for a close from another thread. */ +int native_shutdown_tcp_sock(int sock); +int native_send_till_done(int fd, const char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd); +int native_recv_till_done(int fd, char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd); +/* Blocks until fd is readable (or writable), cancel_fd is readable, or timeout_ms + passes (< 0: no limit). 1 ready, 0 timeout, NATIVE_SOCK_ABORTED cancelled, -1 error. */ +int native_wait(int fd, bool for_write, int timeout_ms, int cancel_fd); +int native_recv(int fd, char *buf, int len); +int native_peek(int fd, char *buf, int len); +bool native_send_buf_not_full(int fd); +bool native_hung_up(int fd); + +#endif diff --git a/ext/aemu_postoffice_client/sock_impl_linux.c b/ext/aemu_postoffice_client/sock_impl_linux.c new file mode 100644 index 0000000000..5aae1ffa14 --- /dev/null +++ b/ext/aemu_postoffice_client/sock_impl_linux.c @@ -0,0 +1,285 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "postoffice_client.h" +#include "sock_impl.h" +#include "log_impl.h" + +void to_native_sock_addr(native_sock_addr *dst, const struct aemu_post_office_sock_addr *src){ + dst->sin_family = AF_INET; + dst->sin_addr.s_addr = src->addr; + dst->sin_port = src->port; +} + +void to_native_sock6_addr(native_sock6_addr *dst, const struct aemu_post_office_sock6_addr *src){ + dst->sin6_family = AF_INET6; + dst->sin6_port = src->port; + dst->sin6_flowinfo = 0; + memcpy(dst->sin6_addr.s6_addr, src->addr, 16); + dst->sin6_scope_id = 0; +} + +int native_wait(int fd, bool for_write, int timeout_ms, int cancel_fd){ + struct pollfd pfd[2]; + int count = 1; + pfd[0].fd = fd; + pfd[0].events = for_write ? POLLOUT : POLLIN; + pfd[0].revents = 0; + if (cancel_fd >= 0){ + pfd[1].fd = cancel_fd; + pfd[1].events = POLLIN; + pfd[1].revents = 0; + count = 2; + } + int result; + do { + result = poll(pfd, count, timeout_ms); + } while (result < 0 && errno == EINTR); + if (result < 0){ + return -1; + } + if (result == 0){ + return 0; + } + if (count == 2 && (pfd[1].revents & POLLIN)){ + return NATIVE_SOCK_ABORTED; + } + /* POLLERR / POLLHUP count as ready: the next call on the socket reports them. */ + return 1; +} + +static int connect_with_timeout(int sock, void *addr, int addrlen, int timeout_ms, int *error, int cancel_fd){ + int flags = fcntl(sock, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + + int ret = 0; + if (connect(sock, (struct sockaddr *)addr, addrlen) != 0){ + *error = errno; + if (*error != EINPROGRESS && *error != EAGAIN && *error != EALREADY){ + return -1; + } + /* One wait for the outcome, a cancel, or the timeout. */ + int wait = native_wait(sock, true, timeout_ms, cancel_fd); + if (wait == 0){ + *error = ETIMEDOUT; + ret = -1; + }else if (wait == NATIVE_SOCK_ABORTED){ + *error = ECANCELED; + ret = -1; + }else if (wait < 0){ + *error = errno; + ret = -1; + }else{ + int so_error = 0; + socklen_t so_len = sizeof(so_error); + getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_error, &so_len); + *error = so_error; + ret = so_error == 0 ? 0 : -1; + } + } + + // just for completness, NBIO is used on the socket after connection anyway + flags = fcntl(sock, F_GETFL, 0); + flags &= ~O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + + return ret; +} + + +int native_connect_tcp_sock(void *addr, int addrlen, int cancel_fd){ + native_sock_addr *native_addr = addr; + int sock = socket(native_addr->sin_family, SOCK_STREAM, 0); + if (sock == -1){ + LOG("%s: failed creating socket, %s\n", __func__, strerror(errno)); + return AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK; + } + + // XXX this restricts latency to 500ms, if a server is even further away, connection won't be possible + + // Connect + int error = 0; + int connect_status = connect_with_timeout(sock, addr, addrlen, 5000, &error, cancel_fd); + if (connect_status == -1){ + LOG("%s: failed connecting, %s\n", __func__, strerror(error)); + close(sock); + return AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK; + } + + // Set socket options + socklen_t sockopt = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &sockopt, sizeof(sockopt)); + int flags = fcntl(sock, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + + sockopt = 2626560; + setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sockopt, sizeof(sockopt)); + setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof(sockopt)); + + #ifndef __linux__ + sockopt = 1; + setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &sockopt, sizeof(sockopt)); + #endif + + // Show some socket options + unsigned int opt_len = sizeof(sockopt); + sockopt = 0; + int get_ret = getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &sockopt, &opt_len); + LOG("%s: TCP_NODELAY is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? errno : 0); + + opt_len = sizeof(sockopt); + sockopt = 0; + get_ret = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sockopt, &opt_len); + LOG("%s: SO_SNDBUF is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? errno : 0); + + opt_len = sizeof(sockopt); + sockopt = 0; + get_ret = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &sockopt, &opt_len); + LOG("%s: SO_RCVBUF is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? errno : 0); + + #ifndef __linux__ + opt_len = sizeof(sockopt); + sockopt = 0; + get_ret = getsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &sockopt, &opt_len); + LOG("%s: SO_NOSIGPIPE is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? errno : 0); + #endif + + return sock; +} + +int native_send_till_done(int fd, const char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd){ + int write_offset = 0; + while(write_offset != len){ + if (retro_atomic_load_acquire_int(abort)){ + return NATIVE_SOCK_ABORTED; + } + #ifdef __linux__ + int write_status = send(fd, &buf[write_offset], len - write_offset, MSG_NOSIGNAL); + #else + int write_status = send(fd, &buf[write_offset], len - write_offset, 0); + #endif + if (write_status == -1){ + int err = errno; + if (err == EAGAIN || err == EWOULDBLOCK){ + if (non_block && write_offset == 0){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + /* Block until it can go on: in block mode, or part of the message is out already. */ + if (native_wait(fd, true, -1, cancel_fd) == NATIVE_SOCK_ABORTED){ + return NATIVE_SOCK_ABORTED; + } + continue; + } + // Other errors + LOG("%s: failed sending, %s\n", __func__, strerror(errno)); + return write_status; + } + write_offset += write_status; + } + return write_offset; +} + +int native_recv(int fd, char *buf, int len){ + int recv_status = recv(fd, buf, len, 0); + if (recv_status == 0){ + return recv_status; + } + if (recv_status < 0){ + int err = errno; + if (err == EAGAIN || err == EWOULDBLOCK){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + // Other errors + LOG("%s: failed receving, %s\n", __func__, strerror(errno)); + return recv_status; + } + return recv_status; +} + +int native_recv_till_done(int fd, char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd){ + int read_offset = 0; + while(read_offset != len){ + if (retro_atomic_load_acquire_int(abort)){ + return NATIVE_SOCK_ABORTED; + } + int recv_status = native_recv(fd, &buf[read_offset], len - read_offset); + if (recv_status == 0){ + return recv_status; + } + if (recv_status < 0){ + if (recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){ + if (non_block && read_offset == 0){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + /* Block until more comes: in block mode, or part of the message is in already. */ + if (native_wait(fd, false, -1, cancel_fd) == NATIVE_SOCK_ABORTED){ + return NATIVE_SOCK_ABORTED; + } + continue; + } + return recv_status; + } + read_offset += recv_status; + } + return read_offset; +} + +int native_close_tcp_sock(int sock){ + return close(sock); +} + +int native_shutdown_tcp_sock(int sock){ + return shutdown(sock, SHUT_RDWR); +} + +int native_peek(int fd, char *buf, int len){ + int read_result = recv(fd, buf, len, MSG_PEEK); + if (read_result == 0){ + return 0; + } + if (read_result == -1){ + int err = errno; + if (err == EAGAIN || err == EWOULDBLOCK){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + LOG("%s: failed peeking, %s\n", __func__, strerror(errno)); + return -1; + } + return read_result; +} + +bool native_send_buf_not_full(int fd){ + struct pollfd pfd; + pfd.fd = fd; + pfd.events = POLLWRNORM; + pfd.revents = 0; + poll(&pfd, 1, 0); + if (pfd.revents & POLLHUP){ + return false; + } + if (pfd.revents & POLLWRNORM){ + return true; + } + return false; +} + +bool native_hung_up(int fd){ + uint8_t buf; + int peek_result = native_peek(fd, (char *)&buf, sizeof(buf)); + if (peek_result == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK || + peek_result == sizeof(buf) + ){ + return false; + } + return true; +} diff --git a/ext/aemu_postoffice_client/sock_impl_windows.c b/ext/aemu_postoffice_client/sock_impl_windows.c new file mode 100644 index 0000000000..d74592e004 --- /dev/null +++ b/ext/aemu_postoffice_client/sock_impl_windows.c @@ -0,0 +1,278 @@ +#include +#include + +#include +#include + +#include "postoffice_client.h" +#include "sock_impl.h" +#include "log_impl.h" + +void to_native_sock_addr(native_sock_addr *dst, const struct aemu_post_office_sock_addr *src){ + dst->sin_family = AF_INET; + dst->sin_addr.s_addr = src->addr; + dst->sin_port = src->port; + memset(dst->sin_zero, 0, sizeof(dst->sin_zero)); +} + +void to_native_sock6_addr(native_sock6_addr *dst, const struct aemu_post_office_sock6_addr *src){ + dst->sin6_family = AF_INET6; + dst->sin6_port = src->port; + dst->sin6_flowinfo = 0; + memcpy(dst->sin6_addr.s6_addr, src->addr, 16); + dst->sin6_scope_id = 0; +} + +static void init_winsock2(){ + static bool initialized = false; + if (!initialized){ + initialized = true; + WSADATA data; + int init_result = WSAStartup(MAKEWORD(2,2), &data); + if (init_result != 0){ + printf("%s: warning: WSAStartup seems to have failed, %d\n", __func__, init_result); + } + } +} + +int native_wait(int fd, bool for_write, int timeout_ms, int cancel_fd){ + fd_set rfds, wfds, efds; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + FD_SET((SOCKET)fd, for_write ? &wfds : &rfds); + /* A failed non-blocking connect is reported as an exception on Windows. */ + FD_SET((SOCKET)fd, &efds); + if (cancel_fd >= 0){ + FD_SET((SOCKET)cancel_fd, &rfds); + } + struct timeval tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + int result = select(0, &rfds, &wfds, &efds, timeout_ms < 0 ? NULL : &tv); + if (result == SOCKET_ERROR){ + return -1; + } + if (result == 0){ + return 0; + } + if (cancel_fd >= 0 && FD_ISSET((SOCKET)cancel_fd, &rfds)){ + return NATIVE_SOCK_ABORTED; + } + return 1; +} + +static int connect_with_timeout(int sock, native_sock_addr *addr, int addrlen, int timeout_ms, int *error, int cancel_fd){ + u_long ioctlopt = 1; + ioctlsocket(sock, FIONBIO, &ioctlopt); + + int ret = 0; + if (connect(sock, (struct sockaddr *)addr, addrlen) != 0){ + *error = WSAGetLastError(); + /* it was found out together with @hrydgard that after using WinHttp, it could also throw WSAEINVAL for WSAEALREADY */ + if (*error == WSAEISCONN){ + *error = 0; + }else if (*error != WSAEWOULDBLOCK && *error != WSAEALREADY && *error != WSAEINVAL){ + ret = -1; + }else{ + /* One wait for the outcome, a cancel, or the timeout. */ + int wait = native_wait(sock, true, timeout_ms, cancel_fd); + if (wait == 0){ + *error = WSAETIMEDOUT; + ret = -1; + }else if (wait == NATIVE_SOCK_ABORTED){ + *error = WSAECANCELLED; + ret = -1; + }else if (wait < 0){ + *error = WSAGetLastError(); + ret = -1; + }else{ + int so_error = 0; + int so_len = sizeof(so_error); + getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&so_error, &so_len); + *error = so_error; + ret = so_error == 0 ? 0 : -1; + } + } + } + + // just for completness, NBIO is used on the socket after connection anyway + ioctlopt = 0; + ioctlsocket(sock, FIONBIO, &ioctlopt); + return ret; +} + +int native_connect_tcp_sock(void *addr, int addrlen, int cancel_fd){ + init_winsock2(); + + native_sock_addr *native_addr = addr; + SOCKET win_sock = socket(native_addr->sin_family, SOCK_STREAM, 0); + if (win_sock == INVALID_SOCKET){ + LOG("%s: failed creating socket, %d\n", __func__, WSAGetLastError()); + return AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK; + } + int sock = win_sock; + + // XXX need to simulate timeout on windows, there's no sockopt for that + // this also restricts latency to 500ms, if a server is even further away, connection won't be possible + + // Connect + int error = 0; + int connect_status = connect_with_timeout(sock, addr, addrlen, 5000, &error, cancel_fd); + if (connect_status == -1){ + LOG("%s: failed connecting, %d\n", __func__, error); + closesocket(sock); + return AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK; + } + + // Set socket options + int sockopt = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&sockopt, sizeof(sockopt)); + u_long ioctlopt = 1; + ioctlsocket(sock, FIONBIO, &ioctlopt); + + sockopt = 2626560; + setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&sockopt, sizeof(sockopt)); + setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&sockopt, sizeof(sockopt)); + + // Show some socket options + int opt_len = sizeof(sockopt); + sockopt = 0; + int get_ret = getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&sockopt, &opt_len); + LOG("%s: TCP_NODELAY is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? WSAGetLastError() : 0); + + opt_len = sizeof(sockopt); + sockopt = 0; + get_ret = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&sockopt, &opt_len); + LOG("%s: SO_SNDBUF is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? WSAGetLastError() : 0); + + opt_len = sizeof(sockopt); + sockopt = 0; + get_ret = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&sockopt, &opt_len); + LOG("%s: SO_RCVBUF is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? WSAGetLastError() : 0); + + return sock; +} + +int native_send_till_done(int fd, const char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd){ + int write_offset = 0; + while(write_offset != len){ + if (retro_atomic_load_acquire_int(abort)){ + return NATIVE_SOCK_ABORTED; + } + int write_status = send(fd, &buf[write_offset], len - write_offset, 0); + if (write_status == -1){ + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK || err == WSAEINPROGRESS){ + if (non_block && write_offset == 0){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + /* Block until it can go on: in block mode, or part of the message is out already. */ + if (native_wait(fd, true, -1, cancel_fd) == NATIVE_SOCK_ABORTED){ + return NATIVE_SOCK_ABORTED; + } + continue; + } + // Other errors + LOG("%s: failed sending, %d\n", __func__, err); + return write_status; + } + write_offset += write_status; + } + return write_offset; +} + +int native_recv(int fd, char *buf, int len){ + int recv_status = recv(fd, buf, len, 0); + if (recv_status == 0){ + return recv_status; + } + if (recv_status < 0){ + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK || err == WSAEINPROGRESS){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + // Other errors + LOG("%s: failed receving, %d\n", __func__, err); + return recv_status; + } + return recv_status; +} + +int native_recv_till_done(int fd, char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd){ + int read_offset = 0; + while(read_offset != len){ + if (retro_atomic_load_acquire_int(abort)){ + return NATIVE_SOCK_ABORTED; + } + int recv_status = native_recv(fd, &buf[read_offset], len - read_offset); + if (recv_status == 0){ + return recv_status; + } + if (recv_status < 0){ + if (recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){ + if (non_block && read_offset == 0){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + /* Block until more comes: in block mode, or part of the message is in already. */ + if (native_wait(fd, false, -1, cancel_fd) == NATIVE_SOCK_ABORTED){ + return NATIVE_SOCK_ABORTED; + } + continue; + } + return recv_status; + } + read_offset += recv_status; + } + return read_offset; +} + +int native_close_tcp_sock(int sock){ + return closesocket(sock); +} + +int native_shutdown_tcp_sock(int sock){ + return shutdown(sock, SD_BOTH); +} + +int native_peek(int fd, char *buf, int len){ + int read_result = recv(fd, buf, len, MSG_PEEK); + if (read_result == 0){ + return 0; + } + if (read_result == -1){ + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK || err == WSAEINPROGRESS){ + return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK; + } + LOG("%s: failed peeking, %d\n", __func__, WSAGetLastError()); + return -1; + } + return read_result; +} + +bool native_send_buf_not_full(int fd){ + WSAPOLLFD pfd; + pfd.fd = fd; + pfd.events = POLLWRNORM; + pfd.revents = 0; + WSAPoll(&pfd, 1, 0); + if (pfd.revents & POLLHUP){ + return false; + } + if (pfd.revents & POLLWRNORM){ + return true; + } + return false; +} + +bool native_hung_up(int fd){ + uint8_t buf; + int peek_result = native_peek(fd, (char *)&buf, sizeof(buf)); + if (peek_result == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK || + peek_result == sizeof(buf) + ){ + return false; + } + return true; +} diff --git a/headless/Headless.cpp b/headless/Headless.cpp index b570e9552e..c92c2afdf2 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -390,9 +390,7 @@ static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &corePar } std::string error_string; - while (PSP_InitUpdate(&error_string) == BootState::Booting) { - sleep_ms(1, "auto-test"); - } + PSP_InitWait(&error_string); if (!PSP_IsInited()) { GitHubActionsPrint("error", "Test init failed for %s", currentTestName.c_str()); diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 2a6173bde5..2f73b3329e 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -325,6 +325,7 @@ SOURCES_CXX += \ $(COMMONDIR)/Net/HTTPHeaders.cpp \ $(COMMONDIR)/Net/HTTPRequest.cpp \ $(COMMONDIR)/Net/HTTPNaettRequest.cpp \ + $(COMMONDIR)/Net/Cancel.cpp \ $(COMMONDIR)/Net/NetBuffer.cpp \ $(COMMONDIR)/Net/Resolve.cpp \ $(COMMONDIR)/Net/Sinks.cpp \ @@ -503,20 +504,19 @@ SOURCES_CXX += \ endif SOURCES_C += \ - $(EXTDIR)/aemu_postoffice/client/postoffice.c \ - $(EXTDIR)/aemu_postoffice/client/postoffice_mem_stdc.c + $(EXTDIR)/aemu_postoffice_client/postoffice.c \ + $(EXTDIR)/aemu_postoffice_client/postoffice_mem_stdc.c SOURCES_CXX += \ - $(EXTDIR)/aemu_postoffice/client/mutex_impl_cpp.cpp \ - $(EXTDIR)/aemu_postoffice/client/delay_impl_cpp.cpp \ - $(EXTDIR)/aemu_postoffice/client/log_impl_ppsspp.cpp \ + $(EXTDIR)/aemu_postoffice_client/mutex_impl_cpp.cpp \ + $(EXTDIR)/aemu_postoffice_client/log_impl_ppsspp.cpp ifeq ($(PLATFORM_EXT), win32) SOURCES_C += \ - $(EXTDIR)/aemu_postoffice/client/sock_impl_windows.c + $(EXTDIR)/aemu_postoffice_client/sock_impl_windows.c else SOURCES_C += \ - $(EXTDIR)/aemu_postoffice/client/sock_impl_linux.c + $(EXTDIR)/aemu_postoffice_client/sock_impl_linux.c endif SOURCES_CXX += \ diff --git a/unittest/TestNetWait.cpp b/unittest/TestNetWait.cpp new file mode 100644 index 0000000000..99d74ca386 --- /dev/null +++ b/unittest/TestNetWait.cpp @@ -0,0 +1,79 @@ +// Socket waits: a wake or a cancel ends one at once, data ends one, and a timeout still does. + +#include "Common/Net/SocketCompat.h" + +#include + +#include "Common/Net/Cancel.h" +#include "Common/Net/HTTPClient.h" +#include "Common/Net/Resolve.h" +#include "Common/TimeUtil.h" + +#include "UnitTest.h" + +static uintptr_t LoopbackUDP(sockaddr_in *addr) { + uintptr_t s = (uintptr_t)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + memset(addr, 0, sizeof(*addr)); + addr->sin_family = AF_INET; + addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + socklen_t len = sizeof(*addr); + bind(s, (sockaddr *)addr, sizeof(*addr)); + getsockname(s, (sockaddr *)addr, &len); + return s; +} + +bool TestNetWait() { + net::Init(); + + // A wake ends a wait that has no socket and no timeout, whether it comes first or second. + { + net::WakeSocket wake; + EXPECT_TRUE(wake.Fd() != -1); + std::thread waker([&] { wake.Wake(); }); + EXPECT_EQ_INT((int)net::WaitSocketsOrWake(nullptr, nullptr, 0, false, -1.0, &wake), (int)net::WaitResult::CANCELLED); + waker.join(); + wake.Drain(); + // Drained, so nothing is pending: a zero timeout times out. + EXPECT_EQ_INT((int)net::WaitSocketsOrWake(nullptr, nullptr, 0, false, 0.0, &wake), (int)net::WaitResult::TIMEOUT); + } + + // Data ends a wait; a quiet socket times out. + { + sockaddr_in addrA, addrB; + uintptr_t a = LoopbackUDP(&addrA); + uintptr_t b = LoopbackUDP(&addrB); + EXPECT_EQ_INT((int)net::WaitSocket(a, false, 0.05, nullptr), (int)net::WaitResult::TIMEOUT); + const char x = 'x'; + sendto(b, &x, 1, 0, (sockaddr *)&addrA, sizeof(addrA)); + EXPECT_EQ_INT((int)net::WaitSocket(a, false, 5.0, nullptr), (int)net::WaitResult::READY); + closesocket(a); + closesocket(b); + } + + // A cancelled token ends every wait on it, before or during, and stays cancelled. + { + sockaddr_in addr; + uintptr_t a = LoopbackUDP(&addr); + net::CancelToken cancel; + std::thread canceller([&] { cancel.Cancel(); }); + EXPECT_EQ_INT((int)net::WaitSocket(a, false, -1.0, &cancel), (int)net::WaitResult::CANCELLED); + canceller.join(); + EXPECT_EQ_INT((int)net::WaitSocket(a, false, -1.0, &cancel), (int)net::WaitResult::CANCELLED); + closesocket(a); + } + + // A cancel ends a connect that would otherwise wait out its timeout (nothing answers here). + { + http::Client client(nullptr); + if (client.Resolve("10.255.255.1", 9)) { + net::CancelToken cancel; + const double start = time_now_d(); + std::thread canceller([&] { cancel.Cancel(); }); + EXPECT_FALSE(client.Connect(1, 30.0, &cancel)); + canceller.join(); + EXPECT_TRUE(time_now_d() - start < 5.0); + } + } + + return true; +} diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 32c0108a1b..a6f417863c 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -2938,6 +2938,7 @@ bool TestShaderGenerators(); bool TestSoftwareGPUJit(); bool TestIRPassSimplify(); bool TestThreadManager(); +bool TestNetWait(); bool TestVFS(); bool TestZipSlip(); bool TestLzrc(); @@ -3141,6 +3142,7 @@ TestItem availableTests[] = { TEST_ITEM(Path), TEST_ITEM(AndroidContentURI), TEST_ITEM(ThreadManager), + TEST_ITEM(NetWait), TEST_ITEM(WrapText), TEST_ITEM(TinySet), TEST_ITEM(FastVec),