diff --git a/libraries/libstratosphere/include/stratosphere/socket/impl/socket_platform_types_translation.hpp b/libraries/libstratosphere/include/stratosphere/socket/impl/socket_platform_types_translation.hpp new file mode 100644 index 000000000..1586af239 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/socket/impl/socket_platform_types_translation.hpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include +#include + +namespace ams::socket::impl { + + #if defined(ATMOSPHERE_OS_WINDOWS) + class PosixWinSockConverter { + private: + struct SocketData { + SOCKET winsock; + bool exempt; + bool shutdown; + + constexpr SocketData() : winsock(static_cast(INVALID_SOCKET)), exempt(), shutdown() { /* ... */ } + }; + private: + os::SdkMutex m_mutex{}; + SocketData m_data[MaxSocketsPerClient]{}; + private: + static constexpr int GetInitialIndex(SOCKET winsock) { + /* The lower 2 bits of a winsock are always zero; Nintendo uses the upper bits as a hashmap index into m_data. */ + return (winsock >> 2) % MaxSocketsPerClient; + } + public: + constexpr PosixWinSockConverter() = default; + + s32 AcquirePosixHandle(SOCKET winsock, bool exempt = false); + s32 GetShutdown(bool &shutdown, s32 posix); + s32 GetSocketExempt(bool &exempt, s32 posix); + SOCKET PosixToWinsockSocket(s32 posix); + void ReleaseAllPosixHandles(); + void ReleasePosixHandle(s32 posix); + s32 SetShutdown(s32 posix, bool shutdown); + s32 SetSocketExempt(s32 posix, bool exempt); + s32 WinsockToPosixSocket(SOCKET winsock); + }; + + s32 MapProtocolValue(Protocol protocol); + Protocol MapProtocolValue(s32 protocol); + + s32 MapTypeValue(Type type); + Type MapTypeValue(s32 type); + + s8 MapFamilyValue(Family family); + Family MapFamilyValue(s8 family); + + s32 MapMsgFlagValue(MsgFlag flag); + MsgFlag MapMsgFlagValue(s32 flag); + + u32 MapAddrInfoFlagValue(AddrInfoFlag flag); + AddrInfoFlag MapAddrInfoFlagValue(u32 flag); + + u32 MapShutdownMethodValue(ShutdownMethod how); + ShutdownMethod MapShutdownMethodValue(u32 how); + + u32 MapFcntlFlagValue(FcntlFlag flag); + FcntlFlag MapFcntlFlagValue(u32 flag); + + s32 MapLevelValue(Level level); + Level MapLevelValue(s32 level); + + s32 MapOptionValue(Level level, Option option); + Option MapOptionValue(s32 level, s32 option); + + s32 MapErrnoValue(Errno error); + Errno MapErrnoValue(s32 error); + + #endif + + #define AMS_SOCKET_IMPL_DECLARE_CONVERSION(AMS, PLATFORM) \ + void CopyToPlatform(PLATFORM *dst, const AMS *src); \ + void CopyFromPlatform(AMS *dst, const PLATFORM *src); + + AMS_SOCKET_IMPL_DECLARE_CONVERSION(SockAddrIn, sockaddr_in); + AMS_SOCKET_IMPL_DECLARE_CONVERSION(TimeVal, timeval); + AMS_SOCKET_IMPL_DECLARE_CONVERSION(Linger, linger); + + #undef AMS_SOCKET_IMPL_DECLARE_CONVERSION + +} diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp index 07acdf968..4523842ed 100644 --- a/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp @@ -49,6 +49,8 @@ namespace ams::socket { s32 Accept(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); s32 Bind(s32 desc, const SockAddr *address, SockLenT len); + s32 Connect(s32 desc, const SockAddr *address, SockLenT len); + s32 GetSockName(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); s32 SetSockOpt(s32 desc, Level level, Option option_name, const void *option_value, SockLenT option_size); diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_constants.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_constants.hpp index f7ac4fdd5..c742dede5 100644 --- a/libraries/libstratosphere/include/stratosphere/socket/socket_constants.hpp +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_constants.hpp @@ -21,6 +21,8 @@ namespace ams::socket { constexpr inline s32 InvalidSocket = -1; constexpr inline s32 SocketError = -1; + constexpr inline u32 MaxSocketsPerClient = 0x80; + constexpr inline auto DefaultTcpAutoBufferSizeMax = 192_KB; constexpr inline auto MinTransferMemorySize = (2 * DefaultTcpAutoBufferSizeMax + 128_KB); constexpr inline auto MinSocketAllocatorSize = 128_KB; diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp index f4bb45198..5a1036e90 100644 --- a/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp @@ -19,21 +19,143 @@ namespace ams::socket { enum class Errno : u32 { - ESuccess = 0, + ESuccess = 0, + EPerm = 1, + ENoEnt = 2, + ESrch = 3, + EIntr = 4, + EIo = 5, + ENxIo = 6, + E2Big = 7, + ENoExec = 8, + EBadf = 9, + EChild = 10, + EAgain = 11, + EWouldBlock = EAgain, + ENoMem = 12, + EAcces = 13, + EFault = 14, + ENotBlk = 15, + EBusy = 16, + EExist = 17, + EXDev = 18, + ENoDev = 19, + ENotDir = 20, + EIsDir = 21, + EInval = 22, + ENFile = 23, + EMFile = 24, + ENotTy = 25, + ETxtBsy = 26, + EFBig = 27, + ENoSpc = 28, + ESPipe = 29, + ERofs = 30, + EMLink = 31, + EPipe = 32, + EDom = 33, + ERange = 34, + EDeadLk = 35, + EDeadLock = EDeadLk, + ENameTooLong = 36, + ENoLck = 37, + ENoSys = 38, + ENotEmpty = 39, + ELoop = 40, + ENoMsg = 42, + EIdrm = 43, + EChrng = 44, + EL2NSync = 45, + EL3Hlt = 46, + EL3Rst = 47, + ELnrng = 48, + EUnatch = 49, + ENoCsi = 50, + EL2Hlt = 51, + EBade = 52, + EBadr = 53, + EXFull = 54, + ENoAno = 55, + EBadRqc = 56, + EBadSsl = 57, + EBFont = 59, + ENoStr = 60, + ENoData = 61, + ETime = 62, + ENoSr = 63, + ENoNet = 64, + ENoPkg = 65, + ERemote = 66, + ENoLink = 67, + EAdv = 68, + ESrmnt = 69, + EComm = 70, + EProto = 71, + EMultiHop = 72, + EDotDot = 73, + EBadMsg = 74, + EOverflow = 75, + ENotUnuq = 76, + EBadFd = 77, + ERemChg = 78, + ELibAcc = 79, + ELibBad = 80, + ELibScn = 81, + ELibMax = 82, + ELibExec = 83, + EIlSeq = 84, + ERestart = 85, + EStrPipe = 86, + EUsers = 87, + ENotSock = 88, + EDestAddrReq = 89, + EMsgSize = 90, + EPrototype = 91, + ENoProtoOpt = 92, + EProtoNoSupport = 93, + ESocktNoSupport = 94, + EOpNotSupp = 95, + ENotSup = EOpNotSupp, + EPfNoSupport = 96, + EAfNoSupport = 97, + EAddrInUse = 98, + EAddrNotAvail = 99, + ENetDown = 100, + ENetUnreach = 101, + ENetReset = 102, + EConnAborted = 103, + EConnReset = 104, + ENoBufs = 105, + EIsConn = 106, + ENotConn = 107, + EShutDown = 108, + ETooManyRefs = 109, + ETimedOut = 110, + EConnRefused = 111, + EHostDown = 112, + EHostUnreach = 113, + EAlready = 114, + EInProgress = 115, + EStale = 116, + EUClean = 117, + ENotNam = 118, + ENAvail = 119, + EIsNam = 120, + ERemoteIo = 121, + EDQuot = 122, + ENoMedium = 123, + EMediumType = 124, + ECanceled = 125, + ENoKey = 126, + EKeyExpired = 127, + EKeyRevoked = 128, + EKeyRejected = 129, + EOwnerDead = 130, + ENotRecoverable = 131, + ERfKill = 132, + EHwPoison = 133, /* ... */ - EAgain = 11, - ENoMem = 12, - /* ... */ - EFault = 14, - /* ... */ - EInval = 22, - /* ... */ - ENoSpc = 28, - /* ... */ - EL3Hlt = 46, - /* ... */ - EOpNotSupp = 95, - ENotSup = EOpNotSupp, + EProcLim = 156, }; enum class HErrno : s32 { diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_options.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_options.hpp index fd4780100..485fdbd66 100644 --- a/libraries/libstratosphere/include/stratosphere/socket/socket_options.hpp +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_options.hpp @@ -29,10 +29,88 @@ namespace ams::socket { }; enum class Option : u32 { - So_Debug = (1 << 0), - /* ... */ - So_ReuseAddr = (1 << 2), - /* ... */ + /* ==================================== */ + So_Debug = (1 << 0), + So_AcceptConn = (1 << 1), + So_ReuseAddr = (1 << 2), + So_KeepAlive = (1 << 3), + So_DontRoute = (1 << 4), + So_Broadcast = (1 << 5), + So_UseLoopback = (1 << 6), + So_Linger = (1 << 7), + So_OobInline = (1 << 8), + So_ReusePort = (1 << 9), + + So_SndBuf = (1 << 12) | 0x01, + So_RcvBuf = (1 << 12) | 0x02, + So_SndLoWat = (1 << 12) | 0x03, + So_RcvLoWat = (1 << 12) | 0x04, + So_SndTimeo = (1 << 12) | 0x05, + So_RcvTimeo = (1 << 12) | 0x06, + So_Error = (1 << 12) | 0x07, + So_Type = (1 << 12) | 0x08, + So_Label = (1 << 12) | 0x09, + So_PeerLabel = (1 << 12) | 0x10, + So_ListenQLimit = (1 << 12) | 0x11, + So_ListenQLen = (1 << 12) | 0x12, + So_ListenIncQLen = (1 << 12) | 0x13, + So_SetFib = (1 << 12) | 0x14, + So_User_Cookie = (1 << 12) | 0x15, + So_Protocol = (1 << 12) | 0x16, + + So_Nn_Shutdown_Exempt = (1 << 16), + + So_Vendor = (1u << 31), + So_Nn_Linger = So_Vendor | 0x01, + /* ==================================== */ + + /* ==================================== */ + Ip_Options = 1, + Ip_HdrIncl = 2, + Ip_Tos = 3, + Ip_Ttl = 4, + Ip_RecvOpts = 5, + Ip_Multicast_If = 9, + Ip_Multicast_Ttl = 10, + Ip_Multicast_Loop = 11, + Ip_Add_Membership = 12, + Ip_Drop_Membership = 13, + Ip_Multicast_Vif = 14, + Ip_Rsvp_On = 15, + Ip_Rsvp_Off = 16, + Ip_Rsvp_Vif_On = 17, + Ip_Rsvp_Vif_Off = 18, + Ip_PortRange = 19, + Ip_Faith = 22, + Ip_OnesBcast = 23, + Ip_BindAny = 24, + + Ip_RecvTtl = 65, + Ip_MinTtl = 66, + Ip_DontFrag = 67, + Ip_RecvTos = 68, + + Ip_Add_Source_Membership = 70, + Ip_Drop_Source_Membership = 71, + Ip_Block_Source = 72, + Ip_Unblock_Source = 73, + /* ==================================== */ + + /* ==================================== */ + Tcp_NoDelay = (1 << 0), + Tcp_MaxSeg = (1 << 1), + Tcp_NoPush = (1 << 2), + Tcp_NoOpt = (1 << 3), + Tcp_Md5Sig = (1 << 4), + Tcp_Info = (1 << 5), + Tcp_Congestion = (1 << 6), + Tcp_KeepInit = (1 << 7), + Tcp_KeepIdle = (1 << 8), + Tcp_KeepIntvl = (1 << 9), + Tcp_KeepCnt = (1 << 10), + + Tcp_Vendor = So_Vendor, + /* ==================================== */ }; } diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp index 367c5f14e..8547cf821 100644 --- a/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp @@ -42,6 +42,8 @@ namespace ams::socket { IpProto_Udp = 17, + IpProto_None = 59, + IpProto_UdpLite = 136, IpProto_Raw = 255, @@ -80,12 +82,29 @@ namespace ams::socket { }; enum class MsgFlag : s32 { - MsgFlag_None = (0 << 0), + Msg_None = (0 << 0), + + Msg_Oob = (1 << 0), + Msg_Peek = (1 << 1), + Msg_DontRoute = (1 << 2), /* ... */ - MsgFlag_WaitAll = (1 << 6), + Msg_Trunc = (1 << 4), + Msg_CTrunc = (1 << 5), + Msg_WaitAll = (1 << 6), + Msg_DontWait = (1 << 7), /* ... */ }; + enum class FcntlCommand : u32 { + F_GetFl = 3, + F_SetFl = 4, + }; + + enum class FcntlFlag : u32 { + None = (0 << 0), + O_NonBlock = (1 << 11), + }; + enum class ShutdownMethod : u32 { Shut_Rd = 0, Shut_Wr = 1, @@ -140,6 +159,16 @@ namespace ams::socket { AddrInfo *ai_next; }; + struct TimeVal { + long tv_sec; + long tv_usec; + }; + + struct Linger { + int l_onoff; + int l_linger; + }; + #define AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(__ENUM__) \ constexpr inline __ENUM__ operator | (__ENUM__ lhs, __ENUM__ rhs) { return static_cast<__ENUM__>(static_cast>(lhs) | static_cast>(rhs)); } \ constexpr inline __ENUM__ operator |=(__ENUM__ &lhs, __ENUM__ rhs) { return lhs = lhs | rhs; } \ @@ -151,6 +180,8 @@ namespace ams::socket { AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(Type) AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(AddrInfoFlag) + AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(MsgFlag) + AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(FcntlFlag) #undef AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.cpp b/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.cpp index 5cb90ee7a..f864efc1d 100644 --- a/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.cpp +++ b/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.cpp @@ -100,7 +100,7 @@ namespace ams::htclow::driver { TmipcHeader header; socket::SockAddr recv_sockaddr; socket::SockLenT recv_sockaddr_len = sizeof(recv_sockaddr); - const auto recv_res = socket::RecvFrom(m_socket, std::addressof(header), sizeof(header), socket::MsgFlag::MsgFlag_None, std::addressof(recv_sockaddr), std::addressof(recv_sockaddr_len)); + const auto recv_res = socket::RecvFrom(m_socket, std::addressof(header), sizeof(header), socket::MsgFlag::Msg_None, std::addressof(recv_sockaddr), std::addressof(recv_sockaddr_len)); /* Check that our receive was valid. */ R_UNLESS(recv_res >= 0, htclow::ResultSocketReceiveFromError()); @@ -126,7 +126,7 @@ namespace ams::htclow::driver { } if (header.data_len > 0) { - const auto body_res = socket::RecvFrom(m_socket, packet_data, header.data_len, socket::MsgFlag::MsgFlag_None, std::addressof(recv_sockaddr), std::addressof(recv_sockaddr_len)); + const auto body_res = socket::RecvFrom(m_socket, packet_data, header.data_len, socket::MsgFlag::Msg_None, std::addressof(recv_sockaddr), std::addressof(recv_sockaddr_len)); R_UNLESS(body_res >= 0, htclow::ResultSocketReceiveFromError()); R_UNLESS(recv_sockaddr_len == sizeof(recv_sockaddr), htclow::ResultSocketReceiveFromError()); @@ -139,7 +139,7 @@ namespace ams::htclow::driver { const auto len = MakeBeaconResponsePacket(packet_data, sizeof(packet_data)); /* Send the beacon response data. */ - const auto send_res = socket::SendTo(m_socket, packet_data, len, socket::MsgFlag::MsgFlag_None, std::addressof(recv_sockaddr), sizeof(recv_sockaddr)); + const auto send_res = socket::SendTo(m_socket, packet_data, len, socket::MsgFlag::Msg_None, std::addressof(recv_sockaddr), sizeof(recv_sockaddr)); R_UNLESS(send_res >= 0, htclow::ResultSocketSendToError()); } diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.cpp b/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.cpp index 23b479acb..63afcf78e 100644 --- a/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.cpp +++ b/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.cpp @@ -175,7 +175,7 @@ namespace ams::htclow::driver { }; /* Send the auto-connect packet. */ - socket::SendTo(desc, auto_connect_packet, len, socket::MsgFlag::MsgFlag_None, reinterpret_cast(std::addressof(sockaddr)), sizeof(sockaddr)); + socket::SendTo(desc, auto_connect_packet, len, socket::MsgFlag::Msg_None, reinterpret_cast(std::addressof(sockaddr)), sizeof(sockaddr)); } Result SocketDriver::Open() { @@ -247,7 +247,7 @@ namespace ams::htclow::driver { /* Repeatedly send data until it's all sent. */ ssize_t cur_sent; for (ssize_t sent = 0; sent < src_size; sent += cur_sent) { - cur_sent = socket::Send(m_client_socket, static_cast(src) + sent, src_size - sent, socket::MsgFlag::MsgFlag_None); + cur_sent = socket::Send(m_client_socket, static_cast(src) + sent, src_size - sent, socket::MsgFlag::Msg_None); R_UNLESS(cur_sent > 0, htclow::ResultSocketSendError()); } @@ -261,7 +261,7 @@ namespace ams::htclow::driver { /* Repeatedly receive data until it's all sent. */ ssize_t cur_recv; for (ssize_t received = 0; received < dst_size; received += cur_recv) { - cur_recv = socket::Recv(m_client_socket, static_cast(dst) + received, dst_size - received, socket::MsgFlag::MsgFlag_None); + cur_recv = socket::Recv(m_client_socket, static_cast(dst) + received, dst_size - received, socket::MsgFlag::Msg_None); R_UNLESS(cur_recv > 0, htclow::ResultSocketReceiveError()); } diff --git a/libraries/libstratosphere/source/socket/impl/socket_api.hpp b/libraries/libstratosphere/source/socket/impl/socket_api.hpp index f1dcc5512..85ffb55e4 100644 --- a/libraries/libstratosphere/source/socket/impl/socket_api.hpp +++ b/libraries/libstratosphere/source/socket/impl/socket_api.hpp @@ -47,6 +47,8 @@ namespace ams::socket::impl { s32 Accept(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); s32 Bind(s32 desc, const SockAddr *address, SockLenT len); + s32 Connect(s32 desc, const SockAddr *address, SockLenT len); + s32 GetSockName(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); s32 SetSockOpt(s32 desc, Level level, Option option_name, const void *option_value, SockLenT option_size); diff --git a/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp b/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp index 676817e75..1ebf01565 100644 --- a/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp +++ b/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp @@ -505,6 +505,28 @@ namespace ams::socket::impl { return result; } + s32 Connect(s32 desc, const SockAddr *address, SockLenT len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Check input. */ + if (address == nullptr || len == 0) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdConnect(desc, ConvertForLibnx(address), len); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + s32 GetSockName(s32 desc, SockAddr *out_address, SockLenT *out_addr_len) { /* Check pre-conditions. */ AMS_ABORT_UNLESS(IsInitialized()); diff --git a/libraries/libstratosphere/source/socket/impl/socket_api.os.windows.cpp b/libraries/libstratosphere/source/socket/impl/socket_api.os.windows.cpp new file mode 100644 index 000000000..10fdd319e --- /dev/null +++ b/libraries/libstratosphere/source/socket/impl/socket_api.os.windows.cpp @@ -0,0 +1,741 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "socket_api.hpp" +#include "socket_allocator.hpp" + +#include + +#include + +namespace ams::socket::impl { + + extern PosixWinSockConverter g_posix_winsock_converter; + + namespace { + + constinit util::Atomic g_init_counter = 0; + + ALWAYS_INLINE bool IsInitialized() { + return g_init_counter > 0; + } + + class FcntlState { + private: + FcntlFlag m_flags[MaxSocketsPerClient]{}; + os::SdkRecursiveMutex m_mutexes[MaxSocketsPerClient]{}; + public: + constexpr FcntlState() = default; + public: + void ClearFlag(int fd, FcntlFlag flag) { + std::scoped_lock lk(m_mutexes[fd]); + + m_flags[fd] &= ~flag; + } + + void ClearFlags(int fd) { + std::scoped_lock lk(m_mutexes[fd]); + + m_flags[fd] = FcntlFlag::None; + } + + FcntlFlag GetFlags(int fd) { + std::scoped_lock lk(m_mutexes[fd]); + + return m_flags[fd]; + } + + int GetFlagsInt(int fd) { + return static_cast(this->GetFlags(fd)); + } + + os::SdkRecursiveMutex &GetSocketLock(int fd) { + return m_mutexes[fd]; + } + + bool IsFlagClear(int fd, FcntlFlag flag) { + return !this->IsFlagSet(fd, flag); + } + + bool IsFlagSet(int fd, FcntlFlag flag) { + std::scoped_lock lk(m_mutexes[fd]); + + return (m_flags[fd] & flag) != static_cast(0); + } + + bool IsSocketBlocking(int fd) { + return !this->IsSocketNonBlocking(fd); + } + + bool IsSocketNonBlocking(int fd) { + return this->IsFlagSet(fd, FcntlFlag::O_NonBlock); + } + + void SetFlag(int fd, FcntlFlag flag) { + std::scoped_lock lk(m_mutexes[fd]); + + m_flags[fd] |= flag; + } + }; + + constinit FcntlState g_fcntl_state; + + void TransmuteWsaError() { + switch (::WSAGetLastError()) { + case WSAEFAULT: ::WSASetLastError(WSAEINVAL); break; + case WSAENOTSOCK: ::WSASetLastError(WSAEBADF); break; + case WSAETIMEDOUT: ::WSASetLastError(WSAEWOULDBLOCK); break; + } + } + + template + void TransmuteWsaError(T res) { + if (static_cast(res) == SOCKET_ERROR) { + TransmuteWsaError(); + } + } + + } + + #define AMS_SOCKET_IMPL_SCOPED_MAKE_NON_BLOCKING(_cond, _fd) \ + /* If the socket is blocking and we need to make it non-blocking, do so. */ \ + int nonblock_##__LINE__ = 1; \ + bool set_nonblock_##__LINE__ = false; \ + if (_cond && g_fcntl_state.IsSocketBlocking(_fd)) { \ + if (const auto res = ::ioctlsocket(handle, FIONBIO, reinterpret_cast(std::addressof( nonblock_##__LINE__ ))); res == SOCKET_ERROR) { \ + TransmuteWsaError(); \ + return res; \ + } \ + \ + set_nonblock_##__LINE__ = true; \ + } \ + \ + ON_SCOPE_EXIT { \ + /* Preserve last error. */ \ + const auto last_err = socket::impl::GetLastError(); \ + ON_SCOPE_EXIT { socket::impl::SetLastError(last_err); }; \ + \ + /* Restore non-blocking state. */ \ + if (set_nonblock_##__LINE__) { \ + nonblock_##__LINE__ = 0; \ + \ + while (true) { \ + const auto restore_res = ::ioctlsocket(handle, FIONBIO, reinterpret_cast(std::addressof( nonblock_##__LINE__ ))); \ + TransmuteWsaError(restore_res); \ + if (!(restore_res == SOCKET_ERROR && socket::impl::GetLastError() == Errno::EInProgress)) { \ + break; \ + } \ + \ + os::SleepThread(TimeSpan::FromMilliSeconds(1)); \ + } \ + } \ + } + + #define AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(expr) ({ const auto res = (expr); TransmuteWsaError(res); res; }) + + + void *Alloc(size_t size) { + return ::std::malloc(size); + } + + void *Calloc(size_t num, size_t size) { + const size_t total_size = size * num; + void *buf = Alloc(size); + if (buf != nullptr) { + std::memset(buf, 0, total_size); + } + return buf; + } + + void Free(void *ptr) { + return ::std::free(ptr); + } + + Errno GetLastError() { + if (AMS_LIKELY(IsInitialized())) { + return MapErrnoValue(::WSAGetLastError()); + } else { + return Errno::EInval; + } + } + + void SetLastError(Errno err) { + if (AMS_LIKELY(IsInitialized())) { + ::WSASetLastError(MapErrnoValue(err)); + } + } + + u32 InetHtonl(u32 host) { + return ::htonl(host); + } + + u16 InetHtons(u16 host) { + return ::htons(host); + } + + u32 InetNtohl(u32 net) { + return ::ntohl(net); + } + + u16 InetNtohs(u16 net) { + return ::ntohs(net); + } + + Result Initialize(const Config &config) { + AMS_UNUSED(config); + + /* Increment init counter. */ + ++g_init_counter; + + /* Initialize winsock. */ + WSADATA wsa_data; + WORD wVersionRequested = MAKEWORD(2, 2); + + const auto res = ::WSAStartup(wVersionRequested, std::addressof(wsa_data)); + AMS_ABORT_UNLESS(res == 0); + + /* Initialize time services. */ + R_ABORT_UNLESS(time::Initialize()); + + R_SUCCEED(); + } + + Result Finalize() { + /* Check pre-conditions. */ + --g_init_counter; + AMS_ABORT_UNLESS(g_init_counter >= 0); + + /* Cleanup WSA. */ + ::WSACleanup(); + + /* Finalize time services. */ + time::Finalize(); + + /* Release all posix handles. */ + g_posix_winsock_converter.ReleaseAllPosixHandles(); + + R_SUCCEED(); + } + + ssize_t RecvFromInternal(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags, SockAddr *out_address, SockLenT *out_addr_len) { + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Convert the sockaddr. */ + sockaddr sa = {}; + socklen_t addr_len = sizeof(sa); + + /* Perform the call. */ + const auto res = ::recvfrom(handle, static_cast(buffer), static_cast(buffer_size), MapMsgFlagValue(flags), std::addressof(sa), std::addressof(addr_len)); + if (res == SOCKET_ERROR) { + if (::WSAGetLastError() == WSAESHUTDOWN) { + ::WSASetLastError(WSAENETDOWN); + } else { + TransmuteWsaError(); + } + } + + /* Set output. */ + if (out_address != nullptr && out_addr_len != nullptr) { + if (addr_len > static_cast(sizeof(*out_address))) { + addr_len = sizeof(*out_address); + } + + if (*out_addr_len != 0) { + if (static_cast(*out_addr_len) > addr_len) { + *out_addr_len = addr_len; + } + + SockAddr sa_pl = {}; + CopyFromPlatform(reinterpret_cast(std::addressof(sa_pl)), reinterpret_cast(std::addressof(sa))); + std::memcpy(out_address, std::addressof(sa_pl), *out_addr_len); + } + } + + return res; + } + + ssize_t RecvFrom(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags, SockAddr *out_address, SockLenT *out_addr_len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* If the flags have DontWait set, clear WaitAll. */ + if ((flags & MsgFlag::Msg_DontWait) == MsgFlag::Msg_DontWait) { + flags &= ~MsgFlag::Msg_WaitAll; + } + + /* If the flags haev WaitAll set but the socket is non-blocking, clear WaitAll. */ + if ((flags & MsgFlag::Msg_WaitAll) == MsgFlag::Msg_WaitAll && g_fcntl_state.IsSocketNonBlocking(desc)) { + flags &= ~MsgFlag::Msg_WaitAll; + } + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } else if (buffer_size == 0) { + return 0; + } else if (buffer == nullptr) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } else if (buffer_size > std::numeric_limits::max()) { + socket::impl::SetLastError(Errno::EFault); + return -1; + } + + /* Handle blocking vs non-blocking. */ + if ((flags & MsgFlag::Msg_DontWait) == MsgFlag::Msg_DontWait) { + return RecvFromInternal(desc, buffer, buffer_size, flags, out_address, out_addr_len); + } else { + /* Lock the socket. */ + std::scoped_lock lk(g_fcntl_state.GetSocketLock(desc)); + + /* Clear don't wait from the flags. */ + flags &= MsgFlag::Msg_DontWait; + + /* If the socket is blocking, we need to make it non-blocking. */ + AMS_SOCKET_IMPL_SCOPED_MAKE_NON_BLOCKING(true, desc); + + /* Do the recv from. */ + return RecvFromInternal(desc, buffer, buffer_size, flags, out_address, out_addr_len); + } + } + + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } else if (buffer_size == 0) { + return 0; + } else if (buffer == nullptr) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } else if (buffer_size > std::numeric_limits::max()) { + socket::impl::SetLastError(Errno::EFault); + return -1; + } + + /* If the socket is blocking, we need to make it non-blocking. */ + AMS_SOCKET_IMPL_SCOPED_MAKE_NON_BLOCKING(((flags & MsgFlag::Msg_DontWait) == MsgFlag::Msg_DontWait), desc); + + /* Perform the call. */ + return AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(::recv(handle, static_cast(buffer), static_cast(buffer_size), MapMsgFlagValue(flags & ~MsgFlag::Msg_DontWait))); + } + + ssize_t SendTo(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags, const SockAddr *address, SockLenT len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Clear don't wait from flags. */ + flags &= ~MsgFlag::Msg_DontWait; + + /* Convert the sockaddr. */ + sockaddr sa = {}; + socket::impl::CopyToPlatform(reinterpret_cast(std::addressof(sa)), reinterpret_cast(address)); + + /* Perform the call. */ + const auto res = ::sendto(handle, static_cast(buffer), static_cast(buffer_size), MapMsgFlagValue(flags), address != nullptr ? std::addressof(sa) : nullptr, static_cast(len)); + if (res == SOCKET_ERROR) { + if (::WSAGetLastError() == WSAESHUTDOWN) { + ::WSASetLastError(109); + } else { + TransmuteWsaError(); + } + } + + return res; + } + + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Perform the call. */ + return AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(::send(handle, static_cast(buffer), static_cast(buffer_size), MapMsgFlagValue(flags))); + } + + s32 Shutdown(s32 desc, ShutdownMethod how) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Perform the call. */ + const auto res = ::shutdown(handle, MapShutdownMethodValue(how)); + g_posix_winsock_converter.SetShutdown(desc, true); + TransmuteWsaError(res); + return res; + } + + s32 Socket(Family domain, Type type, Protocol protocol, bool exempt) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + const auto res = ::socket(MapFamilyValue(domain), MapTypeValue(type), MapProtocolValue(protocol)); + TransmuteWsaError(res); + + s32 posix_socket = -1; + if (res != static_cast::type>(SOCKET_ERROR)) { + if (posix_socket = g_posix_winsock_converter.AcquirePosixHandle(res, exempt); posix_socket < 0) { + /* Preserve last error. */ + const auto last_err = socket::impl::GetLastError(); + ON_SCOPE_EXIT { socket::impl::SetLastError(last_err); }; + + /* Close the socket. */ + ::closesocket(res); + } + } + + return posix_socket; + } + + s32 Socket(Family domain, Type type, Protocol protocol) { + return Socket(domain, type, protocol, false); + } + + s32 SocketExempt(Family domain, Type type, Protocol protocol) { + return Socket(domain, type, protocol, true); + } + + s32 Accept(s32 desc, SockAddr *out_address, SockLenT *out_addr_len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Check shutdown. */ + bool is_shutdown = false; + if (const auto res = g_posix_winsock_converter.GetShutdown(is_shutdown, desc); res == SOCKET_ERROR || (res == 0 && is_shutdown)) { + socket::impl::SetLastError(Errno::EConnAborted); + return -1; + } + + /* Accept. */ + sockaddr sa = {}; + socklen_t sa_len = sizeof(sa); + const auto res = ::accept(handle, std::addressof(sa), std::addressof(sa_len)); + if (res == static_cast::type>(SOCKET_ERROR)) { + if (::WSAGetLastError() == WSAEOPNOTSUPP) { + ::WSASetLastError(WSAEINVAL); + } else { + TransmuteWsaError(); + } + } + + /* Set output. */ + if (out_address != nullptr && out_addr_len != nullptr) { + if (sa_len > static_cast(sizeof(*out_address))) { + sa_len = sizeof(*out_address); + } + + if (*out_addr_len != 0) { + if (static_cast(*out_addr_len) > sa_len) { + *out_addr_len = sa_len; + } + + SockAddr sa_pl = {}; + CopyFromPlatform(reinterpret_cast(std::addressof(sa_pl)), reinterpret_cast(std::addressof(sa))); + std::memcpy(out_address, std::addressof(sa_pl), *out_addr_len); + } + + *out_addr_len = sa_len; + } + + if (res == static_cast::type>(SOCKET_ERROR)) { + return res; + } + + s32 fd = -1; + bool is_exempt = false; + if (g_posix_winsock_converter.GetSocketExempt(is_exempt, desc) == 0) { + fd = g_posix_winsock_converter.AcquirePosixHandle(res, is_exempt); + } + + if (fd < 0) { + /* Preserve last error. */ + const auto last_err = socket::impl::GetLastError(); + ON_SCOPE_EXIT { socket::impl::SetLastError(last_err); }; + + ::closesocket(res); + + return SOCKET_ERROR; + } + + return fd; + } + + s32 Bind(s32 desc, const SockAddr *address, SockLenT len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } else if (address == nullptr) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + + /* Convert the sockaddr. */ + sockaddr sa = {}; + socket::impl::CopyToPlatform(reinterpret_cast(std::addressof(sa)), reinterpret_cast(address)); + + return AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(::bind(handle, std::addressof(sa), static_cast(len))); + } + + s32 Connect(s32 desc, const SockAddr *address, SockLenT len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Convert the sockaddr. */ + sockaddr sa = {}; + if (address != nullptr) { + if (reinterpret_cast(address)->sin_port == 0) { + socket::impl::SetLastError(Errno::EAddrNotAvail); + return -1; + } + + socket::impl::CopyToPlatform(reinterpret_cast(std::addressof(sa)), reinterpret_cast(address)); + } + + const auto res = ::connect(handle, address != nullptr ? std::addressof(sa) : nullptr, len); + if (res == SOCKET_ERROR) { + const auto wsa_err = ::WSAGetLastError(); + if (wsa_err == WSAEWOULDBLOCK) { + ::WSASetLastError(WSAEINPROGRESS); + } else if (wsa_err != WSAETIMEDOUT) { + TransmuteWsaError(); + } + } + + return res; + } + + s32 GetSockName(s32 desc, SockAddr *out_address, SockLenT *out_addr_len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* We may end up preserving the last wsa error. */ + const auto last_err = ::WSAGetLastError(); + + /* Do the call. */ + sockaddr sa = {}; + + auto res = ::getsockname(handle, out_address != nullptr ? std::addressof(sa) : nullptr, reinterpret_cast(out_addr_len)); + if (res == SOCKET_ERROR) { + if (::WSAGetLastError() == WSAEINVAL) { + ::WSASetLastError(last_err); + + sa = {}; + res = 0; + } else { + TransmuteWsaError(); + } + } + + /* Copy out. */ + if (out_address != nullptr) { + CopyFromPlatform(reinterpret_cast(out_address), reinterpret_cast(std::addressof(sa))); + } + + return res; + } + + s32 SetSockOpt(s32 desc, Level level, Option option_name, const void *option_value, SockLenT option_size) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + union SocketOptionValue { + linger option_linger; + DWORD option_timeout_ms; + DWORD option_exempt; + }; + + SocketOptionValue sockopt_value = {}; + socklen_t option_value_length = option_size; + const char *p_option_value = nullptr; + + switch (option_name) { + case Option::So_Linger: + case Option::So_Nn_Linger: + { + if (option_value_length < static_cast(sizeof(sockopt_value.option_linger))) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + option_value_length = sizeof(sockopt_value.option_linger); + CopyToPlatform(std::addressof(sockopt_value.option_linger), reinterpret_cast(option_value)); + p_option_value = reinterpret_cast(std::addressof(sockopt_value.option_linger)); + } + break; + case Option::So_SndTimeo: + case Option::So_RcvTimeo: + { + if (option_value_length < static_cast(sizeof(sockopt_value.option_timeout_ms))) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + option_value_length = sizeof(sockopt_value.option_timeout_ms); + sockopt_value.option_timeout_ms = (reinterpret_cast(option_value)->tv_sec * 1000) + (reinterpret_cast(option_value)->tv_usec / 1000); + p_option_value = reinterpret_cast(std::addressof(sockopt_value.option_timeout_ms)); + } + break; + case Option::So_Nn_Shutdown_Exempt: + { + if (option_value_length < static_cast(sizeof(sockopt_value.option_exempt))) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + + return g_posix_winsock_converter.SetSocketExempt(desc, *reinterpret_cast(option_value) != 0); + } + break; + default: + p_option_value = reinterpret_cast(option_value); + break; + } + + return AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(::setsockopt(handle, MapLevelValue(level), MapOptionValue(level, option_name), p_option_value, option_value_length)); + } + + s32 Listen(s32 desc, s32 backlog) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Check shutdown. */ + bool is_shutdown = false; + if (const auto res = g_posix_winsock_converter.GetShutdown(is_shutdown, desc); res == SOCKET_ERROR || (res == 0 && is_shutdown)) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + + return AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(::listen(handle, backlog)); + } + + s32 Close(s32 desc) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Check that we can close. */ + static constinit os::SdkMutex s_close_lock; + SOCKET handle = static_cast(socket::InvalidSocket); + { + std::scoped_lock lk(s_close_lock); + + handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + if (handle == static_cast(socket::InvalidSocket)) { + return SOCKET_ERROR; + } + + g_posix_winsock_converter.ReleasePosixHandle(desc); + } + + /* Do the close. */ + const auto res = ::closesocket(handle); + g_fcntl_state.ClearFlags(desc); + + return res; + } + +} diff --git a/libraries/libstratosphere/source/socket/impl/socket_platform_types_translation.os.windows.cpp b/libraries/libstratosphere/source/socket/impl/socket_platform_types_translation.os.windows.cpp new file mode 100644 index 000000000..b03f8a58b --- /dev/null +++ b/libraries/libstratosphere/source/socket/impl/socket_platform_types_translation.os.windows.cpp @@ -0,0 +1,776 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "socket_api.hpp" + +#include + +#include + +namespace ams::socket::impl { + + ///* TODO: Custom sys/* headers, probably. */ + #define AF_LINK 18 + + #define MSG_TRUNC 0x10 + #define MSG_CTRUNC 0x20 + #define MSG_DONTWAIT 0x80 + + #define SHUT_RD 0 + #define SHUT_WR 1 + #define SHUT_RDWR 2 + + #define O_NONBLOCK 4 + + #define TCP_MAXSEG 4 + + PosixWinSockConverter g_posix_winsock_converter; + + s32 PosixWinSockConverter::AcquirePosixHandle(SOCKET winsock, bool exempt) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Get initial index. */ + const auto initial_index = GetInitialIndex(winsock); + + /* Try to find an open index. */ + for (auto posix = initial_index; posix < static_cast(MaxSocketsPerClient); ++posix) { + if (m_data[posix].winsock == static_cast(INVALID_SOCKET)) { + m_data[posix].winsock = winsock; + m_data[posix].exempt = exempt; + return posix; + } + } + + for (auto posix = 0; posix < initial_index; ++posix) { + if (m_data[posix].winsock == static_cast(INVALID_SOCKET)) { + m_data[posix].winsock = winsock; + m_data[posix].exempt = exempt; + return posix; + } + } + + /* We're out of open handles. */ + socket::impl::SetLastError(Errno::EMFile); + return SOCKET_ERROR; + } + + s32 PosixWinSockConverter::GetShutdown(bool &shutdown, s32 posix) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Check input. */ + if (static_cast(posix) >= MaxSocketsPerClient || m_data[posix].winsock == static_cast(INVALID_SOCKET)) { + socket::impl::SetLastError(Errno::EBadf); + return SOCKET_ERROR; + } + + /* Set the output. */ + shutdown = m_data[posix].shutdown; + return 0; + } + + s32 PosixWinSockConverter::GetSocketExempt(bool &exempt, s32 posix) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Check input. */ + if (static_cast(posix) >= MaxSocketsPerClient || m_data[posix].winsock == static_cast(INVALID_SOCKET)) { + socket::impl::SetLastError(Errno::EBadf); + return SOCKET_ERROR; + } + + /* Set the output. */ + exempt = m_data[posix].exempt; + return 0; + } + + SOCKET PosixWinSockConverter::PosixToWinsockSocket(s32 posix) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Check input. */ + if (static_cast(posix) >= MaxSocketsPerClient || m_data[posix].winsock == static_cast(INVALID_SOCKET)) { + socket::impl::SetLastError(Errno::EBadf); + return SOCKET_ERROR; + } + + return m_data[posix].winsock; + } + + void PosixWinSockConverter::ReleaseAllPosixHandles() { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + for (size_t i = 0; i < MaxSocketsPerClient; ++i) { + m_data[i] = SocketData{}; + } + } + + void PosixWinSockConverter::ReleasePosixHandle(s32 posix) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(static_cast(posix) < MaxSocketsPerClient); + + m_data[posix] = SocketData{}; + } + + s32 PosixWinSockConverter::SetShutdown(s32 posix, bool shutdown) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Check input. */ + if (static_cast(posix) >= MaxSocketsPerClient || m_data[posix].winsock == static_cast(INVALID_SOCKET)) { + socket::impl::SetLastError(Errno::EBadf); + return SOCKET_ERROR; + } + + /* Set the shutdown. */ + m_data[posix].shutdown = shutdown; + return 0; + } + + s32 PosixWinSockConverter::SetSocketExempt(s32 posix, bool exempt) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Check input. */ + if (static_cast(posix) >= MaxSocketsPerClient || m_data[posix].winsock == static_cast(INVALID_SOCKET)) { + socket::impl::SetLastError(Errno::EBadf); + return SOCKET_ERROR; + } + + /* Set the exempt. */ + m_data[posix].exempt = exempt; + return 0; + } + + s32 PosixWinSockConverter::WinsockToPosixSocket(SOCKET winsock) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Get initial index. */ + const auto initial_index = GetInitialIndex(winsock); + + /* Try to find an open index. */ + for (auto posix = initial_index; posix < static_cast(MaxSocketsPerClient); ++posix) { + if (m_data[posix].winsock == winsock) { + return posix; + } + } + + for (auto posix = 0; posix < initial_index; ++posix) { + if (m_data[posix].winsock == winsock) { + return posix; + } + } + + /* We failed to find the posix handle. */ + return -1; + } + + s32 MapProtocolValue(Protocol protocol) { + s32 mapped = -1; + + switch (protocol) { + case Protocol::IpProto_Ip: mapped = IPPROTO_IP; break; + case Protocol::IpProto_Icmp: mapped = IPPROTO_ICMP; break; + case Protocol::IpProto_Tcp: mapped = IPPROTO_TCP; break; + case Protocol::IpProto_Udp: mapped = IPPROTO_UDP; break; + case Protocol::IpProto_None: mapped = IPPROTO_NONE; break; + case Protocol::IpProto_UdpLite: mapped = IPPROTO_UDP; break; + case Protocol::IpProto_Raw: mapped = IPPROTO_RAW; break; + case Protocol::IpProto_Max: mapped = IPPROTO_MAX; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Protocol %d\n", static_cast(protocol)); + break; + } + + if (mapped == -1) { + AMS_SDK_LOG("WARNING: ams::Socket::Protocol %d is not supported by Win32/Win64.\n", static_cast(protocol)); + } + + return mapped; + } + + Protocol MapProtocolValue(s32 protocol) { + Protocol mapped = Protocol::IpProto_None; + + switch (protocol) { + case IPPROTO_IP: mapped = Protocol::IpProto_Ip; break; + case IPPROTO_ICMP: mapped = Protocol::IpProto_Icmp; break; + case IPPROTO_TCP: mapped = Protocol::IpProto_Tcp; break; + case IPPROTO_UDP: mapped = Protocol::IpProto_Udp; break; + case IPPROTO_NONE: mapped = Protocol::IpProto_None; break; + case IPPROTO_RAW: mapped = Protocol::IpProto_Raw; break; + case IPPROTO_MAX: mapped = Protocol::IpProto_Max; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket protocol %d\n", static_cast(protocol)); + break; + } + + return mapped; + } + + s32 MapTypeValue(Type type) { + s32 mapped = -1; + + switch (type) { + case Type::Sock_Default: mapped = 0; break; + case Type::Sock_Stream: mapped = SOCK_STREAM; break; + case Type::Sock_Dgram: mapped = SOCK_DGRAM; break; + case Type::Sock_Raw: mapped = SOCK_RAW; break; + case Type::Sock_SeqPacket: mapped = SOCK_SEQPACKET; break; + case Type::Sock_NonBlock: mapped = -1; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Type %d\n", static_cast(type)); + break; + } + + if (mapped == -1) { + AMS_SDK_LOG("WARNING: ams::Socket::Type %d is not supported by Win32/Win64.\n", static_cast(type)); + } + + return mapped; + } + + Type MapTypeValue(s32 type) { + Type mapped = Type::Sock_Default; + + switch (type) { + case 0: mapped = Type::Sock_Default; break; + case SOCK_STREAM: mapped = Type::Sock_Stream; break; + case SOCK_DGRAM: mapped = Type::Sock_Dgram; break; + case SOCK_RAW: mapped = Type::Sock_Raw; break; + case SOCK_SEQPACKET: mapped = Type::Sock_SeqPacket; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket type %d\n", static_cast(type)); + break; + } + + return mapped; + } + + s8 MapFamilyValue(Family family) { + s8 mapped = -1; + + switch (family) { + case Family::Af_Unspec: mapped = AF_UNSPEC; break; + case Family::Af_Inet: mapped = AF_INET; break; + case Family::Af_Route: mapped = -1; break; + case Family::Af_Link: mapped = AF_LINK; break; + case Family::Af_Inet6: mapped = AF_INET6; break; + case Family::Af_Max: mapped = AF_MAX; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Family %d\n", static_cast(family)); + break; + } + + if (mapped == -1) { + AMS_SDK_LOG("WARNING: ams::Socket::Family %d is not supported by Win32/Win64.\n", static_cast(family)); + } + + return mapped; + } + + Family MapFamilyValue(s8 family) { + Family mapped = Family::Af_Unspec; + + switch (family) { + case AF_UNSPEC:mapped = Family::Af_Unspec; break; + case AF_INET: mapped = Family::Af_Inet; break; + case AF_LINK: mapped = Family::Af_Link; break; + case AF_INET6: mapped = Family::Af_Inet6; break; + case AF_MAX: mapped = Family::Af_Max; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket family %d\n", static_cast(family)); + break; + } + + return mapped; + } + + s32 MapMsgFlagValue(MsgFlag flag) { + s32 mapped = 0; + + if ((flag & MsgFlag::Msg_Oob) != MsgFlag::Msg_None) { mapped |= MSG_OOB; } + if ((flag & MsgFlag::Msg_Peek) != MsgFlag::Msg_None) { mapped |= MSG_PEEK; } + if ((flag & MsgFlag::Msg_DontRoute) != MsgFlag::Msg_None) { mapped |= MSG_DONTROUTE; } + if ((flag & MsgFlag::Msg_Trunc) != MsgFlag::Msg_None) { mapped |= MSG_TRUNC; } + if ((flag & MsgFlag::Msg_CTrunc) != MsgFlag::Msg_None) { mapped |= MSG_CTRUNC; } + if ((flag & MsgFlag::Msg_WaitAll) != MsgFlag::Msg_None) { mapped |= MSG_WAITALL; } + if ((flag & MsgFlag::Msg_DontWait) != MsgFlag::Msg_None) { mapped |= MSG_DONTWAIT; } + + return mapped; + } + + MsgFlag MapMsgFlagValue(s32 flag) { + MsgFlag mapped = MsgFlag::Msg_None; + + if (flag & MSG_OOB) { mapped |= MsgFlag::Msg_Oob; } + if (flag & MSG_PEEK) { mapped |= MsgFlag::Msg_Peek; } + if (flag & MSG_DONTROUTE) { mapped |= MsgFlag::Msg_DontRoute; } + if (flag & MSG_TRUNC) { mapped |= MsgFlag::Msg_Trunc; } + if (flag & MSG_CTRUNC) { mapped |= MsgFlag::Msg_CTrunc; } + if (flag & MSG_WAITALL) { mapped |= MsgFlag::Msg_WaitAll; } + if (flag & MSG_DONTWAIT) { mapped |= MsgFlag::Msg_DontWait; } + + return mapped; + } + + u32 MapAddrInfoFlagValue(AddrInfoFlag flag) { + u32 mapped = 0; + + if ((flag & AddrInfoFlag::Ai_Passive) != AddrInfoFlag::Ai_None) { mapped |= AI_PASSIVE; } + if ((flag & AddrInfoFlag::Ai_CanonName) != AddrInfoFlag::Ai_None) { mapped |= AI_CANONNAME; } + if ((flag & AddrInfoFlag::Ai_NumericHost) != AddrInfoFlag::Ai_None) { mapped |= AI_NUMERICHOST; } + if ((flag & AddrInfoFlag::Ai_NumericServ) != AddrInfoFlag::Ai_None) { mapped |= AI_NUMERICSERV; } + if ((flag & AddrInfoFlag::Ai_AddrConfig) != AddrInfoFlag::Ai_None) { mapped |= AI_ADDRCONFIG; } + + return mapped; + } + + AddrInfoFlag MapAddrInfoFlagValue(u32 flag) { + AddrInfoFlag mapped = AddrInfoFlag::Ai_None; + + if (flag & AI_PASSIVE) { mapped |= AddrInfoFlag::Ai_Passive; } + if (flag & AI_CANONNAME) { mapped |= AddrInfoFlag::Ai_CanonName; } + if (flag & AI_NUMERICHOST) { mapped |= AddrInfoFlag::Ai_NumericHost; } + if (flag & AI_NUMERICSERV) { mapped |= AddrInfoFlag::Ai_NumericServ; } + if (flag & AI_ADDRCONFIG) { mapped |= AddrInfoFlag::Ai_AddrConfig; } + + return mapped; + } + + u32 MapShutdownMethodValue(ShutdownMethod how) { + u32 mapped = -1; + + switch (how) { + case ShutdownMethod::Shut_Rd: mapped = SHUT_RD; break; + case ShutdownMethod::Shut_Wr: mapped = SHUT_WR; break; + case ShutdownMethod::Shut_RdWr: mapped = SHUT_RDWR; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::ShutdownMethod %d\n", static_cast(how)); + break; + } + + return mapped; + } + + ShutdownMethod MapShutdownMethodValue(u32 how) { + ShutdownMethod mapped = static_cast(-1); + + switch (how) { + case SHUT_RD: mapped = ShutdownMethod::Shut_Rd; break; + case SHUT_WR: mapped = ShutdownMethod::Shut_Wr; break; + case SHUT_RDWR: mapped = ShutdownMethod::Shut_RdWr; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket shutdown %d\n", static_cast(how)); + break; + } + + return mapped; + } + + u32 MapFcntlFlagValue(FcntlFlag flag) { + u32 mapped = 0; + + switch (flag) { + case FcntlFlag::O_NonBlock: mapped = O_NONBLOCK; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::FcntlFlag %d\n", static_cast(flag)); + break; + } + + return mapped; + } + + FcntlFlag MapFcntlFlagValue(u32 flag) { + FcntlFlag mapped = FcntlFlag::None; + + switch (flag) { + case O_NONBLOCK: mapped = FcntlFlag::O_NonBlock; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket fcntl flag %d\n", static_cast(flag)); + break; + } + + return mapped; + } + + s32 MapLevelValue(Level level) { + s32 mapped = -1; + + switch (level) { + case Level::Sol_Socket: mapped = SOL_SOCKET; break; + case Level::Sol_Ip: mapped = IPPROTO_IP; break; + case Level::Sol_Icmp: mapped = IPPROTO_ICMP; break; + case Level::Sol_Tcp: mapped = IPPROTO_TCP; break; + case Level::Sol_Udp: mapped = IPPROTO_UDP; break; + case Level::Sol_UdpLite: mapped = IPPROTO_UDP; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Level %d\n", static_cast(level)); + break; + } + + return mapped; + } + + Level MapLevelValue(s32 level) { + Level mapped = static_cast(0); + + switch (level) { + case SOL_SOCKET: mapped = Level::Sol_Socket; break; + case IPPROTO_IP: mapped = Level::Sol_Ip; break; + case IPPROTO_ICMP: mapped = Level::Sol_Icmp; break; + case IPPROTO_TCP: mapped = Level::Sol_Tcp; break; + case IPPROTO_UDP: mapped = Level::Sol_Udp; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket level %d\n", static_cast(level)); + break; + } + + return mapped; + } + + s32 MapOptionValue(Level level, Option option) { + s32 mapped = -1; + + switch (level) { + case Level::Sol_Socket: + switch (option) { + case Option::So_Debug: mapped = SO_DEBUG; break; + case Option::So_AcceptConn: mapped = SO_ACCEPTCONN; break; + case Option::So_ReuseAddr: mapped = SO_REUSEADDR; break; + case Option::So_KeepAlive: mapped = SO_KEEPALIVE; break; + case Option::So_DontRoute: mapped = SO_DONTROUTE; break; + case Option::So_Broadcast: mapped = SO_BROADCAST; break; + case Option::So_UseLoopback: mapped = SO_USELOOPBACK; break; + case Option::So_Linger: mapped = SO_LINGER; break; + case Option::So_OobInline: mapped = SO_OOBINLINE; break; + case Option::So_ReusePort: mapped = -1; break; + case Option::So_SndBuf: mapped = SO_SNDBUF; break; + case Option::So_RcvBuf: mapped = SO_RCVBUF; break; + case Option::So_SndLoWat: mapped = SO_SNDLOWAT; break; + case Option::So_RcvLoWat: mapped = SO_RCVLOWAT; break; + case Option::So_SndTimeo: mapped = SO_SNDTIMEO; break; + case Option::So_RcvTimeo: mapped = SO_RCVTIMEO; break; + case Option::So_Error: mapped = SO_ERROR; break; + case Option::So_Type: mapped = SO_TYPE; break; + case Option::So_Label: mapped = -1; break; + case Option::So_PeerLabel: mapped = -1; break; + case Option::So_ListenQLimit: mapped = -1; break; + case Option::So_ListenQLen: mapped = -1; break; + case Option::So_ListenIncQLen: mapped = -1; break; + case Option::So_SetFib: mapped = -1; break; + case Option::So_User_Cookie: mapped = -1; break; + case Option::So_Protocol: mapped = -1; break; + case Option::So_Vendor: mapped = -1; break; + case Option::So_Nn_Linger: mapped = -1; break; + case Option::So_Nn_Shutdown_Exempt: mapped = -1; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Option %d for Level::Sol_Socket\n", static_cast(option)); + break; + } + break; + case Level::Sol_Ip: + switch (option) { + case Option::Ip_Options: mapped = IP_OPTIONS; break; + case Option::Ip_HdrIncl: mapped = IP_HDRINCL; break; + case Option::Ip_Tos: mapped = IP_TOS; break; + case Option::Ip_Ttl: mapped = IP_TTL; break; + case Option::Ip_RecvOpts: mapped = -1; break; + case Option::Ip_Multicast_If: mapped = IP_MULTICAST_IF; break; + case Option::Ip_Multicast_Ttl: mapped = IP_MULTICAST_TTL; break; + case Option::Ip_Multicast_Loop: mapped = IP_MULTICAST_LOOP; break; + case Option::Ip_Add_Membership: mapped = IP_ADD_MEMBERSHIP; break; + case Option::Ip_Drop_Membership: mapped = IP_DROP_MEMBERSHIP; break; + case Option::Ip_Multicast_Vif: mapped = -1; break; + case Option::Ip_Rsvp_On: mapped = -1; break; + case Option::Ip_Rsvp_Off: mapped = -1; break; + case Option::Ip_Rsvp_Vif_On: mapped = -1; break; + case Option::Ip_Rsvp_Vif_Off: mapped = -1; break; + case Option::Ip_PortRange: mapped = -1; break; + case Option::Ip_Faith: mapped = -1; break; + case Option::Ip_OnesBcast: mapped = -1; break; + case Option::Ip_BindAny: mapped = -1; break; + case Option::Ip_RecvTtl: mapped = -1; break; + case Option::Ip_MinTtl: mapped = -1; break; + case Option::Ip_DontFrag: mapped = -1; break; + case Option::Ip_RecvTos: mapped = -1; break; + case Option::Ip_Add_Source_Membership: mapped = IP_ADD_SOURCE_MEMBERSHIP; break; + case Option::Ip_Drop_Source_Membership: mapped = IP_DROP_SOURCE_MEMBERSHIP; break; + case Option::Ip_Block_Source: mapped = IP_BLOCK_SOURCE; break; + case Option::Ip_Unblock_Source: mapped = IP_UNBLOCK_SOURCE; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Option %d for Level::Sol_Ip\n", static_cast(option)); + break; + } + break; + case Level::Sol_Tcp: + switch (option) { + case Option::Tcp_NoDelay: mapped = TCP_NODELAY; break; + case Option::Tcp_MaxSeg: mapped = TCP_MAXSEG; break; + case Option::Tcp_NoPush: mapped = -1; break; + case Option::Tcp_NoOpt: mapped = -1; break; + case Option::Tcp_Md5Sig: mapped = -1; break; + case Option::Tcp_Info: mapped = -1; break; + case Option::Tcp_Congestion: mapped = -1; break; + case Option::Tcp_KeepInit: mapped = -1; break; + case Option::Tcp_KeepIdle: mapped = -1; break; + case Option::Tcp_KeepIntvl: mapped = -1; break; + case Option::Tcp_KeepCnt: mapped = -1; break; + case Option::Tcp_Vendor: mapped = -1; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Option %d for Level::Sol_Tcp\n", static_cast(option)); + break; + } + break; + default: + AMS_SDK_LOG("WARNING: Invalid option level %d\n", static_cast(level)); + break; + } + + if (mapped == -1) { + AMS_SDK_LOG("WARNING: ams::Socket::Option %d is not supported by Win32/Win64.\n", static_cast(option)); + } + + return mapped; + } + + Option MapOptionValue(s32 level, s32 option) { + Option mapped = static_cast