Compare commits
2 commits
main
...
smerotta/w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9491304b01 | ||
|
|
7362062556 |
5 changed files with 490 additions and 14 deletions
|
|
@ -19,6 +19,7 @@ using Server.HuePickers;
|
||||||
using Server.Items;
|
using Server.Items;
|
||||||
using Server.Logging;
|
using Server.Logging;
|
||||||
using Server.Menus;
|
using Server.Menus;
|
||||||
|
using Server.Network.Sockets;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
@ -29,6 +30,9 @@ using System.Net.Sockets;
|
||||||
using System.Network;
|
using System.Network;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Text;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace Server.Network;
|
namespace Server.Network;
|
||||||
|
|
||||||
|
|
@ -41,7 +45,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
{
|
{
|
||||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(NetState));
|
private static readonly ILogger logger = LogFactory.GetLogger(typeof(NetState));
|
||||||
|
|
||||||
private static readonly TimeSpan ConnectingSocketIdleLimit = TimeSpan.FromMilliseconds(5000); // 5 seconds
|
private static readonly TimeSpan ConnectingSocketIdleLimit = TimeSpan.FromMilliseconds(50000); // 5 seconds
|
||||||
private const int RecvPipeSize = 1024 * 64;
|
private const int RecvPipeSize = 1024 * 64;
|
||||||
private const int SendPipeSize = 1024 * 256;
|
private const int SendPipeSize = 1024 * 256;
|
||||||
private const int HuePickerCap = 512;
|
private const int HuePickerCap = 512;
|
||||||
|
|
@ -121,7 +125,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
|
|
||||||
public NetState(Socket connection)
|
public NetState(Socket connection)
|
||||||
{
|
{
|
||||||
Connection = connection;
|
Connection = new TcpSocket(connection);
|
||||||
Seeded = false;
|
Seeded = false;
|
||||||
HuePickers = [];
|
HuePickers = [];
|
||||||
Menus = [];
|
Menus = [];
|
||||||
|
|
@ -147,7 +151,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_pollGroup.Add(connection, _handle);
|
_pollGroup.Add(Connection.Socket, _handle);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -216,7 +220,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
|
|
||||||
public bool Running => _running;
|
public bool Running => _running;
|
||||||
|
|
||||||
public Socket Connection { get; private set; }
|
public ISocket Connection { get; private set; }
|
||||||
|
|
||||||
public bool CompressionEnabled { get; set; }
|
public bool CompressionEnabled { get; set; }
|
||||||
|
|
||||||
|
|
@ -577,6 +581,11 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
_protocolState = ProtocolState.LoginServer_AwaitingLogin;
|
_protocolState = ProtocolState.LoginServer_AwaitingLogin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (length > 3 && packetId == 0x47)
|
||||||
|
{
|
||||||
|
_parserState = ParserState.ProcessingPacket;
|
||||||
|
_parserState = HandleWebSocket(buffer) ? ParserState.AwaitingNextPacket : ParserState.Error;
|
||||||
|
}
|
||||||
else if (length >= 4)
|
else if (length >= 4)
|
||||||
{
|
{
|
||||||
int newSeed = (packetId << 24) | (packetReader.ReadByte() << 16) | (packetReader.ReadByte() << 8) | packetReader.ReadByte();
|
int newSeed = (packetId << 24) | (packetReader.ReadByte() << 16) | (packetReader.ReadByte() << 8) | packetReader.ReadByte();
|
||||||
|
|
@ -651,7 +660,12 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
|
|
||||||
case ProtocolState.GameServer_AwaitingGameServerLogin:
|
case ProtocolState.GameServer_AwaitingGameServerLogin:
|
||||||
{
|
{
|
||||||
if (packetId != 0x91 && packetId != 0x80)
|
if (length > 3 && packetId == 0x47)
|
||||||
|
{
|
||||||
|
_parserState = ParserState.ProcessingPacket;
|
||||||
|
_parserState = HandleWebSocket(buffer) ? ParserState.AwaitingNextPacket : ParserState.Error;
|
||||||
|
}
|
||||||
|
else if (packetId != 0x91 && packetId != 0x80)
|
||||||
{
|
{
|
||||||
HandleError(packetId, packetLength);
|
HandleError(packetId, packetLength);
|
||||||
return;
|
return;
|
||||||
|
|
@ -832,7 +846,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bytesWritten = Connection.Send(buffer, SocketFlags.None);
|
bytesWritten = Connection.Send(buffer);
|
||||||
}
|
}
|
||||||
catch (SocketException ex)
|
catch (SocketException ex)
|
||||||
{
|
{
|
||||||
|
|
@ -875,10 +889,11 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytesWritten = 0;
|
var bytesWritten = 0;
|
||||||
|
var forceClose = false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bytesWritten = Connection.Receive(buffer, SocketFlags.None);
|
bytesWritten = Connection.Receive(buffer, out forceClose);
|
||||||
}
|
}
|
||||||
catch (SocketException ex)
|
catch (SocketException ex)
|
||||||
{
|
{
|
||||||
|
|
@ -895,15 +910,18 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
TraceException(ex);
|
TraceException(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytesWritten <= 0)
|
if (bytesWritten < 0 || forceClose)
|
||||||
{
|
{
|
||||||
Disconnect(string.Empty);
|
Disconnect(string.Empty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DecodePacket(buffer, ref bytesWritten);
|
if (bytesWritten > 0)
|
||||||
|
{
|
||||||
|
DecodePacket(buffer, ref bytesWritten);
|
||||||
|
writer.Advance((uint)bytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
writer.Advance((uint)bytesWritten);
|
|
||||||
NextActivityCheck = Core.TickCount + 90000;
|
NextActivityCheck = Core.TickCount + 90000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1090,7 +1108,6 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
}
|
}
|
||||||
|
|
||||||
_running = false;
|
_running = false;
|
||||||
|
|
||||||
_disconnectReason = reason;
|
_disconnectReason = reason;
|
||||||
_disposed.Enqueue(this);
|
_disposed.Enqueue(this);
|
||||||
}
|
}
|
||||||
|
|
@ -1145,7 +1162,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_pollGroup.Remove(Connection, _handle);
|
_pollGroup.Remove(Connection.Socket, _handle);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -1173,6 +1190,42 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
||||||
LogInfo(a != null ? $"Disconnected. [{count} Online] [{a}]" : $"Disconnected. [{count} Online]");
|
LogInfo(a != null ? $"Disconnected. [{count} Online] [{a}]" : $"Disconnected. [{count} Online]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool HandleWebSocket(Span<byte> buffer)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string s = Encoding.UTF8.GetString(buffer);
|
||||||
|
|
||||||
|
if (!s.StartsWith("GET", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
Disconnect("BOH");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string swk = Regex.Match(s, "Sec-WebSocket-Key: (.*)").Groups[1].Value.Trim();
|
||||||
|
string swka = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
byte[] swkaSha1 = SHA1.HashData(Encoding.UTF8.GetBytes(swka));
|
||||||
|
string swkaSha1Base64 = Convert.ToBase64String(swkaSha1);
|
||||||
|
|
||||||
|
byte[] response = Encoding.UTF8.GetBytes(
|
||||||
|
"HTTP/1.1 101 Switching Protocols\r\n" +
|
||||||
|
"Connection: Upgrade\r\n" +
|
||||||
|
"Upgrade: websocket\r\n" +
|
||||||
|
"Sec-WebSocket-Accept: " + swkaSha1Base64 + "\r\n\r\n");
|
||||||
|
|
||||||
|
var old = Connection;
|
||||||
|
Connection = new WebSocket(old.Socket);
|
||||||
|
old.Send(response);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Disconnect("BOH2");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private class NetStateConnectingComparer : IComparer<NetState>
|
private class NetStateConnectingComparer : IComparer<NetState>
|
||||||
{
|
{
|
||||||
public static readonly IComparer<NetState> Instance = new NetStateConnectingComparer();
|
public static readonly IComparer<NetState> Instance = new NetStateConnectingComparer();
|
||||||
|
|
|
||||||
18
Projects/Server/Network/Sockets/ISocket.cs
Normal file
18
Projects/Server/Network/Sockets/ISocket.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace Server.Network.Sockets
|
||||||
|
{
|
||||||
|
public interface ISocket
|
||||||
|
{
|
||||||
|
public Socket Socket { get; }
|
||||||
|
public bool Connected { get; }
|
||||||
|
public IPEndPoint? LocalEndPoint { get; }
|
||||||
|
public IPEndPoint? RemoteEndPoint { get; }
|
||||||
|
|
||||||
|
public int Send(ReadOnlySpan<byte> buffer);
|
||||||
|
public int Receive(Span<byte> buffer, out bool forceClose);
|
||||||
|
public void Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Projects/Server/Network/Sockets/TcpSocket.cs
Normal file
31
Projects/Server/Network/Sockets/TcpSocket.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace Server.Network.Sockets
|
||||||
|
{
|
||||||
|
public sealed class TcpSocket : ISocket
|
||||||
|
{
|
||||||
|
public Socket Socket { get; }
|
||||||
|
public bool Connected => Socket.Connected;
|
||||||
|
public IPEndPoint? LocalEndPoint => Socket.LocalEndPoint as IPEndPoint;
|
||||||
|
public IPEndPoint? RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint;
|
||||||
|
|
||||||
|
public TcpSocket(Socket socket)
|
||||||
|
{
|
||||||
|
Socket = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Send(ReadOnlySpan<byte> buffer) => Socket.Send(buffer, SocketFlags.None);
|
||||||
|
|
||||||
|
public int Receive(Span<byte> buffer, out bool forceClose)
|
||||||
|
{
|
||||||
|
int bytesRead = Socket.Receive(buffer, SocketFlags.None);
|
||||||
|
forceClose = bytesRead == 0;
|
||||||
|
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close() => Socket.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
374
Projects/Server/Network/Sockets/WebSocket.cs
Normal file
374
Projects/Server/Network/Sockets/WebSocket.cs
Normal file
|
|
@ -0,0 +1,374 @@
|
||||||
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Server.Network.Sockets
|
||||||
|
{
|
||||||
|
public sealed class WebSocket : ISocket
|
||||||
|
{
|
||||||
|
private const int MinHeaderSize = 6; // 2 byte header + 4 byte mask
|
||||||
|
private const int MaxheaderSize = 14; // 2 byte header + 8 byte long length + 4 byte mask
|
||||||
|
|
||||||
|
private WriteStatus status;
|
||||||
|
private Mask mask;
|
||||||
|
private int headerLength;
|
||||||
|
private int remainingPayloadLength;
|
||||||
|
private int payloadMaskIndex;
|
||||||
|
|
||||||
|
public Socket Socket { get; }
|
||||||
|
public bool Connected => Socket.Connected;
|
||||||
|
public IPEndPoint LocalEndPoint => Socket.LocalEndPoint as IPEndPoint;
|
||||||
|
public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint;
|
||||||
|
|
||||||
|
public WebSocket(Socket socket)
|
||||||
|
{
|
||||||
|
Socket = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Receive(Span<byte> buffer, out bool forceClose)
|
||||||
|
{
|
||||||
|
forceClose = false;
|
||||||
|
int available = Socket.Available;
|
||||||
|
|
||||||
|
if (available < MinHeaderSize)
|
||||||
|
{
|
||||||
|
if (available == 0)
|
||||||
|
{
|
||||||
|
forceClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> header = stackalloc byte[MaxheaderSize];
|
||||||
|
int bytesWritten = 0;
|
||||||
|
|
||||||
|
while (available >= MinHeaderSize)
|
||||||
|
{
|
||||||
|
int iterationHeaderLength = Math.Min(available, MaxheaderSize);
|
||||||
|
Span<byte> iterationHeader = header[..iterationHeaderLength];
|
||||||
|
|
||||||
|
int bytesRead = Socket.Receive(iterationHeader, SocketFlags.Peek);
|
||||||
|
if (iterationHeaderLength != bytesRead)
|
||||||
|
{
|
||||||
|
throw new Exception("WebSocket header length mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
int iterationBytesWritten = 0;
|
||||||
|
OpCode opCode = (OpCode)(header[0] & 0b0000_1111);
|
||||||
|
|
||||||
|
bool completed = opCode switch
|
||||||
|
{
|
||||||
|
OpCode.BinaryFrame => HandleDataFrame(header, available, buffer, out iterationBytesWritten),
|
||||||
|
OpCode.ContinueFrame => HandleDataFrame(header, available, buffer, out iterationBytesWritten),
|
||||||
|
OpCode.Ping => HandlePing(header, available),
|
||||||
|
OpCode.Pong => HandlePong(header, available),
|
||||||
|
OpCode.Close => HandleClose(header, available, out forceClose),
|
||||||
|
_ => throw new NotSupportedException($"WebSocket OpCode {opCode} not supported"),
|
||||||
|
};
|
||||||
|
|
||||||
|
bytesWritten += iterationBytesWritten;
|
||||||
|
|
||||||
|
if (!completed)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
available -= headerLength + iterationBytesWritten;
|
||||||
|
buffer = buffer[iterationBytesWritten..];
|
||||||
|
status = WriteStatus.WaitingForHeader;
|
||||||
|
payloadMaskIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HandlePing(Span<byte> header, int available)
|
||||||
|
{
|
||||||
|
// packets from client contains always a mask even for Control OpCodes
|
||||||
|
int totalLength = (byte)(header[1] & 0b0111_1111) + MinHeaderSize;
|
||||||
|
if (totalLength > available)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> payload = stackalloc byte[totalLength];
|
||||||
|
|
||||||
|
int bytesRead = Socket.Receive(payload);
|
||||||
|
if (bytesRead != totalLength)
|
||||||
|
{
|
||||||
|
throw new Exception("WebSocket ping length mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
const byte header1 = 0b1000_0000 | (byte)OpCode.Pong;
|
||||||
|
|
||||||
|
Socket.Send([header1, 0, .. payload[MinHeaderSize..]]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HandleClose(Span<byte> header, int available, out bool forceClose)
|
||||||
|
{
|
||||||
|
forceClose = true;
|
||||||
|
|
||||||
|
// packets from client contains always a mask even for Control OpCodes
|
||||||
|
int totalLength = (byte)(header[1] & 0b0111_1111) + MinHeaderSize;
|
||||||
|
if (totalLength > available)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> payload = stackalloc byte[totalLength];
|
||||||
|
|
||||||
|
int bytesRead = Socket.Receive(payload);
|
||||||
|
if (bytesRead != totalLength)
|
||||||
|
{
|
||||||
|
throw new Exception("WebSocket close length mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
const byte header1 = 0b1000_0000 | (byte)OpCode.Close;
|
||||||
|
|
||||||
|
Socket.Send([header1, 0, .. payload[MinHeaderSize..]]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HandlePong(Span<byte> header, int available)
|
||||||
|
{
|
||||||
|
// packets from client contains always a mask even for Control OpCodes
|
||||||
|
int totalLength = (byte)(header[1] & 0b0111_1111) + MinHeaderSize;
|
||||||
|
if (totalLength > available)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> payload = stackalloc byte[totalLength];
|
||||||
|
|
||||||
|
int bytesRead = Socket.Receive(payload);
|
||||||
|
if (bytesRead != totalLength)
|
||||||
|
{
|
||||||
|
throw new Exception("WebSocket pong length mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HandleDataFrame(Span<byte> header, int available, Span<byte> buffer, out int bytesWritten)
|
||||||
|
{
|
||||||
|
bytesWritten = 0;
|
||||||
|
|
||||||
|
if (status == WriteStatus.WaitingForHeader)
|
||||||
|
{
|
||||||
|
if (!ReadDataHeader(header))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == WriteStatus.WaitingForMask)
|
||||||
|
{
|
||||||
|
if (!ReadMask(header))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == WriteStatus.WritingPayload)
|
||||||
|
{
|
||||||
|
return WritePayload(buffer, available, out bytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ReadDataHeader(Span<byte> header)
|
||||||
|
{
|
||||||
|
bool masked = (header[1] & 0b1000_0000) != 0;
|
||||||
|
int length = (byte)(header[1] & 0b0111_1111);
|
||||||
|
|
||||||
|
if (!masked)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("WebSocket packet must be masked");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (length)
|
||||||
|
{
|
||||||
|
case <= 125:
|
||||||
|
{
|
||||||
|
headerLength = MinHeaderSize;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 126:
|
||||||
|
{
|
||||||
|
const int lengthSize = 2;
|
||||||
|
|
||||||
|
if (header.Length < lengthSize + 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = BinaryPrimitives.ReadUInt16BigEndian(header[2..4]);
|
||||||
|
headerLength = lengthSize + MinHeaderSize;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
const int lengthSize = 8;
|
||||||
|
|
||||||
|
if (header.Length < lengthSize + 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = (int)BinaryPrimitives.ReadUInt64BigEndian(header[2..10]);
|
||||||
|
headerLength = lengthSize + MinHeaderSize;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = WriteStatus.WaitingForMask;
|
||||||
|
remainingPayloadLength = length;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ReadMask(Span<byte> header)
|
||||||
|
{
|
||||||
|
if (header.Length < headerLength)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mask = new(header[(headerLength - 4)..]);
|
||||||
|
status = WriteStatus.WritingPayload;
|
||||||
|
|
||||||
|
int bytesRead = Socket.Receive(header[..headerLength]); // advance socket buffer to payload
|
||||||
|
if (bytesRead != headerLength)
|
||||||
|
{
|
||||||
|
throw new Exception("WebSocket header length mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool WritePayload(Span<byte> buffer, int available, out int bytesWritten)
|
||||||
|
{
|
||||||
|
int length = Math.Min(remainingPayloadLength, Math.Min(buffer.Length, available - headerLength));
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
bytesWritten = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = buffer[..length];
|
||||||
|
|
||||||
|
int bytesRead = Socket.Receive(buffer);
|
||||||
|
if (bytesRead != length)
|
||||||
|
{
|
||||||
|
throw new Exception("WebSocket payload length mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyMask(buffer);
|
||||||
|
|
||||||
|
remainingPayloadLength -= bytesRead;
|
||||||
|
bytesWritten = bytesRead;
|
||||||
|
|
||||||
|
return remainingPayloadLength == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyMask(Span<byte> buffer)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < buffer.Length; i++)
|
||||||
|
{
|
||||||
|
buffer[i] = (byte)(buffer[i] ^ mask[payloadMaskIndex++]);
|
||||||
|
|
||||||
|
if (payloadMaskIndex == 4)
|
||||||
|
{
|
||||||
|
payloadMaskIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Send(ReadOnlySpan<byte> buffer)
|
||||||
|
{
|
||||||
|
const byte header1 = 0b1000_0000 | (byte)OpCode.BinaryFrame;
|
||||||
|
|
||||||
|
switch (buffer.Length)
|
||||||
|
{
|
||||||
|
case <= 125:
|
||||||
|
{
|
||||||
|
Socket.Send([header1, (byte)buffer.Length, .. buffer]);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case < ushort.MaxValue:
|
||||||
|
{
|
||||||
|
ushort length = (ushort)buffer.Length;
|
||||||
|
|
||||||
|
Socket.Send([header1, 126, (byte)(length >> 8), (byte)length, .. buffer]);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
int length = buffer.Length;
|
||||||
|
|
||||||
|
Socket.Send([header1, 127, 0, 0, 0, 0, (byte)(length >> 24), (byte)(length >> 16),
|
||||||
|
(byte)(length >> 8), (byte)length, .. buffer]);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const byte header1 = 0b1000_0000 | (byte)OpCode.Close;
|
||||||
|
Socket.Send([header1, 0]);
|
||||||
|
Socket.Close();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
/* ignore already closed sockets */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum OpCode : byte
|
||||||
|
{
|
||||||
|
ContinueFrame = 0,
|
||||||
|
TextFrame = 1,
|
||||||
|
BinaryFrame = 2,
|
||||||
|
Close = 8,
|
||||||
|
Ping = 9,
|
||||||
|
Pong = 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
[InlineArray(Size)]
|
||||||
|
private struct Mask
|
||||||
|
{
|
||||||
|
public const int Size = 4;
|
||||||
|
|
||||||
|
[SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Inline array")]
|
||||||
|
private byte element0;
|
||||||
|
|
||||||
|
public Mask(Span<byte> mask)
|
||||||
|
{
|
||||||
|
mask[..4].CopyTo(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum WriteStatus
|
||||||
|
{
|
||||||
|
WaitingForHeader,
|
||||||
|
WaitingForMask,
|
||||||
|
WritingPayload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -70,7 +70,7 @@ namespace Server.Misc
|
||||||
{
|
{
|
||||||
var ns = e.State;
|
var ns = e.State;
|
||||||
|
|
||||||
var ipep = (IPEndPoint)ns.Connection?.LocalEndPoint;
|
var ipep = ns.Connection?.LocalEndPoint;
|
||||||
if (ipep == null)
|
if (ipep == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue