mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-10 12:32:20 +02:00
529 lines
No EOL
11 KiB
Beef
529 lines
No EOL
11 KiB
Beef
using System;
|
|
using System.Diagnostics;
|
|
|
|
namespace System.Net
|
|
{
|
|
class Socket
|
|
{
|
|
const uint16 WINSOCK_VERSION = 0x0202;
|
|
const int32 WSAENETRESET = 10052;
|
|
const int32 WSAECONNABORTED = 10053;
|
|
const int32 WSAECONNRESET = 10054;
|
|
|
|
#if BF_PLATFORM_WINDOWS
|
|
public struct HSocket : uint
|
|
{
|
|
}
|
|
|
|
[CRepr]
|
|
public struct FDSet
|
|
{
|
|
const int cMaxCount = 64;
|
|
|
|
int32 mCount;
|
|
HSocket[64] mSockets;
|
|
|
|
public bool Add(HSocket s) mut
|
|
{
|
|
if (mCount >= cMaxCount)
|
|
return false;
|
|
mSockets[mCount++] = s;
|
|
return true;
|
|
}
|
|
|
|
public bool IsSet(HSocket s)
|
|
{
|
|
for (int i < mCount)
|
|
if (s == mSockets[i])
|
|
return true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
[CRepr]
|
|
public struct TimeVal
|
|
{
|
|
public int32 mSec;
|
|
public int32 mUSec;
|
|
}
|
|
|
|
#else
|
|
public struct HSocket : uint32
|
|
{
|
|
}
|
|
|
|
[CRepr]
|
|
public struct FDSet
|
|
{
|
|
const uint BITS_PER_MASK = sizeof(uint) * 8;
|
|
const uint MASK_COUNT = 4096 / sizeof(uint);
|
|
const uint MAX_ALLOWED_FD = MASK_COUNT * BITS_PER_MASK;
|
|
|
|
uint[MASK_COUNT] mSocketBitMasks;
|
|
uint32 afterLastBit;
|
|
|
|
public bool Add(HSocket s) mut
|
|
{
|
|
let fd = (uint32)s;
|
|
|
|
if (fd > MAX_ALLOWED_FD)
|
|
return false;
|
|
|
|
if (fd >= afterLastBit)
|
|
afterLastBit = fd + 1;
|
|
|
|
mSocketBitMasks[fd / BITS_PER_MASK] |= 1U << (fd & (BITS_PER_MASK - 1));
|
|
return true;
|
|
}
|
|
|
|
public bool IsSet(HSocket s)
|
|
{
|
|
let fd = (uint32)s;
|
|
if (fd > MAX_ALLOWED_FD)
|
|
return false;
|
|
return (mSocketBitMasks[fd / BITS_PER_MASK] & (1U << (fd & (BITS_PER_MASK - 1)))) != 0;
|
|
}
|
|
}
|
|
|
|
[CRepr]
|
|
public struct TimeVal
|
|
{
|
|
public int64 mSec;
|
|
public int32 mUSec;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if BF_PLATFORM_WINDOWS
|
|
[CRepr]
|
|
struct WSAData
|
|
{
|
|
public uint16 wVersion;
|
|
public uint16 wHighVersion;
|
|
#if BF_64_BIT
|
|
public uint16 iMaxSockets;
|
|
public uint16 iMaxUdpDg;
|
|
public char8* lpVendorInfo;
|
|
public char8[256+1] szDescription;
|
|
public char8[128+1] szSystemStatus;
|
|
#else
|
|
char8[256+1] szDescription;
|
|
char8[128+1] szSystemStatus;
|
|
uint16 iMaxSockets;
|
|
uint16 iMaxUdpDg;
|
|
char8* lpVendorInfo;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
[CRepr]
|
|
public struct IPv4Address
|
|
{
|
|
public uint8 b1;
|
|
public uint8 b2;
|
|
public uint8 b3;
|
|
public uint8 b4;
|
|
|
|
public this(uint8 b1, uint8 b2, uint8 b3, uint8 b4)
|
|
{
|
|
this.b1 = b1;
|
|
this.b2 = b2;
|
|
this.b3 = b3;
|
|
this.b4 = b4;
|
|
}
|
|
}
|
|
|
|
[CRepr]
|
|
struct SockAddr
|
|
{
|
|
|
|
}
|
|
|
|
[CRepr]
|
|
struct SockAddr_in : SockAddr
|
|
{
|
|
public int16 sin_family;
|
|
public uint16 sin_port;
|
|
public IPv4Address sin_addr;
|
|
public char8[8] sin_zero;
|
|
}
|
|
|
|
[CRepr]
|
|
public struct HostEnt
|
|
{
|
|
public char8* h_name; /* official name of host */
|
|
public char8** h_aliases; /* alias list */
|
|
public int16 h_addrtype; /* host address type */
|
|
public int16 h_length; /* length of address */
|
|
public char8** h_addr_list; /* list of addresses */
|
|
}
|
|
|
|
const HSocket INVALID_SOCKET = (HSocket)-1;
|
|
const int32 SOCKET_ERROR = -1;
|
|
const int AF_INET = 2;
|
|
const int SOCK_STREAM = 1;
|
|
const int IPPROTO_TCP = 6;
|
|
|
|
#if BF_PLATFORM_WINDOWS
|
|
const int FIONBIO = (int)0x8004667e;
|
|
#else
|
|
const int FIONBIO = (int)0x00005421;
|
|
#endif
|
|
|
|
HSocket mHandle = INVALID_SOCKET;
|
|
bool mIsConnected;
|
|
bool mIsBlocking = false;
|
|
|
|
public bool IsOpen
|
|
{
|
|
get
|
|
{
|
|
return mHandle != INVALID_SOCKET;
|
|
}
|
|
}
|
|
|
|
public bool IsConnected
|
|
{
|
|
get
|
|
{
|
|
return mIsConnected;
|
|
}
|
|
}
|
|
|
|
public HSocket NativeSocket
|
|
{
|
|
get
|
|
{
|
|
return mHandle;
|
|
}
|
|
}
|
|
|
|
public bool Blocking
|
|
{
|
|
get => mIsBlocking;
|
|
set
|
|
{
|
|
mIsBlocking = value;
|
|
if (mHandle != INVALID_SOCKET)
|
|
SetBlocking(mIsBlocking);
|
|
}
|
|
}
|
|
|
|
#if BF_PLATFORM_WINDOWS
|
|
[Import("wsock32.lib"), CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 WSAStartup(uint16 versionRequired, WSAData* wsaData);
|
|
|
|
[Import("wsock32.lib"), CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 WSACleanup();
|
|
|
|
[Import("wsock32.lib"), CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 WSAGetLastError();
|
|
#elif BF_PLATFORM_LINUX
|
|
[LinkName("__errno_location")]
|
|
static extern int32* _errno();
|
|
#elif BF_PLATFORM_MACOS
|
|
[LinkName("__error")]
|
|
static extern int32* _errno();
|
|
#else
|
|
[CLink]
|
|
static int32 errno;
|
|
static int32* _errno() => &errno;
|
|
#endif
|
|
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern HostEnt* gethostbyname(char8* name);
|
|
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern HSocket socket(int32 af, int32 type, int32 protocol);
|
|
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 connect(HSocket s, SockAddr* name, int32 nameLen);
|
|
|
|
#if BF_PLATFORM_WINDOWS
|
|
[Import("wsock32.lib"), CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 closesocket(HSocket s);
|
|
#else
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 close(HSocket s);
|
|
#endif
|
|
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 bind(HSocket s, SockAddr* name, int32 nameLen);
|
|
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 listen(HSocket s, int32 backlog);
|
|
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern HSocket accept(HSocket s, SockAddr* addr, int32* addrLen);
|
|
|
|
#if BF_PLATFORM_WINDOWS
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 ioctlsocket(HSocket s, int cmd, int* argp);
|
|
#else
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 ioctl(HSocket s, int cmd, int* argp);
|
|
#endif
|
|
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 select(int32 nfds, FDSet* readFDS, FDSet* writeFDS, FDSet* exceptFDS, TimeVal* timeVal);
|
|
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 recv(HSocket s, void* ptr, int32 len, int32 flags);
|
|
|
|
[CLink, CallingConvention(.Stdcall)]
|
|
static extern int32 send(HSocket s, void* ptr, int32 len, int32 flags);
|
|
|
|
public ~this()
|
|
{
|
|
if (mHandle != INVALID_SOCKET)
|
|
#if BF_PLATFORM_WINDOWS
|
|
closesocket(mHandle);
|
|
#else
|
|
close(mHandle);
|
|
#endif
|
|
}
|
|
|
|
public static int32 Init(uint16 versionRequired = WINSOCK_VERSION)
|
|
{
|
|
#if BF_PLATFORM_WINDOWS
|
|
WSAData wsaData = default;
|
|
return WSAStartup(versionRequired, &wsaData);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
public static int32 Uninit()
|
|
{
|
|
#if BF_PLATFORM_WINDOWS
|
|
return WSACleanup();
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int32 GetLastError()
|
|
{
|
|
#if BF_PLATFORM_WINDOWS
|
|
return WSAGetLastError();
|
|
#else
|
|
return *_errno();
|
|
#endif
|
|
}
|
|
|
|
int32 htons(int32 val)
|
|
{
|
|
return ((val & 0x000000FF) << 24) | ((val & 0x0000FF00) << 8) |
|
|
((val & 0x00FF0000) >> 8) | ((val & (int32)0xFF000000) >> 24);
|
|
}
|
|
|
|
int16 htons(int16 val)
|
|
{
|
|
return (int16)(((val & 0x00FF) << 8) |
|
|
((val & 0xFF00) >> 8));
|
|
}
|
|
|
|
void SetBlocking(bool blocking)
|
|
{
|
|
int param = blocking ? 0 : 1;
|
|
|
|
#if BF_PLATFORM_WINDOWS
|
|
ioctlsocket(mHandle, FIONBIO, ¶m);
|
|
#else
|
|
ioctl(mHandle, FIONBIO, ¶m);
|
|
#endif
|
|
}
|
|
|
|
void RehupSettings()
|
|
{
|
|
SetBlocking(mIsBlocking);
|
|
}
|
|
|
|
public Result<void> Listen(IPv4Address address, int32 port, int32 backlog = 5)
|
|
{
|
|
Debug.Assert(mHandle == INVALID_SOCKET);
|
|
|
|
mHandle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
|
|
if (mHandle == INVALID_SOCKET)
|
|
{
|
|
#unwarn
|
|
int32 err = GetLastError();
|
|
return .Err;
|
|
}
|
|
|
|
RehupSettings();
|
|
|
|
SockAddr_in service;
|
|
service.sin_family = AF_INET;
|
|
service.sin_addr = address;
|
|
service.sin_port = (uint16)htons((int16)port);
|
|
|
|
if (bind(mHandle, &service, sizeof(SockAddr_in)) == SOCKET_ERROR)
|
|
{
|
|
Close();
|
|
return .Err;
|
|
}
|
|
|
|
if (listen(mHandle, backlog) == SOCKET_ERROR)
|
|
{
|
|
#unwarn
|
|
int err = GetLastError();
|
|
Close();
|
|
return .Err;
|
|
}
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
public Result<void> Connect(StringView addr, int32 port)
|
|
{
|
|
mHandle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (mHandle == INVALID_SOCKET)
|
|
return .Err;
|
|
|
|
var hostEnt = gethostbyname(scope String(addr));
|
|
if (hostEnt == null)
|
|
return .Err;
|
|
|
|
SockAddr_in sockAddr;
|
|
sockAddr.sin_family = AF_INET;
|
|
Internal.MemCpy(&sockAddr.sin_addr, hostEnt.h_addr_list[0], sizeof(IPv4Address));
|
|
sockAddr.sin_port = (uint16)htons((int16)port);
|
|
|
|
if (connect(mHandle, &sockAddr, sizeof(SockAddr_in)) == SOCKET_ERROR)
|
|
return .Err;
|
|
|
|
if (mHandle == INVALID_SOCKET)
|
|
{
|
|
#unwarn
|
|
int32 err = GetLastError();
|
|
return .Err;
|
|
}
|
|
|
|
mIsConnected = true;
|
|
RehupSettings();
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
public Result<void> AcceptFrom(Socket listenSocket)
|
|
{
|
|
SockAddr_in clientAddr;
|
|
int32 clientAddrLen = sizeof(SockAddr_in);
|
|
mHandle = accept(listenSocket.mHandle, &clientAddr, &clientAddrLen);
|
|
if (mHandle == INVALID_SOCKET)
|
|
{
|
|
#unwarn
|
|
int lastErr = GetLastError();
|
|
return .Err;
|
|
}
|
|
|
|
RehupSettings();
|
|
mIsConnected = true;
|
|
return .Ok;
|
|
}
|
|
|
|
public static int32 Select(FDSet* readFDS, FDSet* writeFDS, FDSet* exceptFDS, int waitTimeMS)
|
|
{
|
|
TimeVal timeVal;
|
|
timeVal.mSec = (.)(waitTimeMS / 1000);
|
|
timeVal.mUSec = (.)((waitTimeMS % 1000) * 1000);
|
|
|
|
return Select(readFDS, writeFDS, exceptFDS, &timeVal);
|
|
}
|
|
|
|
public static int32 Select(FDSet* readFDS, FDSet* writeFDS, FDSet* exceptFDS, float waitTimeMS)
|
|
{
|
|
int waitTimeUS = (int)(waitTimeMS * 1000);
|
|
TimeVal timeVal;
|
|
timeVal.mSec = (.)(waitTimeUS / (1000*1000));
|
|
timeVal.mUSec = (.)(waitTimeUS % (1000*1000));
|
|
|
|
return Select(readFDS, writeFDS, exceptFDS, &timeVal);
|
|
}
|
|
|
|
private static int32 Select(FDSet* readFDS, FDSet* writeFDS, FDSet* exceptFDS, TimeVal* timeVal)
|
|
{
|
|
#if BF_PLATFORM_WINDOWS
|
|
const int32 nfds = 0; // Ignored
|
|
#else
|
|
int32 nfds = 0;
|
|
if (readFDS != null)
|
|
nfds = (.)readFDS.[Friend]afterLastBit;
|
|
if (writeFDS != null)
|
|
nfds = Math.Max(nfds, (.)writeFDS.[Friend]afterLastBit);
|
|
if (exceptFDS != null)
|
|
nfds = Math.Max(nfds, (.)exceptFDS.[Friend]afterLastBit);
|
|
#endif
|
|
return select(nfds, readFDS, writeFDS, exceptFDS, timeVal);
|
|
}
|
|
|
|
public int32 DbgRecv(void* ptr, int32 size)
|
|
{
|
|
int32 result = recv(mHandle, ptr, size, 0);
|
|
|
|
String str = scope String("\"");
|
|
for (int i = 0; i < result; i++)
|
|
{
|
|
str.AppendF(@"\x{0:X}", ((uint8*)ptr)[i]);
|
|
}
|
|
str.Append("\"");
|
|
Debug.WriteLine(str);
|
|
|
|
return result;
|
|
}
|
|
|
|
void CheckDisconnected()
|
|
{
|
|
int32 lastErr = GetLastError();
|
|
switch (lastErr)
|
|
{
|
|
case WSAENETRESET,
|
|
WSAECONNABORTED,
|
|
WSAECONNRESET:
|
|
mIsConnected = false;
|
|
}
|
|
}
|
|
|
|
public Result<int> Recv(void* ptr, int size)
|
|
{
|
|
int32 result = recv(mHandle, ptr, (int32)size, 0);
|
|
if (result == 0)
|
|
{
|
|
mIsConnected = false;
|
|
return .Err;
|
|
}
|
|
if (result == -1)
|
|
{
|
|
CheckDisconnected();
|
|
if (!mIsConnected)
|
|
return .Err;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public Result<int> Send(void* ptr, int size)
|
|
{
|
|
int32 result = send(mHandle, ptr, (int32)size, 0);
|
|
if (result < 0)
|
|
{
|
|
CheckDisconnected();
|
|
if (!mIsConnected)
|
|
return .Err;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
mIsConnected = false;
|
|
#if BF_PLATFORM_WINDOWS
|
|
closesocket(mHandle);
|
|
#else
|
|
close(mHandle);
|
|
#endif
|
|
mHandle = INVALID_SOCKET;
|
|
}
|
|
}
|
|
} |