Files
KytyPS5/src/libs/network.cpp
T

4394 lines
114 KiB
C++

#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <winsock2.h>
#include <windows.h>
#include <iphlpapi.h>
#include <ws2tcpip.h>
#ifdef s_addr
#undef s_addr
#endif
#else
#include <arpa/inet.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/route.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/threads.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "libs/network.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdio>
#include <cstring>
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <fstream>
#include <limits>
#include <memory>
#include <mutex>
#include <optional>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
namespace Libs::Network {
class Network {
public:
class Id {
public:
static constexpr int MAX_ID = 65536;
enum class Type : uint32_t {
Invalid = 0,
Http = 1,
Ssl = 2,
Template = 3,
Connection = 4,
Request = 5,
};
explicit Id(int id)
: m_id(static_cast<uint32_t>(id) & 0xffffu), m_type(static_cast<uint32_t>(id) >> 16u) {}
[[nodiscard]] int ToInt() const {
return static_cast<int>(m_id + (static_cast<uint32_t>(m_type) << 16u));
}
[[nodiscard]] bool IsValid() const { return GetType() != Type::Invalid; }
[[nodiscard]] Type GetType() const {
switch (m_type) {
case static_cast<uint32_t>(Type::Http): return Type::Http;
case static_cast<uint32_t>(Type::Ssl): return Type::Ssl;
case static_cast<uint32_t>(Type::Template): return Type::Template;
case static_cast<uint32_t>(Type::Connection): return Type::Connection;
case static_cast<uint32_t>(Type::Request): return Type::Request;
default: return Type::Invalid;
}
}
friend class Network;
private:
Id() = default;
static Id Invalid() { return {}; }
static Id Create(int net_id, Type type) {
Id r;
r.m_id = net_id;
r.m_type = static_cast<uint32_t>(type);
return r;
}
[[nodiscard]] int GetId() const { return static_cast<int>(m_id); }
uint32_t m_id = 0;
uint32_t m_type = static_cast<uint32_t>(Type::Invalid);
};
using HttpsCallback = KYTY_SYSV_ABI int (*)(int, unsigned int, void* const*, int, void*);
Network() = default;
virtual ~Network() = default;
KYTY_CLASS_NO_COPY(Network);
int PoolCreate(const char* name, int size);
bool PoolDestroy(int memid);
int ResolverCreate(const char* name, int memid);
bool ResolverDestroy(int rid);
bool ResolverValid(int rid);
Id SslInit(uint64_t pool_size);
bool SslTerm(Id ssl_ctx_id);
bool SslValid(Id ssl_ctx_id);
Id HttpInit(int memid, Id ssl_ctx_id, uint64_t pool_size);
bool HttpTerm(Id http_ctx_id);
Id HttpCreateTemplate(Id http_ctx_id, const char* user_agent, int http_ver,
bool is_auto_proxy_conf);
bool HttpDeleteTemplate(Id tmpl_id);
bool HttpSetNonblock(Id id, bool enable);
bool HttpsSetSslCallback(Id id, HttpsCallback cbfunc, void* user_arg);
bool HttpsSetMinSslVersion(Id id, uint32_t ssl_version);
bool HttpsDisableOption(Id id, uint32_t ssl_flags);
bool HttpAddRequestHeader(Id id, const char* name, const char* value, bool add);
bool HttpValid(Id http_ctx_id);
bool HttpValidTemplate(Id tmpl_id);
bool HttpValidConnection(Id conn_id);
bool HttpValidRequest(Id req_id);
Id HttpCreateConnection(Id tmpl_id, const char* server_name, const char* scheme, uint16_t port,
bool enable_keep_alive);
Id HttpCreateConnectionWithURL(Id tmpl_id, const char* url, bool enable_keep_alive);
bool HttpDeleteConnection(Id conn_id);
Id HttpCreateRequestWithURL2(Id conn_id, const char* method, const char* url,
uint64_t content_length);
bool HttpSetRequestContentLength(Id req_id, uint64_t content_length);
bool HttpDeleteRequest(Id req_id);
bool HttpSetResolveTimeOut(Id id, uint32_t usec);
bool HttpSetResolveRetry(Id id, int32_t retry);
bool HttpSetConnectTimeOut(Id id, uint32_t usec);
bool HttpSetSendTimeOut(Id id, uint32_t usec);
bool HttpSetRecvTimeOut(Id id, uint32_t usec);
bool HttpSetAutoRedirect(Id id, int enable);
bool HttpSetAuthEnabled(Id id, int enable);
bool HttpMarkRequestSent(Id req_id, int result);
bool HttpGetRequestResponse(Id req_id, int* send_result, int* status_code, const char** headers,
size_t* headers_size, uint64_t* content_length);
private:
struct Pool {
bool used = false;
std::string name;
int size = 0;
};
struct Ssl {
bool used = false;
uint64_t size = 0;
};
struct Resolver {
bool used = false;
std::string name;
int memid = 0;
};
struct Http {
bool used = false;
uint64_t size = 0;
int memid = 0;
int ssl_ctx_id = 0;
};
struct HttpHeader {
std::string name;
std::string value;
};
struct HttpBase {
std::vector<HttpHeader> headers;
bool used = false;
bool nonblock = false;
bool auto_redirect = true;
bool auth_enabled = true;
HttpsCallback ssl_cbfunc = nullptr;
void* ssl_user_arg = nullptr;
uint32_t ssl_flags = 0xA7;
uint32_t min_ssl_version = 0;
int http_ctx_id = 0;
uint32_t resolve_timeout = 1'000000;
int32_t resolve_retry = 4;
uint32_t connect_timeout = 30'000000;
uint32_t send_timeout = 120'000000;
uint32_t recv_timeout = 120'000000;
};
struct HttpTemplate: public HttpBase {
std::string user_agent;
int http_ver = 0;
bool is_auto_proxy_conf = true;
};
struct HttpConnection: public HttpTemplate {
explicit HttpConnection(const HttpTemplate& tmpl): HttpTemplate(tmpl) {}
// int tmpl_id = 0;
std::string url;
bool enable_keep_alive = false;
};
struct HttpRequest: public HttpConnection {
explicit HttpRequest(HttpConnection& conn): HttpConnection(conn) {}
// int conn_id = 0;
std::string method;
std::string url;
uint64_t content_length = 0;
bool send_attempted = false;
int send_result = HTTP_ERROR_BEFORE_SEND;
int status_code = 0;
std::string response_headers;
};
HttpBase* FindHttpBase(Id id, bool include_request);
static constexpr int POOLS_MAX = 32;
static constexpr int RESOLVERS_MAX = 32;
static constexpr int SSL_MAX = 32;
static constexpr int HTTP_MAX = 32;
Common::Mutex m_mutex;
Pool m_pools[POOLS_MAX];
Resolver m_resolvers[RESOLVERS_MAX];
Ssl m_ssl[SSL_MAX];
Http m_http[HTTP_MAX];
std::vector<HttpTemplate> m_templates;
std::vector<HttpConnection> m_connections;
std::vector<HttpRequest> m_requests;
};
static Network* g_net = nullptr;
void Initialize() {
EXIT_IF(g_net != nullptr);
g_net = new Network;
}
void Shutdown() {
delete g_net;
g_net = nullptr;
}
int Network::PoolCreate(const char* name, int size) {
Common::LockGuard lock(m_mutex);
for (int id = 1; id < POOLS_MAX; id++) {
if (!m_pools[id].used) {
m_pools[id].used = true;
m_pools[id].size = size;
m_pools[id].name = std::string(name);
return id;
}
}
return -1;
}
bool Network::PoolDestroy(int memid) {
Common::LockGuard lock(m_mutex);
if (memid >= 0 && memid < POOLS_MAX && m_pools[memid].used) {
m_pools[memid].used = false;
return true;
}
return false;
}
int Network::ResolverCreate(const char* name, int memid) {
Common::LockGuard lock(m_mutex);
if (memid < 0 || memid >= POOLS_MAX || !m_pools[memid].used) {
return -1;
}
for (int id = 0; id < RESOLVERS_MAX; id++) {
if (!m_resolvers[id].used) {
m_resolvers[id].used = true;
m_resolvers[id].name = std::string(name);
m_resolvers[id].memid = memid;
return id;
}
}
return -1;
}
bool Network::ResolverDestroy(int rid) {
Common::LockGuard lock(m_mutex);
if (rid >= 0 && rid < RESOLVERS_MAX && m_resolvers[rid].used) {
m_resolvers[rid] = {};
return true;
}
return false;
}
bool Network::ResolverValid(int rid) {
Common::LockGuard lock(m_mutex);
return rid >= 0 && rid < RESOLVERS_MAX && m_resolvers[rid].used;
}
Network::Id Network::SslInit(uint64_t pool_size) {
Common::LockGuard lock(m_mutex);
for (int id = 0; id < SSL_MAX; id++) {
if (!m_ssl[id].used) {
m_ssl[id].used = true;
m_ssl[id].size = pool_size;
return Id::Create(id, Id::Type::Ssl);
}
}
return Id::Invalid();
}
bool Network::SslTerm(Id ssl_ctx_id) {
Common::LockGuard lock(m_mutex);
if (ssl_ctx_id.GetType() == Id::Type::Ssl && ssl_ctx_id.GetId() >= 0 &&
ssl_ctx_id.GetId() < SSL_MAX && m_ssl[ssl_ctx_id.GetId()].used) {
m_ssl[ssl_ctx_id.GetId()].used = false;
return true;
}
return false;
}
bool Network::SslValid(Id ssl_ctx_id) {
Common::LockGuard lock(m_mutex);
return ssl_ctx_id.GetType() == Id::Type::Ssl && ssl_ctx_id.GetId() >= 0 &&
ssl_ctx_id.GetId() < SSL_MAX && m_ssl[ssl_ctx_id.GetId()].used;
}
Network::Id Network::HttpInit(int memid, Id ssl_ctx_id, uint64_t pool_size) {
Common::LockGuard lock(m_mutex);
if (ssl_ctx_id.GetType() == Id::Type::Ssl && ssl_ctx_id.GetId() >= 0 &&
ssl_ctx_id.GetId() < SSL_MAX && m_ssl[ssl_ctx_id.GetId()].used && memid >= 0 &&
memid < POOLS_MAX && m_pools[memid].used) {
for (int id = 0; id < HTTP_MAX; id++) {
if (!m_http[id].used) {
m_http[id].used = true;
m_http[id].size = pool_size;
m_http[id].ssl_ctx_id = ssl_ctx_id.GetId();
m_http[id].memid = memid;
return Id::Create(id, Id::Type::Http);
}
}
}
return Id::Invalid();
}
bool Network::HttpValid(Id http_ctx_id) {
Common::LockGuard lock(m_mutex);
return (http_ctx_id.GetType() == Id::Type::Http && http_ctx_id.GetId() >= 0 &&
http_ctx_id.GetId() < HTTP_MAX && m_http[http_ctx_id.GetId()].used);
}
bool Network::HttpValidTemplate(Id tmpl_id) {
Common::LockGuard lock(m_mutex);
return (tmpl_id.GetType() == Id::Type::Template && tmpl_id.GetId() >= 0 &&
static_cast<size_t>(tmpl_id.GetId()) < m_templates.size() &&
m_templates[tmpl_id.GetId()].used);
}
bool Network::HttpValidConnection(Id conn_id) {
Common::LockGuard lock(m_mutex);
return (conn_id.GetType() == Id::Type::Connection && conn_id.GetId() >= 0 &&
static_cast<size_t>(conn_id.GetId()) < m_connections.size() &&
m_connections[conn_id.GetId()].used);
}
bool Network::HttpValidRequest(Id req_id) {
Common::LockGuard lock(m_mutex);
return (req_id.GetType() == Id::Type::Request && req_id.GetId() >= 0 &&
static_cast<size_t>(req_id.GetId()) < m_requests.size() &&
m_requests[req_id.GetId()].used);
}
Network::HttpBase* Network::FindHttpBase(Id id, bool include_request) {
const auto index = id.GetId();
switch (id.GetType()) {
case Id::Type::Template:
if (index >= 0 && static_cast<size_t>(index) < m_templates.size() &&
m_templates[index].used) {
return &m_templates[index];
}
break;
case Id::Type::Connection:
if (index >= 0 && static_cast<size_t>(index) < m_connections.size() &&
m_connections[index].used) {
return &m_connections[index];
}
break;
case Id::Type::Request:
if (include_request && index >= 0 && static_cast<size_t>(index) < m_requests.size() &&
m_requests[index].used) {
return &m_requests[index];
}
break;
default: break;
}
return nullptr;
}
bool Network::HttpTerm(Id http_ctx_id) {
Common::LockGuard lock(m_mutex);
if (HttpValid(http_ctx_id)) {
m_http[http_ctx_id.GetId()].used = false;
return true;
}
return false;
}
Network::Id Network::HttpCreateTemplate(Id http_ctx_id, const char* user_agent, int http_ver,
bool is_auto_proxy_conf) {
Common::LockGuard lock(m_mutex);
if (HttpValid(http_ctx_id)) {
HttpTemplate tn {};
tn.used = true;
tn.http_ver = http_ver;
tn.user_agent = std::string(user_agent);
tn.is_auto_proxy_conf = is_auto_proxy_conf;
tn.http_ctx_id = http_ctx_id.GetId();
tn.nonblock = false;
int index = 0;
for (auto& t: m_templates) {
if (!t.used) {
t = tn;
return Id::Create(index, Id::Type::Template);
}
index++;
}
if (index < Id::MAX_ID) {
m_templates.push_back(tn);
return Id::Create(index, Id::Type::Template);
}
}
return Id::Invalid();
}
Network::Id Network::HttpCreateConnectionWithURL(Id tmpl_id, const char* url,
bool enable_keep_alive) {
Common::LockGuard lock(m_mutex);
if (url != nullptr && url[0] != '\0' && HttpValidTemplate(tmpl_id)) {
HttpConnection cn(m_templates[tmpl_id.GetId()]);
cn.used = true;
cn.enable_keep_alive = enable_keep_alive;
cn.url = std::string(url);
// cn.tmpl_id = tmpl_id.ToInt();
int index = 0;
for (auto& t: m_connections) {
if (!t.used) {
t = cn;
return Id::Create(index, Id::Type::Connection);
}
index++;
}
if (index < Id::MAX_ID) {
m_connections.push_back(cn);
return Id::Create(index, Id::Type::Connection);
}
}
return Id::Invalid();
}
Network::Id Network::HttpCreateConnection(Id tmpl_id, const char* server_name, const char* scheme,
uint16_t port, bool enable_keep_alive) {
Common::LockGuard lock(m_mutex);
if (server_name != nullptr && scheme != nullptr && HttpValidTemplate(tmpl_id)) {
HttpConnection cn(m_templates[tmpl_id.GetId()]);
cn.used = true;
cn.enable_keep_alive = enable_keep_alive;
cn.url = std::string(scheme) + "://" + std::string(server_name);
if (port != 0) {
cn.url = cn.url + fmt::format(":{}", static_cast<unsigned>(port));
}
int index = 0;
for (auto& t: m_connections) {
if (!t.used) {
t = cn;
return Id::Create(index, Id::Type::Connection);
}
index++;
}
if (index < Id::MAX_ID) {
m_connections.push_back(cn);
return Id::Create(index, Id::Type::Connection);
}
}
return Id::Invalid();
}
bool Network::HttpDeleteConnection(Id conn_id) {
Common::LockGuard lock(m_mutex);
if (HttpValidConnection(conn_id)) {
m_connections[conn_id.GetId()].used = false;
return true;
}
return false;
}
Network::Id Network::HttpCreateRequestWithURL2(Id conn_id, const char* method, const char* url,
uint64_t content_length) {
Common::LockGuard lock(m_mutex);
if (method != nullptr && url != nullptr && url[0] != '\0' && HttpValidConnection(conn_id)) {
HttpRequest cn(m_connections[conn_id.GetId()]);
cn.used = true;
cn.method = std::string(method);
cn.url = std::string(url);
// cn.conn_id = conn_id.ToInt();
cn.content_length = content_length;
int index = 0;
for (auto& t: m_requests) {
if (!t.used) {
t = cn;
return Id::Create(index, Id::Type::Request);
}
index++;
}
if (index < Id::MAX_ID) {
m_requests.push_back(cn);
return Id::Create(index, Id::Type::Request);
}
}
return Id::Invalid();
}
bool Network::HttpDeleteRequest(Id req_id) {
Common::LockGuard lock(m_mutex);
if (HttpValidRequest(req_id)) {
m_requests[req_id.GetId()].used = false;
return true;
}
return false;
}
bool Network::HttpSetRequestContentLength(Id req_id, uint64_t content_length) {
Common::LockGuard lock(m_mutex);
if (HttpValidRequest(req_id)) {
m_requests[req_id.GetId()].content_length = content_length;
return true;
}
return false;
}
bool Network::HttpMarkRequestSent(Id req_id, int result) {
Common::LockGuard lock(m_mutex);
if (req_id.GetType() == Id::Type::Request && req_id.GetId() >= 0 &&
static_cast<size_t>(req_id.GetId()) < m_requests.size() &&
m_requests[req_id.GetId()].used) {
auto& request = m_requests[req_id.GetId()];
request.send_attempted = true;
request.send_result = result;
request.status_code = (result == OK ? 200 : 0);
request.response_headers.clear();
return true;
}
return false;
}
bool Network::HttpGetRequestResponse(Id req_id, int* send_result, int* status_code,
const char** headers, size_t* headers_size,
uint64_t* content_length) {
Common::LockGuard lock(m_mutex);
if (req_id.GetType() != Id::Type::Request || req_id.GetId() < 0 ||
static_cast<size_t>(req_id.GetId()) >= m_requests.size() ||
!m_requests[req_id.GetId()].used) {
return false;
}
const auto& request = m_requests[req_id.GetId()];
if (send_result != nullptr) {
*send_result = (request.send_attempted ? request.send_result : HTTP_ERROR_BEFORE_SEND);
}
if (status_code != nullptr) {
*status_code = request.status_code;
}
if (headers != nullptr) {
*headers = request.response_headers.c_str();
}
if (headers_size != nullptr) {
*headers_size = request.response_headers.size();
}
if (content_length != nullptr) {
*content_length = 0;
}
return true;
}
bool Network::HttpDeleteTemplate(Id tmpl_id) {
Common::LockGuard lock(m_mutex);
if (HttpValidTemplate(tmpl_id)) {
m_templates[tmpl_id.GetId()].used = false;
return true;
}
return false;
}
bool Network::HttpSetNonblock(Id id, bool enable) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, true);
if (base != nullptr) {
base->nonblock = enable;
return true;
}
return false;
}
bool Network::HttpsSetSslCallback(Id id, HttpsCallback cbfunc, void* user_arg) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, true);
if (base != nullptr) {
base->ssl_cbfunc = cbfunc;
base->ssl_user_arg = user_arg;
return true;
}
return false;
}
bool Network::HttpsSetMinSslVersion(Id id, uint32_t ssl_version) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, true);
if (base != nullptr) {
base->min_ssl_version = ssl_version;
return true;
}
return false;
}
bool Network::HttpsDisableOption(Id id, uint32_t ssl_flags) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, true);
if (base != nullptr) {
base->ssl_flags &= ~ssl_flags;
return true;
}
return false;
}
bool Network::HttpAddRequestHeader(Id id, const char* name, const char* value, bool add) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, true);
if (base != nullptr) {
HttpHeader nh({std::string(name), std::string(value)});
if (add) {
base->headers.push_back(nh);
} else {
for (auto& h: base->headers) {
if (h.name == nh.name) {
h.value = nh.value;
}
}
}
return true;
}
return false;
}
bool Network::HttpSetResolveTimeOut(Id id, uint32_t usec) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, false);
if (base != nullptr) {
base->resolve_timeout = usec;
return true;
}
return false;
}
bool Network::HttpSetResolveRetry(Id id, int32_t retry) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, false);
if (base != nullptr) {
base->resolve_retry = retry;
return true;
}
return false;
}
bool Network::HttpSetConnectTimeOut(Id id, uint32_t usec) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, true);
if (base != nullptr) {
base->connect_timeout = usec;
return true;
}
return false;
}
bool Network::HttpSetSendTimeOut(Id id, uint32_t usec) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, true);
if (base != nullptr) {
base->send_timeout = usec;
return true;
}
return false;
}
bool Network::HttpSetRecvTimeOut(Id id, uint32_t usec) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, true);
if (base != nullptr) {
base->recv_timeout = usec;
return true;
}
return false;
}
bool Network::HttpSetAutoRedirect(Id id, int enable) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, true);
if (base != nullptr) {
base->auto_redirect = (enable != 0);
return true;
}
return false;
}
bool Network::HttpSetAuthEnabled(Id id, int enable) {
Common::LockGuard lock(m_mutex);
HttpBase* base = FindHttpBase(id, true);
if (base != nullptr) {
base->auth_enabled = (enable != 0);
return true;
}
return false;
}
namespace Net {
LIB_NAME("Net", "Net");
#if defined(_WIN32)
using NativeSocket = SOCKET;
static constexpr NativeSocket INVALID_NATIVE_SOCKET = INVALID_SOCKET;
using SocketLength = int;
using SocketIoLength = int;
#else
using NativeSocket = int;
static constexpr NativeSocket INVALID_NATIVE_SOCKET = -1;
using SocketLength = socklen_t;
using SocketIoLength = size_t;
#endif
struct SocketTransport {
SocketTransport(NativeSocket value, int socket_type): socket(value), type(socket_type) {}
~SocketTransport() { Close(); }
bool HasPendingReceive() const {
#if defined(_WIN32)
return buffered_bytes.load(std::memory_order_relaxed) != 0 ||
receive_shutdown.load(std::memory_order_relaxed);
#else
return false;
#endif
}
int Close() {
#if defined(_WIN32)
receive_shutdown.store(true, std::memory_order_relaxed);
#endif
const auto value = socket.exchange(INVALID_NATIVE_SOCKET);
if (value == INVALID_NATIVE_SOCKET) {
return 0;
}
#if defined(_WIN32)
return closesocket(value);
#else
return ::close(value);
#endif
}
std::atomic<NativeSocket> socket;
const int type;
#if defined(_WIN32)
std::mutex receive_mutex;
std::vector<char> receive_buffer;
std::atomic_size_t buffered_bytes = 0;
std::atomic_bool receive_shutdown = false;
#endif
};
struct P2pEndpoint {
bool bound = false;
uint32_t address = 0;
uint16_t port = 0;
uint16_t virtual_port = 0;
int nonblocking = 0;
int broadcast = 0;
int send_buffer = 9216;
int receive_buffer = 40 * 1024;
};
struct SocketSlot {
std::shared_ptr<SocketTransport> transport;
std::optional<P2pEndpoint> p2p;
};
struct NetTimeval {
int64_t tv_sec;
int64_t tv_usec;
};
static constexpr int SOCKET_FD_MIN = 128;
static constexpr int SOCKET_FD_MAX = 1024;
static Common::Mutex g_socket_mutex;
static std::array<SocketSlot, SOCKET_FD_MAX> g_sockets;
static std::atomic_bool g_winsock_initialized = false;
constexpr int EPOLL_ID_BASE = SOCKET_FD_MAX;
constexpr uint32_t EPOLL_IN = 0x00000001;
constexpr uint32_t EPOLL_OUT = 0x00000002;
constexpr uint32_t EPOLL_ERR = 0x00000008;
struct EpollRegistration {
int id = -1;
NetEpollEvent event {};
};
struct EpollSlot {
bool used = false;
std::string name;
std::vector<EpollRegistration> registrations;
};
static std::mutex g_epoll_mutex;
static std::condition_variable g_epoll_changed;
static std::array<EpollSlot, 32> g_epolls;
static EpollSlot* GetEpollSlot(int eid) {
const auto index = eid - EPOLL_ID_BASE;
if (index < 0 || index >= static_cast<int>(g_epolls.size())) {
return nullptr;
}
auto& slot = g_epolls[static_cast<size_t>(index)];
return slot.used ? &slot : nullptr;
}
static void RemoveSocketFromEpolls(int id) {
std::lock_guard lock(g_epoll_mutex);
for (auto& epoll: g_epolls) {
if (!epoll.used) {
continue;
}
std::erase_if(epoll.registrations,
[id](const auto& registration) { return registration.id == id; });
}
g_epoll_changed.notify_all();
}
static bool EnsureSocketBackend() {
#if defined(_WIN32)
if (!g_winsock_initialized.load()) {
WSADATA data {};
if (WSAStartup(MAKEWORD(2, 2), &data) != 0) {
return false;
}
g_winsock_initialized = true;
}
#endif
return true;
}
static int SetGuestSocketError(int error) {
*Posix::GetErrorAddr() = error;
return -1;
}
static int ConvertHostSocketError(int error) {
if (error == 0) {
return 0;
}
int posix_error = Posix::POSIX_EIO;
#if defined(_WIN32)
switch (error) {
case WSAEACCES: posix_error = Posix::POSIX_EACCES; break;
case WSAEADDRINUSE: posix_error = Posix::POSIX_EADDRINUSE; break;
case WSAEADDRNOTAVAIL: posix_error = Posix::POSIX_EADDRNOTAVAIL; break;
case WSAEAFNOSUPPORT: posix_error = Posix::POSIX_EAFNOSUPPORT; break;
case WSAEFAULT: posix_error = Posix::POSIX_EFAULT; break;
case WSAEINTR: posix_error = Posix::POSIX_EINTR; break;
case WSAEINVAL: posix_error = Posix::POSIX_EINVAL; break;
case WSAEISCONN: posix_error = Posix::POSIX_EISCONN; break;
case WSAEMFILE: posix_error = Posix::POSIX_EMFILE; break;
case WSAEMSGSIZE: posix_error = Posix::POSIX_EMSGSIZE; break;
case WSAENOBUFS: posix_error = Posix::POSIX_ENOBUFS; break;
case WSAENETDOWN: posix_error = Posix::POSIX_ENETDOWN; break;
case WSAENETRESET: posix_error = Posix::POSIX_ENETRESET; break;
case WSAENETUNREACH: posix_error = Posix::POSIX_ENETUNREACH; break;
case WSAENOTCONN: posix_error = Posix::POSIX_ENOTCONN; break;
case WSAENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break;
case WSAENOPROTOOPT: posix_error = Posix::POSIX_ENOPROTOOPT; break;
case WSAEOPNOTSUPP: posix_error = Posix::POSIX_EOPNOTSUPP; break;
case WSAEPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break;
case WSAESHUTDOWN: posix_error = Posix::POSIX_ESHUTDOWN; break;
case WSAETIMEDOUT: posix_error = Posix::POSIX_ETIMEDOUT; break;
case WSAEWOULDBLOCK: posix_error = Posix::POSIX_EWOULDBLOCK; break;
case WSAECONNABORTED: posix_error = Posix::POSIX_ECONNABORTED; break;
case WSAECONNREFUSED: posix_error = Posix::POSIX_ECONNREFUSED; break;
case WSAECONNRESET: posix_error = Posix::POSIX_ECONNRESET; break;
case WSAEDESTADDRREQ: posix_error = Posix::POSIX_EDESTADDRREQ; break;
case WSAEHOSTUNREACH: posix_error = Posix::POSIX_EHOSTUNREACH; break;
case WSAEINPROGRESS: posix_error = Posix::POSIX_EINPROGRESS; break;
case WSAEALREADY: posix_error = Posix::POSIX_EALREADY; break;
default: break;
}
#else
switch (error) {
case EACCES: posix_error = Posix::POSIX_EACCES; break;
case EADDRINUSE: posix_error = Posix::POSIX_EADDRINUSE; break;
case EADDRNOTAVAIL: posix_error = Posix::POSIX_EADDRNOTAVAIL; break;
case EAFNOSUPPORT: posix_error = Posix::POSIX_EAFNOSUPPORT; break;
case EAGAIN: posix_error = Posix::POSIX_EWOULDBLOCK; break;
case EBADF: posix_error = Posix::POSIX_EBADF; break;
case EFAULT: posix_error = Posix::POSIX_EFAULT; break;
case EINTR: posix_error = Posix::POSIX_EINTR; break;
case EINVAL: posix_error = Posix::POSIX_EINVAL; break;
case EISCONN: posix_error = Posix::POSIX_EISCONN; break;
case EMFILE: posix_error = Posix::POSIX_EMFILE; break;
case EMSGSIZE: posix_error = Posix::POSIX_EMSGSIZE; break;
case ENFILE: posix_error = Posix::POSIX_ENFILE; break;
case ENOBUFS: posix_error = Posix::POSIX_ENOBUFS; break;
case ENOMEM: posix_error = Posix::POSIX_ENOMEM; break;
case ENETDOWN: posix_error = Posix::POSIX_ENETDOWN; break;
case ENETRESET: posix_error = Posix::POSIX_ENETRESET; break;
case ENETUNREACH: posix_error = Posix::POSIX_ENETUNREACH; break;
case ENOTCONN: posix_error = Posix::POSIX_ENOTCONN; break;
case ENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break;
case ENOPROTOOPT: posix_error = Posix::POSIX_ENOPROTOOPT; break;
case EOPNOTSUPP: posix_error = Posix::POSIX_EOPNOTSUPP; break;
case EPIPE: posix_error = Posix::POSIX_EPIPE; break;
case EPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break;
case ESHUTDOWN: posix_error = Posix::POSIX_ESHUTDOWN; break;
case ETIMEDOUT: posix_error = Posix::POSIX_ETIMEDOUT; break;
case ECONNABORTED: posix_error = Posix::POSIX_ECONNABORTED; break;
case ECONNREFUSED: posix_error = Posix::POSIX_ECONNREFUSED; break;
case ECONNRESET: posix_error = Posix::POSIX_ECONNRESET; break;
case EDESTADDRREQ: posix_error = Posix::POSIX_EDESTADDRREQ; break;
case EHOSTUNREACH: posix_error = Posix::POSIX_EHOSTUNREACH; break;
case EINPROGRESS: posix_error = Posix::POSIX_EINPROGRESS; break;
case EALREADY: posix_error = Posix::POSIX_EALREADY; break;
default: break;
}
#endif
return posix_error;
}
static int SetHostSocketError() {
#if defined(_WIN32)
return SetGuestSocketError(ConvertHostSocketError(WSAGetLastError()));
#else
return SetGuestSocketError(ConvertHostSocketError(errno));
#endif
}
static int ConvertFamily(int family) {
switch (family) {
case 2: return AF_INET;
case 28: return AF_INET6;
default: return -1;
}
}
static int ConvertSocketOptionLevel(int level) {
return (level == 0xffff ? SOL_SOCKET : level);
}
static int ConvertSocketOptionName(int level, int option) {
if (level == 0xffff) {
switch (option) {
case 0x0020: return SO_BROADCAST;
case 0x1001: return SO_SNDBUF;
case 0x1002: return SO_RCVBUF;
case 0x1007: return SO_ERROR;
case 0x1105: return SO_SNDTIMEO;
default: break;
}
} else if (level == 6 && option == 1) {
return TCP_NODELAY;
}
#if defined(_WIN32)
return option;
#else
return -1;
#endif
}
static int* P2pSocketOption(P2pEndpoint& endpoint, int option) {
switch (option) {
case 0x0020: return &endpoint.broadcast;
case 0x1001: return &endpoint.send_buffer;
case 0x1002: return &endpoint.receive_buffer;
case 0x1200: return &endpoint.nonblocking;
default: return nullptr;
}
}
static int ConvertMessageFlags(int flags) {
constexpr int guest_msg_peek = 0x00000002;
constexpr int guest_msg_dontroute = 0x00000004;
constexpr int guest_msg_waitall = 0x00000040;
constexpr int guest_msg_dontwait = 0x00000080;
constexpr int guest_msg_nosignal = 0x00020000;
int host_flags = 0;
if ((flags & guest_msg_peek) != 0) {
host_flags |= MSG_PEEK;
}
if ((flags & guest_msg_dontroute) != 0) {
host_flags |= MSG_DONTROUTE;
}
if ((flags & guest_msg_waitall) != 0) {
host_flags |= MSG_WAITALL;
}
#if !defined(_WIN32)
if ((flags & guest_msg_dontwait) != 0) {
host_flags |= MSG_DONTWAIT;
}
if ((flags & guest_msg_nosignal) != 0) {
host_flags |= MSG_NOSIGNAL;
}
#endif
flags &= ~(guest_msg_peek | guest_msg_dontroute | guest_msg_waitall | guest_msg_dontwait |
guest_msg_nosignal);
if (flags != 0) {
*Posix::GetErrorAddr() = Posix::POSIX_EOPNOTSUPP;
return -1;
}
return host_flags;
}
static int ConvertGuestSockaddr(const void* addr, uint32_t addrlen, sockaddr_storage* out,
SocketLength* out_len) {
EXIT_IF(out == nullptr);
EXIT_IF(out_len == nullptr);
if (addr == nullptr) {
*Posix::GetErrorAddr() = Posix::POSIX_EFAULT;
return -1;
}
const auto* bytes = static_cast<const uint8_t*>(addr);
if (addrlen < 2) {
*Posix::GetErrorAddr() = Posix::POSIX_EINVAL;
return -1;
}
const int family = ConvertFamily(bytes[1]);
if (family != AF_INET) {
*Posix::GetErrorAddr() = (family < 0 ? Posix::POSIX_EAFNOSUPPORT : Posix::POSIX_EOPNOTSUPP);
return -1;
}
if (addrlen < 8) {
*Posix::GetErrorAddr() = Posix::POSIX_EINVAL;
return -1;
}
std::memset(out, 0, sizeof(*out));
auto* in = reinterpret_cast<sockaddr_in*>(out);
in->sin_family = AF_INET;
std::memcpy(&in->sin_port, bytes + 2, sizeof(in->sin_port));
std::memcpy(&in->sin_addr, bytes + 4, sizeof(in->sin_addr));
*out_len = sizeof(sockaddr_in);
return 0;
}
static int ConvertHostSockaddr(const sockaddr_storage* addr, SocketLength addrlen, void* out,
uint32_t* out_len) {
EXIT_IF(addr == nullptr);
EXIT_IF(out_len == nullptr);
if (out == nullptr) {
*Posix::GetErrorAddr() = Posix::POSIX_EFAULT;
return -1;
}
if (addr->ss_family != AF_INET ||
addrlen < static_cast<SocketLength>(sizeof(sockaddr_in))) {
*Posix::GetErrorAddr() = Posix::POSIX_EOPNOTSUPP;
return -1;
}
static constexpr uint32_t guest_addrlen = 16;
if (*out_len < guest_addrlen) {
*out_len = guest_addrlen;
*Posix::GetErrorAddr() = Posix::POSIX_EINVAL;
return -1;
}
auto* bytes = static_cast<uint8_t*>(out);
const auto* in = reinterpret_cast<const sockaddr_in*>(addr);
std::memset(bytes, 0, guest_addrlen);
bytes[0] = static_cast<uint8_t>(guest_addrlen);
bytes[1] = 2;
std::memcpy(bytes + 2, &in->sin_port, sizeof(in->sin_port));
std::memcpy(bytes + 4, &in->sin_addr, sizeof(in->sin_addr));
*out_len = guest_addrlen;
return 0;
}
static bool GetSocketBackend(int guest_fd, NativeSocket* out, SocketSlot* state = nullptr,
std::shared_ptr<SocketTransport>* transport = nullptr) {
EXIT_IF(out == nullptr);
if (guest_fd < 0 || guest_fd >= SOCKET_FD_MAX) {
SetGuestSocketError(Posix::POSIX_EBADF);
return false;
}
Common::LockGuard lock(g_socket_mutex);
const auto& slot = g_sockets[static_cast<size_t>(guest_fd)];
if (!slot.transport) {
SetGuestSocketError(Posix::POSIX_EBADF);
return false;
}
// Native-only operations cannot act on a transport shared by virtual ports.
if (slot.p2p && state == nullptr) {
SetGuestSocketError(Posix::POSIX_EOPNOTSUPP);
return false;
}
*out = slot.transport->socket;
if (state != nullptr) {
*state = slot;
}
if (transport != nullptr) {
*transport = slot.transport;
}
return true;
}
bool KYTY_SYSV_ABI IsSocket(int s) {
if (s < 0 || s >= SOCKET_FD_MAX) {
return false;
}
Common::LockGuard lock(g_socket_mutex);
return g_sockets[static_cast<size_t>(s)].transport != nullptr;
}
static bool TakeSocketBackend(int guest_fd, SocketSlot* out) {
EXIT_IF(out == nullptr);
if (guest_fd < 0 || guest_fd >= SOCKET_FD_MAX) {
return false;
}
Common::LockGuard lock(g_socket_mutex);
auto& slot = g_sockets[static_cast<size_t>(guest_fd)];
if (!slot.transport) {
return false;
}
*out = std::move(slot);
slot = {};
return true;
}
static int AllocSocketFd(const std::shared_ptr<SocketTransport>& transport, bool p2p = false) {
Common::LockGuard lock(g_socket_mutex);
for (int fd = SOCKET_FD_MIN; fd < SOCKET_FD_MAX; fd++) {
auto& slot = g_sockets[static_cast<size_t>(fd)];
if (!slot.transport) {
slot.transport = transport;
if (p2p) {
slot.p2p.emplace();
}
return fd;
}
}
return -1;
}
static int SelectSockets(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
timeval* timeout,
const std::vector<std::shared_ptr<SocketTransport>>& stream_reads) {
#if defined(_WIN32)
if (!stream_reads.empty()) {
const auto requested_read = *readfds;
const auto requested_write = writefds != nullptr ? *writefds : fd_set {};
const auto requested_except = exceptfds != nullptr ? *exceptfds : fd_set {};
std::optional<std::chrono::steady_clock::time_point> deadline;
if (timeout != nullptr) {
deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout->tv_sec) +
std::chrono::microseconds(timeout->tv_usec);
}
for (;;) {
*readfds = requested_read;
if (writefds != nullptr) {
*writefds = requested_write;
}
if (exceptfds != nullptr) {
*exceptfds = requested_except;
}
const bool ready = std::any_of(stream_reads.begin(), stream_reads.end(),
[](const auto& transport) { return transport->HasPendingReceive(); });
// A concurrent peek can move native bytes into the guest buffer after this check.
// Bound native waits so both readiness sources are rechecked without holding receive locks.
auto wait = std::chrono::microseconds(ready ? 0 : 10'000);
if (deadline) {
const auto remaining = std::chrono::duration_cast<std::chrono::microseconds>(
*deadline - std::chrono::steady_clock::now());
wait = std::min(wait, std::max(remaining, std::chrono::microseconds::zero()));
}
timeval slice {};
slice.tv_usec = static_cast<long>(wait.count());
int result = ::select(nfds, readfds, writefds, exceptfds, &slice);
if (result < 0) {
return result;
}
for (const auto& transport: stream_reads) {
const auto socket = transport->socket.load();
if (socket == INVALID_NATIVE_SOCKET) {
WSASetLastError(WSAENOTSOCK);
return SOCKET_ERROR;
}
if (transport->HasPendingReceive() && !FD_ISSET(socket, readfds)) {
FD_SET(socket, readfds);
result++;
}
}
if (result != 0 || (deadline && std::chrono::steady_clock::now() >= *deadline)) {
return result;
}
}
}
#endif
return ::select(nfds, readfds, writefds, exceptfds, timeout);
}
static bool GuestFdIsSet(const void* fds, int fd) {
if (fds == nullptr || fd < 0 || fd >= SOCKET_FD_MAX) {
return false;
}
const auto* words = static_cast<const uint64_t*>(fds);
return (words[fd / 64] & (uint64_t {1} << (fd % 64))) != 0;
}
static void GuestFdZero(void* fds, int nfds) {
if (fds == nullptr || nfds <= 0) {
return;
}
std::memset(fds, 0, static_cast<size_t>((nfds + 63) / 64) * sizeof(uint64_t));
}
static void GuestFdSet(void* fds, int fd) {
if (fds == nullptr || fd < 0 || fd >= SOCKET_FD_MAX) {
return;
}
auto* words = static_cast<uint64_t*>(fds);
words[fd / 64] |= (uint64_t {1} << (fd % 64));
}
int KYTY_SYSV_ABI NetInit() {
PRINT_NAME();
EnsureSocketBackend();
return OK;
}
int KYTY_SYSV_ABI NetTerm() {
PRINT_NAME();
return OK;
}
int KYTY_SYSV_ABI NetPoolCreate(const char* name, int size, int flags) {
PRINT_NAME();
LOGF("\t name = %s\n"
"\t size = %d\n"
"\t flags = %d\n",
name, size, flags);
EXIT_IF(g_net == nullptr);
EXIT_NOT_IMPLEMENTED(flags != 0);
EXIT_NOT_IMPLEMENTED(size == 0);
int id = g_net->PoolCreate(name, size);
if (id < 0) {
return NET_ERROR_ENFILE;
}
return id;
}
int KYTY_SYSV_ABI NetPoolDestroy(int memid) {
PRINT_NAME();
EXIT_IF(g_net == nullptr);
if (!g_net->PoolDestroy(memid)) {
return NET_ERROR_EBADF;
}
return OK;
}
int KYTY_SYSV_ABI NetResolverCreate(const char* name, int memid, int flags) {
PRINT_NAME();
LOGF("\t name = %s\n"
"\t memid = %d\n"
"\t flags = %d\n",
name != nullptr ? name : "<null>", memid, flags);
if (name == nullptr || flags != 0) {
return NET_ERROR_EINVAL;
}
EXIT_IF(g_net == nullptr);
const int id = g_net->ResolverCreate(name, memid);
if (id < 0) {
return NET_ERROR_RESOLVER_ENOSPACE;
}
return id;
}
int KYTY_SYSV_ABI NetResolverDestroy(int rid) {
PRINT_NAME();
LOGF("\t rid = %d\n", rid);
EXIT_IF(g_net == nullptr);
if (!g_net->ResolverDestroy(rid)) {
return NET_ERROR_EBADF;
}
return OK;
}
int KYTY_SYSV_ABI NetResolverAbort(int rid, int flags) {
PRINT_NAME();
LOGF("\t rid = %d\n\t flags = 0x%08x\n", rid, flags);
return OK;
}
int KYTY_SYSV_ABI NetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout,
int retry, int flags) {
PRINT_NAME();
LOGF("\t rid = %d\n"
"\t hostname = %s\n"
"\t timeout = %d\n"
"\t retry = %d\n"
"\t flags = 0x%08x\n",
rid, hostname != nullptr ? hostname : "<null>", timeout, retry, flags);
(void)timeout;
(void)retry;
if (hostname == nullptr || addr == nullptr || (flags & ~0x00010000) != 0) {
return NET_ERROR_EINVAL;
}
EXIT_IF(g_net == nullptr);
if (!g_net->ResolverValid(rid)) {
return NET_ERROR_EBADF;
}
if ((flags & 0x00010000) == 0 && NetInetPton(2, hostname, addr) == 1) {
return OK;
}
if (!EnsureSocketBackend()) {
return NET_ERROR_ENETDOWN;
}
addrinfo hints {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
addrinfo* result = nullptr;
const int ret = getaddrinfo(hostname, nullptr, &hints, &result);
if (ret != 0 || result == nullptr) {
if (result != nullptr) {
freeaddrinfo(result);
}
if (ret == EAI_NONAME) {
return NET_ERROR_RESOLVER_ENOHOST;
}
if (ret == EAI_NODATA) {
return NET_ERROR_RESOLVER_ENORECORD;
}
return NET_ERROR_RESOLVER_EINTERNAL;
}
for (auto* ai = result; ai != nullptr; ai = ai->ai_next) {
if (ai->ai_family == AF_INET && ai->ai_addr != nullptr &&
ai->ai_addrlen >= sizeof(sockaddr_in)) {
const auto* in = reinterpret_cast<const sockaddr_in*>(ai->ai_addr);
std::memcpy(addr, &in->sin_addr, sizeof(in->sin_addr));
freeaddrinfo(result);
return OK;
}
}
freeaddrinfo(result);
return NET_ERROR_RESOLVER_ENORECORD;
}
int KYTY_SYSV_ABI NetInetPton(int af, const char* src, void* dst) {
PRINT_NAME();
if (src == nullptr || dst == nullptr) {
return NET_ERROR_EINVAL;
}
const int host_family = ConvertFamily(af);
if (host_family < 0) {
return NET_ERROR_EAFNOSUPPORT;
}
LOGF("\t src = %.46s\n", src);
#if defined(_WIN32)
const int result = ::InetPtonA(host_family, src, dst);
#else
const int result = ::inet_pton(host_family, src, dst);
#endif
return result < 0 ? NET_ERROR_EINVAL : result;
}
const char* KYTY_SYSV_ABI NetInetNtop(int af, const void* src, char* dst, uint32_t size) {
PRINT_NAME();
if (src == nullptr || dst == nullptr || size == 0) {
return nullptr;
}
if (af != 2 && af != 28) {
return nullptr;
}
#if defined(_WIN32)
const int win_af = (af == 28 ? AF_INET6 : AF_INET);
if (::InetNtopA(win_af, const_cast<void*>(src), dst, size) == nullptr) {
return nullptr;
}
#else
if (::inet_ntop(af, src, dst, size) == nullptr) {
return nullptr;
}
#endif
return dst;
}
int KYTY_SYSV_ABI NetEtherNtostr(const NetEtherAddr* n, char* str, size_t len) {
PRINT_NAME();
if (n == nullptr || str == nullptr || len < 18) {
return NET_ERROR_EINVAL;
}
std::snprintf(str, len, "%02x:%02x:%02x:%02x:%02x:%02x", n->data[0], n->data[1],
n->data[2], n->data[3], n->data[4], n->data[5]);
return OK;
}
int KYTY_SYSV_ABI NetGetMacAddress(NetEtherAddr* addr, int flags) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(addr == nullptr);
EXIT_NOT_IMPLEMENTED(flags != 0);
memset(addr->data, 0, sizeof(addr->data));
return OK;
}
int KYTY_SYSV_ABI NetGetSockInfo(int s, void* info, int n, int flags) {
PRINT_NAME();
LOGF("\t s = %d\n"
"\t info = 0x%016" PRIx64 "\n"
"\t n = %d\n"
"\t flags = %d\n",
s, reinterpret_cast<uint64_t>(info), n, flags);
return OK;
}
int KYTY_SYSV_ABI EpollCreate(const char* name, int flags) {
if (name == nullptr || flags != 0) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
std::lock_guard lock(g_epoll_mutex);
for (size_t index = 0; index < g_epolls.size(); index++) {
auto& slot = g_epolls[index];
if (!slot.used) {
slot.used = true;
slot.name = name;
slot.registrations.clear();
return EPOLL_ID_BASE + static_cast<int>(index);
}
}
return SetGuestSocketError(Posix::POSIX_EMFILE);
}
int KYTY_SYSV_ABI EpollControl(int eid, int op, int id, const NetEpollEvent* event) {
constexpr int EPOLL_CTL_ADD = 1;
constexpr int EPOLL_CTL_MOD = 2;
constexpr int EPOLL_CTL_DEL = 3;
if ((op == EPOLL_CTL_ADD || op == EPOLL_CTL_MOD) && event == nullptr) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if (op == EPOLL_CTL_DEL && event != nullptr) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if (op < EPOLL_CTL_ADD || op > EPOLL_CTL_DEL) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if (!IsSocket(id)) {
return SetGuestSocketError(Posix::POSIX_EBADF);
}
std::lock_guard lock(g_epoll_mutex);
auto* slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetGuestSocketError(Posix::POSIX_EBADF);
}
auto registration = std::find_if(slot->registrations.begin(), slot->registrations.end(),
[id](const auto& value) { return value.id == id; });
switch (op) {
case EPOLL_CTL_ADD:
if (registration != slot->registrations.end()) {
return SetGuestSocketError(Posix::POSIX_EEXIST);
}
slot->registrations.push_back({id, *event});
break;
case EPOLL_CTL_MOD:
if (registration == slot->registrations.end()) {
return SetGuestSocketError(Posix::POSIX_ENOENT);
}
registration->event = *event;
break;
case EPOLL_CTL_DEL:
if (registration == slot->registrations.end()) {
return SetGuestSocketError(Posix::POSIX_ENOENT);
}
slot->registrations.erase(registration);
break;
default: break;
}
g_epoll_changed.notify_all();
return 0;
}
int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int timeout) {
if (events == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
if (maxevents <= 0 || timeout < -1) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
std::vector<EpollRegistration> registrations;
{
std::unique_lock lock(g_epoll_mutex);
auto* slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetGuestSocketError(Posix::POSIX_EBADF);
}
if (slot->registrations.empty() && timeout != 0) {
auto ready = [eid] {
auto* current = GetEpollSlot(eid);
return current == nullptr || !current->registrations.empty();
};
if (timeout < 0) {
g_epoll_changed.wait(lock, ready);
} else {
g_epoll_changed.wait_for(lock, std::chrono::microseconds(timeout), ready);
}
slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetGuestSocketError(Posix::POSIX_EBADF);
}
}
registrations = slot->registrations;
}
if (registrations.empty()) {
return 0;
}
struct HostRegistration {
EpollRegistration guest;
NativeSocket socket = INVALID_NATIVE_SOCKET;
SocketSlot state;
};
fd_set host_read {};
fd_set host_write {};
fd_set host_except {};
FD_ZERO(&host_read);
FD_ZERO(&host_write);
FD_ZERO(&host_except);
std::vector<HostRegistration> host_registrations;
std::vector<std::shared_ptr<SocketTransport>> stream_reads;
host_registrations.reserve(registrations.size());
int host_nfds = 0;
for (const auto& registration: registrations) {
NativeSocket socket = INVALID_NATIVE_SOCKET;
SocketSlot state;
if (!GetSocketBackend(registration.id, &socket, &state)) {
continue;
}
#if defined(_WIN32)
if (host_registrations.size() >= FD_SETSIZE) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
#else
if (socket >= FD_SETSIZE) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
host_nfds = std::max(host_nfds, socket + 1);
#endif
if ((registration.event.events & EPOLL_IN) != 0) {
FD_SET(socket, &host_read);
#if defined(_WIN32)
if (state.transport->type == SOCK_STREAM) {
stream_reads.push_back(state.transport);
}
#endif
}
if ((registration.event.events & EPOLL_OUT) != 0) {
FD_SET(socket, &host_write);
}
FD_SET(socket, &host_except);
host_registrations.push_back({registration, socket, std::move(state)});
}
if (host_registrations.empty()) {
return 0;
}
timeval host_timeout {};
timeval* host_timeout_ptr = nullptr;
if (timeout >= 0) {
host_timeout.tv_sec = timeout / 1000000;
host_timeout.tv_usec = timeout % 1000000;
host_timeout_ptr = &host_timeout;
}
const int result = SelectSockets(host_nfds, &host_read, &host_write, &host_except,
host_timeout_ptr, stream_reads);
if (result < 0) {
return SetHostSocketError();
}
if (result == 0) {
return 0;
}
int count = 0;
for (const auto& registration: host_registrations) {
uint32_t ready = 0;
if ((registration.guest.event.events & EPOLL_IN) != 0 &&
FD_ISSET(registration.socket, &host_read)) {
if (registration.state.p2p) {
return SetGuestSocketError(Posix::POSIX_EOPNOTSUPP);
}
ready |= EPOLL_IN;
}
if ((registration.guest.event.events & EPOLL_OUT) != 0 &&
FD_ISSET(registration.socket, &host_write)) {
ready |= EPOLL_OUT;
}
if (FD_ISSET(registration.socket, &host_except)) {
ready |= EPOLL_ERR;
}
if (ready == 0) {
continue;
}
auto& output = events[count++];
output = registration.guest.event;
output.events = ready;
output.reserved = 0;
output.ident = static_cast<uint64_t>(registration.guest.id);
if (count == maxevents) {
break;
}
}
return count;
}
int KYTY_SYSV_ABI EpollDestroy(int eid) {
std::lock_guard lock(g_epoll_mutex);
auto* slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetGuestSocketError(Posix::POSIX_EBADF);
}
slot->used = false;
slot->name.clear();
slot->registrations.clear();
g_epoll_changed.notify_all();
return 0;
}
int KYTY_SYSV_ABI SocketClose(int s) {
PRINT_NAME();
LOGF("\t s = %d\n", s);
SocketSlot slot;
if (!TakeSocketBackend(s, &slot)) {
return NET_ERROR_EBADF;
}
RemoveSocketFromEpolls(s);
bool close_transport = slot.transport.use_count() == 1;
#if defined(_WIN32)
// Retained receive/readiness state must not defer native TCP close cancellation.
close_transport |= !slot.p2p && slot.transport->type == SOCK_STREAM;
#endif
if (close_transport && slot.transport->Close() != 0) {
return NET_ERROR_EBADF;
}
return OK;
}
int KYTY_SYSV_ABI Socket(int family, int type, int protocol) {
PRINT_NAME();
LOGF("\t family = %d\n"
"\t type = %d\n"
"\t protocol = %d\n",
family, type, protocol);
if (!EnsureSocketBackend()) {
return SetGuestSocketError(Posix::POSIX_ENETDOWN);
}
const int host_family = ConvertFamily(family);
if (host_family < 0) {
*Posix::GetErrorAddr() = Posix::POSIX_EAFNOSUPPORT;
return -1;
}
constexpr int socket_dgram_p2p = 6;
const bool p2p = type == socket_dgram_p2p;
if (p2p && (family != 2 || protocol != 0)) {
return SetGuestSocketError(Posix::POSIX_EPROTONOSUPPORT);
}
NativeSocket socket = ::socket(host_family, p2p ? SOCK_DGRAM : type, protocol);
if (socket == INVALID_NATIVE_SOCKET) {
return SetHostSocketError();
}
auto transport = std::make_shared<SocketTransport>(socket, p2p ? SOCK_DGRAM : type);
if (p2p) {
// Logical sockets keep their own blocking mode; the shared transport never blocks.
#if defined(_WIN32)
u_long enabled = 1;
const int result = ioctlsocket(socket, FIONBIO, &enabled);
#else
const int result = ::fcntl(socket, F_SETFL, O_NONBLOCK);
#endif
if (result != 0) {
return SetHostSocketError();
}
}
const int fd = AllocSocketFd(transport, p2p);
if (fd < 0) {
*Posix::GetErrorAddr() = Posix::POSIX_EMFILE;
return -1;
}
LOGF("\t fd = %d\n", fd);
return fd;
}
int KYTY_SYSV_ABI Bind(int s, const void* addr, uint32_t addrlen) {
PRINT_NAME();
LOGF("\t s = %d\n"
"\t addr = 0x%016" PRIx64 "\n"
"\t addrlen = %" PRIu32 "\n",
s, reinterpret_cast<uint64_t>(addr), addrlen);
NativeSocket socket = INVALID_NATIVE_SOCKET;
SocketSlot state;
if (addr == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
if (!GetSocketBackend(s, &socket, &state)) {
return -1;
}
sockaddr_storage host_addr {};
SocketLength host_addrlen = 0;
if (ConvertGuestSockaddr(addr, addrlen, &host_addr, &host_addrlen) != 0) {
return -1;
}
if (state.p2p) {
if (addrlen < 16) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
const auto& in = *reinterpret_cast<const sockaddr_in*>(&host_addr);
uint32_t address = 0;
std::memcpy(&address, &in.sin_addr, sizeof(address));
uint16_t virtual_port = 0;
std::memcpy(&virtual_port, static_cast<const uint8_t*>(addr) + 8, sizeof(virtual_port));
const auto requested_port = ntohs(virtual_port);
if (requested_port == 0) {
return SetGuestSocketError(Posix::POSIX_EOPNOTSUPP);
}
if (ntohs(in.sin_port) != 3658) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if (requested_port == 5353 || requested_port >= 32768) {
return SetGuestSocketError(Posix::POSIX_EACCES);
}
Common::LockGuard lock(g_socket_mutex);
auto& slot = g_sockets[static_cast<size_t>(s)];
if (slot.transport != state.transport) {
return SetGuestSocketError(Posix::POSIX_EBADF);
}
auto& endpoint = *slot.p2p;
if (endpoint.bound) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
auto conflicts = [&](uint16_t port) {
return std::any_of(g_sockets.begin(), g_sockets.end(), [&](const auto& other) {
return other.p2p && other.p2p->bound && other.p2p->port == in.sin_port &&
other.p2p->address == address && other.p2p->virtual_port == port;
});
};
if (conflicts(virtual_port)) {
return SetGuestSocketError(Posix::POSIX_EADDRINUSE);
}
std::shared_ptr<SocketTransport> shared;
for (const auto& other: g_sockets) {
if (other.p2p && other.p2p->bound && other.p2p->port == in.sin_port &&
other.p2p->address == address) {
shared = other.transport;
break;
}
}
if (shared) {
slot.transport = std::move(shared);
} else if (::bind(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) !=
0) {
return SetHostSocketError();
}
endpoint.bound = true;
endpoint.address = address;
endpoint.port = in.sin_port;
endpoint.virtual_port = virtual_port;
LOGF("\t UDPP2P bound UDP port=%u virtual port=%u\n", ntohs(endpoint.port),
ntohs(endpoint.virtual_port));
return 0;
}
if (::bind(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) != 0) {
return SetHostSocketError();
}
return 0;
}
int KYTY_SYSV_ABI Connect(int s, const void* addr, uint32_t addrlen) {
PRINT_NAME();
LOGF("\t s = %d\n"
"\t addr = 0x%016" PRIx64 "\n"
"\t addrlen = %" PRIu32 "\n",
s, reinterpret_cast<uint64_t>(addr), addrlen);
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (addr == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
if (!GetSocketBackend(s, &socket)) {
return -1;
}
sockaddr_storage host_addr {};
SocketLength host_addrlen = 0;
if (ConvertGuestSockaddr(addr, addrlen, &host_addr, &host_addrlen) != 0) {
return -1;
}
if (::connect(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) != 0) {
return SetHostSocketError();
}
return 0;
}
int KYTY_SYSV_ABI Listen(int s, int backlog) {
PRINT_NAME();
LOGF("\t s = %d\n"
"\t backlog = %d\n",
s, backlog);
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (!GetSocketBackend(s, &socket)) {
return -1;
}
if (::listen(socket, backlog) != 0) {
return SetHostSocketError();
}
return 0;
}
int KYTY_SYSV_ABI Accept(int s, void* addr, uint32_t* addrlen) {
PRINT_NAME();
LOGF("\t s = %d\n"
"\t addr = 0x%016" PRIx64 "\n"
"\t addrlen = 0x%016" PRIx64 "\n",
s, reinterpret_cast<uint64_t>(addr), reinterpret_cast<uint64_t>(addrlen));
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (!GetSocketBackend(s, &socket)) {
return -1;
}
sockaddr_storage host_addr {};
SocketLength host_addrlen = sizeof(host_addr);
NativeSocket accepted =
::accept(socket, reinterpret_cast<sockaddr*>(&host_addr), &host_addrlen);
if (accepted == INVALID_NATIVE_SOCKET) {
return SetHostSocketError();
}
auto transport = std::make_shared<SocketTransport>(accepted, SOCK_STREAM);
const int fd = AllocSocketFd(transport);
if (fd < 0) {
*Posix::GetErrorAddr() = Posix::POSIX_EMFILE;
return -1;
}
if (addr != nullptr || addrlen != nullptr) {
if (addr == nullptr || addrlen == nullptr) {
SocketClose(fd);
*Posix::GetErrorAddr() = Posix::POSIX_EFAULT;
return -1;
}
if (ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen) != 0) {
SocketClose(fd);
return -1;
}
}
return fd;
}
int KYTY_SYSV_ABI Shutdown(int s, int how) {
PRINT_NAME();
LOGF("\t s = %d\n"
"\t how = %d\n",
s, how);
NativeSocket socket = INVALID_NATIVE_SOCKET;
std::shared_ptr<SocketTransport> transport;
if (!GetSocketBackend(s, &socket, nullptr, &transport)) {
return -1;
}
if (how < 0 || how > 2) {
*Posix::GetErrorAddr() = Posix::POSIX_EINVAL;
return -1;
}
if (::shutdown(socket, how) != 0) {
return SetHostSocketError();
}
#if defined(_WIN32)
if (how == SD_RECEIVE || how == SD_BOTH) {
transport->receive_shutdown.store(true, std::memory_order_relaxed);
}
#endif
return 0;
}
int KYTY_SYSV_ABI Getsockname(int s, void* addr, uint32_t* addrlen) {
PRINT_NAME();
LOGF("\t s = %d\n"
"\t addr = 0x%016" PRIx64 "\n"
"\t addrlen = 0x%016" PRIx64 "\n",
s, reinterpret_cast<uint64_t>(addr), reinterpret_cast<uint64_t>(addrlen));
NativeSocket socket = INVALID_NATIVE_SOCKET;
SocketSlot state;
if (!GetSocketBackend(s, &socket, &state)) {
return -1;
}
if (addr == nullptr || addrlen == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
sockaddr_storage host_addr {};
SocketLength host_addrlen = sizeof(host_addr);
if (::getsockname(socket, reinterpret_cast<sockaddr*>(&host_addr), &host_addrlen) != 0) {
return SetHostSocketError();
}
const int result = ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen);
if (result == 0 && state.p2p) {
std::memcpy(static_cast<uint8_t*>(addr) + 8, &state.p2p->virtual_port, sizeof(uint16_t));
}
return result;
}
int KYTY_SYSV_ABI Getsockopt(int s, int level, int optname, void* optval, uint32_t* optlen) {
PRINT_NAME();
LOGF("\t s = %d\n"
"\t level = 0x%08" PRIx32 "\n"
"\t optname = 0x%08" PRIx32 "\n",
s, static_cast<uint32_t>(level), static_cast<uint32_t>(optname));
NativeSocket socket = INVALID_NATIVE_SOCKET;
SocketSlot state;
if (!GetSocketBackend(s, &socket, &state)) {
return -1;
}
if (optval == nullptr || optlen == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
if (state.p2p) {
const auto* value = level == 0xffff ? P2pSocketOption(*state.p2p, optname) : nullptr;
if (value == nullptr) {
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
if (*optlen < sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
std::memcpy(optval, value, sizeof(int));
*optlen = sizeof(int);
return 0;
}
const bool socket_error = (level == 0xffff && optname == 0x1007);
const bool send_timeout = (level == 0xffff && optname == 0x1105);
optname = ConvertSocketOptionName(level, optname);
if (optname < 0) {
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
#if defined(_WIN32)
DWORD timeout {};
#else
timeval timeout {};
#endif
SocketLength len = static_cast<SocketLength>(*optlen);
void* value = optval;
if (send_timeout) {
if (*optlen < sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
value = &timeout;
len = sizeof(timeout);
}
if (::getsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast<char*>(value),
&len) != 0) {
return SetHostSocketError();
}
if (send_timeout) {
#if defined(_WIN32)
const int64_t usec = static_cast<int64_t>(timeout) * 1000;
#else
const int64_t usec = static_cast<int64_t>(timeout.tv_sec) * 1'000'000 + timeout.tv_usec;
#endif
const int guest_timeout =
static_cast<int>(std::min<int64_t>(usec, std::numeric_limits<int>::max()));
std::memcpy(optval, &guest_timeout, sizeof(guest_timeout));
len = sizeof(guest_timeout);
}
if (socket_error && len >= static_cast<SocketLength>(sizeof(int))) {
auto* error = static_cast<int*>(optval);
*error = ConvertHostSocketError(*error);
}
*optlen = static_cast<uint32_t>(len);
return 0;
}
int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval, uint32_t optlen) {
PRINT_NAME();
LOGF("\t s = %d\n"
"\t level = 0x%08" PRIx32 "\n"
"\t optname = 0x%08" PRIx32 "\n"
"\t optlen = %" PRIu32 "\n",
s, static_cast<uint32_t>(level), static_cast<uint32_t>(optname), optlen);
NativeSocket socket = INVALID_NATIVE_SOCKET;
SocketSlot state;
if (optval == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
if (!GetSocketBackend(s, &socket, &state)) {
return -1;
}
if (state.p2p) {
Common::LockGuard lock(g_socket_mutex);
auto& slot = g_sockets[static_cast<size_t>(s)];
if (slot.transport != state.transport) {
return SetGuestSocketError(Posix::POSIX_EBADF);
}
auto* option = level == 0xffff ? P2pSocketOption(*slot.p2p, optname) : nullptr;
if (option == nullptr) {
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
if (optlen != sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
int value = 0;
std::memcpy(&value, optval, sizeof(value));
if (optname == 0x1001 || optname == 0x1002) {
if (value <= 0) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
*option = optname == 0x1002 ? std::min(value, 512 * 1024) : value;
} else {
*option = value != 0;
}
return 0;
}
#if defined(__linux__)
if (level == 0xffff && optname == 0x10000) {
if (optlen != sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
int enabled = 0;
std::memcpy(&enabled, optval, sizeof(enabled));
// Native sends preserve 255.255.255.255 without changing broadcast permission.
return enabled != 0 ? 0 : SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
#endif
constexpr int ORBIS_SO_NBIO = 0x1200;
if (ConvertSocketOptionLevel(level) == SOL_SOCKET && optname == ORBIS_SO_NBIO &&
optlen >= sizeof(int)) {
const bool enabled = *static_cast<const int*>(optval) != 0;
#if defined(_WIN32)
u_long mode = enabled ? 1 : 0;
const bool failed = ioctlsocket(socket, FIONBIO, &mode) == SOCKET_ERROR;
#else
const int flags = ::fcntl(socket, F_GETFL, 0);
const bool failed =
flags < 0 ||
::fcntl(socket, F_SETFL, enabled ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)) != 0;
#endif
return failed ? SetHostSocketError() : 0;
}
const bool send_timeout = (level == 0xffff && optname == 0x1105);
optname = ConvertSocketOptionName(level, optname);
if (optname < 0) {
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
#if defined(_WIN32)
DWORD timeout {};
#else
timeval timeout {};
#endif
if (send_timeout) {
if (optlen != sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
int usec = 0;
std::memcpy(&usec, optval, sizeof(usec));
usec = std::max(usec, 0);
#if defined(_WIN32)
timeout = static_cast<DWORD>((static_cast<int64_t>(usec) + 999) / 1000);
#else
timeout.tv_sec = usec / 1'000'000;
timeout.tv_usec = usec % 1'000'000;
#endif
optval = &timeout;
optlen = sizeof(timeout);
}
if (::setsockopt(socket, ConvertSocketOptionLevel(level), optname,
static_cast<const char*>(optval), static_cast<SocketLength>(optlen)) != 0) {
return SetHostSocketError();
}
return 0;
}
int64_t KYTY_SYSV_ABI Send(int s, const void* buf, uint64_t len, int flags) {
return Sendto(s, buf, len, flags, nullptr, 0);
}
int64_t KYTY_SYSV_ABI Sendto(int s, const void* buf, uint64_t len, int flags, const void* addr,
uint32_t addrlen) {
PRINT_NAME();
LOGF("\t s = %d\n"
"\t buf = 0x%016" PRIx64 "\n"
"\t len = %" PRIu64 "\n"
"\t flags = 0x%08" PRIx32 "\n"
"\t addr = 0x%016" PRIx64 "\n"
"\t addrlen = %" PRIu32 "\n",
s, reinterpret_cast<uint64_t>(buf), len, static_cast<uint32_t>(flags),
reinterpret_cast<uint64_t>(addr), addrlen);
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (buf == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
// UDPP2P requires protocol framing; the native-only lookup rejects raw sends.
if (!GetSocketBackend(s, &socket)) {
return -1;
}
const int host_flags = ConvertMessageFlags(flags);
if (host_flags < 0) {
return -1;
}
const auto host_len = static_cast<SocketIoLength>(
std::min<uint64_t>(len, std::numeric_limits<SocketIoLength>::max()));
int64_t result = 0;
if (addr == nullptr) {
result = ::send(socket, static_cast<const char*>(buf), host_len, host_flags);
} else {
sockaddr_storage host_addr {};
SocketLength host_addrlen = 0;
if (ConvertGuestSockaddr(addr, addrlen, &host_addr, &host_addrlen) != 0) {
return -1;
}
result = ::sendto(socket, static_cast<const char*>(buf), host_len, host_flags,
reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen);
}
if (result < 0) {
return SetHostSocketError();
}
return result;
}
#if defined(_WIN32)
static int ReceiveStream(SocketTransport& transport, char* output, int length, int flags,
bool dontwait) {
std::unique_lock lock(transport.receive_mutex, std::defer_lock);
if (dontwait) {
if (!lock.try_lock()) {
return SetGuestSocketError(Posix::POSIX_EWOULDBLOCK);
}
} else {
lock.lock();
}
if (transport.receive_shutdown.load(std::memory_order_relaxed)) {
return SetGuestSocketError(Posix::POSIX_ESHUTDOWN);
}
auto& buffered = transport.receive_buffer;
const bool peek = (flags & MSG_PEEK) != 0;
const bool waitall = (flags & MSG_WAITALL) != 0;
int copied = static_cast<int>(std::min<size_t>(buffered.size(), length));
if (copied != 0) {
std::memcpy(output, buffered.data(), copied);
if (!peek) {
buffered.erase(buffered.begin(), buffered.begin() + copied);
transport.buffered_bytes.store(buffered.size(), std::memory_order_relaxed);
}
}
if (copied == length || (copied != 0 && !waitall)) {
return copied;
}
// Winsock rejects PEEK | WAITALL. Consume into transport-owned storage when peeking,
// and let ordinary recv report EOF, timeouts, or nonblocking partial progress.
flags &= ~(MSG_PEEK | MSG_WAITALL);
do {
if (transport.receive_shutdown.load(std::memory_order_relaxed)) {
return copied != 0 ? copied : SetGuestSocketError(Posix::POSIX_ESHUTDOWN);
}
const auto socket = transport.socket.load();
if (dontwait) {
fd_set readable {};
FD_SET(socket, &readable);
timeval immediate {};
const int ready = ::select(0, &readable, nullptr, nullptr, &immediate);
if (ready <= 0) {
return copied != 0 ? copied : (ready < 0 ? SetHostSocketError() :
SetGuestSocketError(Posix::POSIX_EWOULDBLOCK));
}
}
const auto previous_size = buffered.size();
const int requested = peek ? std::min(length - copied, 64 * 1024) : length - copied;
char* destination = output + copied;
if (peek) {
buffered.resize(previous_size + requested);
destination = buffered.data() + previous_size;
}
const int received = ::recv(socket, destination, requested, flags);
if (peek) {
if (received > 0) {
std::memcpy(output + copied, destination, received);
}
buffered.resize(previous_size + std::max(received, 0));
transport.buffered_bytes.store(buffered.size(), std::memory_order_relaxed);
}
if (received <= 0) {
return copied != 0 || received == 0 ? copied : SetHostSocketError();
}
copied += received;
} while (waitall && copied < length);
return copied;
}
#endif
int64_t KYTY_SYSV_ABI Recv(int s, void* buf, uint64_t len, int flags) {
return Recvfrom(s, buf, len, flags, nullptr, nullptr);
}
int64_t KYTY_SYSV_ABI Recvfrom(int s, void* buf, uint64_t len, int flags, void* addr,
uint32_t* addrlen) {
PRINT_NAME();
LOGF("\t s = %d\n"
"\t buf = 0x%016" PRIx64 "\n"
"\t len = %" PRIu64 "\n"
"\t flags = 0x%08" PRIx32 "\n"
"\t addr = 0x%016" PRIx64 "\n"
"\t addrlen = 0x%016" PRIx64 "\n",
s, reinterpret_cast<uint64_t>(buf), len, static_cast<uint32_t>(flags),
reinterpret_cast<uint64_t>(addr), reinterpret_cast<uint64_t>(addrlen));
if (buf == nullptr || (addr != nullptr && addrlen == nullptr)) {
*Posix::GetErrorAddr() = Posix::POSIX_EFAULT;
return -1;
}
NativeSocket socket = INVALID_NATIVE_SOCKET;
std::shared_ptr<SocketTransport> transport;
if (!GetSocketBackend(s, &socket, nullptr, &transport)) {
return -1;
}
const int host_flags = ConvertMessageFlags(flags);
if (host_flags < 0) {
return -1;
}
const auto host_len = static_cast<SocketIoLength>(
std::min<uint64_t>(len, std::numeric_limits<SocketIoLength>::max()));
sockaddr_storage host_addr {};
SocketLength host_addrlen = sizeof(host_addr);
int64_t result = 0;
#if defined(_WIN32)
if (transport->type == SOCK_STREAM) {
if (addr != nullptr &&
::getpeername(socket, reinterpret_cast<sockaddr*>(&host_addr), &host_addrlen) != 0) {
return SetHostSocketError();
}
result = ReceiveStream(*transport, static_cast<char*>(buf), host_len, host_flags,
(flags & 0x80) != 0);
if (result < 0) {
return result;
}
} else
#endif
{
transport.reset();
if (addr == nullptr) {
result = ::recv(socket, static_cast<char*>(buf), host_len, host_flags);
} else {
result = ::recvfrom(socket, static_cast<char*>(buf), host_len, host_flags,
reinterpret_cast<sockaddr*>(&host_addr), &host_addrlen);
}
}
#if defined(_WIN32)
if (result < 0 && WSAGetLastError() == WSAEMSGSIZE) {
// Winsock copied the datagram prefix; POSIX reports its length as success.
result = host_len;
}
#endif
if (result < 0) {
return SetHostSocketError();
}
if (addr != nullptr && ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen) != 0) {
return -1;
}
return result;
}
int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfds,
const void* timeout) {
PRINT_NAME();
if (nfds < 0 || nfds > SOCKET_FD_MAX) {
*Posix::GetErrorAddr() = Posix::POSIX_EINVAL;
return -1;
}
static std::atomic_uint32_t select_log_count = 0;
const bool log_select = select_log_count.fetch_add(1, std::memory_order_relaxed) < 64;
if (log_select) {
const auto read0 = (readfds != nullptr ? *static_cast<const uint64_t*>(readfds) : 0);
const auto write0 = (writefds != nullptr ? *static_cast<const uint64_t*>(writefds) : 0);
const auto except0 = (exceptfds != nullptr ? *static_cast<const uint64_t*>(exceptfds) : 0);
const auto* guest_timeout = static_cast<const NetTimeval*>(timeout);
LOGF("\t nfds = %d\n"
"\t read = 0x%016" PRIx64 " bits0=0x%016" PRIx64 "\n"
"\t write = 0x%016" PRIx64 " bits0=0x%016" PRIx64 "\n"
"\t except = 0x%016" PRIx64 " bits0=0x%016" PRIx64 "\n"
"\t timeout = 0x%016" PRIx64,
nfds, reinterpret_cast<uint64_t>(readfds), read0, reinterpret_cast<uint64_t>(writefds),
write0, reinterpret_cast<uint64_t>(exceptfds), except0,
reinterpret_cast<uint64_t>(timeout));
if (guest_timeout != nullptr) {
LOGF(" sec=%" PRId64 " usec=%" PRId64, guest_timeout->tv_sec, guest_timeout->tv_usec);
}
LOGF("\n");
}
fd_set host_read {};
fd_set host_write {};
fd_set host_except {};
FD_ZERO(&host_read);
FD_ZERO(&host_write);
FD_ZERO(&host_except);
std::vector<std::pair<int, NativeSocket>> descriptors;
std::vector<std::shared_ptr<SocketTransport>> stream_reads;
int host_nfds = 0;
for (int fd = 0; fd < nfds; fd++) {
const bool read = GuestFdIsSet(readfds, fd);
const bool write = GuestFdIsSet(writefds, fd);
const bool except = GuestFdIsSet(exceptfds, fd);
if (!read && !write && !except) {
continue;
}
NativeSocket socket = INVALID_NATIVE_SOCKET;
std::shared_ptr<SocketTransport> transport;
#if !defined(_WIN32)
if (fd < 3) {
socket = fd;
} else
#endif
if (!GetSocketBackend(fd, &socket, nullptr, &transport)) {
return -1;
}
#if defined(_WIN32)
if ((read && host_read.fd_count == FD_SETSIZE) ||
(write && host_write.fd_count == FD_SETSIZE) ||
(except && host_except.fd_count == FD_SETSIZE)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
#else
if (socket >= FD_SETSIZE) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
host_nfds = std::max(host_nfds, socket + 1);
#endif
descriptors.emplace_back(fd, socket);
if (read) {
FD_SET(socket, &host_read);
#if defined(_WIN32)
if (transport->type == SOCK_STREAM) {
stream_reads.push_back(std::move(transport));
}
#endif
}
if (write) {
FD_SET(socket, &host_write);
}
if (except) {
FD_SET(socket, &host_except);
}
}
timeval host_timeout {};
timeval* host_timeout_ptr = nullptr;
if (timeout != nullptr) {
const auto* guest_timeout = static_cast<const NetTimeval*>(timeout);
if (guest_timeout->tv_sec < 0 || guest_timeout->tv_usec < 0 ||
guest_timeout->tv_usec >= 1'000'000) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
host_timeout.tv_sec = static_cast<decltype(host_timeout.tv_sec)>(guest_timeout->tv_sec);
host_timeout.tv_usec = static_cast<decltype(host_timeout.tv_usec)>(guest_timeout->tv_usec);
host_timeout_ptr = &host_timeout;
}
int result = 0;
#if defined(_WIN32)
if (descriptors.empty()) {
Sleep(host_timeout_ptr == nullptr ? INFINITE :
static_cast<DWORD>(host_timeout.tv_sec * 1000 + host_timeout.tv_usec / 1000));
} else
#endif
{
result = SelectSockets(host_nfds, readfds != nullptr ? &host_read : nullptr,
writefds != nullptr ? &host_write : nullptr,
exceptfds != nullptr ? &host_except : nullptr, host_timeout_ptr,
stream_reads);
if (result < 0) {
return SetHostSocketError();
}
}
GuestFdZero(readfds, nfds);
GuestFdZero(writefds, nfds);
GuestFdZero(exceptfds, nfds);
result = 0;
for (const auto& [fd, socket]: descriptors) {
if (FD_ISSET(socket, &host_read)) {
GuestFdSet(readfds, fd);
result++;
}
if (FD_ISSET(socket, &host_write)) {
GuestFdSet(writefds, fd);
result++;
}
if (FD_ISSET(socket, &host_except)) {
GuestFdSet(exceptfds, fd);
result++;
}
}
return result;
}
} // namespace Net
namespace Ssl {
LIB_NAME("Ssl", "Ssl");
struct SslData {
char* ptr = nullptr;
size_t size = 0;
};
struct SslCaCerts {
SslData* cert_data = nullptr;
size_t cert_data_num = 0;
void* pool = nullptr;
};
int KYTY_SYSV_ABI SslInit(uint64_t pool_size) {
PRINT_NAME();
LOGF("\t size = %" PRIu64 "\n", pool_size);
EXIT_IF(g_net == nullptr);
EXIT_NOT_IMPLEMENTED(pool_size == 0);
auto id = g_net->SslInit(pool_size);
if (!id.IsValid()) {
return SSL_ERROR_OUT_OF_SIZE;
}
return id.ToInt();
}
int KYTY_SYSV_ABI SslTerm(int ssl_ctx_id) {
PRINT_NAME();
EXIT_IF(g_net == nullptr);
if (!g_net->SslTerm(Network::Id(ssl_ctx_id))) {
return SSL_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI SslGetCaCerts(int ssl_ctx_id, void* ca_certs) {
PRINT_NAME();
LOGF("\t ssl_ctx_id = %d\n", ssl_ctx_id);
if (ca_certs == nullptr) {
return SSL_ERROR_INVALID_ARG;
}
EXIT_IF(g_net == nullptr);
if (!g_net->SslValid(Network::Id(ssl_ctx_id))) {
return SSL_ERROR_INVALID_ID;
}
auto* certs = static_cast<SslCaCerts*>(ca_certs);
certs->cert_data = nullptr;
certs->cert_data_num = 0;
certs->pool = nullptr;
return SSL_ERROR_NOT_FOUND;
}
int KYTY_SYSV_ABI SslFreeCaCerts(int ssl_ctx_id, void* ca_certs) {
PRINT_NAME();
LOGF("\t ssl_ctx_id = %d\n", ssl_ctx_id);
if (ca_certs == nullptr) {
return SSL_ERROR_INVALID_ARG;
}
EXIT_IF(g_net == nullptr);
if (!g_net->SslValid(Network::Id(ssl_ctx_id))) {
return SSL_ERROR_INVALID_ID;
}
auto* certs = static_cast<SslCaCerts*>(ca_certs);
certs->cert_data = nullptr;
certs->cert_data_num = 0;
certs->pool = nullptr;
return OK;
}
} // namespace Ssl
namespace Http {
struct HttpEpoll {
Network::Id http_ctx_id = Network::Id(0);
Network::Id request_id = Network::Id(0);
void* user_arg = nullptr;
};
struct HttpNBEvent {
uint32_t events = 0;
uint32_t event_detail = 0;
int id = 0;
void* user_arg = nullptr;
};
LIB_NAME("Http", "Http");
static const char* HttpMethodToString(int method) {
switch (method) {
case 0: return "GET";
case 1: return "POST";
case 2: return "HEAD";
case 3: return "OPTIONS";
case 4: return "PUT";
case 5: return "DELETE";
case 6: return "TRACE";
case 7: return "CONNECT";
default: return nullptr;
}
}
int KYTY_SYSV_ABI HttpInit(int memid, int ssl_ctx_id, uint64_t pool_size) {
PRINT_NAME();
LOGF("\t memid = %d\n"
"\t ssl_ctx_id = %d\n"
"\t size = %" PRIu64 "\n",
memid, ssl_ctx_id, pool_size);
EXIT_IF(g_net == nullptr);
EXIT_NOT_IMPLEMENTED(pool_size == 0);
auto id = g_net->HttpInit(memid, Network::Id(ssl_ctx_id), pool_size);
if (!id.IsValid()) {
return HTTP_ERROR_OUT_OF_MEMORY;
}
return id.ToInt();
}
int KYTY_SYSV_ABI HttpTerm(int http_ctx_id) {
PRINT_NAME();
EXIT_IF(g_net == nullptr);
if (!g_net->HttpTerm(Network::Id(http_ctx_id))) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpCreateTemplate(int http_ctx_id, const char* user_agent, int http_ver,
int is_auto_proxy_conf) {
PRINT_NAME();
LOGF("\t http_ctx_id = %d\n"
"\t user_agent = %s\n"
"\t http_ver = %d\n"
"\t is_auto_proxy_conf = %d\n",
http_ctx_id, user_agent, http_ver, is_auto_proxy_conf);
EXIT_IF(g_net == nullptr);
auto id = g_net->HttpCreateTemplate(Network::Id(http_ctx_id), user_agent, http_ver,
is_auto_proxy_conf != 0);
if (!id.IsValid()) {
return HTTP_ERROR_OUT_OF_MEMORY;
}
return id.ToInt();
}
int KYTY_SYSV_ABI HttpDeleteTemplate(int tmpl_id) {
PRINT_NAME();
EXIT_IF(g_net == nullptr);
if (!g_net->HttpDeleteTemplate(Network::Id(tmpl_id))) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpSetNonblock(int id, int enable) {
PRINT_NAME();
LOGF("\t id = %d\n"
"\t enable = %d\n",
id, enable);
if (!g_net->HttpSetNonblock(Network::Id(id), enable != 0)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpsSetSslCallback(int id, HttpsCallback cbfunc, void* user_arg) {
PRINT_NAME();
LOGF("\t id = %d\n", id);
if (!g_net->HttpsSetSslCallback(Network::Id(id), cbfunc, user_arg)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpsSetMinSslVersion(int id, uint32_t ssl_version) {
PRINT_NAME();
LOGF("\t id = %d\n"
"\t ssl_version = %u\n",
id, ssl_version);
if (!g_net->HttpsSetMinSslVersion(Network::Id(id), ssl_version)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpsDisableOption(int id, uint32_t ssl_flags) {
PRINT_NAME();
LOGF("\t id = %d\n"
"\t ssl_flags = %u\n",
id, ssl_flags);
if (!g_net->HttpsDisableOption(Network::Id(id), ssl_flags)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpSetResolveTimeOut(int id, uint32_t usec) {
PRINT_NAME();
LOGF("\t id = %d\n"
"\t usec = %u\n",
id, usec);
if (!g_net->HttpSetResolveTimeOut(Network::Id(id), usec)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpSetResolveRetry(int id, int32_t retry) {
PRINT_NAME();
LOGF("\t id = %d\n"
"\t retry = %d\n",
id, retry);
if (!g_net->HttpSetResolveRetry(Network::Id(id), retry)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpSetConnectTimeOut(int id, uint32_t usec) {
PRINT_NAME();
LOGF("\t id = %d\n"
"\t usec = %u\n",
id, usec);
if (!g_net->HttpSetConnectTimeOut(Network::Id(id), usec)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpSetSendTimeOut(int id, uint32_t usec) {
PRINT_NAME();
LOGF("\t id = %d\n"
"\t usec = %u\n",
id, usec);
if (!g_net->HttpSetSendTimeOut(Network::Id(id), usec)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpSetRecvTimeOut(int id, uint32_t usec) {
PRINT_NAME();
LOGF("\t id = %d\n"
"\t usec = %u\n",
id, usec);
if (!g_net->HttpSetRecvTimeOut(Network::Id(id), usec)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpSetAutoRedirect(int id, int enable) {
PRINT_NAME();
LOGF("\t id = %d\n"
"\t enable = %d\n",
id, enable);
if (!g_net->HttpSetAutoRedirect(Network::Id(id), enable)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpSetAuthEnabled(int id, int enable) {
PRINT_NAME();
LOGF("\t id = %d\n"
"\t enable = %d\n",
id, enable);
if (!g_net->HttpSetAuthEnabled(Network::Id(id), enable)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpAddRequestHeader(int id, const char* name, const char* value, uint32_t mode) {
PRINT_NAME();
LOGF("\t id = %d\n"
"\t name = %s\n"
"\t value = %s\n"
"\t mode = %u\n",
id, name, value, mode);
EXIT_NOT_IMPLEMENTED(mode != 0 && mode != 1);
if (!g_net->HttpAddRequestHeader(Network::Id(id), name, value, mode == 1)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpCreateEpoll(int http_ctx_id, HttpEpollHandle* eh) {
PRINT_NAME();
LOGF("\t http_ctx_id = %d\n", http_ctx_id);
EXIT_IF(g_net == nullptr);
EXIT_NOT_IMPLEMENTED(eh == nullptr);
EXIT_NOT_IMPLEMENTED(!g_net->HttpValid(Network::Id(http_ctx_id)));
*eh = new HttpEpoll;
(*eh)->http_ctx_id = Network::Id(http_ctx_id);
return OK;
}
int KYTY_SYSV_ABI HttpDestroyEpoll(int http_ctx_id, HttpEpollHandle eh) {
PRINT_NAME();
LOGF("\t http_ctx_id = %d\n", http_ctx_id);
EXIT_IF(g_net == nullptr);
EXIT_NOT_IMPLEMENTED(eh == nullptr);
EXIT_NOT_IMPLEMENTED(!g_net->HttpValid(Network::Id(http_ctx_id)));
delete eh;
return OK;
}
int KYTY_SYSV_ABI HttpSetEpoll(int id, HttpEpollHandle eh, void* user_arg) {
PRINT_NAME();
LOGF("\t id = %d\n", id);
EXIT_NOT_IMPLEMENTED(eh == nullptr);
EXIT_NOT_IMPLEMENTED(!g_net->HttpValidRequest(Network::Id(id)));
eh->request_id = Network::Id(id);
eh->user_arg = user_arg;
return OK;
}
int KYTY_SYSV_ABI HttpUnsetEpoll(int id) {
PRINT_NAME();
LOGF("\t id = %d\n", id);
EXIT_NOT_IMPLEMENTED(!g_net->HttpValidRequest(Network::Id(id)));
return OK;
}
int KYTY_SYSV_ABI HttpSendRequest(int request_id, const void* /*post_data*/, size_t /*size*/) {
PRINT_NAME();
LOGF("\t request_id = %d\n", request_id);
EXIT_IF(g_net == nullptr);
if (!g_net->HttpMarkRequestSent(Network::Id(request_id), HTTP_ERROR_TIMEOUT)) {
return HTTP_ERROR_INVALID_ID;
}
return HTTP_ERROR_TIMEOUT;
}
int KYTY_SYSV_ABI HttpAbortRequest(int request_id) {
PRINT_NAME();
LOGF("\t request_id = %d\n", request_id);
EXIT_IF(g_net == nullptr);
if (!g_net->HttpValidRequest(Network::Id(request_id))) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpWaitRequest(HttpEpollHandle eh, HttpNBEvent* nbev, int maxevents,
int timeout) {
PRINT_NAME();
LOGF("\t eh = 0x%016" PRIx64 "\n"
"\t nbev = 0x%016" PRIx64 "\n"
"\t maxevents = %d\n"
"\t timeout = %d\n",
reinterpret_cast<uint64_t>(eh), reinterpret_cast<uint64_t>(nbev), maxevents, timeout);
EXIT_IF(g_net == nullptr);
if (eh == nullptr || maxevents < 0 || (maxevents > 0 && nbev == nullptr)) {
return HTTP_ERROR_INVALID_VALUE;
}
return 0;
}
int KYTY_SYSV_ABI HttpGetStatusCode(int request_id, int* status_code) {
PRINT_NAME();
LOGF("\t request_id = %d\n"
"\t status_code = 0x%016" PRIx64 "\n",
request_id, reinterpret_cast<uint64_t>(status_code));
if (status_code == nullptr) {
return HTTP_ERROR_INVALID_VALUE;
}
*status_code = 0;
EXIT_IF(g_net == nullptr);
int send_result = HTTP_ERROR_BEFORE_SEND;
if (!g_net->HttpGetRequestResponse(Network::Id(request_id), &send_result, status_code, nullptr,
nullptr, nullptr)) {
return HTTP_ERROR_INVALID_ID;
}
return send_result;
}
int KYTY_SYSV_ABI HttpGetAllResponseHeaders(int request_id, char** header, size_t* header_size) {
PRINT_NAME();
LOGF("\t request_id = %d\n"
"\t header = 0x%016" PRIx64 "\n"
"\t header_size = 0x%016" PRIx64 "\n",
request_id, reinterpret_cast<uint64_t>(header), reinterpret_cast<uint64_t>(header_size));
if (header == nullptr || header_size == nullptr) {
return HTTP_ERROR_INVALID_VALUE;
}
*header = nullptr;
*header_size = 0;
EXIT_IF(g_net == nullptr);
int send_result = HTTP_ERROR_BEFORE_SEND;
const char* headers = nullptr;
size_t headers_size = 0;
if (!g_net->HttpGetRequestResponse(Network::Id(request_id), &send_result, nullptr, &headers,
&headers_size, nullptr)) {
return HTTP_ERROR_INVALID_ID;
}
*header = const_cast<char*>(headers);
*header_size = headers_size;
return send_result;
}
int KYTY_SYSV_ABI HttpGetResponseContentLength(int request_id, int* result,
uint64_t* content_length) {
PRINT_NAME();
LOGF("\t request_id = %d\n"
"\t result = 0x%016" PRIx64 "\n"
"\t content_length = 0x%016" PRIx64 "\n",
request_id, reinterpret_cast<uint64_t>(result),
reinterpret_cast<uint64_t>(content_length));
if (result == nullptr || content_length == nullptr) {
return HTTP_ERROR_INVALID_VALUE;
}
*result = 0;
*content_length = 0;
EXIT_IF(g_net == nullptr);
int send_result = HTTP_ERROR_BEFORE_SEND;
if (!g_net->HttpGetRequestResponse(Network::Id(request_id), &send_result, nullptr, nullptr,
nullptr, content_length)) {
return HTTP_ERROR_INVALID_ID;
}
*result = (send_result == OK ? 0 : -1);
return send_result;
}
int KYTY_SYSV_ABI HttpCreateConnection(int tmpl_id, const char* server_name, const char* scheme,
uint16_t port, int enable_keep_alive) {
PRINT_NAME();
LOGF("\t tmpl_id = %d\n"
"\t server_name = %s\n"
"\t scheme = %s\n"
"\t port = %u\n"
"\t enable_keep_alive = %d\n",
tmpl_id, server_name, scheme, static_cast<unsigned>(port), enable_keep_alive);
EXIT_IF(g_net == nullptr);
if (server_name == nullptr || scheme == nullptr) {
return HTTP_ERROR_INVALID_VALUE;
}
auto id = g_net->HttpCreateConnection(Network::Id(tmpl_id), server_name, scheme, port,
enable_keep_alive != 0);
if (!id.IsValid()) {
return HTTP_ERROR_OUT_OF_MEMORY;
}
return id.ToInt();
}
int KYTY_SYSV_ABI HttpCreateConnectionWithURL(int tmpl_id, const char* url, int enable_keep_alive) {
PRINT_NAME();
LOGF("\t tmpl_id = %d\n"
"\t url = %s\n"
"\t enable_keep_alive = %d\n",
tmpl_id, url, enable_keep_alive);
EXIT_IF(g_net == nullptr);
if (url == nullptr || url[0] == '\0') {
return HTTP_ERROR_INVALID_URL;
}
auto id = g_net->HttpCreateConnectionWithURL(Network::Id(tmpl_id), url, enable_keep_alive != 0);
if (!id.IsValid()) {
return HTTP_ERROR_OUT_OF_MEMORY;
}
return id.ToInt();
}
int KYTY_SYSV_ABI HttpDeleteConnection(int conn_id) {
PRINT_NAME();
LOGF("\t conn_id = %d\n", conn_id);
EXIT_IF(g_net == nullptr);
if (!g_net->HttpDeleteConnection(Network::Id(conn_id))) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpCreateRequest(int conn_id, int method, const char* path,
uint64_t content_length) {
PRINT_NAME();
LOGF("\t conn_id = %d\n"
"\t method = %d\n"
"\t path = %s\n"
"\t content_length = %" PRIu64 "\n",
conn_id, method, path, content_length);
EXIT_IF(g_net == nullptr);
auto method_name = HttpMethodToString(method);
if (method_name == nullptr) {
return HTTP_ERROR_UNKNOWN_METHOD;
}
if (path == nullptr) {
return HTTP_ERROR_INVALID_VALUE;
}
auto id =
g_net->HttpCreateRequestWithURL2(Network::Id(conn_id), method_name, path, content_length);
if (!id.IsValid()) {
return HTTP_ERROR_OUT_OF_MEMORY;
}
return id.ToInt();
}
int KYTY_SYSV_ABI HttpCreateRequest2(int conn_id, const char* method, const char* path,
uint64_t content_length) {
PRINT_NAME();
LOGF("\t conn_id = %d\n"
"\t method = %s\n"
"\t path = %s\n"
"\t content_length = %" PRIu64 "\n",
conn_id, method, path, content_length);
EXIT_IF(g_net == nullptr);
if (method == nullptr || path == nullptr) {
return HTTP_ERROR_INVALID_VALUE;
}
auto id =
g_net->HttpCreateRequestWithURL2(Network::Id(conn_id), method, path, content_length);
return id.IsValid() ? id.ToInt() : HTTP_ERROR_OUT_OF_MEMORY;
}
int KYTY_SYSV_ABI HttpCreateRequestWithURL2(int conn_id, const char* method, const char* url,
uint64_t content_length) {
PRINT_NAME();
LOGF("\t conn_id = %d\n"
"\t url = %s\n"
"\t method = %s\n"
"\t content_length = %" PRIu64 "\n",
conn_id, url, method, content_length);
EXIT_IF(g_net == nullptr);
if (method == nullptr) {
return HTTP_ERROR_INVALID_VALUE;
}
if (url == nullptr || url[0] == '\0') {
return HTTP_ERROR_INVALID_URL;
}
auto id = g_net->HttpCreateRequestWithURL2(Network::Id(conn_id), method, url, content_length);
if (!id.IsValid()) {
return HTTP_ERROR_OUT_OF_MEMORY;
}
return id.ToInt();
}
int KYTY_SYSV_ABI HttpDeleteRequest(int req_id) {
PRINT_NAME();
LOGF("\t req_id = %d\n", req_id);
EXIT_IF(g_net == nullptr);
if (!g_net->HttpDeleteRequest(Network::Id(req_id))) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
int KYTY_SYSV_ABI HttpSetRequestContentLength(int request_id, uint64_t content_length) {
PRINT_NAME();
LOGF("\t request_id = %d\n"
"\t content_length = %" PRIu64 "\n",
request_id, content_length);
EXIT_IF(g_net == nullptr);
if (!g_net->HttpSetRequestContentLength(Network::Id(request_id), content_length)) {
return HTTP_ERROR_INVALID_ID;
}
return OK;
}
} // namespace Http
namespace NetCtl {
LIB_NAME("NetCtl", "NetCtl");
constexpr int NET_CTL_STATE_DISCONNECTED = 0;
constexpr int NET_CTL_STATE_IPOBTAINED = 3;
constexpr int NET_CTL_DEVICE_WIRED = 0;
constexpr int NET_CTL_DEVICE_WIRELESS = 1;
constexpr int NET_CTL_LINK_CONNECTED = 1;
constexpr int NET_CTL_EVENT_DISCONNECTED = 1;
constexpr int NET_CTL_EVENT_IPOBTAINED = 3;
constexpr int NET_CTL_IP_DHCP = 0;
constexpr int NET_CTL_HTTP_PROXY_OFF = 0;
constexpr int NET_CTL_CALLBACK_MAX = 8;
constexpr int NET_CTL_ERROR_CALLBACK_MAX = -2143215357; /* 0x80412103 */
constexpr int NET_CTL_ERROR_ID_NOT_FOUND = -2143215356; /* 0x80412104 */
constexpr int NET_CTL_ERROR_INVALID_ID = -2143215355; /* 0x80412105 */
constexpr int NET_CTL_ERROR_INVALID_CODE = -2143215354; /* 0x80412106 */
constexpr int NET_CTL_ERROR_INVALID_ADDR = -2143215353; /* 0x80412107 */
constexpr int NET_CTL_ERROR_NOT_CONNECTED = -2143215352; /* 0x80412108 */
constexpr int NET_CTL_ERROR_INVALID_TYPE = -2143215345; /* 0x8041210f */
struct NetInAddr {
uint32_t s_addr = 0;
};
struct NetEtherAddr {
uint8_t data[6];
};
struct NetCtlNatInfo {
unsigned int size = sizeof(NetCtlNatInfo);
int stunStatus = 0;
int natType = 0;
NetInAddr mappedAddr;
};
union NetCtlInfo {
uint32_t device;
NetEtherAddr ether_addr;
uint32_t mtu;
uint32_t link;
NetEtherAddr bssid;
char ssid[32 + 1];
uint32_t wifi_security;
int32_t rssi_dbm;
uint8_t rssi_percentage;
uint8_t channel;
uint32_t ip_config;
char dhcp_hostname[255 + 1];
char pppoe_auth_name[127 + 1];
char ip_address[16];
char netmask[16];
char default_route[16];
char primary_dns[16];
char secondary_dns[16];
uint32_t http_proxy_config;
char http_proxy_server[255 + 1];
uint16_t http_proxy_port;
};
struct NetCtlCallbackSlot {
NetCtlCallback func = nullptr;
void* arg = nullptr;
int last_event = 0;
};
static Common::Mutex g_net_ctl_callbacks_mutex;
static std::array<NetCtlCallbackSlot, 8> g_net_ctl_callbacks;
struct HostNetworkInfo {
bool connected = false;
bool wireless = false;
NetEtherAddr ether_addr {};
char ip_address[16] {};
char netmask[16] {};
char default_route[16] {};
char primary_dns[16] {};
char secondary_dns[16] {};
};
static void CopyIpv4String(char* dst, size_t dst_size, const sockaddr* addr) {
if (dst == nullptr || dst_size == 0 || addr == nullptr || addr->sa_family != AF_INET) {
return;
}
const auto* in = reinterpret_cast<const sockaddr_in*>(addr);
inet_ntop(AF_INET, &in->sin_addr, dst, static_cast<socklen_t>(dst_size));
}
#if defined(_WIN32)
static void CopyIpv4Netmask(char* dst, size_t dst_size, uint8_t prefix_len) {
if (dst == nullptr || dst_size == 0 || prefix_len > 32) {
return;
}
const uint32_t mask = (prefix_len == 0 ? 0 : (0xffffffffu << (32u - prefix_len)));
in_addr in {};
in.S_un.S_addr = htonl(mask);
inet_ntop(AF_INET, &in, dst, static_cast<socklen_t>(dst_size));
}
#endif
static HostNetworkInfo QueryHostNetworkInfo() {
HostNetworkInfo info;
#if defined(_WIN32)
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS;
ULONG size = 0;
auto ret = GetAdaptersAddresses(AF_INET, flags, nullptr, nullptr, &size);
if (ret != ERROR_BUFFER_OVERFLOW || size == 0) {
return info;
}
std::vector<uint8_t> buffer(size);
auto* adapters = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(buffer.data());
ret = GetAdaptersAddresses(AF_INET, flags, nullptr, adapters, &size);
if (ret != NO_ERROR) {
return info;
}
for (auto* adapter = adapters; adapter != nullptr; adapter = adapter->Next) {
if (adapter->OperStatus != IfOperStatusUp || adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK ||
adapter->IfType == IF_TYPE_TUNNEL) {
continue;
}
for (auto* unicast = adapter->FirstUnicastAddress; unicast != nullptr;
unicast = unicast->Next) {
const auto* addr = unicast->Address.lpSockaddr;
if (addr == nullptr) {
continue;
}
if (addr->sa_family == AF_INET) {
const auto* in = reinterpret_cast<const sockaddr_in*>(addr);
const auto ip = ntohl(in->sin_addr.S_un.S_addr);
if ((ip & 0xff000000u) != 0x7f000000u && ip != 0) {
info.connected = true;
info.wireless = (adapter->IfType == IF_TYPE_IEEE80211);
if (adapter->PhysicalAddressLength >= sizeof(info.ether_addr.data)) {
std::memcpy(info.ether_addr.data, adapter->PhysicalAddress,
sizeof(info.ether_addr.data));
}
CopyIpv4String(info.ip_address, sizeof(info.ip_address), addr);
CopyIpv4Netmask(info.netmask, sizeof(info.netmask),
unicast->OnLinkPrefixLength);
if (adapter->FirstGatewayAddress != nullptr) {
CopyIpv4String(info.default_route, sizeof(info.default_route),
adapter->FirstGatewayAddress->Address.lpSockaddr);
}
int dns_index = 0;
for (auto* dns = adapter->FirstDnsServerAddress; dns != nullptr;
dns = dns->Next) {
if (dns->Address.lpSockaddr != nullptr &&
dns->Address.lpSockaddr->sa_family == AF_INET) {
CopyIpv4String(dns_index == 0 ? info.primary_dns : info.secondary_dns,
dns_index == 0 ? sizeof(info.primary_dns)
: sizeof(info.secondary_dns),
dns->Address.lpSockaddr);
dns_index++;
if (dns_index >= 2) {
break;
}
}
}
return info;
}
}
}
}
return info;
#else
ifaddrs* addresses = nullptr;
if (getifaddrs(&addresses) != 0) {
return info;
}
const auto usable = [](const ifaddrs* address) {
return address->ifa_addr != nullptr && address->ifa_addr->sa_family == AF_INET &&
(address->ifa_flags & (IFF_UP | IFF_RUNNING | IFF_LOOPBACK)) ==
(IFF_UP | IFF_RUNNING) &&
reinterpret_cast<const sockaddr_in*>(address->ifa_addr)->sin_addr.s_addr != 0;
};
const ifaddrs* selected = nullptr;
for (auto* address = addresses; address != nullptr; address = address->ifa_next) {
if (usable(address)) {
selected = address;
break;
}
}
std::ifstream routes("/proc/net/route");
std::string line;
uint32_t best_metric = std::numeric_limits<uint32_t>::max();
uint32_t gateway = 0;
bool has_default_route = false;
while (std::getline(routes, line)) {
std::istringstream fields(line);
std::string name;
uint32_t destination = 0, candidate_gateway = 0, flags = 0, refs = 0, uses = 0;
uint32_t metric = 0, mask = 0;
if (!(fields >> name >> std::hex >> destination >> candidate_gateway >> flags >>
std::dec >> refs >> uses >> metric >> std::hex >> mask) ||
destination != 0 || mask != 0 || (flags & (RTF_UP | RTF_REJECT)) != RTF_UP ||
(has_default_route && metric >= best_metric)) {
continue;
}
for (auto* address = addresses; address != nullptr; address = address->ifa_next) {
if (usable(address) && name == address->ifa_name) {
selected = address;
gateway = candidate_gateway;
best_metric = metric;
has_default_route = true;
break;
}
}
}
if (selected != nullptr) {
CopyIpv4String(info.ip_address, sizeof(info.ip_address), selected->ifa_addr);
CopyIpv4String(info.netmask, sizeof(info.netmask), selected->ifa_netmask);
info.connected = info.ip_address[0] != '\0';
if (has_default_route) {
in_addr address {gateway};
inet_ntop(AF_INET, &address, info.default_route, sizeof(info.default_route));
}
}
freeifaddrs(addresses);
return info;
#endif
}
static bool NetCtlConnected() {
return Config::IsNetworkConnected() && QueryHostNetworkInfo().connected;
}
int KYTY_SYSV_ABI NetCtlInit() {
PRINT_NAME();
return OK;
}
void KYTY_SYSV_ABI NetCtlTerm() {
PRINT_NAME();
Common::LockGuard lock(g_net_ctl_callbacks_mutex);
for (auto& cb: g_net_ctl_callbacks) {
cb = {};
}
}
int KYTY_SYSV_ABI NetCtlGetNatInfo(NetCtlNatInfo* nat_info) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(nat_info == nullptr);
EXIT_NOT_IMPLEMENTED(nat_info->size != sizeof(NetCtlNatInfo));
nat_info->stunStatus = 0;
nat_info->natType = 0;
nat_info->mappedAddr.s_addr = 0;
return OK;
}
int KYTY_SYSV_ABI NetCtlCheckCallback() {
PRINT_NAME();
Common::LockGuard lock(g_net_ctl_callbacks_mutex);
const int event = (NetCtlConnected() ? NET_CTL_EVENT_IPOBTAINED : NET_CTL_EVENT_DISCONNECTED);
for (auto& cb: g_net_ctl_callbacks) {
if (cb.func != nullptr && cb.last_event != event) {
cb.func(event, cb.arg);
cb.last_event = event;
}
}
return OK;
}
int KYTY_SYSV_ABI NetCtlGetState(int* state) {
PRINT_NAME();
if (state == nullptr) {
return NET_CTL_ERROR_INVALID_ADDR;
}
*state = (NetCtlConnected() ? NET_CTL_STATE_IPOBTAINED : NET_CTL_STATE_DISCONNECTED);
return OK;
}
int KYTY_SYSV_ABI NetCtlGetStateV6(int* state) {
PRINT_NAME();
if (state == nullptr) {
return NET_CTL_ERROR_INVALID_ADDR;
}
*state = NET_CTL_STATE_DISCONNECTED;
return OK;
}
int KYTY_SYSV_ABI NetCtlRegisterCallback(NetCtlCallback func, void* arg, int* cid) {
PRINT_NAME();
if (func == nullptr || cid == nullptr) {
return NET_CTL_ERROR_INVALID_ADDR;
}
Common::LockGuard lock(g_net_ctl_callbacks_mutex);
for (int i = 0; i < NET_CTL_CALLBACK_MAX; i++) {
if (g_net_ctl_callbacks[static_cast<size_t>(i)].func == nullptr) {
g_net_ctl_callbacks[static_cast<size_t>(i)].func = func;
g_net_ctl_callbacks[static_cast<size_t>(i)].arg = arg;
g_net_ctl_callbacks[static_cast<size_t>(i)].last_event = 0;
*cid = i;
return OK;
}
}
return NET_CTL_ERROR_CALLBACK_MAX;
}
int KYTY_SYSV_ABI NetCtlUnregisterCallback(int cid) {
PRINT_NAME();
if (cid < 0 || cid >= NET_CTL_CALLBACK_MAX) {
return NET_CTL_ERROR_INVALID_ID;
}
Common::LockGuard lock(g_net_ctl_callbacks_mutex);
auto& cb = g_net_ctl_callbacks[static_cast<size_t>(cid)];
if (cb.func == nullptr) {
return NET_CTL_ERROR_ID_NOT_FOUND;
}
cb = {};
return OK;
}
int KYTY_SYSV_ABI NetCtlGetResult(int event_type, int* error_code) {
PRINT_NAME();
if (error_code == nullptr) {
return NET_CTL_ERROR_INVALID_ADDR;
}
LOGF("\t event_type = %d\n", event_type);
switch (event_type) {
case NET_CTL_EVENT_DISCONNECTED:
case 2:
case NET_CTL_EVENT_IPOBTAINED: *error_code = OK; break;
default: return NET_CTL_ERROR_INVALID_TYPE;
}
return OK;
}
int KYTY_SYSV_ABI NetCtlGetInfo(int code, NetCtlInfo* info) {
PRINT_NAME();
if (info == nullptr) {
return NET_CTL_ERROR_INVALID_ADDR;
}
if (code < 1 || code > 21) {
return NET_CTL_ERROR_INVALID_CODE;
}
if (!Config::IsNetworkConnected()) {
return NET_CTL_ERROR_NOT_CONNECTED;
}
const auto host = QueryHostNetworkInfo();
if (!host.connected || (code == 16 && host.default_route[0] == '\0')) {
return NET_CTL_ERROR_NOT_CONNECTED;
}
memset(info, 0, sizeof(NetCtlInfo));
switch (code) {
case 1:
info->device = (host.wireless ? NET_CTL_DEVICE_WIRELESS : NET_CTL_DEVICE_WIRED);
break;
case 2: info->ether_addr = host.ether_addr; break;
case 3: info->mtu = 1500; break;
case 4: info->link = NET_CTL_LINK_CONNECTED; break;
case 5: break;
case 6: break;
case 7: info->wifi_security = 0; break;
case 8: info->rssi_dbm = 0; break;
case 9: info->rssi_percentage = 0; break;
case 10: info->channel = 0; break;
case 11: info->ip_config = NET_CTL_IP_DHCP; break;
case 12: break;
case 13: break;
case 14:
std::strncpy(info->ip_address, host.ip_address,
sizeof(info->ip_address) - 1);
break;
case 15:
std::strncpy(info->netmask, host.netmask, sizeof(info->netmask) - 1);
break;
case 16:
std::strncpy(info->default_route, host.default_route,
sizeof(info->default_route) - 1);
break;
case 17:
std::strncpy(info->primary_dns, host.primary_dns, sizeof(info->primary_dns) - 1);
break;
case 18:
std::strncpy(info->secondary_dns, host.secondary_dns, sizeof(info->secondary_dns) - 1);
break;
case 19: info->http_proxy_config = NET_CTL_HTTP_PROXY_OFF; break;
case 20: break;
case 21: info->http_proxy_port = 0; break;
}
return OK;
}
} // namespace NetCtl
namespace NpManager {
LIB_NAME("NpManager", "NpManager");
struct NpTitleId {
char id[12 + 1];
uint8_t padding[3];
};
struct NpTitleSecret {
uint8_t data[128];
};
struct NpCountryCode {
char data[2];
char term;
char padding[1];
};
struct NpAgeRestriction {
NpCountryCode country_code;
int8_t age;
uint8_t padding[3];
};
struct NpContentRestriction {
size_t size;
int8_t default_age_restriction;
char padding[3];
int32_t age_restriction_count;
const NpAgeRestriction* age_restriction;
};
struct NpOnlineId {
char data[16];
char term;
char dummy[3];
};
struct NpId {
NpOnlineId handle;
uint8_t opt[8];
uint8_t reserved[8];
};
struct NpCreateAsyncRequestParameter {
size_t size;
LibKernel::KernelCpumask cpu_affinity_mask;
int thread_priority;
uint8_t padding[4];
};
struct NpCheckPremiumParameter {
size_t size;
int user_id;
char padding[4];
uint64_t features;
uint8_t reserved[32];
};
struct NpCheckPremiumResult {
bool authorized;
uint8_t reserved[32];
};
constexpr int np_error_invalid_argument = -2141913085; /* 0x80550003 */
constexpr int np_error_signed_out = -2141913082; /* 0x80550006 */
constexpr int np_error_callback_already_registered = -2141913080; /* 0x80550008 */
constexpr int np_error_invalid_size = -2141913071; /* 0x80550011 */
constexpr int np_error_aborted = -2141913070; /* 0x80550012 */
constexpr int np_error_request_max = -2141913069; /* 0x80550013 */
constexpr int np_error_request_not_found = -2141913068; /* 0x80550014 */
constexpr int np_error_invalid_id = -2141913067; /* 0x80550015 */
constexpr int np_request_max = 128;
constexpr uint32_t np_state_signed_out = 1;
struct NpStateCallback {
NpStateCallbackA callback = nullptr;
void* userdata = nullptr;
bool pending = false;
};
static std::vector<NpStateCallback> g_np_state_callbacks;
enum class NpRequestState {
Free,
Ready,
Aborted,
Complete,
};
struct NpRequest {
NpRequestState state = NpRequestState::Free;
bool async = false;
int result = OK;
};
static std::mutex g_np_request_mutex;
static std::vector<NpRequest> g_np_requests;
static int np_create_request(bool async) {
std::lock_guard lock(g_np_request_mutex);
for (size_t i = 0; i < g_np_requests.size(); i++) {
auto& request = g_np_requests[i];
if (request.state == NpRequestState::Free) {
request.state = NpRequestState::Ready;
request.async = async;
request.result = OK;
return static_cast<int>(i + 1);
}
}
if (g_np_requests.size() >= np_request_max) {
return np_error_request_max;
}
g_np_requests.push_back({NpRequestState::Ready, async, OK});
return static_cast<int>(g_np_requests.size());
}
static NpRequest* np_get_request_locked(int req_id) {
if (req_id <= 0) {
return nullptr;
}
const auto index = static_cast<size_t>(req_id - 1);
if (index >= g_np_requests.size() || g_np_requests[index].state == NpRequestState::Free) {
return nullptr;
}
return &g_np_requests[index];
}
static int np_complete_signed_out_locked(NpRequest* request) {
if (request->state == NpRequestState::Complete) {
request->result = np_error_invalid_argument;
return np_error_invalid_argument;
}
if (request->state == NpRequestState::Aborted) {
request->result = np_error_aborted;
return np_error_aborted;
}
request->state = NpRequestState::Complete;
request->result = np_error_signed_out;
return (request->async ? OK : np_error_signed_out);
}
int KYTY_SYSV_ABI NpCheckCallback() {
PRINT_NAME();
std::vector<NpStateCallback> pending;
for (auto& entry: g_np_state_callbacks) {
if (entry.pending) {
pending.push_back(entry);
entry.pending = false;
}
}
for (const auto& entry: pending) {
entry.callback(Config::GetUserId(), np_state_signed_out, entry.userdata);
}
return OK;
}
int KYTY_SYSV_ABI NpSetNpTitleId(const NpTitleId* title_id, const NpTitleSecret* title_secret) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(title_id == nullptr);
EXIT_NOT_IMPLEMENTED(title_secret == nullptr);
LOGF("\t title_id = %.12s\n"
"\t title_secret = %s\n",
title_id->id, fmt::format("{:02X}", fmt::join(title_secret->data, "")));
return OK;
}
int KYTY_SYSV_ABI NpSetContentRestriction(const NpContentRestriction* restriction) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(restriction == nullptr);
EXIT_NOT_IMPLEMENTED(restriction->size != sizeof(NpContentRestriction));
LOGF("\t default_age_restriction = %" PRIi8 "\n"
"\t age_restriction_count = %" PRIi32 "\n",
restriction->default_age_restriction, restriction->age_restriction_count);
for (int i = 0; i < restriction->age_restriction_count; i++) {
LOGF("\t age_restriction[%d].age = %" PRIi8 "\n"
"\t age_restriction[%d].country_code.data = %.2s\n",
i, restriction->age_restriction[i].age, i,
restriction->age_restriction[i].country_code.data);
}
return OK;
}
int KYTY_SYSV_ABI NpRegisterStateCallback(void* /*callback*/, void* /*userdata*/) {
PRINT_NAME();
return OK;
}
int KYTY_SYSV_ABI NpUnregisterStateCallback() {
PRINT_NAME();
return OK;
}
int KYTY_SYSV_ABI NpRegisterStateCallbackA(NpStateCallbackA callback, void* userdata) {
PRINT_NAME();
if (callback == nullptr) {
return np_error_invalid_argument;
}
for (const auto& entry: g_np_state_callbacks) {
if (entry.callback == callback) {
return np_error_callback_already_registered;
}
}
g_np_state_callbacks.push_back({callback, userdata, true});
return static_cast<int>(g_np_state_callbacks.size());
}
void KYTY_SYSV_ABI NpRegisterGamePresenceCallback(void* /*callback*/, void* /*userdata*/) {
PRINT_NAME();
}
int KYTY_SYSV_ABI NpRegisterPlusEventCallback(void* /*callback*/, void* /*userdata*/) {
PRINT_NAME();
return OK;
}
int KYTY_SYSV_ABI NpRegisterPremiumEventCallback(void* /*callback*/, void* /*userdata*/) {
PRINT_NAME();
return OK;
}
int KYTY_SYSV_ABI NpRegisterNpReachabilityStateCallback(void* /*callback*/, void* /*userdata*/) {
PRINT_NAME();
return OK;
}
int KYTY_SYSV_ABI NpGetNpId(int user_id, NpId* np_id) {
PRINT_NAME();
LOGF("\t user_id = %d\n", user_id);
EXIT_NOT_IMPLEMENTED(np_id == nullptr);
// int s = snprintf(np_id->handle.data, 16, "Kyty");
// EXIT_NOT_IMPLEMENTED(s >= 16);
// np_id->handle.term = 0;
std::memset(np_id, 0, sizeof(*np_id));
// return OK;
return np_error_signed_out;
}
int KYTY_SYSV_ABI NpGetOnlineId(int user_id, NpOnlineId* online_id) {
PRINT_NAME();
LOGF("\t user_id = %d\n", user_id);
EXIT_NOT_IMPLEMENTED(online_id == nullptr);
// int s = snprintf(online_id->data, 16, "Kyty");
// EXIT_NOT_IMPLEMENTED(s >= 16);
// online_id->term = 0;
std::memset(online_id, 0, sizeof(*online_id));
// return OK;
return np_error_signed_out;
}
int KYTY_SYSV_ABI NpGetAccountIdA(int user_id, uint64_t* account_id) {
PRINT_NAME();
LOGF("\t user_id = %d\n", user_id);
EXIT_NOT_IMPLEMENTED(account_id == nullptr);
// *account_id = 0x00000000feedfaceull;
*account_id = 0;
// return OK;
return np_error_signed_out;
}
int KYTY_SYSV_ABI NpGetAccountCountryA(int user_id, void* country_code) {
PRINT_NAME();
LOGF("\t user_id = %d\n", user_id);
EXIT_NOT_IMPLEMENTED(country_code == nullptr);
auto* code = static_cast<NpCountryCode*>(country_code);
std::memset(code, 0, sizeof(*code));
// code->data[0] = '';
// code->data[1] = 'S';
// return OK;
return np_error_signed_out;
}
int KYTY_SYSV_ABI NpGetAccountAge(int req_id, int user_id, uint8_t* age) {
PRINT_NAME();
LOGF("\t req_id = %d\n", req_id);
LOGF("\t user_id = %d\n", user_id);
LOGF("\t age = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(age));
if (req_id <= 0 || age == nullptr) {
return np_error_invalid_argument;
}
// *age = 21;
*age = 0;
// return OK;
return np_error_signed_out;
}
int KYTY_SYSV_ABI NpGetAccountLanguage2(int req_id, int user_id, void* language_code) {
PRINT_NAME();
LOGF("\t req_id = %d\n", req_id);
LOGF("\t user_id = %d\n", user_id);
LOGF("\t language_code = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(language_code));
if (req_id <= 0 || language_code == nullptr) {
return np_error_invalid_argument;
}
std::lock_guard lock(g_np_request_mutex);
if (np_get_request_locked(req_id) == nullptr) {
return np_error_request_not_found;
}
return np_error_signed_out;
}
int KYTY_SYSV_ABI NpCreateRequest() {
PRINT_NAME();
const auto req_id = np_create_request(false);
LOGF("\t req_id = %d\n", req_id);
return req_id;
}
int KYTY_SYSV_ABI NpCreateAsyncRequest(const NpCreateAsyncRequestParameter* param) {
PRINT_NAME();
if (param == nullptr) {
return np_error_invalid_argument;
}
if (param->size < sizeof(NpCreateAsyncRequestParameter)) {
return np_error_invalid_size;
}
LOGF("\t size = %" PRIu64 "\n"
"\t cpu_affinity_mask = %" PRIu64 "\n"
"\t thread_priority = %d\n",
param->size, param->cpu_affinity_mask, param->thread_priority);
const auto req_id = np_create_request(true);
LOGF("\t req_id = %d\n", req_id);
return req_id;
}
int KYTY_SYSV_ABI NpDeleteRequest(int req_id) {
PRINT_NAME();
LOGF("\t req_id = %d\n", req_id);
std::lock_guard lock(g_np_request_mutex);
auto* request = np_get_request_locked(req_id);
if (request == nullptr) {
return np_error_request_not_found;
}
request->state = NpRequestState::Free;
request->async = false;
request->result = OK;
return OK;
}
int KYTY_SYSV_ABI NpAbortRequest(int req_id) {
PRINT_NAME();
LOGF("\t req_id = %d\n", req_id);
std::lock_guard lock(g_np_request_mutex);
auto* request = np_get_request_locked(req_id);
if (request == nullptr) {
return np_error_request_not_found;
}
request->state = NpRequestState::Aborted;
request->result = np_error_aborted;
return OK;
}
int KYTY_SYSV_ABI NpCheckNpAvailability(int req_id, const char* user, void* result) {
PRINT_NAME();
// EXIT_NOT_IMPLEMENTED(req_id <= 0);
// EXIT_NOT_IMPLEMENTED(user == nullptr);
// EXIT_NOT_IMPLEMENTED(result != nullptr);
LOGF("\t req_id = %d\n"
"\t user = %s\n",
req_id, user != nullptr ? user : "(null)");
if (req_id <= 0 || user == nullptr) {
return np_error_invalid_argument;
}
if (result != nullptr) {
std::memset(result, 0, sizeof(int));
}
std::lock_guard lock(g_np_request_mutex);
auto* request = np_get_request_locked(req_id);
if (request == nullptr) {
return np_error_request_not_found;
}
// return OK;
return np_complete_signed_out_locked(request);
}
int KYTY_SYSV_ABI NpCheckNpReachability(int req_id, int user_id) {
PRINT_NAME();
// EXIT_NOT_IMPLEMENTED(req_id <= 0);
LOGF("\t req_id = %d\n", req_id);
LOGF("\t user_id = %d\n", user_id);
if (req_id <= 0) {
return np_error_invalid_argument;
}
std::lock_guard lock(g_np_request_mutex);
auto* request = np_get_request_locked(req_id);
if (request == nullptr) {
return np_error_request_not_found;
}
// return OK;
return np_complete_signed_out_locked(request);
}
int KYTY_SYSV_ABI NpPollAsync(int req_id, int* result) {
PRINT_NAME();
if (result == nullptr) {
return np_error_invalid_argument;
}
LOGF("\t req_id = %d\n", req_id);
std::lock_guard lock(g_np_request_mutex);
auto* request = np_get_request_locked(req_id);
if (request == nullptr) {
return np_error_request_not_found;
}
if (!request->async || request->state == NpRequestState::Ready) {
return np_error_invalid_id;
}
*result = request->result;
// return request->state == NpRequestState::Aborted ? np_error_aborted : OK;
return OK;
}
int KYTY_SYSV_ABI NpCheckPremium(int req_id, const NpCheckPremiumParameter* param,
NpCheckPremiumResult* result) {
PRINT_NAME();
LOGF("\t req_id = %d\n", req_id);
LOGF("\t param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(param));
LOGF("\t result = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(result));
if (param == nullptr || result == nullptr) {
return np_error_invalid_argument;
}
if (param->size < sizeof(NpCheckPremiumParameter)) {
return np_error_invalid_size;
}
LOGF("\t size = %" PRIu64 "\n"
"\t user_id = %d\n"
"\t features = 0x%016" PRIx64 "\n",
param->size, param->user_id, param->features);
std::lock_guard lock(g_np_request_mutex);
auto* request = np_get_request_locked(req_id);
if (request == nullptr) {
return np_error_request_not_found;
}
if (request->state == NpRequestState::Complete) {
request->result = np_error_invalid_argument;
return np_error_invalid_argument;
}
if (request->state == NpRequestState::Aborted) {
return np_error_aborted;
}
std::memset(result, 0, sizeof(*result));
// result->authorized = true;
// request->state = NpRequestState::Complete;
// request->result = OK;
// return OK;
return np_complete_signed_out_locked(request);
}
int KYTY_SYSV_ABI NpGetState(int user_id, uint32_t* state) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(state == nullptr);
LOGF("\t user_id = %d\n", user_id);
*state = np_state_signed_out;
return OK;
}
int KYTY_SYSV_ABI NpGetNpReachabilityState(int user_id, uint32_t* state) {
PRINT_NAME();
if (state == nullptr) {
return np_error_invalid_argument;
}
LOGF("\t user_id = %d\n", user_id);
// *state = 2; // SCE_NP_REACHABILITY_STATE_REACHABLE
*state = 0; // SCE_NP_REACHABILITY_STATE_UNAVAILABLE
return OK;
}
int KYTY_SYSV_ABI NpHasSignedUp(int user_id, bool* has_signed_up) {
PRINT_NAME();
if (has_signed_up == nullptr) {
return np_error_invalid_argument;
}
LOGF("\t user_id = %d\n", user_id);
*has_signed_up = false;
return OK;
}
} // namespace NpManager
} // namespace Libs::Network