Merge pull request #1373 from gjonn/feat/kernel-select-net-sockets
feat(libkernel): implement select over libSceNet sockets
This commit is contained in:
commit
f100d1d67a
|
|
@ -5,7 +5,7 @@ configure_windows_unwind(libSceNet)
|
|||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_sce_net_tests ${LIBS_INCLUDE_DIR}/tests/GuestSceNet.cpp)
|
||||
target_include_directories(guest_sce_net_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_sce_net_tests PRIVATE libSceNet libSceNetCtl libc)
|
||||
target_link_libraries(guest_sce_net_tests PRIVATE libSceNet libSceNetCtl libkernel libc)
|
||||
configure_windows_unwind(guest_sce_net_tests)
|
||||
add_test(NAME guest_sce_net COMMAND guest_sce_net_tests)
|
||||
set_tests_properties(guest_sce_net PROPERTIES TIMEOUT 20)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include <string>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
|
||||
|
||||
// Guest socket descriptors retain PS5 semantics while their transport is backed by host sockets.
|
||||
|
||||
|
|
@ -293,6 +294,65 @@ int* APS5_VABI sceNetErrnoLoc(void) {
|
|||
return errno_slot();
|
||||
}
|
||||
|
||||
int PollSockets(KernelSocketPoll::Entry* entries, int count, int timeoutMilliseconds) {
|
||||
std::vector<std::shared_ptr<NativeSocketHandle>> natives(static_cast<std::size_t>(count));
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_mutex);
|
||||
for (int index = 0; index < count; ++index) {
|
||||
const auto socket = g_socks.find(entries[index].descriptor);
|
||||
if (socket != g_socks.end()) natives[static_cast<std::size_t>(index)] = socket->second.native;
|
||||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
std::vector<WSAPOLLFD> descriptors;
|
||||
constexpr short NativeReadable = POLLRDNORM;
|
||||
constexpr short NativeUrgent = POLLRDBAND;
|
||||
constexpr short NativeWritable = POLLWRNORM;
|
||||
#else
|
||||
std::vector<pollfd> descriptors;
|
||||
constexpr short NativeReadable = POLLIN;
|
||||
constexpr short NativeUrgent = POLLPRI;
|
||||
constexpr short NativeWritable = POLLOUT;
|
||||
#endif
|
||||
std::vector<int> owners;
|
||||
for (int index = 0; index < count; ++index) {
|
||||
auto& entry = entries[index];
|
||||
if (!natives[static_cast<std::size_t>(index)]) {
|
||||
entry.revents = KernelSocketPoll::Unknown;
|
||||
continue;
|
||||
}
|
||||
decltype(descriptors)::value_type descriptor{};
|
||||
descriptor.fd = natives[static_cast<std::size_t>(index)]->value;
|
||||
if (entry.events & KernelSocketPoll::Readable) descriptor.events |= NativeReadable;
|
||||
if (entry.events & KernelSocketPoll::Urgent) descriptor.events |= NativeUrgent;
|
||||
if (entry.events & KernelSocketPoll::Writable) descriptor.events |= NativeWritable;
|
||||
descriptors.push_back(descriptor);
|
||||
owners.push_back(index);
|
||||
}
|
||||
if (descriptors.empty()) return 0;
|
||||
#ifdef _WIN32
|
||||
const int result = WSAPoll(descriptors.data(), static_cast<ULONG>(descriptors.size()), timeoutMilliseconds);
|
||||
#else
|
||||
const int result = ::poll(descriptors.data(), descriptors.size(), timeoutMilliseconds);
|
||||
#endif
|
||||
if (result < 0) return -native_error();
|
||||
int ready = 0;
|
||||
for (std::size_t index = 0; index < descriptors.size(); ++index) {
|
||||
const short native = descriptors[index].revents;
|
||||
short revents = 0;
|
||||
if (native & NativeReadable) revents |= KernelSocketPoll::Readable;
|
||||
if (native & NativeUrgent) revents |= KernelSocketPoll::Urgent;
|
||||
if (native & NativeWritable) revents |= KernelSocketPoll::Writable;
|
||||
if (native & (POLLERR | POLLNVAL)) revents |= KernelSocketPoll::Error;
|
||||
if (native & POLLHUP) revents |= KernelSocketPoll::HangUp;
|
||||
entries[owners[index]].revents = revents;
|
||||
if (revents != 0) ++ready;
|
||||
}
|
||||
return ready;
|
||||
}
|
||||
|
||||
const bool g_socketPollerRegistered = (KernelSetSocketPoller_nid_no_patch(&PollSockets), true);
|
||||
|
||||
int APS5_VABI sceNetInit_nid_postfix(void) {
|
||||
std::lock_guard<std::mutex> lk(g_mutex);
|
||||
g_net_inited = true;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace KernelSocketPoll {
|
||||
constexpr short Readable = 0x1;
|
||||
constexpr short Urgent = 0x2;
|
||||
constexpr short Writable = 0x4;
|
||||
constexpr short Error = 0x8;
|
||||
constexpr short HangUp = 0x10;
|
||||
constexpr short Unknown = 0x20;
|
||||
|
||||
struct Entry {
|
||||
int descriptor;
|
||||
short events;
|
||||
short revents;
|
||||
};
|
||||
|
||||
using Poller = int (*)(Entry* entries, int count, int timeoutMilliseconds);
|
||||
}
|
||||
|
||||
extern "C" void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller);
|
||||
|
|
@ -2,6 +2,35 @@
|
|||
#include <cstddef>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int GuestEinval = 22;
|
||||
constexpr int SelectDescriptorLimit = 1024;
|
||||
|
||||
struct GuestTimeval {
|
||||
std::int64_t seconds;
|
||||
std::int64_t microseconds;
|
||||
};
|
||||
|
||||
std::atomic<KernelSocketPoll::Poller> g_socketPoller{nullptr};
|
||||
|
||||
bool DescriptorSet(const void* set, int descriptor) {
|
||||
return set != nullptr && ((static_cast<const std::uint64_t*>(set)[descriptor / 64] >> (descriptor % 64)) & 1u) != 0;
|
||||
}
|
||||
|
||||
void ClearDescriptor(void* set, int descriptor) {
|
||||
if (set != nullptr) static_cast<std::uint64_t*>(set)[descriptor / 64] &= ~(std::uint64_t{1} << (descriptor % 64));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
|
|
@ -16,14 +45,72 @@ int APS5_VABI inet_pton_nid_postfix(int af, const char* src, void* dst) {
|
|||
return __inet_pton_nid_postfix(af, src, dst);
|
||||
}
|
||||
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
|
||||
void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller) {
|
||||
g_socketPoller.store(poller);
|
||||
}
|
||||
|
||||
int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* exceptfds, const void* timeout) {
|
||||
(void)nfds;
|
||||
(void)readfds;
|
||||
(void)writefds;
|
||||
(void)exceptfds;
|
||||
(void)timeout;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
const auto* limit = static_cast<const GuestTimeval*>(timeout);
|
||||
if (nfds < 0 || nfds > SelectDescriptorLimit || (limit != nullptr && (limit->seconds < 0 || limit->microseconds < 0 || limit->microseconds >= 1000000))) {
|
||||
*__error_nid_postfix() = GuestEinval;
|
||||
return -1;
|
||||
}
|
||||
std::vector<KernelSocketPoll::Entry> entries;
|
||||
for (int descriptor = 0; descriptor < nfds; ++descriptor) {
|
||||
short events = 0;
|
||||
if (DescriptorSet(readfds, descriptor)) events |= KernelSocketPoll::Readable;
|
||||
if (DescriptorSet(writefds, descriptor)) events |= KernelSocketPoll::Writable;
|
||||
if (DescriptorSet(exceptfds, descriptor)) events |= KernelSocketPoll::Urgent;
|
||||
if (events != 0) entries.push_back({descriptor, events, 0});
|
||||
}
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
const auto deadline = limit == nullptr ? std::chrono::steady_clock::time_point::max()
|
||||
: start + std::chrono::seconds(limit->seconds) + std::chrono::microseconds(limit->microseconds);
|
||||
for (;;) {
|
||||
int waitMilliseconds = 100;
|
||||
if (limit != nullptr) {
|
||||
const auto remaining = std::chrono::duration_cast<std::chrono::microseconds>(deadline - std::chrono::steady_clock::now()).count();
|
||||
waitMilliseconds = static_cast<int>(std::clamp<std::int64_t>((remaining + 999) / 1000, 0, 100));
|
||||
}
|
||||
if (!entries.empty()) {
|
||||
const auto poller = g_socketPoller.load();
|
||||
if (poller == nullptr) NotImplemented_nid_no_patch("select on descriptors that are not libSceNet sockets");
|
||||
for (auto& entry : entries) entry.revents = 0;
|
||||
const int result = poller(entries.data(), static_cast<int>(entries.size()), waitMilliseconds);
|
||||
if (result < 0) {
|
||||
*__error_nid_postfix() = -result;
|
||||
return -1;
|
||||
}
|
||||
int ready = 0;
|
||||
for (const auto& entry : entries) {
|
||||
if ((entry.revents & KernelSocketPoll::Unknown) != 0) NotImplemented_nid_no_patch("select on descriptors that are not libSceNet sockets");
|
||||
const bool readable = (entry.events & KernelSocketPoll::Readable) != 0 && (entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) != 0;
|
||||
const bool writable = (entry.events & KernelSocketPoll::Writable) != 0 && (entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) != 0;
|
||||
const bool urgent = (entry.events & KernelSocketPoll::Urgent) != 0 && (entry.revents & KernelSocketPoll::Urgent) != 0;
|
||||
ready += static_cast<int>(readable) + static_cast<int>(writable) + static_cast<int>(urgent);
|
||||
}
|
||||
if (ready > 0) {
|
||||
for (const auto& entry : entries) {
|
||||
if ((entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) == 0) ClearDescriptor(readfds, entry.descriptor);
|
||||
if ((entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) == 0) ClearDescriptor(writefds, entry.descriptor);
|
||||
if ((entry.revents & KernelSocketPoll::Urgent) == 0) ClearDescriptor(exceptfds, entry.descriptor);
|
||||
}
|
||||
return ready;
|
||||
}
|
||||
} else if (waitMilliseconds > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(waitMilliseconds));
|
||||
}
|
||||
if (limit != nullptr && std::chrono::steady_clock::now() >= deadline) {
|
||||
for (const auto& entry : entries) {
|
||||
ClearDescriptor(readfds, entry.descriptor);
|
||||
ClearDescriptor(writefds, entry.descriptor);
|
||||
ClearDescriptor(exceptfds, entry.descriptor);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ int APS5_VABI sceNetResolverStartNtoa(int, const char*, void*, int, int, int);
|
|||
int APS5_VABI sceNetResolverDestroy(int);
|
||||
int APS5_VABI sceNetResolverGetError(int, int*);
|
||||
int APS5_VABI sceNetCtlGetState(int*);
|
||||
int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*);
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
int APS5_VABI sceNetInetPton(int, const char*, void*);
|
||||
const char* APS5_VABI sceNetInetNtop(int, const void*, char*, std::uint32_t);
|
||||
int* APS5_VABI sceNetErrnoLoc(void);
|
||||
|
|
@ -264,6 +266,20 @@ int main() {
|
|||
Require(sceNetRecvfrom(udp_receiver, datagram_result, sizeof(datagram_result), 0,
|
||||
source.data(), &address_size) == sizeof(datagram_result));
|
||||
Require(std::strcmp(datagram, datagram_result) == 0 && source[1] == 2);
|
||||
std::uint64_t readable[16]{};
|
||||
readable[udp_receiver / 64] |= std::uint64_t{1} << (udp_receiver % 64);
|
||||
const std::int64_t poll_now[2]{0, 0};
|
||||
Require(select_nid_postfix(udp_receiver + 1, readable, nullptr, nullptr, poll_now) == 0);
|
||||
Require(readable[udp_receiver / 64] == 0);
|
||||
Require(sceNetSendto(udp_sender, datagram, sizeof(datagram), 0, address.data(), address.size()) == sizeof(datagram));
|
||||
readable[udp_receiver / 64] |= std::uint64_t{1} << (udp_receiver % 64);
|
||||
const std::int64_t wait_second[2]{1, 0};
|
||||
Require(select_nid_postfix(udp_receiver + 1, readable, nullptr, nullptr, wait_second) == 1);
|
||||
Require(((readable[udp_receiver / 64] >> (udp_receiver % 64)) & 1u) != 0);
|
||||
Require(sceNetRecvfrom(udp_receiver, datagram_result, sizeof(datagram_result), 0, nullptr, nullptr) == sizeof(datagram_result));
|
||||
const std::int64_t bad_timeout[2]{0, 1000000};
|
||||
Require(select_nid_postfix(udp_receiver + 1, readable, nullptr, nullptr, bad_timeout) == -1 && *__error_nid_postfix() == 22);
|
||||
Require(select_nid_postfix(1025, nullptr, nullptr, nullptr, poll_now) == -1 && *__error_nid_postfix() == 22);
|
||||
CheckMessages(udp_receiver, udp_sender, address);
|
||||
Require(sceNetSocketClose(udp_sender) == 0);
|
||||
Require(sceNetSocketClose(udp_receiver) == 0);
|
||||
|
|
|
|||
Loading…
Reference in New Issue