diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs new file mode 100644 index 000000000..071c13176 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs @@ -0,0 +1,150 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + class BsdContext + { + private static ConcurrentDictionary _registry = new ConcurrentDictionary(); + + private readonly object _lock = new object(); + + private List _fds; + + private BsdContext() + { + _fds = new List(); + } + + public ISocket RetrieveSocket(int socketFd) + { + IFileDescriptor file = RetrieveFileDescriptor(socketFd); + + if (file is ISocket socket) + { + return socket; + } + + return null; + } + + public IFileDescriptor RetrieveFileDescriptor(int fd) + { + lock (_lock) + { + if (fd >= 0 && _fds.Count > fd) + { + return _fds[fd]; + } + } + + return null; + } + + public int RegisterFileDescriptor(IFileDescriptor file) + { + lock (_lock) + { + for (int fd = 0; fd < _fds.Count; fd++) + { + if (_fds[fd] == null) + { + _fds[fd] = file; + + return fd; + } + } + + _fds.Add(file); + + return _fds.Count - 1; + } + } + + public int DuplicateFileDescriptor(int fd) + { + IFileDescriptor oldFile = RetrieveFileDescriptor(fd); + + if (oldFile != null) + { + lock (_lock) + { + oldFile.Refcount++; + + return RegisterFileDescriptor(oldFile); + } + } + + return -1; + } + + public bool CloseFileDescriptor(int fd) + { + IFileDescriptor file = RetrieveFileDescriptor(fd); + + if (file != null) + { + file.Refcount--; + + if (file.Refcount <= 0) + { + file.Dispose(); + } + + lock (_lock) + { + _fds[fd] = null; + } + + return true; + } + + return false; + } + + public LinuxError ShutdownAllSockets(BsdSocketShutdownFlags how) + { + lock (_lock) + { + foreach (IFileDescriptor file in _fds) + { + if (file is ISocket socket) + { + LinuxError errno = socket.Shutdown(how); + + if (errno != LinuxError.SUCCESS) + { + return errno; + } + } + } + } + + return LinuxError.SUCCESS; + } + + public static BsdContext GetOrRegister(long processId) + { + BsdContext context = GetContext(processId); + + if (context == null) + { + context = new BsdContext(); + + _registry.TryAdd(processId, context); + } + + return context; + } + + public static BsdContext GetContext(long processId) + { + if (!_registry.TryGetValue(processId, out BsdContext processContext)) + { + return null; + } + + return processContext; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs index 76f80f929..ae245ec88 100644 --- a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -1,11 +1,12 @@ -using Ryujinx.Common.Logging; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; using System; -using System.Buffers.Binary; using System.Collections.Generic; using System.Net; using System.Net.Sockets; +using System.Runtime.CompilerServices; using System.Text; -using System.Threading; namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd { @@ -13,221 +14,21 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd [Service("bsd:u", false)] class IClient : IpcService { - private static readonly Dictionary _errorMap = new() + private static readonly List _pollManagers = new List { - // WSAEINTR - {WsaError.WSAEINTR, LinuxError.EINTR}, - // WSAEWOULDBLOCK - {WsaError.WSAEWOULDBLOCK, LinuxError.EWOULDBLOCK}, - // WSAEINPROGRESS - {WsaError.WSAEINPROGRESS, LinuxError.EINPROGRESS}, - // WSAEALREADY - {WsaError.WSAEALREADY, LinuxError.EALREADY}, - // WSAENOTSOCK - {WsaError.WSAENOTSOCK, LinuxError.ENOTSOCK}, - // WSAEDESTADDRREQ - {WsaError.WSAEDESTADDRREQ, LinuxError.EDESTADDRREQ}, - // WSAEMSGSIZE - {WsaError.WSAEMSGSIZE, LinuxError.EMSGSIZE}, - // WSAEPROTOTYPE - {WsaError.WSAEPROTOTYPE, LinuxError.EPROTOTYPE}, - // WSAENOPROTOOPT - {WsaError.WSAENOPROTOOPT, LinuxError.ENOPROTOOPT}, - // WSAEPROTONOSUPPORT - {WsaError.WSAEPROTONOSUPPORT, LinuxError.EPROTONOSUPPORT}, - // WSAESOCKTNOSUPPORT - {WsaError.WSAESOCKTNOSUPPORT, LinuxError.ESOCKTNOSUPPORT}, - // WSAEOPNOTSUPP - {WsaError.WSAEOPNOTSUPP, LinuxError.EOPNOTSUPP}, - // WSAEPFNOSUPPORT - {WsaError.WSAEPFNOSUPPORT, LinuxError.EPFNOSUPPORT}, - // WSAEAFNOSUPPORT - {WsaError.WSAEAFNOSUPPORT, LinuxError.EAFNOSUPPORT}, - // WSAEADDRINUSE - {WsaError.WSAEADDRINUSE, LinuxError.EADDRINUSE}, - // WSAEADDRNOTAVAIL - {WsaError.WSAEADDRNOTAVAIL, LinuxError.EADDRNOTAVAIL}, - // WSAENETDOWN - {WsaError.WSAENETDOWN, LinuxError.ENETDOWN}, - // WSAENETUNREACH - {WsaError.WSAENETUNREACH, LinuxError.ENETUNREACH}, - // WSAENETRESET - {WsaError.WSAENETRESET, LinuxError.ENETRESET}, - // WSAECONNABORTED - {WsaError.WSAECONNABORTED, LinuxError.ECONNABORTED}, - // WSAECONNRESET - {WsaError.WSAECONNRESET, LinuxError.ECONNRESET}, - // WSAENOBUFS - {WsaError.WSAENOBUFS, LinuxError.ENOBUFS}, - // WSAEISCONN - {WsaError.WSAEISCONN, LinuxError.EISCONN}, - // WSAENOTCONN - {WsaError.WSAENOTCONN, LinuxError.ENOTCONN}, - // WSAESHUTDOWN - {WsaError.WSAESHUTDOWN, LinuxError.ESHUTDOWN}, - // WSAETOOMANYREFS - {WsaError.WSAETOOMANYREFS, LinuxError.ETOOMANYREFS}, - // WSAETIMEDOUT - {WsaError.WSAETIMEDOUT, LinuxError.ETIMEDOUT}, - // WSAECONNREFUSED - {WsaError.WSAECONNREFUSED, LinuxError.ECONNREFUSED}, - // WSAELOOP - {WsaError.WSAELOOP, LinuxError.ELOOP}, - // WSAENAMETOOLONG - {WsaError.WSAENAMETOOLONG, LinuxError.ENAMETOOLONG}, - // WSAEHOSTDOWN - {WsaError.WSAEHOSTDOWN, LinuxError.EHOSTDOWN}, - // WSAEHOSTUNREACH - {WsaError.WSAEHOSTUNREACH, LinuxError.EHOSTUNREACH}, - // WSAENOTEMPTY - {WsaError.WSAENOTEMPTY, LinuxError.ENOTEMPTY}, - // WSAEUSERS - {WsaError.WSAEUSERS, LinuxError.EUSERS}, - // WSAEDQUOT - {WsaError.WSAEDQUOT, LinuxError.EDQUOT}, - // WSAESTALE - {WsaError.WSAESTALE, LinuxError.ESTALE}, - // WSAEREMOTE - {WsaError.WSAEREMOTE, LinuxError.EREMOTE}, - // WSAEINVAL - {WsaError.WSAEINVAL, LinuxError.EINVAL}, - // WSAEFAULT - {WsaError.WSAEFAULT, LinuxError.EFAULT}, - // NOERROR - {0, 0} - }; - - private static readonly Dictionary _soSocketOptionMap = new() - { - { BsdSocketOption.SoDebug, SocketOptionName.Debug }, - { BsdSocketOption.SoReuseAddr, SocketOptionName.ReuseAddress }, - { BsdSocketOption.SoKeepAlive, SocketOptionName.KeepAlive }, - { BsdSocketOption.SoDontRoute, SocketOptionName.DontRoute }, - { BsdSocketOption.SoBroadcast, SocketOptionName.Broadcast }, - { BsdSocketOption.SoUseLoopBack, SocketOptionName.UseLoopback }, - { BsdSocketOption.SoLinger, SocketOptionName.Linger }, - { BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline }, - { BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress }, - { BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer }, - { BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer }, - { BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater }, - { BsdSocketOption.SoRcvLoWat, SocketOptionName.ReceiveLowWater }, - { BsdSocketOption.SoSndTimeo, SocketOptionName.SendTimeout }, - { BsdSocketOption.SoRcvTimeo, SocketOptionName.ReceiveTimeout }, - { BsdSocketOption.SoError, SocketOptionName.Error }, - { BsdSocketOption.SoType, SocketOptionName.Type } - }; - - private static readonly Dictionary _ipSocketOptionMap = new() - { - { BsdSocketOption.IpOptions, SocketOptionName.IPOptions }, - { BsdSocketOption.IpHdrIncl, SocketOptionName.HeaderIncluded }, - { BsdSocketOption.IpTtl, SocketOptionName.IpTimeToLive }, - { BsdSocketOption.IpMulticastIf, SocketOptionName.MulticastInterface }, - { BsdSocketOption.IpMulticastTtl, SocketOptionName.MulticastTimeToLive }, - { BsdSocketOption.IpMulticastLoop, SocketOptionName.MulticastLoopback }, - { BsdSocketOption.IpAddMembership, SocketOptionName.AddMembership }, - { BsdSocketOption.IpDropMembership, SocketOptionName.DropMembership }, - { BsdSocketOption.IpDontFrag, SocketOptionName.DontFragment }, - { BsdSocketOption.IpAddSourceMembership, SocketOptionName.AddSourceMembership }, - { BsdSocketOption.IpDropSourceMembership, SocketOptionName.DropSourceMembership } - }; - - private static readonly Dictionary _tcpSocketOptionMap = new() - { - { BsdSocketOption.TcpNoDelay, SocketOptionName.NoDelay }, - { BsdSocketOption.TcpKeepIdle, SocketOptionName.TcpKeepAliveTime }, - { BsdSocketOption.TcpKeepIntvl, SocketOptionName.TcpKeepAliveInterval }, - { BsdSocketOption.TcpKeepCnt, SocketOptionName.TcpKeepAliveRetryCount } + EventFileDescriptorPollManager.Instance, + ManagedSocketPollManager.Instance }; + private BsdContext _context; private bool _isPrivileged; - private List _sockets = new List(); - public IClient(ServiceCtx context, bool isPrivileged) : base(context.Device.System.BsdServer) { _isPrivileged = isPrivileged; } - private static LinuxError ConvertError(WsaError errorCode) - { - if (!_errorMap.TryGetValue(errorCode, out LinuxError errno)) - { - errno = (LinuxError)errorCode; - } - - return errno; - } - - private static SocketFlags ConvertBsdSocketFlags(BsdSocketFlags bsdSocketFlags) - { - SocketFlags socketFlags = SocketFlags.None; - - if (bsdSocketFlags.HasFlag(BsdSocketFlags.Oob)) - { - socketFlags |= SocketFlags.OutOfBand; - } - - if (bsdSocketFlags.HasFlag(BsdSocketFlags.Peek)) - { - socketFlags |= SocketFlags.Peek; - } - - if (bsdSocketFlags.HasFlag(BsdSocketFlags.DontRoute)) - { - socketFlags |= SocketFlags.DontRoute; - } - - if (bsdSocketFlags.HasFlag(BsdSocketFlags.Trunc)) - { - socketFlags |= SocketFlags.Truncated; - } - - if (bsdSocketFlags.HasFlag(BsdSocketFlags.CTrunc)) - { - socketFlags |= SocketFlags.ControlDataTruncated; - } - - bsdSocketFlags &= ~(BsdSocketFlags.Oob | - BsdSocketFlags.Peek | - BsdSocketFlags.DontRoute | - BsdSocketFlags.Trunc | - BsdSocketFlags.CTrunc); - - if (bsdSocketFlags != BsdSocketFlags.None) - { - Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported socket flags: {bsdSocketFlags}"); - } - - return socketFlags; - } - - private static bool TryConvertSocketOption(BsdSocketOption option, SocketOptionLevel level, out SocketOptionName name) - { - var table = level switch - { - SocketOptionLevel.Socket => _soSocketOptionMap, - SocketOptionLevel.IP => _ipSocketOptionMap, - SocketOptionLevel.Tcp => _tcpSocketOptionMap, - _ => null - }; - - if (table == null) - { - name = default; - return false; - } - - return table.TryGetValue(option, out name); - } - - private ResultCode WriteWinSock2Error(ServiceCtx context, WsaError errorCode) - { - return WriteBsdResult(context, -1, ConvertError(errorCode)); - } - - private ResultCode WriteBsdResult(ServiceCtx context, int result, LinuxError errorCode = 0) + private ResultCode WriteBsdResult(ServiceCtx context, int result, LinuxError errorCode = LinuxError.SUCCESS) { if (errorCode != LinuxError.SUCCESS) { @@ -240,100 +41,96 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd return ResultCode.Success; } - private BsdSocket RetrieveSocket(int socketFd) + private static AddressFamily ConvertBsdAddressFamily(BsdAddressFamily family) { - if (socketFd >= 0 && _sockets.Count > socketFd) + switch (family) { - return _sockets[socketFd]; + case BsdAddressFamily.Unspecified: + return AddressFamily.Unspecified; + case BsdAddressFamily.InterNetwork: + return AddressFamily.InterNetwork; + case BsdAddressFamily.InterNetworkV6: + return AddressFamily.InterNetworkV6; + case BsdAddressFamily.Unknown: + return AddressFamily.Unknown; + default: + throw new NotImplementedException(family.ToString()); } - - return null; } - private LinuxError SetResultErrno(Socket socket, int result) + private LinuxError SetResultErrno(IFileDescriptor socket, int result) { return result == 0 && !socket.Blocking ? LinuxError.EWOULDBLOCK : LinuxError.SUCCESS; } - private AddressFamily ConvertFromBsd(int domain) - { - if (domain == 2) - { - return AddressFamily.InterNetwork; - } - - // FIXME: AF_ROUTE ignored, is that really needed? - return AddressFamily.Unknown; - } - private ResultCode SocketInternal(ServiceCtx context, bool exempt) { - AddressFamily domain = (AddressFamily)context.RequestData.ReadInt32(); - SocketType type = (SocketType)context.RequestData.ReadInt32(); - ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32(); + BsdAddressFamily domain = (BsdAddressFamily)context.RequestData.ReadInt32(); + BsdSocketType type = (BsdSocketType)context.RequestData.ReadInt32(); + ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32(); - if (domain == AddressFamily.Unknown) + BsdSocketCreationFlags creationFlags = (BsdSocketCreationFlags)((int)type >> (int)BsdSocketCreationFlags.FlagsShift); + type &= BsdSocketType.TypeMask; + + if (domain == BsdAddressFamily.Unknown) { return WriteBsdResult(context, -1, LinuxError.EPROTONOSUPPORT); } - else if ((type == SocketType.Seqpacket || type == SocketType.Raw) && !_isPrivileged) + else if ((type == BsdSocketType.Seqpacket || type == BsdSocketType.Raw) && !_isPrivileged) { - if (domain != AddressFamily.InterNetwork || type != SocketType.Raw || protocol != ProtocolType.Icmp) + if (domain != BsdAddressFamily.InterNetwork || type != BsdSocketType.Raw || protocol != ProtocolType.Icmp) { return WriteBsdResult(context, -1, LinuxError.ENOENT); } } - BsdSocket newBsdSocket = new BsdSocket - { - Family = (int)domain, - Type = (int)type, - Protocol = (int)protocol, - Handle = new Socket(domain, type, protocol) - }; + AddressFamily netDomain = ConvertBsdAddressFamily(domain); - _sockets.Add(newBsdSocket); + if (protocol == ProtocolType.IP) + { + if (type == BsdSocketType.Stream) + { + protocol = ProtocolType.Tcp; + } + else if (type == BsdSocketType.Dgram) + { + protocol = ProtocolType.Udp; + } + } + + ISocket newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol); + newBsdSocket.Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking); + + LinuxError errno = LinuxError.SUCCESS; + + int newSockFd = _context.RegisterFileDescriptor(newBsdSocket); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } if (exempt) { - newBsdSocket.Handle.Disconnect(true); + newBsdSocket.Disconnect(); } - return WriteBsdResult(context, _sockets.Count - 1); + return WriteBsdResult(context, newSockFd, errno); } - private IPEndPoint ParseSockAddr(ServiceCtx context, ulong bufferPosition, ulong bufferSize) + private void WriteSockAddr(ServiceCtx context, ulong bufferPosition, ISocket socket, bool isRemote) { - int size = context.Memory.Read(bufferPosition); - int family = context.Memory.Read(bufferPosition + 1); - int port = BinaryPrimitives.ReverseEndianness(context.Memory.Read(bufferPosition + 2)); + IPEndPoint endPoint = isRemote ? socket.RemoteEndPoint : socket.LocalEndPoint; - byte[] rawIp = new byte[4]; - - context.Memory.Read(bufferPosition + 4, rawIp); - - return new IPEndPoint(new IPAddress(rawIp), port); - } - - private void WriteSockAddr(ServiceCtx context, ulong bufferPosition, IPEndPoint endPoint) - { - context.Memory.Write(bufferPosition, (byte)0); - context.Memory.Write(bufferPosition + 1, (byte)endPoint.AddressFamily); - context.Memory.Write(bufferPosition + 2, BinaryPrimitives.ReverseEndianness((ushort)endPoint.Port)); - context.Memory.Write(bufferPosition + 4, endPoint.Address.GetAddressBytes()); - } - - private void WriteSockAddr(ServiceCtx context, ulong bufferPosition, BsdSocket socket, bool isRemote) - { - IPEndPoint endPoint = (isRemote ? socket.Handle.RemoteEndPoint : socket.Handle.LocalEndPoint) as IPEndPoint; - - WriteSockAddr(context, bufferPosition, endPoint); + context.Memory.Write(bufferPosition, BsdSockAddr.FromIPEndPoint(endPoint)); } [CommandHipc(0)] // Initialize(nn::socket::BsdBufferConfig config, u64 pid, u64 transferMemorySize, KObject, pid) -> u32 bsd_errno public ResultCode RegisterClient(ServiceCtx context) { + _context = BsdContext.GetOrRegister(context.Request.HandleDesc.PId); + /* typedef struct { u32 version; // Observed 1 on 2.0 LibAppletWeb, 2 on 3.0. @@ -424,7 +221,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); - if (timeout < -1 || fdsCount < 0 || (ulong)(fdsCount * 8) > bufferSize) { return WriteBsdResult(context, -1, LinuxError.EINVAL); @@ -434,75 +230,94 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd for (int i = 0; i < fdsCount; i++) { - int socketFd = context.Memory.Read(bufferPosition + (ulong)i * 8); + PollEventData pollEventData = context.Memory.Read(bufferPosition + (ulong)(i * Unsafe.SizeOf())); - BsdSocket socket = RetrieveSocket(socketFd); + IFileDescriptor fileDescriptor = _context.RetrieveFileDescriptor(pollEventData.SocketFd); - if (socket == null) + if (fileDescriptor == null) { - return WriteBsdResult(context, -1, LinuxError.EBADF);} + return WriteBsdResult(context, -1, LinuxError.EBADF); + } - PollEvent.EventTypeMask inputEvents = (PollEvent.EventTypeMask)context.Memory.Read(bufferPosition + (ulong)i * 8 + 4); - PollEvent.EventTypeMask outputEvents = (PollEvent.EventTypeMask)context.Memory.Read(bufferPosition + (ulong)i * 8 + 6); - - events[i] = new PollEvent(socketFd, socket, inputEvents, outputEvents); + events[i] = new PollEvent(pollEventData, fileDescriptor); } - List readEvents = new List(); - List writeEvents = new List(); - List errorEvents = new List(); + List discoveredEvents = new List(); + List[] eventsByPollManager = new List[_pollManagers.Count]; - foreach (PollEvent Event in events) + for (int i = 0; i < eventsByPollManager.Length; i++) { - bool isValidEvent = false; + eventsByPollManager[i] = new List(); - if ((Event.InputEvents & PollEvent.EventTypeMask.Input) != 0) + foreach (PollEvent evnt in events) { - readEvents.Add(Event.Socket.Handle); - errorEvents.Add(Event.Socket.Handle); - - isValidEvent = true; - } - - if ((Event.InputEvents & PollEvent.EventTypeMask.UrgentInput) != 0) - { - readEvents.Add(Event.Socket.Handle); - errorEvents.Add(Event.Socket.Handle); - - isValidEvent = true; - } - - if ((Event.InputEvents & PollEvent.EventTypeMask.Output) != 0) - { - writeEvents.Add(Event.Socket.Handle); - errorEvents.Add(Event.Socket.Handle); - - isValidEvent = true; - } - - if ((Event.InputEvents & PollEvent.EventTypeMask.Error) != 0) - { - errorEvents.Add(Event.Socket.Handle); - isValidEvent = true; - } - - if (!isValidEvent) - { - Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {Event.InputEvents}"); - return WriteBsdResult(context, -1, LinuxError.EINVAL); + if (_pollManagers[i].IsCompatible(evnt)) + { + eventsByPollManager[i].Add(evnt); + discoveredEvents.Add(evnt); + } } } + foreach (PollEvent evnt in events) + { + if (!discoveredEvents.Contains(evnt)) + { + Logger.Error?.Print(LogClass.ServiceBsd, $"Poll operation is not supported for {evnt.FileDescriptor.GetType().Name}!"); + + return WriteBsdResult(context, -1, LinuxError.EBADF); + } + } + + int updateCount = 0; + + LinuxError errno = LinuxError.SUCCESS; + if (fdsCount != 0) { - try + bool IsUnexpectedLinuxError(LinuxError error) { - System.Net.Sockets.Socket.Select(readEvents, writeEvents, errorEvents, timeout); + return errno != LinuxError.SUCCESS && errno != LinuxError.ETIMEDOUT; } - catch (SocketException exception) + + // Hybrid approach + long budgetLeftMilliseconds; + + if (timeout == -1) { - return WriteWinSock2Error(context, (WsaError)exception.ErrorCode); + budgetLeftMilliseconds = PerformanceCounter.ElapsedMilliseconds + uint.MaxValue; } + else + { + budgetLeftMilliseconds = PerformanceCounter.ElapsedMilliseconds + timeout; + } + + do + { + for (int i = 0; i < eventsByPollManager.Length; i++) + { + if (eventsByPollManager[i].Count == 0) + { + continue; + } + + errno = _pollManagers[i].Poll(eventsByPollManager[i], 0, out updateCount); + + if (IsUnexpectedLinuxError(errno)) + { + break; + } + + if (updateCount > 0) + { + break; + } + } + + // If we are here, that mean nothing was availaible, sleep for 50ms + context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000); + } + while (PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds); } else if (timeout == -1) { @@ -511,47 +326,16 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd } else { - // FIXME: We should make the KThread sleep but we can't do much about it yet. - Thread.Sleep(timeout); + context.Device.System.KernelContext.Syscall.SleepThread(timeout); } + // TODO: Spanify for (int i = 0; i < fdsCount; i++) { - PollEvent Event = events[i]; - context.Memory.Write(bufferPosition + (ulong)i * 8, Event.SocketFd); - context.Memory.Write(bufferPosition + (ulong)i * 8 + 4, (short)Event.InputEvents); - - PollEvent.EventTypeMask outputEvents = 0; - - Socket socket = Event.Socket.Handle; - - if (errorEvents.Contains(socket)) - { - outputEvents |= PollEvent.EventTypeMask.Error; - - if (!socket.Connected || !socket.IsBound) - { - outputEvents |= PollEvent.EventTypeMask.Disconnected; - } - } - - if (readEvents.Contains(socket)) - { - if ((Event.InputEvents & PollEvent.EventTypeMask.Input) != 0) - { - outputEvents |= PollEvent.EventTypeMask.Input; - } - } - - if (writeEvents.Contains(socket)) - { - outputEvents |= PollEvent.EventTypeMask.Output; - } - - context.Memory.Write(bufferPosition + (ulong)i * 8 + 6, (short)outputEvents); + context.Memory.Write(bufferPosition + (ulong)(i * Unsafe.SizeOf()), events[i].Data); } - return WriteBsdResult(context, readEvents.Count + writeEvents.Count + errorEvents.Count, LinuxError.SUCCESS); + return WriteBsdResult(context, updateCount, errno); } [CommandHipc(7)] @@ -574,24 +358,21 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(); + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); int result = -1; if (socket != null) { - byte[] receivedBuffer = new byte[receiveLength]; + errno = socket.Receive(out result, receiveRegion.Memory.Span, socketFlags); - try + if (errno == LinuxError.SUCCESS) { - result = socket.Handle.Receive(receivedBuffer, ConvertBsdSocketFlags(socketFlags)); - errno = SetResultErrno(socket.Handle, result); + SetResultErrno(socket, result); - context.Memory.Write(receivePosition, receivedBuffer); - } - catch (SocketException exception) - { - errno = ConvertError((WsaError)exception.ErrorCode); + receiveRegion.Dispose(); } } @@ -605,29 +386,26 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd int socketFd = context.RequestData.ReadInt32(); BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); - (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(); + (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(0); (ulong sockAddrOutPosition, ulong sockAddrOutSize) = context.Request.GetBufferType0x22(1); + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); int result = -1; if (socket != null) { - byte[] receivedBuffer = new byte[receiveLength]; - EndPoint endPoint = new IPEndPoint(IPAddress.Any, 0); + errno = socket.ReceiveFrom(out result, receiveRegion.Memory.Span, receiveRegion.Memory.Span.Length, socketFlags, out IPEndPoint endPoint); - try + if (errno == LinuxError.SUCCESS) { - result = socket.Handle.ReceiveFrom(receivedBuffer, receivedBuffer.Length, ConvertBsdSocketFlags(socketFlags), ref endPoint); - errno = SetResultErrno(socket.Handle, result); + SetResultErrno(socket, result); - context.Memory.Write(receivePosition, receivedBuffer); - WriteSockAddr(context, sockAddrOutPosition, (IPEndPoint)endPoint); - } - catch (SocketException exception) - { - errno = ConvertError((WsaError)exception.ErrorCode); + receiveRegion.Dispose(); + + context.Memory.Write(sockAddrOutPosition, BsdSockAddr.FromIPEndPoint(endPoint)); } } @@ -643,26 +421,20 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(); + ReadOnlySpan sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); + LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); int result = -1; if (socket != null) { - byte[] sendBuffer = new byte[sendSize]; + errno = socket.Send(out result, sendBuffer, socketFlags); - context.Memory.Read(sendPosition, sendBuffer); - - try + if (errno == LinuxError.SUCCESS) { - result = socket.Handle.Send(sendBuffer, ConvertBsdSocketFlags(socketFlags)); - errno = SetResultErrno(socket.Handle, result); + SetResultErrno(socket, result); } - catch (SocketException exception) - { - errno = ConvertError((WsaError)exception.ErrorCode); - } - } return WriteBsdResult(context, result, errno); @@ -675,31 +447,25 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd int socketFd = context.RequestData.ReadInt32(); BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); - (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(); + (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(0); (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(1); + ReadOnlySpan sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); + LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); int result = -1; if (socket != null) { - byte[] sendBuffer = new byte[sendSize]; + IPEndPoint endPoint = context.Memory.Read(bufferPosition).ToIPEndPoint(); - context.Memory.Read(sendPosition, sendBuffer); + errno = socket.SendTo(out result, sendBuffer, sendBuffer.Length, socketFlags, endPoint); - EndPoint endPoint = ParseSockAddr(context, bufferPosition, bufferSize); - - try + if (errno == LinuxError.SUCCESS) { - result = socket.Handle.SendTo(sendBuffer, sendBuffer.Length, ConvertBsdSocketFlags(socketFlags), endPoint); - errno = SetResultErrno(socket.Handle, result); + SetResultErrno(socket, result); } - catch (SocketException exception) - { - errno = ConvertError((WsaError)exception.ErrorCode); - } - } return WriteBsdResult(context, result, errno); @@ -714,22 +480,11 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x22(); LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); if (socket != null) { - errno = LinuxError.SUCCESS; - - Socket newSocket = null; - - try - { - newSocket = socket.Handle.Accept(); - } - catch (SocketException exception) - { - errno = ConvertError((WsaError)exception.ErrorCode); - } + errno = socket.Accept(out ISocket newSocket); if (newSocket == null && errno == LinuxError.SUCCESS) { @@ -737,19 +492,18 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd } else if (errno == LinuxError.SUCCESS) { - BsdSocket newBsdSocket = new BsdSocket + int newSockFd = _context.RegisterFileDescriptor(newSocket); + + if (newSockFd == -1) { - Family = (int)newSocket.AddressFamily, - Type = (int)newSocket.SocketType, - Protocol = (int)newSocket.ProtocolType, - Handle = newSocket - }; + errno = LinuxError.EBADF; + } + else + { + WriteSockAddr(context, bufferPos, newSocket, true); + } - _sockets.Add(newBsdSocket); - - WriteSockAddr(context, bufferPos, newBsdSocket, true); - - WriteBsdResult(context, _sockets.Count - 1, errno); + WriteBsdResult(context, newSockFd, errno); context.ResponseData.Write(0x10); @@ -766,25 +520,16 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd { int socketFd = context.RequestData.ReadInt32(); - (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x21(); + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); if (socket != null) { - errno = LinuxError.SUCCESS; + IPEndPoint endPoint = context.Memory.Read(bufferPosition).ToIPEndPoint(); - try - { - IPEndPoint endPoint = ParseSockAddr(context, bufferPos, bufferSize); - - socket.Handle.Bind(endPoint); - } - catch (SocketException exception) - { - errno = ConvertError((WsaError)exception.ErrorCode); - } + errno = socket.Bind(endPoint); } return WriteBsdResult(context, 0, errno); @@ -796,31 +541,16 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd { int socketFd = context.RequestData.ReadInt32(); - (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x21(); + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); if (socket != null) { - errno = LinuxError.SUCCESS; - try - { - IPEndPoint endPoint = ParseSockAddr(context, bufferPos, bufferSize); + IPEndPoint endPoint = context.Memory.Read(bufferPosition).ToIPEndPoint(); - socket.Handle.Connect(endPoint); - } - catch (SocketException exception) - { - if (!socket.Handle.Blocking && exception.ErrorCode == (int)WsaError.WSAEWOULDBLOCK) - { - errno = LinuxError.EINPROGRESS; - } - else - { - errno = ConvertError((WsaError)exception.ErrorCode); - } - } + errno = socket.Connect(endPoint); } return WriteBsdResult(context, 0, errno); @@ -832,18 +562,18 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd { int socketFd = context.RequestData.ReadInt32(); - (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x22(); + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); - LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); if (socket != null) { errno = LinuxError.SUCCESS; - WriteSockAddr(context, bufferPos, socket, true); + WriteSockAddr(context, bufferPosition, socket, true); WriteBsdResult(context, 0, errno); - context.ResponseData.Write(0x10); + context.ResponseData.Write(Unsafe.SizeOf()); } return WriteBsdResult(context, 0, errno); @@ -858,7 +588,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x22(); LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); if (socket != null) { @@ -866,7 +596,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd WriteSockAddr(context, bufferPos, socket, false); WriteBsdResult(context, 0, errno); - context.ResponseData.Write(0x10); + context.ResponseData.Write(Unsafe.SizeOf()); } return WriteBsdResult(context, 0, errno); @@ -881,13 +611,19 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32(); (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); + WritableRegion optionValue = context.Memory.GetWritableRegion(bufferPosition, (int)bufferSize); LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); if (socket != null) { - errno = HandleGetSocketOption(context, socket, option, level, bufferPosition, bufferSize); + errno = socket.GetSocketOption(option, level, optionValue.Memory.Span); + + if (errno == LinuxError.SUCCESS) + { + optionValue.Dispose(); + } } return WriteBsdResult(context, 0, errno); @@ -901,20 +637,11 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd int backlog = context.RequestData.ReadInt32(); LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); if (socket != null) { - errno = LinuxError.SUCCESS; - - try - { - socket.Handle.Listen(backlog); - } - catch (SocketException exception) - { - errno = ConvertError((WsaError)exception.ErrorCode); - } + errno = socket.Listen(backlog); } return WriteBsdResult(context, 0, errno); @@ -929,7 +656,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd int bufferCount = context.RequestData.ReadInt32(); LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); if (socket != null) { @@ -965,7 +692,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd int result = 0; LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); if (socket != null) { @@ -973,11 +700,11 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd if (cmd == 0x3) { - result = !socket.Handle.Blocking ? 0x800 : 0; + result = !socket.Blocking ? 0x800 : 0; } else if (cmd == 0x4 && arg == 0x800) { - socket.Handle.Blocking = false; + socket.Blocking = false; result = 0; } else @@ -989,74 +716,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd return WriteBsdResult(context, result, errno); } - private static LinuxError HandleGetSocketOption( - ServiceCtx context, - BsdSocket socket, - BsdSocketOption option, - SocketOptionLevel level, - ulong optionValuePosition, - ulong optionValueSize) - { - try - { - if (!TryConvertSocketOption(option, level, out SocketOptionName optionName)) - { - Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}"); - - return LinuxError.EOPNOTSUPP; - } - - byte[] optionValue = new byte[optionValueSize]; - - socket.Handle.GetSocketOption(level, optionName, optionValue); - context.Memory.Write(optionValuePosition, optionValue); - - return LinuxError.SUCCESS; - } - catch (SocketException exception) - { - return ConvertError((WsaError)exception.ErrorCode); - } - } - - private static LinuxError HandleSetSocketOption( - ServiceCtx context, - BsdSocket socket, - BsdSocketOption option, - SocketOptionLevel level, - ulong optionValuePosition, - ulong optionValueSize) - { - try - { - if (!TryConvertSocketOption(option, level, out SocketOptionName optionName)) - { - Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}"); - - return LinuxError.EOPNOTSUPP; - } - - int value = context.Memory.Read((ulong)optionValuePosition); - - if (option == BsdSocketOption.SoLinger) - { - int value2 = context.Memory.Read((ulong)optionValuePosition + 4); - - socket.Handle.SetSocketOption(level, SocketOptionName.Linger, new LingerOption(value != 0, value2)); - } - else - { - socket.Handle.SetSocketOption(level, optionName, value); - } - - return LinuxError.SUCCESS; - } - catch (SocketException exception) - { - return ConvertError((WsaError)exception.ErrorCode); - } - } - [CommandHipc(21)] // SetSockOpt(u32 socket, u32 level, u32 option_name, buffer option_value) -> (i32 ret, u32 bsd_errno) public ResultCode SetSockOpt(ServiceCtx context) @@ -1067,12 +726,14 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x21(); + ReadOnlySpan optionValue = context.Memory.GetSpan(bufferPos, (int)bufferSize); + LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); if (socket != null) { - errno = HandleSetSocketOption(context, socket, option, level, bufferPos, bufferSize); + errno = socket.SetSocketOption(option, level, optionValue); } return WriteBsdResult(context, 0, errno); @@ -1086,7 +747,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd int how = context.RequestData.ReadInt32(); LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + ISocket socket = _context.RetrieveSocket(socketFd); if (socket != null) { @@ -1094,16 +755,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd if (how >= 0 && how <= 2) { - errno = LinuxError.SUCCESS; - - try - { - socket.Handle.Shutdown((SocketShutdown)how); - } - catch (SocketException exception) - { - errno = ConvertError((WsaError)exception.ErrorCode); - } + errno = socket.Shutdown((BsdSocketShutdownFlags)how); } } @@ -1120,54 +772,33 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd if (how >= 0 && how <= 2) { - errno = LinuxError.SUCCESS; - - foreach (BsdSocket socket in _sockets) - { - if (socket != null) - { - try - { - socket.Handle.Shutdown((SocketShutdown)how); - } - catch (SocketException exception) - { - errno = ConvertError((WsaError)exception.ErrorCode); - break; - } - } - } + errno = _context.ShutdownAllSockets((BsdSocketShutdownFlags)how); } return WriteBsdResult(context, 0, errno); } [CommandHipc(24)] - // Write(u32 socket, buffer message) -> (i32 ret, u32 bsd_errno) + // Write(u32 fd, buffer message) -> (i32 ret, u32 bsd_errno) public ResultCode Write(ServiceCtx context) { - int socketFd = context.RequestData.ReadInt32(); + int fd = context.RequestData.ReadInt32(); (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(); - LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); - int result = -1; + ReadOnlySpan sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); - if (socket != null) + LinuxError errno = LinuxError.EBADF; + IFileDescriptor file = _context.RetrieveFileDescriptor(fd); + int result = -1; + + if (file != null) { - byte[] sendBuffer = new byte[sendSize]; + errno = file.Write(out result, sendBuffer); - context.Memory.Read(sendPosition, sendBuffer); - - try + if (errno == LinuxError.SUCCESS) { - result = socket.Handle.Send(sendBuffer); - errno = SetResultErrno(socket.Handle, result); - } - catch (SocketException exception) - { - errno = ConvertError((WsaError)exception.ErrorCode); + SetResultErrno(file, result); } } @@ -1175,30 +806,28 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd } [CommandHipc(25)] - // Read(u32 socket) -> (i32 ret, u32 bsd_errno, buffer message) + // Read(u32 fd) -> (i32 ret, u32 bsd_errno, buffer message) public ResultCode Read(ServiceCtx context) { - int socketFd = context.RequestData.ReadInt32(); + int fd = context.RequestData.ReadInt32(); (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(); - LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); - int result = -1; + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); - if (socket != null) + LinuxError errno = LinuxError.EBADF; + IFileDescriptor file = _context.RetrieveFileDescriptor(fd); + int result = -1; + + if (file != null) { - byte[] receivedBuffer = new byte[receiveLength]; + errno = file.Read(out result, receiveRegion.Memory.Span); - try + if (errno == LinuxError.SUCCESS) { - result = socket.Handle.Receive(receivedBuffer); - errno = SetResultErrno(socket.Handle, result); - context.Memory.Write(receivePosition, receivedBuffer); - } - catch (SocketException exception) - { - errno = ConvertError((WsaError)exception.ErrorCode); + SetResultErrno(file, result); + + receiveRegion.Dispose(); } } @@ -1206,20 +835,15 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd } [CommandHipc(26)] - // Close(u32 socket) -> (i32 ret, u32 bsd_errno) + // Close(u32 fd) -> (i32 ret, u32 bsd_errno) public ResultCode Close(ServiceCtx context) { - int socketFd = context.RequestData.ReadInt32(); + int fd = context.RequestData.ReadInt32(); - LinuxError errno = LinuxError.EBADF; - BsdSocket socket = RetrieveSocket(socketFd); + LinuxError errno = LinuxError.EBADF; - if (socket != null) + if (_context.CloseFileDescriptor(fd)) { - socket.Handle.Close(); - - _sockets[socketFd] = null; - errno = LinuxError.SUCCESS; } @@ -1227,29 +851,49 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd } [CommandHipc(27)] - // DuplicateSocket(u32 socket, u64 reserved) -> (i32 ret, u32 bsd_errno) + // DuplicateSocket(u32 fd, u64 reserved) -> (i32 ret, u32 bsd_errno) public ResultCode DuplicateSocket(ServiceCtx context) { - int socketFd = context.RequestData.ReadInt32(); + int fd = context.RequestData.ReadInt32(); ulong reserved = context.RequestData.ReadUInt64(); - LinuxError errno = LinuxError.ENOENT; - int newSockFd = -1; + LinuxError errno = LinuxError.ENOENT; + int newSockFd = -1; if (_isPrivileged) { - errno = LinuxError.EBADF; + errno = LinuxError.SUCCESS; - BsdSocket oldSocket = RetrieveSocket(socketFd); + newSockFd = _context.DuplicateFileDescriptor(fd); - if (oldSocket != null) + if (newSockFd == -1) { - _sockets.Add(oldSocket); - newSockFd = _sockets.Count - 1; + errno = LinuxError.EBADF; } } return WriteBsdResult(context, newSockFd, errno); } + + [CommandHipc(31)] // 7.0.0+ + // EventFd(u64 initval, nn::socket::EventFdFlags flags) -> (i32 ret, u32 bsd_errno) + public ResultCode EventFd(ServiceCtx context) + { + ulong initialValue = context.RequestData.ReadUInt64(); + EventFdFlags flags = (EventFdFlags)context.RequestData.ReadUInt32(); + + EventFileDescriptor newEventFile = new EventFileDescriptor(initialValue, flags); + + LinuxError errno = LinuxError.SUCCESS; + + int newSockFd = _context.RegisterFileDescriptor(newEventFile); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + + return WriteBsdResult(context, newSockFd, errno); + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs new file mode 100644 index 000000000..56f67539b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + interface IFileDescriptor : IDisposable + { + bool Blocking { get; set; } + int Refcount { get; set; } + + LinuxError Read(out int readSize, Span buffer); + + LinuxError Write(out int writeSize, ReadOnlySpan buffer); + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs new file mode 100644 index 000000000..ee6bd9e8e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs @@ -0,0 +1,47 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + interface ISocket : IDisposable, IFileDescriptor + { + IPEndPoint RemoteEndPoint { get; } + IPEndPoint LocalEndPoint { get; } + + AddressFamily AddressFamily { get; } + + SocketType SocketType { get; } + + ProtocolType ProtocolType { get; } + + IntPtr Handle { get; } + + LinuxError Receive(out int receiveSize, Span buffer, BsdSocketFlags flags); + + LinuxError ReceiveFrom(out int receiveSize, Span buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint); + + LinuxError Send(out int sendSize, ReadOnlySpan buffer, BsdSocketFlags flags); + + LinuxError SendTo(out int sendSize, ReadOnlySpan buffer, int size, BsdSocketFlags flags, IPEndPoint remoteEndPoint); + + LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span optionValue); + LinuxError SetSocketOption(BsdSocketOption option, SocketOptionLevel level, ReadOnlySpan optionValue); + + bool Poll(int microSeconds, SelectMode mode); + + LinuxError Bind(IPEndPoint localEndPoint); + + LinuxError Connect(IPEndPoint remoteEndPoint); + + LinuxError Listen(int backlog); + + LinuxError Accept(out ISocket newSocket); + + void Disconnect(); + + LinuxError Shutdown(BsdSocketShutdownFlags how); + + void Close(); + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs new file mode 100644 index 000000000..e92b42ef0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs @@ -0,0 +1,130 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + class EventFileDescriptor : IFileDescriptor + { + private ulong _value; + private readonly EventFdFlags _flags; + private AutoResetEvent _event; + + private object _lock = new object(); + + public bool Blocking { get => !_flags.HasFlag(EventFdFlags.NonBlocking); set => throw new NotSupportedException(); } + + public ManualResetEvent WriteEvent { get; } + public ManualResetEvent ReadEvent { get; } + + public EventFileDescriptor(ulong value, EventFdFlags flags) + { + _value = value; + _flags = flags; + _event = new AutoResetEvent(false); + + WriteEvent = new ManualResetEvent(true); + ReadEvent = new ManualResetEvent(true); + } + + public int Refcount { get; set; } + + public void Dispose() + { + _event.Dispose(); + WriteEvent.Dispose(); + ReadEvent.Dispose(); + } + + public LinuxError Read(out int readSize, Span buffer) + { + if (buffer.Length < sizeof(ulong)) + { + readSize = 0; + + return LinuxError.EINVAL; + } + + ReadEvent.Reset(); + + lock (_lock) + { + ref ulong count = ref MemoryMarshal.Cast(buffer)[0]; + + if (_value == 0) + { + if (Blocking) + { + while (_value == 0) + { + _event.WaitOne(); + } + } + else + { + readSize = 0; + + return LinuxError.EAGAIN; + } + } + + readSize = sizeof(ulong); + + if (_flags.HasFlag(EventFdFlags.Semaphore)) + { + --_value; + + count = 1; + } + else + { + count = _value; + + _value = 0; + } + + ReadEvent.Set(); + + return LinuxError.SUCCESS; + } + } + + public LinuxError Write(out int writeSize, ReadOnlySpan buffer) + { + if (!MemoryMarshal.TryRead(buffer, out ulong count) || count == ulong.MaxValue) + { + writeSize = 0; + + return LinuxError.EINVAL; + } + + WriteEvent.Reset(); + + lock (_lock) + { + if (_value > _value + count) + { + if (Blocking) + { + _event.WaitOne(); + } + else + { + writeSize = 0; + + return LinuxError.EAGAIN; + } + } + + writeSize = sizeof(ulong); + + _value += count; + _event.Set(); + + WriteEvent.Set(); + + return LinuxError.SUCCESS; + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs new file mode 100644 index 000000000..8bd9652bf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs @@ -0,0 +1,96 @@ +using Ryujinx.Common.Logging; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + class EventFileDescriptorPollManager : IPollManager + { + private static EventFileDescriptorPollManager _instance; + + public static EventFileDescriptorPollManager Instance + { + get + { + if (_instance == null) + { + _instance = new EventFileDescriptorPollManager(); + } + + return _instance; + } + } + + public bool IsCompatible(PollEvent evnt) + { + return evnt.FileDescriptor is EventFileDescriptor; + } + + public LinuxError Poll(List events, int timeoutMilliseconds, out int updatedCount) + { + updatedCount = 0; + + List waiters = new List(); + + for (int i = 0; i < events.Count; i++) + { + PollEvent evnt = events[i]; + + EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor; + + bool isValidEvent = false; + + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input) || + evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput)) + { + waiters.Add(socket.ReadEvent); + + isValidEvent = true; + } + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) + { + waiters.Add(socket.WriteEvent); + + isValidEvent = true; + } + + if (!isValidEvent) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}"); + + return LinuxError.EINVAL; + } + } + + int index = WaitHandle.WaitAny(waiters.ToArray(), timeoutMilliseconds); + + if (index != WaitHandle.WaitTimeout) + { + for (int i = 0; i < events.Count; i++) + { + PollEvent evnt = events[i]; + + EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor; + + if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input) || + evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput)) + && socket.ReadEvent.WaitOne(0)) + { + waiters.Add(socket.ReadEvent); + } + if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) + && socket.WriteEvent.WaitOne(0)) + { + waiters.Add(socket.WriteEvent); + } + } + } + else + { + return LinuxError.ETIMEDOUT; + } + + return LinuxError.SUCCESS; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs new file mode 100644 index 000000000..349dbde0a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs @@ -0,0 +1,338 @@ +using Ryujinx.Common.Logging; +using System; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + class ManagedSocket : ISocket + { + public int Refcount { get; set; } + + public AddressFamily AddressFamily => Socket.AddressFamily; + + public SocketType SocketType => Socket.SocketType; + + public ProtocolType ProtocolType => Socket.ProtocolType; + + public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; } + + public IntPtr Handle => Socket.Handle; + + public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint; + + public IPEndPoint LocalEndPoint => Socket.LocalEndPoint as IPEndPoint; + + public Socket Socket { get; } + + public ManagedSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) + { + Socket = new Socket(addressFamily, socketType, protocolType); + Refcount = 1; + } + + private ManagedSocket(Socket socket) + { + Socket = socket; + Refcount = 1; + } + + private static SocketFlags ConvertBsdSocketFlags(BsdSocketFlags bsdSocketFlags) + { + SocketFlags socketFlags = SocketFlags.None; + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Oob)) + { + socketFlags |= SocketFlags.OutOfBand; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Peek)) + { + socketFlags |= SocketFlags.Peek; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.DontRoute)) + { + socketFlags |= SocketFlags.DontRoute; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Trunc)) + { + socketFlags |= SocketFlags.Truncated; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.CTrunc)) + { + socketFlags |= SocketFlags.ControlDataTruncated; + } + + bsdSocketFlags &= ~(BsdSocketFlags.Oob | + BsdSocketFlags.Peek | + BsdSocketFlags.DontRoute | + BsdSocketFlags.DontWait | + BsdSocketFlags.Trunc | + BsdSocketFlags.CTrunc); + + if (bsdSocketFlags != BsdSocketFlags.None) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported socket flags: {bsdSocketFlags}"); + } + + return socketFlags; + } + + public LinuxError Accept(out ISocket newSocket) + { + try + { + newSocket = new ManagedSocket(Socket.Accept()); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + newSocket = null; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Bind(IPEndPoint localEndPoint) + { + try + { + Socket.Bind(localEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public void Close() + { + Socket.Close(); + } + + public LinuxError Connect(IPEndPoint remoteEndPoint) + { + try + { + Socket.Connect(remoteEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + if (!Blocking && exception.ErrorCode == (int)WsaError.WSAEWOULDBLOCK) + { + return LinuxError.EINPROGRESS; + } + else + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + } + + public void Disconnect() + { + Socket.Disconnect(true); + } + + public void Dispose() + { + Socket.Close(); + Socket.Dispose(); + } + + public LinuxError Listen(int backlog) + { + try + { + Socket.Listen(backlog); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public bool Poll(int microSeconds, SelectMode mode) + { + return Socket.Poll(microSeconds, mode); + } + + public LinuxError Shutdown(BsdSocketShutdownFlags how) + { + try + { + Socket.Shutdown((SocketShutdown)how); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Receive(out int receiveSize, Span buffer, BsdSocketFlags flags) + { + try + { + receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags)); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + receiveSize = -1; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError ReceiveFrom(out int receiveSize, Span buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint) + { + remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); + + LinuxError result; + + bool shouldBlockAfterOperation = false; + + try + { + EndPoint temp = new IPEndPoint(IPAddress.Any, 0); + + if (Blocking && flags.HasFlag(BsdSocketFlags.DontWait)) + { + Blocking = false; + shouldBlockAfterOperation = true; + } + + receiveSize = Socket.ReceiveFrom(buffer[..size], ConvertBsdSocketFlags(flags), ref temp); + + remoteEndPoint = (IPEndPoint)temp; + result = LinuxError.SUCCESS; + } + catch (SocketException exception) + { + receiveSize = -1; + + result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + + if (shouldBlockAfterOperation) + { + Blocking = true; + } + + return result; + } + + public LinuxError Send(out int sendSize, ReadOnlySpan buffer, BsdSocketFlags flags) + { + try + { + sendSize = Socket.Send(buffer, ConvertBsdSocketFlags(flags)); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + sendSize = -1; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError SendTo(out int sendSize, ReadOnlySpan buffer, int size, BsdSocketFlags flags, IPEndPoint remoteEndPoint) + { + try + { + sendSize = Socket.SendTo(buffer[..size], ConvertBsdSocketFlags(flags), remoteEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + sendSize = -1; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span optionValue) + { + try + { + if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}"); + + return LinuxError.EOPNOTSUPP; + } + + byte[] tempOptionValue = new byte[optionValue.Length]; + + Socket.GetSocketOption(level, optionName, tempOptionValue); + + tempOptionValue.AsSpan().CopyTo(optionValue); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError SetSocketOption(BsdSocketOption option, SocketOptionLevel level, ReadOnlySpan optionValue) + { + try + { + if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}"); + + return LinuxError.EOPNOTSUPP; + } + + int value = MemoryMarshal.Read(optionValue); + + if (option == BsdSocketOption.SoLinger) + { + int value2 = MemoryMarshal.Read(optionValue[4..]); + + Socket.SetSocketOption(level, SocketOptionName.Linger, new LingerOption(value != 0, value2)); + } + else + { + Socket.SetSocketOption(level, optionName, value); + } + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Read(out int readSize, Span buffer) + { + return Receive(out readSize, buffer, BsdSocketFlags.None); + } + + public LinuxError Write(out int writeSize, ReadOnlySpan buffer) + { + return Send(out writeSize, buffer, BsdSocketFlags.None); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs new file mode 100644 index 000000000..b2414bc12 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs @@ -0,0 +1,129 @@ +using Ryujinx.Common.Logging; +using System.Collections.Generic; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + class ManagedSocketPollManager : IPollManager + { + private static ManagedSocketPollManager _instance; + + public static ManagedSocketPollManager Instance + { + get + { + if (_instance == null) + { + _instance = new ManagedSocketPollManager(); + } + + return _instance; + } + } + + public bool IsCompatible(PollEvent evnt) + { + return evnt.FileDescriptor is ManagedSocket; + } + + public LinuxError Poll(List events, int timeoutMilliseconds, out int updatedCount) + { + List readEvents = new List(); + List writeEvents = new List(); + List errorEvents = new List(); + + updatedCount = 0; + + foreach (PollEvent evnt in events) + { + ManagedSocket socket = (ManagedSocket)evnt.FileDescriptor; + + bool isValidEvent = false; + + if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0) + { + readEvents.Add(socket.Socket); + errorEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if ((evnt.Data.InputEvents & PollEventTypeMask.UrgentInput) != 0) + { + readEvents.Add(socket.Socket); + errorEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if ((evnt.Data.InputEvents & PollEventTypeMask.Output) != 0) + { + writeEvents.Add(socket.Socket); + errorEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if ((evnt.Data.InputEvents & PollEventTypeMask.Error) != 0) + { + errorEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if (!isValidEvent) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}"); + return LinuxError.EINVAL; + } + } + + try + { + int actualTimeoutMicroseconds = timeoutMilliseconds == -1 ? -1 : timeoutMilliseconds * 1000; + + Socket.Select(readEvents, writeEvents, errorEvents, actualTimeoutMicroseconds); + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + + foreach (PollEvent evnt in events) + { + Socket socket = ((ManagedSocket)evnt.FileDescriptor).Socket; + + PollEventTypeMask outputEvents = 0; + + if (errorEvents.Contains(socket)) + { + outputEvents |= PollEventTypeMask.Error; + + if (!socket.Connected || !socket.IsBound) + { + outputEvents |= PollEventTypeMask.Disconnected; + } + } + + if (readEvents.Contains(socket)) + { + if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0) + { + outputEvents |= PollEventTypeMask.Input; + } + } + + if (writeEvents.Contains(socket)) + { + outputEvents |= PollEventTypeMask.Output; + } + + evnt.Data.OutputEvents = outputEvents; + } + + updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count; + + return LinuxError.SUCCESS; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/WSAError.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs similarity index 100% rename from Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/WSAError.cs rename to Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs new file mode 100644 index 000000000..ad12745e8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs @@ -0,0 +1,165 @@ +using System.Collections.Generic; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + static class WinSockHelper + { + private static readonly Dictionary _errorMap = new() + { + // WSAEINTR + {WsaError.WSAEINTR, LinuxError.EINTR}, + // WSAEWOULDBLOCK + {WsaError.WSAEWOULDBLOCK, LinuxError.EWOULDBLOCK}, + // WSAEINPROGRESS + {WsaError.WSAEINPROGRESS, LinuxError.EINPROGRESS}, + // WSAEALREADY + {WsaError.WSAEALREADY, LinuxError.EALREADY}, + // WSAENOTSOCK + {WsaError.WSAENOTSOCK, LinuxError.ENOTSOCK}, + // WSAEDESTADDRREQ + {WsaError.WSAEDESTADDRREQ, LinuxError.EDESTADDRREQ}, + // WSAEMSGSIZE + {WsaError.WSAEMSGSIZE, LinuxError.EMSGSIZE}, + // WSAEPROTOTYPE + {WsaError.WSAEPROTOTYPE, LinuxError.EPROTOTYPE}, + // WSAENOPROTOOPT + {WsaError.WSAENOPROTOOPT, LinuxError.ENOPROTOOPT}, + // WSAEPROTONOSUPPORT + {WsaError.WSAEPROTONOSUPPORT, LinuxError.EPROTONOSUPPORT}, + // WSAESOCKTNOSUPPORT + {WsaError.WSAESOCKTNOSUPPORT, LinuxError.ESOCKTNOSUPPORT}, + // WSAEOPNOTSUPP + {WsaError.WSAEOPNOTSUPP, LinuxError.EOPNOTSUPP}, + // WSAEPFNOSUPPORT + {WsaError.WSAEPFNOSUPPORT, LinuxError.EPFNOSUPPORT}, + // WSAEAFNOSUPPORT + {WsaError.WSAEAFNOSUPPORT, LinuxError.EAFNOSUPPORT}, + // WSAEADDRINUSE + {WsaError.WSAEADDRINUSE, LinuxError.EADDRINUSE}, + // WSAEADDRNOTAVAIL + {WsaError.WSAEADDRNOTAVAIL, LinuxError.EADDRNOTAVAIL}, + // WSAENETDOWN + {WsaError.WSAENETDOWN, LinuxError.ENETDOWN}, + // WSAENETUNREACH + {WsaError.WSAENETUNREACH, LinuxError.ENETUNREACH}, + // WSAENETRESET + {WsaError.WSAENETRESET, LinuxError.ENETRESET}, + // WSAECONNABORTED + {WsaError.WSAECONNABORTED, LinuxError.ECONNABORTED}, + // WSAECONNRESET + {WsaError.WSAECONNRESET, LinuxError.ECONNRESET}, + // WSAENOBUFS + {WsaError.WSAENOBUFS, LinuxError.ENOBUFS}, + // WSAEISCONN + {WsaError.WSAEISCONN, LinuxError.EISCONN}, + // WSAENOTCONN + {WsaError.WSAENOTCONN, LinuxError.ENOTCONN}, + // WSAESHUTDOWN + {WsaError.WSAESHUTDOWN, LinuxError.ESHUTDOWN}, + // WSAETOOMANYREFS + {WsaError.WSAETOOMANYREFS, LinuxError.ETOOMANYREFS}, + // WSAETIMEDOUT + {WsaError.WSAETIMEDOUT, LinuxError.ETIMEDOUT}, + // WSAECONNREFUSED + {WsaError.WSAECONNREFUSED, LinuxError.ECONNREFUSED}, + // WSAELOOP + {WsaError.WSAELOOP, LinuxError.ELOOP}, + // WSAENAMETOOLONG + {WsaError.WSAENAMETOOLONG, LinuxError.ENAMETOOLONG}, + // WSAEHOSTDOWN + {WsaError.WSAEHOSTDOWN, LinuxError.EHOSTDOWN}, + // WSAEHOSTUNREACH + {WsaError.WSAEHOSTUNREACH, LinuxError.EHOSTUNREACH}, + // WSAENOTEMPTY + {WsaError.WSAENOTEMPTY, LinuxError.ENOTEMPTY}, + // WSAEUSERS + {WsaError.WSAEUSERS, LinuxError.EUSERS}, + // WSAEDQUOT + {WsaError.WSAEDQUOT, LinuxError.EDQUOT}, + // WSAESTALE + {WsaError.WSAESTALE, LinuxError.ESTALE}, + // WSAEREMOTE + {WsaError.WSAEREMOTE, LinuxError.EREMOTE}, + // WSAEINVAL + {WsaError.WSAEINVAL, LinuxError.EINVAL}, + // WSAEFAULT + {WsaError.WSAEFAULT, LinuxError.EFAULT}, + // NOERROR + {0, 0} + }; + + private static readonly Dictionary _soSocketOptionMap = new() + { + { BsdSocketOption.SoDebug, SocketOptionName.Debug }, + { BsdSocketOption.SoReuseAddr, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoKeepAlive, SocketOptionName.KeepAlive }, + { BsdSocketOption.SoDontRoute, SocketOptionName.DontRoute }, + { BsdSocketOption.SoBroadcast, SocketOptionName.Broadcast }, + { BsdSocketOption.SoUseLoopBack, SocketOptionName.UseLoopback }, + { BsdSocketOption.SoLinger, SocketOptionName.Linger }, + { BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline }, + { BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer }, + { BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer }, + { BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater }, + { BsdSocketOption.SoRcvLoWat, SocketOptionName.ReceiveLowWater }, + { BsdSocketOption.SoSndTimeo, SocketOptionName.SendTimeout }, + { BsdSocketOption.SoRcvTimeo, SocketOptionName.ReceiveTimeout }, + { BsdSocketOption.SoError, SocketOptionName.Error }, + { BsdSocketOption.SoType, SocketOptionName.Type } + }; + + private static readonly Dictionary _ipSocketOptionMap = new() + { + { BsdSocketOption.IpOptions, SocketOptionName.IPOptions }, + { BsdSocketOption.IpHdrIncl, SocketOptionName.HeaderIncluded }, + { BsdSocketOption.IpTtl, SocketOptionName.IpTimeToLive }, + { BsdSocketOption.IpMulticastIf, SocketOptionName.MulticastInterface }, + { BsdSocketOption.IpMulticastTtl, SocketOptionName.MulticastTimeToLive }, + { BsdSocketOption.IpMulticastLoop, SocketOptionName.MulticastLoopback }, + { BsdSocketOption.IpAddMembership, SocketOptionName.AddMembership }, + { BsdSocketOption.IpDropMembership, SocketOptionName.DropMembership }, + { BsdSocketOption.IpDontFrag, SocketOptionName.DontFragment }, + { BsdSocketOption.IpAddSourceMembership, SocketOptionName.AddSourceMembership }, + { BsdSocketOption.IpDropSourceMembership, SocketOptionName.DropSourceMembership } + }; + + private static readonly Dictionary _tcpSocketOptionMap = new() + { + { BsdSocketOption.TcpNoDelay, SocketOptionName.NoDelay }, + { BsdSocketOption.TcpKeepIdle, SocketOptionName.TcpKeepAliveTime }, + { BsdSocketOption.TcpKeepIntvl, SocketOptionName.TcpKeepAliveInterval }, + { BsdSocketOption.TcpKeepCnt, SocketOptionName.TcpKeepAliveRetryCount } + }; + + public static LinuxError ConvertError(WsaError errorCode) + { + if (!_errorMap.TryGetValue(errorCode, out LinuxError errno)) + { + errno = (LinuxError)errorCode; + } + + return errno; + } + + public static bool TryConvertSocketOption(BsdSocketOption option, SocketOptionLevel level, out SocketOptionName name) + { + var table = level switch + { + SocketOptionLevel.Socket => _soSocketOptionMap, + SocketOptionLevel.IP => _ipSocketOptionMap, + SocketOptionLevel.Tcp => _tcpSocketOptionMap, + _ => null + }; + + if (table == null) + { + name = default; + return false; + } + + return table.TryGetValue(option, out name); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs new file mode 100644 index 000000000..dcc9f0fd7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + enum BsdAddressFamily : uint + { + Unspecified, + InterNetwork = 2, + InterNetworkV6 = 28, + + Unknown = uint.MaxValue + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs new file mode 100644 index 000000000..916ca2bbc --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs @@ -0,0 +1,39 @@ +using Ryujinx.Common.Memory; +using System; +using System.Net; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + struct BsdSockAddr + { + public byte Length; + public byte Family; + public ushort Port; + public Array4 Address; + private Array8 _reserved; + + public IPEndPoint ToIPEndPoint() + { + IPAddress address = new IPAddress(Address.ToSpan()); + int port = (ushort)IPAddress.NetworkToHostOrder((short)Port); + + return new IPEndPoint(address, port); + } + + public static BsdSockAddr FromIPEndPoint(IPEndPoint endpoint) + { + BsdSockAddr result = new BsdSockAddr + { + Length = 0, + Family = (byte)endpoint.AddressFamily, + Port = (ushort)IPAddress.HostToNetworkOrder((short)endpoint.Port) + }; + + endpoint.Address.GetAddressBytes().AsSpan().CopyTo(result.Address.ToSpan()); + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocket.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocket.cs deleted file mode 100644 index 2d5bf4290..000000000 --- a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocket.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Net.Sockets; - -namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd -{ - class BsdSocket - { - public int Family; - public int Type; - public int Protocol; - - public Socket Handle; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs new file mode 100644 index 000000000..77718800d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Flags] + enum BsdSocketCreationFlags + { + None = 0, + CloseOnExecution = 1, + NonBlocking = 2, + + FlagsShift = 28 + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs new file mode 100644 index 000000000..2588376bb --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + enum BsdSocketShutdownFlags + { + Receive, + Send, + ReceiveAndSend + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs new file mode 100644 index 000000000..9b13e669a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + enum BsdSocketType + { + Stream = 1, + Dgram, + Raw, + Rdm, + Seqpacket, + + TypeMask = 0xFFFFFFF, + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs new file mode 100644 index 000000000..7d08fb24a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Flags] + enum EventFdFlags : uint + { + None = 0, + Semaphore = 1 << 0, + NonBlocking = 1 << 2 + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs new file mode 100644 index 000000000..8b0959fd0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + interface IPollManager + { + bool IsCompatible(PollEvent evnt); + + LinuxError Poll(List events, int timeoutMilliseconds, out int updatedCount); + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs index ff47a4c70..8056e7a87 100644 --- a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs @@ -2,27 +2,13 @@ { class PollEvent { - public enum EventTypeMask - { - Input = 1, - UrgentInput = 2, - Output = 4, - Error = 8, - Disconnected = 0x10, - Invalid = 0x20 - } + public PollEventData Data; + public IFileDescriptor FileDescriptor { get; } - public int SocketFd { get; private set; } - public BsdSocket Socket { get; private set; } - public EventTypeMask InputEvents { get; private set; } - public EventTypeMask OutputEvents { get; private set; } - - public PollEvent(int socketFd, BsdSocket socket, EventTypeMask inputEvents, EventTypeMask outputEvents) + public PollEvent(PollEventData data, IFileDescriptor fileDescriptor) { - SocketFd = socketFd; - Socket = socket; - InputEvents = inputEvents; - OutputEvents = outputEvents; + Data = data; + FileDescriptor = fileDescriptor; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs new file mode 100644 index 000000000..ee400e695 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs @@ -0,0 +1,13 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + struct PollEventData + { +#pragma warning disable CS0649 + public int SocketFd; + public PollEventTypeMask InputEvents; +#pragma warning restore CS0649 + public PollEventTypeMask OutputEvents; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs new file mode 100644 index 000000000..899072bf0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Flags] + enum PollEventTypeMask : ushort + { + Input = 1, + UrgentInput = 2, + Output = 4, + Error = 8, + Disconnected = 0x10, + Invalid = 0x20 + } +}