diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3e9d0268f6..2ebc3ce615 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -804,6 +804,8 @@ add_library(Common STATIC
Common/Math/math_util.h
Common/Math/Statistics.h
Common/Math/Statistics.cpp
+ Common/Net/Connection.cpp
+ Common/Net/Connection.h
Common/Net/HTTPClient.cpp
Common/Net/HTTPClient.h
Common/Net/HTTPHeaders.cpp
diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj
index 7bea24a69d..c4a706bfaa 100644
--- a/Common/Common.vcxproj
+++ b/Common/Common.vcxproj
@@ -451,6 +451,7 @@
+
@@ -906,6 +907,7 @@
+
diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters
index 10c2cc8417..1b17924f6a 100644
--- a/Common/Common.vcxproj.filters
+++ b/Common/Common.vcxproj.filters
@@ -689,6 +689,9 @@
Audio
+
+ Net
+
@@ -1290,6 +1293,9 @@
ext\imgui
+
+ Net
+
diff --git a/Common/Net/Connection.cpp b/Common/Net/Connection.cpp
new file mode 100644
index 0000000000..b8c587bf5b
--- /dev/null
+++ b/Common/Net/Connection.cpp
@@ -0,0 +1,205 @@
+#include
+#include
+#include
+
+#include
+
+#include "Common/File/Path.h"
+
+#include "Common/TimeUtil.h"
+#include "Common/Log.h"
+#include "Common/Net/SocketCompat.h"
+#include "Common/Buffer.h"
+#include "Common/File/FileDescriptor.h"
+#include "Common/SysError.h"
+#include "Common/Net/Connection.h"
+
+namespace net {
+
+Connection::~Connection() {
+ Disconnect();
+ if (resolved_ != nullptr)
+ DNSResolveFree(resolved_);
+}
+
+// For whatever crazy reason, htons isn't available on android x86 on the build server. so here we go.
+
+// TODO: Fix for big-endian
+inline unsigned short myhtons(unsigned short x) {
+ return (x >> 8) | (x << 8);
+}
+
+const char *DNSTypeAsString(DNSType type) {
+ switch (type) {
+ case DNSType::IPV4:
+ return "IPV4";
+ case DNSType::IPV6:
+ return "IPV6";
+ case DNSType::ANY:
+ return "ANY";
+ default:
+ return "N/A";
+ }
+}
+
+bool Connection::Resolve(const char *host, int port, DNSType type) {
+ if ((intptr_t)sock_ != -1) {
+ ERROR_LOG(Log::IO, "Resolve: Already have a socket");
+ return false;
+ }
+ if (!host || port < 1 || port > 65535) {
+ ERROR_LOG(Log::IO, "Resolve: Invalid host or port (%d)", port);
+ return false;
+ }
+
+ host_ = host;
+ port_ = port;
+
+ char port_str[16];
+ snprintf(port_str, sizeof(port_str), "%d", port);
+
+ std::string processedHostname(host);
+
+ if (customResolve_) {
+ processedHostname = customResolve_(host);
+ }
+
+ std::string err;
+ if (!net::DNSResolve(processedHostname.c_str(), port_str, &resolved_, err, type)) {
+ WARN_LOG(Log::IO, "Failed to resolve host '%s': '%s' (%s)", host, err.c_str(), DNSTypeAsString(type));
+ // Zero port so that future calls fail.
+ port_ = 0;
+ return false;
+ }
+
+ return true;
+}
+
+static void FormatAddr(char *addrbuf, size_t bufsize, const addrinfo *info) {
+ switch (info->ai_family) {
+ case AF_INET:
+ case AF_INET6:
+ inet_ntop(info->ai_family, &((sockaddr_in *)info->ai_addr)->sin_addr, addrbuf, bufsize);
+ break;
+ default:
+ snprintf(addrbuf, bufsize, "(Unknown AF %d)", info->ai_family);
+ break;
+ }
+}
+
+bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) {
+ if (port_ <= 0) {
+ ERROR_LOG(Log::IO, "Bad port");
+ return false;
+ }
+ sock_ = -1;
+
+ 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;
+
+ int sock = socket(possible->ai_family, SOCK_STREAM, IPPROTO_TCP);
+ if ((intptr_t)sock == -1) {
+ ERROR_LOG(Log::IO, "Bad socket");
+ continue;
+ }
+ // Windows sockets aren't limited by socket number, just by count, so checking FD_SETSIZE there is wrong.
+#if !PPSSPP_PLATFORM(WINDOWS)
+ if (sock >= FD_SETSIZE) {
+ ERROR_LOG(Log::IO, "Socket doesn't fit in FD_SET: %d We probably have a leak.", sock);
+ closesocket(sock);
+ continue;
+ }
+#endif
+ fd_util::SetNonBlocking(sock, true);
+
+ // Start trying to connect (async with timeout.)
+ errno = 0;
+ if (connect(sock, possible->ai_addr, (int)possible->ai_addrlen) < 0) {
+ int errorCode = socket_errno;
+ std::string errorString = GetStringErrorMsg(errorCode);
+ bool unreachable = errorCode == ENETUNREACH;
+ bool inProgress = errorCode == EINPROGRESS || errorCode == EWOULDBLOCK;
+ if (!inProgress) {
+ char addrStr[128]{};
+ FormatAddr(addrStr, sizeof(addrStr), possible);
+ if (!unreachable) {
+ ERROR_LOG(Log::HTTP, "connect(%d) call to %s failed (%d: %s)", sock, addrStr, errorCode, errorString.c_str());
+ } else {
+ INFO_LOG(Log::HTTP, "connect(%d): Ignoring unreachable resolved address %s", sock, addrStr);
+ }
+ closesocket(sock);
+ continue;
+ }
+ }
+ sockets.push_back(sock);
+ FD_SET(sock, &fds);
+ if (maxfd < sock + 1) {
+ maxfd = sock + 1;
+ }
+ }
+
+ 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) {
+ // 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;
+ } else {
+ closesocket(sock);
+ }
+ }
+
+ // 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::HTTP, "connect: cancelled (2): %s:%d", host_.c_str(), port_);
+ break;
+ }
+
+ sleep_ms(1, "connect");
+ }
+
+ // Nothing connected, unfortunately.
+ return false;
+}
+
+void Connection::Disconnect() {
+ if ((intptr_t)sock_ != -1) {
+ closesocket(sock_);
+ sock_ = -1;
+ }
+}
+
+} // net
diff --git a/Common/Net/Connection.h b/Common/Net/Connection.h
new file mode 100644
index 0000000000..abfe9bbb08
--- /dev/null
+++ b/Common/Net/Connection.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "Common/Net/Resolve.h"
+
+namespace net {
+
+typedef std::function ResolveFunc;
+
+class Connection {
+public:
+ virtual ~Connection();
+
+ explicit Connection(ResolveFunc func) : customResolve_(func) {}
+
+ // Inits the sockaddr_in.
+ bool Resolve(const char *host, int port, DNSType type = DNSType::ANY);
+
+ bool Connect(int maxTries = 2, double timeout = 20.0f, bool *cancelConnect = nullptr);
+ void Disconnect();
+
+ // Only to be used for bring-up and debugging.
+ uintptr_t sock() const { return sock_; }
+
+protected:
+ // Store the remote host here, so we can send it along through HTTP/1.1 requests.
+ // TODO: Move to http::client?
+ std::string host_;
+ int port_ = -1;
+
+ addrinfo *resolved_ = nullptr;
+
+private:
+ uintptr_t sock_ = -1;
+ ResolveFunc customResolve_;
+};
+
+} // namespace net
diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp
index e17565f2c0..ea05fb50b7 100644
--- a/Common/Net/HTTPClient.cpp
+++ b/Common/Net/HTTPClient.cpp
@@ -3,7 +3,6 @@
#include
#include "Common/Net/HTTPClient.h"
-
#include "Common/TimeUtil.h"
#include "Common/StringUtils.h"
#include "Common/System/OSD.h"
@@ -19,196 +18,6 @@
#include "Common/Net/NetBuffer.h"
#include "Common/Log.h"
-namespace net {
-
-Connection::~Connection() {
- Disconnect();
- if (resolved_ != nullptr)
- DNSResolveFree(resolved_);
-}
-
-// For whatever crazy reason, htons isn't available on android x86 on the build server. so here we go.
-
-// TODO: Fix for big-endian
-inline unsigned short myhtons(unsigned short x) {
- return (x >> 8) | (x << 8);
-}
-
-const char *DNSTypeAsString(DNSType type) {
- switch (type) {
- case DNSType::IPV4:
- return "IPV4";
- case DNSType::IPV6:
- return "IPV6";
- case DNSType::ANY:
- return "ANY";
- default:
- return "N/A";
- }
-}
-
-bool Connection::Resolve(const char *host, int port, DNSType type) {
- if ((intptr_t)sock_ != -1) {
- ERROR_LOG(Log::IO, "Resolve: Already have a socket");
- return false;
- }
- if (!host || port < 1 || port > 65535) {
- ERROR_LOG(Log::IO, "Resolve: Invalid host or port (%d)", port);
- return false;
- }
-
- host_ = host;
- port_ = port;
-
- char port_str[16];
- snprintf(port_str, sizeof(port_str), "%d", port);
-
- std::string processedHostname(host);
-
- if (customResolve_) {
- processedHostname = customResolve_(host);
- }
-
- std::string err;
- if (!net::DNSResolve(processedHostname.c_str(), port_str, &resolved_, err, type)) {
- WARN_LOG(Log::IO, "Failed to resolve host '%s': '%s' (%s)", host, err.c_str(), DNSTypeAsString(type));
- // Zero port so that future calls fail.
- port_ = 0;
- return false;
- }
-
- return true;
-}
-
-static void FormatAddr(char *addrbuf, size_t bufsize, const addrinfo *info) {
- switch (info->ai_family) {
- case AF_INET:
- case AF_INET6:
- inet_ntop(info->ai_family, &((sockaddr_in *)info->ai_addr)->sin_addr, addrbuf, bufsize);
- break;
- default:
- snprintf(addrbuf, bufsize, "(Unknown AF %d)", info->ai_family);
- break;
- }
-}
-
-bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) {
- if (port_ <= 0) {
- ERROR_LOG(Log::IO, "Bad port");
- return false;
- }
- sock_ = -1;
-
- 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;
-
- int sock = socket(possible->ai_family, SOCK_STREAM, IPPROTO_TCP);
- if ((intptr_t)sock == -1) {
- ERROR_LOG(Log::IO, "Bad socket");
- continue;
- }
- // Windows sockets aren't limited by socket number, just by count, so checking FD_SETSIZE there is wrong.
-#if !PPSSPP_PLATFORM(WINDOWS)
- if (sock >= FD_SETSIZE) {
- ERROR_LOG(Log::IO, "Socket doesn't fit in FD_SET: %d We probably have a leak.", sock);
- closesocket(sock);
- continue;
- }
-#endif
- fd_util::SetNonBlocking(sock, true);
-
- // Start trying to connect (async with timeout.)
- errno = 0;
- if (connect(sock, possible->ai_addr, (int)possible->ai_addrlen) < 0) {
- int errorCode = socket_errno;
- std::string errorString = GetStringErrorMsg(errorCode);
- bool unreachable = errorCode == ENETUNREACH;
- bool inProgress = errorCode == EINPROGRESS || errorCode == EWOULDBLOCK;
- if (!inProgress) {
- char addrStr[128]{};
- FormatAddr(addrStr, sizeof(addrStr), possible);
- if (!unreachable) {
- ERROR_LOG(Log::HTTP, "connect(%d) call to %s failed (%d: %s)", sock, addrStr, errorCode, errorString.c_str());
- } else {
- INFO_LOG(Log::HTTP, "connect(%d): Ignoring unreachable resolved address %s", sock, addrStr);
- }
- closesocket(sock);
- continue;
- }
- }
- sockets.push_back(sock);
- FD_SET(sock, &fds);
- if (maxfd < sock + 1) {
- maxfd = sock + 1;
- }
- }
-
- 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) {
- // 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;
- } else {
- closesocket(sock);
- }
- }
-
- // 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::HTTP, "connect: cancelled (2): %s:%d", host_.c_str(), port_);
- break;
- }
-
- sleep_ms(1, "connect");
- }
-
- // Nothing connected, unfortunately.
- return false;
-}
-
-void Connection::Disconnect() {
- if ((intptr_t)sock_ != -1) {
- closesocket(sock_);
- sock_ = -1;
- }
-}
-
-} // net
-
namespace http {
// TODO: do something sane here
diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h
index c8a147db56..397e3d1683 100644
--- a/Common/Net/HTTPClient.h
+++ b/Common/Net/HTTPClient.h
@@ -11,39 +11,7 @@
#include "Common/Net/Resolve.h"
#include "Common/Net/HTTPRequest.h"
-namespace net {
-
-typedef std::function ResolveFunc;
-
-class Connection {
-public:
- virtual ~Connection();
-
- explicit Connection(ResolveFunc func) : customResolve_(func) {}
-
- // Inits the sockaddr_in.
- bool Resolve(const char *host, int port, DNSType type = DNSType::ANY);
-
- bool Connect(int maxTries = 2, double timeout = 20.0f, bool *cancelConnect = nullptr);
- void Disconnect();
-
- // Only to be used for bring-up and debugging.
- uintptr_t sock() const { return sock_; }
-
-protected:
- // Store the remote host here, so we can send it along through HTTP/1.1 requests.
- // TODO: Move to http::client?
- std::string host_;
- int port_ = -1;
-
- addrinfo *resolved_ = nullptr;
-
-private:
- uintptr_t sock_ = -1;
- ResolveFunc customResolve_;
-};
-
-} // namespace net
+#include "Common/Net/Connection.h"
namespace http {
diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj
index 46d2098b88..2d95008f9d 100644
--- a/UWP/CommonUWP/CommonUWP.vcxproj
+++ b/UWP/CommonUWP/CommonUWP.vcxproj
@@ -99,6 +99,7 @@
+
@@ -285,6 +286,7 @@
+
diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters
index 2da873848c..e2c9ed7cf6 100644
--- a/UWP/CommonUWP/CommonUWP.vcxproj.filters
+++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters
@@ -520,6 +520,9 @@
ext\imgui
+
+ Net
+
@@ -999,6 +1002,9 @@
ext\imgui
+
+ Net
+
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index aa551d1152..48849ec3a6 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -336,6 +336,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Common/Math/expression_parser.cpp \
$(SRC)/Common/Math/lin/vec3.cpp.arm \
$(SRC)/Common/Math/lin/matrix4x4.cpp.arm \
+ $(SRC)/Common/Net/Connection.cpp \
$(SRC)/Common/Net/HTTPClient.cpp \
$(SRC)/Common/Net/HTTPHeaders.cpp \
$(SRC)/Common/Net/HTTPRequest.cpp \
diff --git a/libretro/Makefile.common b/libretro/Makefile.common
index 28a0cacb24..68c260b285 100644
--- a/libretro/Makefile.common
+++ b/libretro/Makefile.common
@@ -441,6 +441,7 @@ SOURCES_CXX += \
$(COMMONDIR)/Math/Statistics.cpp \
$(COMMONDIR)/Math/lin/vec3.cpp \
$(COMMONDIR)/Math/lin/matrix4x4.cpp \
+ $(COMMONDIR)/Net/Connection.cpp \
$(COMMONDIR)/Net/HTTPClient.cpp \
$(COMMONDIR)/Net/HTTPHeaders.cpp \
$(COMMONDIR)/Net/HTTPServer.cpp \