[Network] Add socket epoll registration and readiness waits
Co-authored-by: Sekai <140012992+SekaixX@users.noreply.github.com>
This commit is contained in:
parent
b368247d64
commit
53f233da9b
|
|
@ -14,28 +14,42 @@ namespace SharpEmu.Libs.Network;
|
|||
|
||||
public static class NetExports
|
||||
{
|
||||
private const int NetErrorNoEntry = unchecked((int)0x80410102);
|
||||
private const int NetErrorBadFileDescriptor = unchecked((int)0x80410109);
|
||||
private const int NetErrorFault = unchecked((int)0x8041010E);
|
||||
private const int NetErrorAlreadyExists = unchecked((int)0x80410111);
|
||||
private const int NetErrorInvalidArgument = unchecked((int)0x80410116);
|
||||
private const int NetErrorTooManyFiles = unchecked((int)0x80410118);
|
||||
private const int NetErrorWouldBlock = unchecked((int)0x80410123);
|
||||
private const int NetErrorAddressInUse = unchecked((int)0x80410130);
|
||||
private const int NetErrorTooManyOpenFiles = unchecked((int)0x80410118);
|
||||
private const int NetErrorResolverNoDns = unchecked((int)0x804101E1);
|
||||
private const int NetErrorNotInitialized = unchecked((int)0x804101C8);
|
||||
private const int NetErrnoNoEntry = 2;
|
||||
private const int NetErrnoBadFileDescriptor = 9;
|
||||
private const int NetErrnoFault = 14;
|
||||
private const int NetErrnoAlreadyExists = 17;
|
||||
private const int NetErrnoInvalidArgument = 22;
|
||||
private const int NetErrnoTooManyFiles = 24;
|
||||
private const int NetErrnoWouldBlock = 35;
|
||||
private const int NetErrnoAddressInUse = 48;
|
||||
private const int NetErrnoTooManyOpenFiles = 24;
|
||||
private const int NetErrnoNotInitialized = 200;
|
||||
private const int NetErrnoResolverNoDns = 225;
|
||||
private const int MaxNameLength = 256;
|
||||
private const int EpollIdBase = 1024;
|
||||
private const int EpollCapacity = 32;
|
||||
private const int NetEpollEventSize = 24;
|
||||
private const uint NetEpollIn = 0x00000001;
|
||||
private const uint NetEpollOut = 0x00000002;
|
||||
private const uint NetEpollError = 0x00000008;
|
||||
private static ReadOnlySpan<byte> OfflineMacAddress => [0x02, 0x53, 0x48, 0x41, 0x52, 0x50];
|
||||
|
||||
private static readonly ConcurrentDictionary<int, NetPool> _pools = new();
|
||||
private static readonly ConcurrentDictionary<int, ResolverContext> _resolvers = new();
|
||||
private static readonly ConcurrentDictionary<int, Socket> _sockets = new();
|
||||
private static readonly object EpollGate = new();
|
||||
private static readonly Dictionary<int, NetEpollContext> _epolls = new();
|
||||
private static int _nextPoolId;
|
||||
private static int _nextResolverId = 0x2000;
|
||||
private static int _nextSocketId = 255;
|
||||
|
|
@ -52,6 +66,26 @@ public static class NetExports
|
|||
|
||||
private sealed record ResolverContext(string Name, int PoolId, int Flags, int LastError);
|
||||
|
||||
private sealed class NetEpollContext(string name)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
|
||||
public List<NetEpollRegistration> Registrations { get; } = [];
|
||||
}
|
||||
|
||||
private readonly record struct NetEpollRegistration(int SocketId, NetEpollEvent Event);
|
||||
|
||||
private readonly record struct NetEpollEvent(uint Events, ulong Data);
|
||||
|
||||
internal static void ResetEpollsForTests()
|
||||
{
|
||||
lock (EpollGate)
|
||||
{
|
||||
_epolls.Clear();
|
||||
Monitor.PulseAll(EpollGate);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool TryGetReadEventState(
|
||||
int socketId,
|
||||
ulong lowWater,
|
||||
|
|
@ -116,6 +150,11 @@ public static class NetExports
|
|||
_initialized = false;
|
||||
_pools.Clear();
|
||||
_resolvers.Clear();
|
||||
lock (EpollGate)
|
||||
{
|
||||
_epolls.Clear();
|
||||
Monitor.PulseAll(EpollGate);
|
||||
}
|
||||
foreach (var socket in _sockets.Values)
|
||||
{
|
||||
socket.Dispose();
|
||||
|
|
@ -721,6 +760,291 @@ public static class NetExports
|
|||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetPoolCreate(CpuContext ctx)
|
||||
[SysAbiExport(
|
||||
Nid = "SF47kB2MNTo",
|
||||
ExportName = "sceNetEpollCreate",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetEpollCreate(CpuContext ctx)
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
return SetNetError(ctx, NetErrorNotInitialized, NetErrnoNotInitialized);
|
||||
}
|
||||
|
||||
var nameAddress = ctx[CpuRegister.Rdi];
|
||||
var flags = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
if (nameAddress == 0 || flags != 0 ||
|
||||
!TryReadUtf8Z(ctx, nameAddress, MaxNameLength, out var name))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorInvalidArgument, NetErrnoInvalidArgument);
|
||||
}
|
||||
|
||||
lock (EpollGate)
|
||||
{
|
||||
for (var index = 0; index < EpollCapacity; index++)
|
||||
{
|
||||
var id = EpollIdBase + index;
|
||||
if (_epolls.ContainsKey(id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_epolls.Add(id, new NetEpollContext(name));
|
||||
TraceNet("epoll.create", id, nameAddress, 0, 0);
|
||||
return ctx.SetReturn(id);
|
||||
}
|
||||
}
|
||||
|
||||
return SetNetError(ctx, NetErrorTooManyOpenFiles, NetErrnoTooManyOpenFiles);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ZVw46bsasAk",
|
||||
ExportName = "sceNetEpollControl",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetEpollControl(CpuContext ctx)
|
||||
{
|
||||
const int EpollControlAdd = 1;
|
||||
const int EpollControlModify = 2;
|
||||
const int EpollControlDelete = 3;
|
||||
|
||||
var epollId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var operation = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var socketId = unchecked((int)ctx[CpuRegister.Rdx]);
|
||||
var eventAddress = ctx[CpuRegister.Rcx];
|
||||
|
||||
if (operation is < EpollControlAdd or > EpollControlDelete ||
|
||||
(operation is EpollControlAdd or EpollControlModify && eventAddress == 0) ||
|
||||
(operation == EpollControlDelete && eventAddress != 0))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorInvalidArgument, NetErrnoInvalidArgument);
|
||||
}
|
||||
|
||||
if (!_sockets.ContainsKey(socketId))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorBadFileDescriptor, NetErrnoBadFileDescriptor);
|
||||
}
|
||||
|
||||
NetEpollEvent requestedEvent = default;
|
||||
if (eventAddress != 0 && !TryReadNetEpollEvent(ctx, eventAddress, out requestedEvent))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorFault, NetErrnoFault);
|
||||
}
|
||||
|
||||
lock (EpollGate)
|
||||
{
|
||||
if (!_epolls.TryGetValue(epollId, out var epoll))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorBadFileDescriptor, NetErrnoBadFileDescriptor);
|
||||
}
|
||||
|
||||
var registrationIndex = epoll.Registrations.FindIndex(value => value.SocketId == socketId);
|
||||
switch (operation)
|
||||
{
|
||||
case EpollControlAdd when registrationIndex >= 0:
|
||||
return SetNetError(ctx, NetErrorAlreadyExists, NetErrnoAlreadyExists);
|
||||
case EpollControlAdd:
|
||||
epoll.Registrations.Add(new NetEpollRegistration(socketId, requestedEvent));
|
||||
break;
|
||||
case EpollControlModify when registrationIndex < 0:
|
||||
return SetNetError(ctx, NetErrorNoEntry, NetErrnoNoEntry);
|
||||
case EpollControlModify:
|
||||
epoll.Registrations[registrationIndex] =
|
||||
new NetEpollRegistration(socketId, requestedEvent);
|
||||
break;
|
||||
case EpollControlDelete when registrationIndex < 0:
|
||||
return SetNetError(ctx, NetErrorNoEntry, NetErrnoNoEntry);
|
||||
case EpollControlDelete:
|
||||
epoll.Registrations.RemoveAt(registrationIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
Monitor.PulseAll(EpollGate);
|
||||
}
|
||||
|
||||
TraceNet(
|
||||
"epoll.control",
|
||||
epollId,
|
||||
unchecked((uint)operation),
|
||||
unchecked((uint)socketId),
|
||||
requestedEvent.Events);
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "drjIbDbA7UQ",
|
||||
ExportName = "sceNetEpollWait",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetEpollWait(CpuContext ctx)
|
||||
{
|
||||
var epollId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var eventsAddress = ctx[CpuRegister.Rsi];
|
||||
var maxEvents = unchecked((int)ctx[CpuRegister.Rdx]);
|
||||
var timeoutMicroseconds = unchecked((int)ctx[CpuRegister.Rcx]);
|
||||
|
||||
if (eventsAddress == 0)
|
||||
{
|
||||
return SetNetError(ctx, NetErrorFault, NetErrnoFault);
|
||||
}
|
||||
|
||||
if (maxEvents <= 0 || timeoutMicroseconds < -1)
|
||||
{
|
||||
return SetNetError(ctx, NetErrorInvalidArgument, NetErrnoInvalidArgument);
|
||||
}
|
||||
|
||||
NetEpollRegistration[] registrations;
|
||||
lock (EpollGate)
|
||||
{
|
||||
if (!_epolls.TryGetValue(epollId, out var epoll))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorBadFileDescriptor, NetErrnoBadFileDescriptor);
|
||||
}
|
||||
|
||||
if (epoll.Registrations.Count == 0 && timeoutMicroseconds != 0)
|
||||
{
|
||||
if (timeoutMicroseconds < 0)
|
||||
{
|
||||
while (_epolls.TryGetValue(epollId, out epoll) && epoll.Registrations.Count == 0)
|
||||
{
|
||||
Monitor.Wait(EpollGate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Monitor.Wait(
|
||||
EpollGate,
|
||||
TimeSpan.FromTicks((long)timeoutMicroseconds * TimeSpan.TicksPerMicrosecond));
|
||||
}
|
||||
|
||||
if (!_epolls.TryGetValue(epollId, out epoll))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorBadFileDescriptor, NetErrnoBadFileDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
registrations = epoll.Registrations.ToArray();
|
||||
}
|
||||
|
||||
if (registrations.Length == 0)
|
||||
{
|
||||
TraceNet("epoll.wait", epollId, unchecked((uint)maxEvents), unchecked((uint)timeoutMicroseconds), 0);
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
var readSockets = new List<Socket>();
|
||||
var writeSockets = new List<Socket>();
|
||||
var errorSockets = new List<Socket>();
|
||||
var hostRegistrations = new Dictionary<Socket, NetEpollRegistration>();
|
||||
foreach (var registration in registrations)
|
||||
{
|
||||
if (!_sockets.TryGetValue(registration.SocketId, out var socket))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
hostRegistrations[socket] = registration;
|
||||
if ((registration.Event.Events & NetEpollIn) != 0)
|
||||
{
|
||||
readSockets.Add(socket);
|
||||
}
|
||||
if ((registration.Event.Events & NetEpollOut) != 0)
|
||||
{
|
||||
writeSockets.Add(socket);
|
||||
}
|
||||
errorSockets.Add(socket);
|
||||
}
|
||||
|
||||
if (hostRegistrations.Count == 0)
|
||||
{
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Socket.Select(readSockets, writeSockets, errorSockets, timeoutMicroseconds);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return SetNetError(ctx, NetErrorBadFileDescriptor, NetErrnoBadFileDescriptor);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
return SetNetError(ctx, NetErrorInvalidArgument, NetErrnoInvalidArgument);
|
||||
}
|
||||
|
||||
var readyCount = 0;
|
||||
foreach (var (socket, registration) in hostRegistrations)
|
||||
{
|
||||
uint readyEvents = 0;
|
||||
if (readSockets.Contains(socket))
|
||||
{
|
||||
readyEvents |= NetEpollIn;
|
||||
}
|
||||
if (writeSockets.Contains(socket))
|
||||
{
|
||||
readyEvents |= NetEpollOut;
|
||||
}
|
||||
if (errorSockets.Contains(socket))
|
||||
{
|
||||
readyEvents |= NetEpollError;
|
||||
}
|
||||
if (readyEvents == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var outputAddress = eventsAddress + (ulong)(readyCount * NetEpollEventSize);
|
||||
if (!TryWriteNetEpollEvent(
|
||||
ctx,
|
||||
outputAddress,
|
||||
readyEvents,
|
||||
registration.SocketId,
|
||||
registration.Event.Data))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorFault, NetErrnoFault);
|
||||
}
|
||||
|
||||
readyCount++;
|
||||
if (readyCount == maxEvents)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TraceNet(
|
||||
"epoll.wait",
|
||||
epollId,
|
||||
unchecked((uint)maxEvents),
|
||||
unchecked((uint)timeoutMicroseconds),
|
||||
unchecked((uint)readyCount));
|
||||
return ctx.SetReturn(readyCount);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Inp1lfL+Jdw",
|
||||
ExportName = "sceNetEpollDestroy",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetEpollDestroy(CpuContext ctx)
|
||||
{
|
||||
var epollId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
lock (EpollGate)
|
||||
{
|
||||
if (!_epolls.Remove(epollId))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorBadFileDescriptor, NetErrnoBadFileDescriptor);
|
||||
}
|
||||
|
||||
Monitor.PulseAll(EpollGate);
|
||||
}
|
||||
|
||||
TraceNet("epoll.destroy", epollId, 0, 0, 0);
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
{
|
||||
var nameAddress = ctx[CpuRegister.Rdi];
|
||||
var size = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
|
|
@ -1078,6 +1402,39 @@ public static class NetExports
|
|||
LibraryName = "libSceNet")]
|
||||
public static int NetInetNtop(CpuContext ctx) => PosixInetNtop(ctx);
|
||||
|
||||
private static bool TryReadNetEpollEvent(
|
||||
CpuContext ctx,
|
||||
ulong address,
|
||||
out NetEpollEvent epollEvent)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[NetEpollEventSize];
|
||||
if (!ctx.Memory.TryRead(address, bytes))
|
||||
{
|
||||
epollEvent = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
epollEvent = new NetEpollEvent(
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(bytes),
|
||||
BinaryPrimitives.ReadUInt64LittleEndian(bytes[16..]));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryWriteNetEpollEvent(
|
||||
CpuContext ctx,
|
||||
ulong address,
|
||||
uint events,
|
||||
int socketId,
|
||||
ulong data)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[NetEpollEventSize];
|
||||
bytes.Clear();
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(bytes, events);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(bytes[8..], unchecked((uint)socketId));
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(bytes[16..], data);
|
||||
return ctx.Memory.TryWrite(address, bytes);
|
||||
}
|
||||
|
||||
private static void TraceNet(string operation, int id, ulong arg0, ulong arg1, ulong arg2)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_NET"), "1", StringComparison.Ordinal))
|
||||
|
|
|
|||
Loading…
Reference in New Issue