Permissions, RateControl and Auditing

This commit is contained in:
2026-07-16 14:01:08 +03:00
parent 3a1b95dbc5
commit ba64a0c95a
62 changed files with 6095 additions and 2366 deletions
+213 -135
View File
@@ -41,6 +41,12 @@ namespace Esiur.Net.Sockets;
public class SSLSocket : ISocket
{
private sealed class PendingSend
{
public AsyncReply<bool> Reply;
public byte[] Buffer;
}
public INetworkReceiver<ISocket> Receiver { get; set; }
Socket sock;
@@ -54,7 +60,10 @@ public class SSLSocket : ISocket
readonly object sendLock = new object();
Queue<KeyValuePair<AsyncReply<bool>, byte[]>> sendBufferQueue = new Queue<KeyValuePair<AsyncReply<bool>, byte[]>>();// Queue<byte[]>();
readonly Queue<PendingSend> sendBufferQueue = new Queue<PendingSend>();
PendingSend currentSend;
long pendingSendBytes;
long maximumPendingSendBytes = 16 * 1024 * 1024;
bool asyncSending;
bool began = false;
@@ -72,33 +81,50 @@ public class SSLSocket : ISocket
bool server;
string hostname;
public long PendingSendBytes => Interlocked.Read(ref pendingSendBytes);
/// <summary>Maximum number of unsent plaintext bytes retained for a slow TLS peer.</summary>
public long MaximumPendingSendBytes
{
get => Interlocked.Read(ref maximumPendingSendBytes);
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value));
Interlocked.Exchange(ref maximumPendingSendBytes, value);
}
}
public async AsyncReply<bool> Connect(string hostname, ushort port)
{
var rt = new AsyncReply<bool>();
this.hostname = hostname;
this.server = false;
state = SocketState.Connecting;
await sock.ConnectAsync(hostname, port);
try
{
await BeginAsync();
await sock.ConnectAsync(hostname, port);
ssl = new SslStream(new NetworkStream(sock));
state = SocketState.Established;
if (!await BeginAsync())
{
Close();
return false;
}
//OnConnect?.Invoke();
Receiver?.NetworkConnect(this);
return true;
}
catch (Exception ex)
{
state = SocketState.Closed;// .Terminated;
Close();
Global.Log(ex);
return false;
}
return true;
}
//private void DataSent(Task task)
@@ -132,64 +158,55 @@ public class SSLSocket : ISocket
//}
private void SendCallback(IAsyncResult ar)
private async Task ProcessSendQueueAsync()
{
if (ar != null)
while (true)
{
try
PendingSend pending;
lock (sendLock)
{
ssl.EndWrite(ar);
if (ar.AsyncState != null)
((AsyncReply<bool>)ar.AsyncState).Trigger(true);
}
catch
{
if (state != SocketState.Closed && !sock.Connected)
{
//state = SocketState.Closed;//.Terminated;
Close();
}
}
}
lock (sendLock)
{
if (sendBufferQueue.Count > 0)
{
var kv = sendBufferQueue.Dequeue();
try
{
ssl.BeginWrite(kv.Value, 0, kv.Value.Length, SendCallback, kv.Key);
}
catch //(Exception ex)
if (held || state == SocketState.Closed || sendBufferQueue.Count == 0)
{
asyncSending = false;
try
{
if (kv.Key != null)
kv.Key.Trigger(false);
if (state != SocketState.Closed && !sock.Connected)
{
//state = SocketState.Terminated;
Close();
}
}
catch //(Exception ex2)
{
//state = SocketState.Closed;// .Terminated;
Close();
}
//Global.Log("TCPSocket", LogType.Error, ex.ToString());
return;
}
pending = sendBufferQueue.Dequeue();
currentSend = pending;
}
else
try
{
asyncSending = false;
await ssl.WriteAsync(pending.Buffer, 0, pending.Buffer.Length).ConfigureAwait(false);
lock (sendLock)
{
if (ReferenceEquals(currentSend, pending))
{
currentSend = null;
Interlocked.Add(ref pendingSendBytes, -pending.Buffer.Length);
}
}
TryCompleteSend(pending, true, null);
}
catch (Exception exception)
{
lock (sendLock)
{
if (ReferenceEquals(currentSend, pending))
{
currentSend = null;
Interlocked.Add(ref pendingSendBytes, -pending.Buffer.Length);
}
asyncSending = false;
}
TryCompleteSend(pending, false, exception);
Close();
return;
}
}
}
@@ -257,25 +274,52 @@ public class SSLSocket : ISocket
public void Close()
{
if (state != SocketState.Closed)// && state != SocketState.Terminated)
List<PendingSend> abandoned;
lock (sendLock)
{
state = SocketState.Closed;
if (state == SocketState.Closed)
return;
if (sock.Connected)
state = SocketState.Closed;
abandoned = sendBufferQueue.ToList();
sendBufferQueue.Clear();
if (currentSend != null)
{
try
{
sock.Shutdown(SocketShutdown.Both);
}
catch
{
//state = SocketState.Terminated;
}
abandoned.Insert(0, currentSend);
currentSend = null;
}
Receiver?.NetworkClose(this);
//OnClose?.Invoke();
foreach (var pending in abandoned)
Interlocked.Add(ref pendingSendBytes, -pending.Buffer.Length);
asyncSending = false;
}
if (sock != null)
{
try
{
if (sock.Connected)
sock.Shutdown(SocketShutdown.Both);
}
catch
{
//state = SocketState.Terminated;
}
// Closing the underlying socket is what aborts an in-progress TLS
// handshake. Shutdown alone can leave AuthenticateAsServerAsync waiting
// forever for a peer that never sends a ClientHello.
try { sock.Close(); } catch { }
}
try { ssl?.Dispose(); } catch { }
foreach (var pending in abandoned)
TryCompleteSend(pending, false, null);
try { Receiver?.NetworkClose(this); }
catch (Exception exception) { Global.Log(exception); }
//OnClose?.Invoke();
}
@@ -287,35 +331,26 @@ public class SSLSocket : ISocket
public void Send(byte[] message, int offset, int size)
{
ValidateRange(message, offset, size);
if (size == 0)
return;
var msg = message.Clip((uint)offset, (uint)size);
bool startPump = false;
lock (sendLock)
{
if (!sock.Connected)
if (state != SocketState.Established)
return;
if (asyncSending || held)
{
sendBufferQueue.Enqueue(new KeyValuePair<AsyncReply<bool>, byte[]>(null, msg));// message.Clip((uint)offset, (uint)size));
}
else
{
asyncSending = true;
try
{
ssl.BeginWrite(msg, 0, msg.Length, SendCallback, null);
}
catch
{
asyncSending = false;
//state = SocketState.Terminated;
Close();
}
}
EnsureSendCapacity_NoLock(size);
var msg = new byte[size];
Buffer.BlockCopy(message, offset, msg, 0, size);
sendBufferQueue.Enqueue(new PendingSend { Buffer = msg });
Interlocked.Add(ref pendingSendBytes, size);
startPump = TryStartSendPump_NoLock();
}
if (startPump)
_ = ProcessSendQueueAsync();
}
//public void Send(byte[] message)
@@ -438,15 +473,16 @@ public class SSLSocket : ISocket
ssl.BeginRead(receiveBuffer, 0, receiveBuffer.Length, ReceiveCallback, this);
}
catch //(Exception ex)
catch (Exception ex)
{
if (state != SocketState.Closed && !sock.Connected)
{
//state = SocketState.Terminated;
// Socket.Connected reports the state of the last operation and can
// remain true after a TLS read failure. Any read exception ends this
// receive loop, so close deterministically instead of leaving a
// half-open connection that will never read again.
if (state != SocketState.Closed)
Close();
}
//Global.Log("SSLSocket", LogType.Error, ex.ToString());
Global.Log("SSLSocket", LogType.Warning, ex.ToString());
}
}
@@ -489,59 +525,101 @@ public class SSLSocket : ISocket
public void Hold()
{
held = true;
lock (sendLock)
held = true;
}
public void Unhold()
{
try
{
SendCallback(null);
}
catch (Exception ex)
{
Global.Log(ex);
}
finally
bool startPump;
lock (sendLock)
{
held = false;
startPump = TryStartSendPump_NoLock();
}
if (startPump)
_ = ProcessSendQueueAsync();
}
public AsyncReply<bool> SendAsync(byte[] message, int offset, int length)
{
ValidateRange(message, offset, length);
if (length == 0)
return new AsyncReply<bool>(true);
var msg = message.Clip((uint)offset, (uint)length);
var rt = new AsyncReply<bool>();
bool startPump = false;
Exception capacityError = null;
lock (sendLock)
{
if (!sock.Connected)
if (state != SocketState.Established)
return new AsyncReply<bool>(false);
var rt = new AsyncReply<bool>();
if (asyncSending || held)
try
{
sendBufferQueue.Enqueue(new KeyValuePair<AsyncReply<bool>, byte[]>(rt, msg));
EnsureSendCapacity_NoLock(length);
var msg = new byte[length];
Buffer.BlockCopy(message, offset, msg, 0, length);
sendBufferQueue.Enqueue(new PendingSend { Reply = rt, Buffer = msg });
Interlocked.Add(ref pendingSendBytes, length);
startPump = TryStartSendPump_NoLock();
}
catch (Exception exception)
{
capacityError = exception;
}
}
if (capacityError != null)
rt.TriggerError(capacityError);
else if (startPump)
_ = ProcessSendQueueAsync();
return rt;
}
private bool TryStartSendPump_NoLock()
{
if (asyncSending || held || state != SocketState.Established || sendBufferQueue.Count == 0)
return false;
asyncSending = true;
return true;
}
private void EnsureSendCapacity_NoLock(int length)
{
var limit = Interlocked.Read(ref maximumPendingSendBytes);
var pending = Interlocked.Read(ref pendingSendBytes);
if (length > limit - pending)
throw new InvalidOperationException($"The TLS send queue exceeded its {limit}-byte limit.");
}
private static void ValidateRange(byte[] message, int offset, int length)
{
if (message == null)
throw new ArgumentNullException(nameof(message));
if (offset < 0 || length < 0 || offset > message.Length - length)
throw new ArgumentOutOfRangeException();
}
private static void TryCompleteSend(PendingSend pending, bool succeeded, Exception exception)
{
if (pending?.Reply == null)
return;
try
{
if (exception != null)
pending.Reply.TriggerError(exception);
else
{
asyncSending = true;
try
{
ssl.BeginWrite(msg, 0, msg.Length, SendCallback, rt);// null);
}
catch (Exception ex)
{
rt.TriggerError(ex);
asyncSending = false;
//state = SocketState.Terminated;
Close();
}
}
return rt;
pending.Reply.Trigger(succeeded);
}
catch (Exception callbackException)
{
Global.Log(callbackException);
}
}
+189 -44
View File
@@ -21,6 +21,20 @@ public class TcpSocket : ISocket
public AsyncReply<bool> Reply;
}
private readonly struct SendReplyCompletion
{
public SendReplyCompletion(AsyncReply<bool> reply, bool result, Exception error)
{
Reply = reply;
Result = result;
Error = error;
}
public AsyncReply<bool> Reply { get; }
public bool Result { get; }
public Exception Error { get; }
}
public INetworkReceiver<ISocket> Receiver { get; set; }
public event DestroyedEvent OnDestroy;
@@ -37,6 +51,8 @@ public class TcpSocket : ISocket
private SocketAsyncEventArgs sendArgs;
private PendingSend currentSend;
private long pendingSendBytes;
private long maximumPendingSendBytes = 16 * 1024 * 1024;
private bool sendInProgress;
private bool began;
private bool held;
@@ -52,6 +68,24 @@ public class TcpSocket : ISocket
public SocketState State => state;
public int BytesSent => bytesSent;
public int BytesReceived => bytesReceived;
public long PendingSendBytes => Interlocked.Read(ref pendingSendBytes);
/// <summary>
/// Maximum number of unsent bytes retained by this socket. This bounds the
/// copies made by <see cref="Send(byte[], int, int)"/> and
/// <see cref="SendAsync(byte[], int, int)"/> when a peer is slow.
/// </summary>
public long MaximumPendingSendBytes
{
get => Interlocked.Read(ref maximumPendingSendBytes);
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value));
Interlocked.Exchange(ref maximumPendingSendBytes, value);
}
}
public IPEndPoint LocalEndPoint => sock.LocalEndPoint as IPEndPoint;
public IPEndPoint RemoteEndPoint => sock.RemoteEndPoint as IPEndPoint;
@@ -227,14 +261,19 @@ public class TcpSocket : ISocket
if (destroyed || state != SocketState.Established)
return;
var copy = new byte[length];
Buffer.BlockCopy(message, offset, copy, 0, length);
List<SendReplyCompletion> completions = null;
Exception sendError = null;
lock (sendLock)
{
if (destroyed || state != SocketState.Established)
return;
EnsureSendCapacity_NoLock(length);
var copy = new byte[length];
Buffer.BlockCopy(message, offset, copy, 0, length);
sendQueue.Enqueue(new PendingSend
{
Buffer = copy,
@@ -242,9 +281,12 @@ public class TcpSocket : ISocket
Count = copy.Length,
Reply = null
});
Interlocked.Add(ref pendingSendBytes, length);
TryStartNextSend_NoLock();
TryStartNextSend_NoLock(ref completions, ref sendError);
}
FinishSendWork(completions, sendError);
}
public AsyncReply<bool> SendAsync(byte[] message, int offset, int length)
@@ -268,8 +310,8 @@ public class TcpSocket : ISocket
return rt;
}
var copy = new byte[length];
Buffer.BlockCopy(message, offset, copy, 0, length);
List<SendReplyCompletion> completions = null;
Exception sendError = null;
lock (sendLock)
{
@@ -279,6 +321,19 @@ public class TcpSocket : ISocket
return rt;
}
try
{
EnsureSendCapacity_NoLock(length);
}
catch (Exception ex)
{
rt.TriggerError(ex);
return rt;
}
var copy = new byte[length];
Buffer.BlockCopy(message, offset, copy, 0, length);
sendQueue.Enqueue(new PendingSend
{
Buffer = copy,
@@ -286,10 +341,13 @@ public class TcpSocket : ISocket
Count = copy.Length,
Reply = rt
});
Interlocked.Add(ref pendingSendBytes, length);
TryStartNextSend_NoLock();
TryStartNextSend_NoLock(ref completions, ref sendError);
}
FinishSendWork(completions, sendError);
return rt;
}
@@ -323,15 +381,22 @@ public class TcpSocket : ISocket
public void Hold()
{
held = true;
lock (sendLock)
held = true;
}
public void Unhold()
{
held = false;
List<SendReplyCompletion> completions = null;
Exception sendError = null;
lock (sendLock)
TryStartNextSend_NoLock();
{
held = false;
TryStartNextSend_NoLock(ref completions, ref sendError);
}
FinishSendWork(completions, sendError);
}
public void Close()
@@ -444,16 +509,20 @@ public class TcpSocket : ISocket
}
}
private void TryStartNextSend_NoLock()
private void TryStartNextSend_NoLock(
ref List<SendReplyCompletion> completions,
ref Exception sendError)
{
if (held || destroyed || state != SocketState.Established || sendInProgress)
return;
sendInProgress = true;
PumpSendQueue_NoLock();
PumpSendQueue_NoLock(ref completions, ref sendError);
}
private void PumpSendQueue_NoLock()
private void PumpSendQueue_NoLock(
ref List<SendReplyCompletion> completions,
ref Exception sendError)
{
while (true)
{
@@ -492,9 +561,9 @@ public class TcpSocket : ISocket
var reply = currentSend?.Reply;
currentSend = null;
sendInProgress = false;
reply?.TriggerError(ex);
FailPendingSends_NoLock(ex);
CloseDueToSendError_NoLock(ex);
QueueSendCompletion(ref completions, reply, false, ex);
FailPendingSends_NoLock(ex, ref completions);
sendError = ex;
return;
}
@@ -503,23 +572,35 @@ public class TcpSocket : ISocket
return;
}
if (!ProcessSendCompletion_NoLock(sendArgs))
if (!ProcessSendCompletion_NoLock(
sendArgs,
ref completions,
ref sendError))
return;
}
}
private void ProcessSend(SocketAsyncEventArgs e)
{
List<SendReplyCompletion> completions = null;
Exception sendError = null;
lock (sendLock)
{
if (!ProcessSendCompletion_NoLock(e))
return;
PumpSendQueue_NoLock();
if (ProcessSendCompletion_NoLock(
e,
ref completions,
ref sendError))
PumpSendQueue_NoLock(ref completions, ref sendError);
}
FinishSendWork(completions, sendError);
}
private bool ProcessSendCompletion_NoLock(SocketAsyncEventArgs e)
private bool ProcessSendCompletion_NoLock(
SocketAsyncEventArgs e,
ref List<SendReplyCompletion> completions,
ref Exception sendError)
{
try
{
@@ -532,26 +613,27 @@ public class TcpSocket : ISocket
if (e.SocketError != SocketError.Success)
{
var ex = new SocketException((int)e.SocketError);
currentSend.Reply?.TriggerError(ex);
QueueSendCompletion(ref completions, currentSend.Reply, false, ex);
currentSend = null;
sendInProgress = false;
FailPendingSends_NoLock(ex);
CloseDueToSendError_NoLock(ex);
FailPendingSends_NoLock(ex, ref completions);
sendError = ex;
return false;
}
if (e.BytesTransferred <= 0)
{
var ex = new SocketException((int)SocketError.ConnectionReset);
currentSend.Reply?.TriggerError(ex);
QueueSendCompletion(ref completions, currentSend.Reply, false, ex);
currentSend = null;
sendInProgress = false;
FailPendingSends_NoLock(ex);
CloseDueToSendError_NoLock(ex);
FailPendingSends_NoLock(ex, ref completions);
sendError = ex;
return false;
}
Interlocked.Add(ref bytesSent, e.BytesTransferred);
Interlocked.Add(ref pendingSendBytes, -e.BytesTransferred);
currentSend.Offset += e.BytesTransferred;
currentSend.Count -= e.BytesTransferred;
@@ -561,42 +643,42 @@ public class TcpSocket : ISocket
return true;
}
currentSend.Reply?.Trigger(true);
QueueSendCompletion(ref completions, currentSend.Reply, true, null);
currentSend = null;
return true;
}
catch (Exception ex)
{
currentSend?.Reply?.TriggerError(ex);
QueueSendCompletion(ref completions, currentSend?.Reply, false, ex);
currentSend = null;
sendInProgress = false;
FailPendingSends_NoLock(ex);
CloseDueToSendError_NoLock(ex);
FailPendingSends_NoLock(ex, ref completions);
sendError = ex;
return false;
}
}
private void FailPendingSends_NoLock(Exception ex)
private void FailPendingSends_NoLock(
Exception ex,
ref List<SendReplyCompletion> completions)
{
while (sendQueue.Count > 0)
{
var item = sendQueue.Dequeue();
try
{
item.Reply?.TriggerError(ex);
}
catch { }
QueueSendCompletion(ref completions, item.Reply, false, ex);
}
Interlocked.Exchange(ref pendingSendBytes, 0);
}
private void CloseDueToSendError_NoLock(Exception ex)
private bool CloseDueToSendError(Exception ex)
{
bool notify = false;
lock (stateLock)
{
if (state == SocketState.Closed)
return;
return false;
state = SocketState.Closed;
notify = !closeNotified;
@@ -609,6 +691,17 @@ public class TcpSocket : ISocket
Global.Log(ex);
return notify;
}
private void FinishSendWork(
List<SendReplyCompletion> completions,
Exception sendError)
{
var notify = sendError != null && CloseDueToSendError(sendError);
CompleteSendReplies(completions);
if (notify)
{
try { Receiver?.NetworkClose(this); }
@@ -616,9 +709,44 @@ public class TcpSocket : ISocket
}
}
private static void QueueSendCompletion(
ref List<SendReplyCompletion> completions,
AsyncReply<bool> reply,
bool result,
Exception error)
{
if (reply == null)
return;
completions ??= new List<SendReplyCompletion>();
completions.Add(new SendReplyCompletion(reply, result, error));
}
private static void CompleteSendReplies(List<SendReplyCompletion> completions)
{
if (completions == null)
return;
foreach (var completion in completions)
{
try
{
if (completion.Error != null)
completion.Reply.TriggerError(completion.Error);
else
completion.Reply.Trigger(completion.Result);
}
catch (Exception ex)
{
Global.Log(ex);
}
}
}
private void SafeClose(Exception ex, bool notifyReceiver)
{
bool notify = false;
List<SendReplyCompletion> completions = null;
lock (stateLock)
{
@@ -636,25 +764,30 @@ public class TcpSocket : ISocket
if (ex != null)
{
try { currentSend?.Reply?.TriggerError(ex); } catch { }
QueueSendCompletion(ref completions, currentSend?.Reply, false, ex);
currentSend = null;
FailPendingSends_NoLock(ex);
FailPendingSends_NoLock(ex, ref completions);
}
else
{
QueueSendCompletion(ref completions, currentSend?.Reply, false, null);
currentSend = null;
while (sendQueue.Count > 0)
{
var item = sendQueue.Dequeue();
try { item.Reply?.Trigger(false); } catch { }
QueueSendCompletion(ref completions, item.Reply, false, null);
}
}
Interlocked.Exchange(ref pendingSendBytes, 0);
}
try { sock.Shutdown(SocketShutdown.Both); } catch { }
try { sock.Close(); } catch { }
try { sock.Dispose(); } catch { }
CompleteSendReplies(completions);
if (ex != null)
Global.Log(ex);
@@ -667,7 +800,19 @@ public class TcpSocket : ISocket
private static void ValidateRange(byte[] message, int offset, int length)
{
if (offset < 0 || length < 0 || offset + length > message.Length)
if (offset < 0 || length < 0 || offset > message.Length - length)
throw new ArgumentOutOfRangeException();
}
}
private void EnsureSendCapacity_NoLock(int length)
{
var limit = Interlocked.Read(ref maximumPendingSendBytes);
var pending = Interlocked.Read(ref pendingSendBytes);
if (length > limit - pending)
{
throw new InvalidOperationException(
$"The socket send queue exceeded its {limit}-byte limit.");
}
}
}
+197 -41
View File
@@ -45,9 +45,16 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
ISocket sock;
NetworkBuffer receiveNetworkBuffer = new NetworkBuffer();
NetworkBuffer sendNetworkBuffer = new NetworkBuffer();
NetworkBuffer fragmentedMessageBuffer = new NetworkBuffer();
bool fragmentedMessage;
WebsocketPacket.WSOpcode fragmentedMessageOpcode;
ulong fragmentedMessageLength;
object sendLock = new object();
bool held;
bool destroyed;
ulong maximumMessageLength = WebsocketPacket.DefaultMaximumPayloadLength;
//public event ISocketReceiveEvent OnReceive;
//public event ISocketConnectEvent OnConnect;
@@ -79,11 +86,32 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
public INetworkReceiver<ISocket> Receiver { get; set; }
public WSocket(ISocket socket)
/// <summary>Whether this endpoint receives client frames and sends server frames.</summary>
public bool IsServer { get; }
/// <summary>Maximum payload accepted for one complete message.</summary>
public ulong MaximumMessageLength
{
get => maximumMessageLength;
set
{
maximumMessageLength = value;
pkt_receive.MaximumPayloadLength = value;
}
}
public WSocket(ISocket socket)
: this(socket, true)
{
}
public WSocket(ISocket socket, bool isServer)
{
IsServer = isServer;
pkt_send.FIN = true;
pkt_send.Mask = false;
pkt_send.Mask = !isServer;
pkt_send.Opcode = WebsocketPacket.WSOpcode.BinaryFrame;
pkt_receive.ExpectedMask = isServer;
sock = socket;
sock.Receiver = this;
@@ -111,8 +139,11 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
public void Send(WebsocketPacket packet)
{
lock (sendLock)
{
PrepareOutboundPacket(packet);
if (packet.Compose())
sock.Send(packet.Data);
}
}
public void Send(byte[] message)
@@ -131,6 +162,7 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
pkt_send.Message = message;
PrepareOutboundPacket(pkt_send);
if (pkt_send.Compose())
sock?.Send(pkt_send.Data);
@@ -154,6 +186,7 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
pkt_send.Message = new byte[size];
Buffer.BlockCopy(message, offset, pkt_send.Message, 0, size);
PrepareOutboundPacket(pkt_send);
if (pkt_send.Compose())
sock.Send(pkt_send.Data);
}
@@ -184,18 +217,36 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
public void Destroy()
{
Close();
//OnClose = null;
//OnConnect = null;
//OnReceive = null;
receiveNetworkBuffer = null;
//sock.OnReceive -= Sock_OnReceive;
//sock.OnClose -= Sock_OnClose;
//sock.OnConnect -= Sock_OnConnect;
sock.Receiver = null;
sock = null;
OnDestroy?.Invoke(this);
OnDestroy = null;
ISocket socket;
DestroyedEvent onDestroy;
lock (sendLock)
{
if (destroyed)
return;
destroyed = true;
socket = sock;
onDestroy = OnDestroy;
OnDestroy = null;
}
// Close can synchronously re-enter Destroy through NetworkClose. The
// guard above keeps that path idempotent while the captured socket is
// still valid for the outer cleanup.
try { socket?.Close(); } catch (Exception ex) { Global.Log(ex); }
lock (sendLock)
{
if (socket != null && ReferenceEquals(socket.Receiver, this))
socket.Receiver = null;
sock = null;
receiveNetworkBuffer = null;
fragmentedMessageBuffer = null;
}
onDestroy?.Invoke(this);
}
public AsyncReply<ISocket> AcceptAsync()
@@ -205,8 +256,8 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
public void Hold()
{
//Console.WriteLine("WS Hold ");
held = true;
lock (sendLock)
held = true;
}
public void Unhold()
@@ -225,6 +276,7 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
totalSent += message.Length;
pkt_send.Message = message;
PrepareOutboundPacket(pkt_send);
if (pkt_send.Compose())
sock.Send(pkt_send.Data);
@@ -248,7 +300,7 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
public void NetworkClose(ISocket sender)
{
Receiver?.NetworkClose(sender);
Receiver?.NetworkClose(this);
}
public void NetworkReceive(ISocket sender, NetworkBuffer buffer)
@@ -267,7 +319,8 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
if (msg == null)
return;
var wsPacketLength = pkt_receive.Parse(msg, 0, (uint)msg.Length);
if (!TryParseFrame(msg, 0, out var wsPacketLength))
return;
if (wsPacketLength < 0)
{
@@ -289,45 +342,33 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
var pkt_pong = new WebsocketPacket()
{
FIN = true,
Mask = false,
Mask = !IsServer,
Opcode = WebsocketPacket.WSOpcode.Pong,
Message = pkt_receive.Message
};
offset += (uint)wsPacketLength;
Send(pkt_pong);
}
else if (pkt_receive.Opcode == WebsocketPacket.WSOpcode.Pong)
{
offset += (uint)wsPacketLength;
}
else if (pkt_receive.Opcode == WebsocketPacket.WSOpcode.BinaryFrame
|| pkt_receive.Opcode == WebsocketPacket.WSOpcode.TextFrame
|| pkt_receive.Opcode == WebsocketPacket.WSOpcode.ContinuationFrame)
{
totalReceived += pkt_receive.Message.Length;
//Console.WriteLine("RX " + pkt_receive.Message.Length + "/" + totalReceived);// + " " + DC.ToHex(message, 0, (uint)size));
receiveNetworkBuffer.Write(pkt_receive.Message);
offset += (uint)wsPacketLength;
//Console.WriteLine("WS IN: " + pkt_receive.Opcode.ToString() + " " + pkt_receive.Message.Length + " | " + offset + " " + string.Join(" ", pkt_receive.Message));// DC.ToHex(pkt_receive.Message));
}
else
{
Global.Log("WSocket", LogType.Debug, "Unknown WS opcode:" + pkt_receive.Opcode);
if (!ProcessDataFrame(pkt_receive))
return;
}
// Pong frames need no further processing. All successfully handled frames
// advance by the same parsed length.
offset += (uint)wsPacketLength;
if (offset == msg.Length)
{
Receiver?.NetworkReceive(this, receiveNetworkBuffer);
DeliverReceivedData();
return;
}
wsPacketLength = pkt_receive.Parse(msg, offset, (uint)msg.Length);
if (!TryParseFrame(msg, offset, out wsPacketLength))
return;
}
if (wsPacketLength < 0)
@@ -339,13 +380,128 @@ public class WSocket : ISocket, INetworkReceiver<ISocket>
//Console.WriteLine("WS IN: " + receiveNetworkBuffer.Available);
Receiver?.NetworkReceive(this, receiveNetworkBuffer);
DeliverReceivedData();
if (buffer.Available > 0 && !buffer.Protected)
NetworkReceive(this, buffer);
}
private bool ProcessDataFrame(WebsocketPacket packet)
{
var payload = packet.Message ?? Array.Empty<byte>();
if (MaximumMessageLength > 0 && (ulong)payload.LongLength > MaximumMessageLength)
return RejectProtocol($"WebSocket message exceeds the {MaximumMessageLength}-byte limit.");
if (packet.Opcode == WebsocketPacket.WSOpcode.TextFrame
|| packet.Opcode == WebsocketPacket.WSOpcode.BinaryFrame)
{
if (fragmentedMessage)
return RejectProtocol("A new WebSocket data frame arrived before the fragmented message completed.");
if (packet.FIN)
{
receiveNetworkBuffer.Write(payload);
return true;
}
fragmentedMessage = true;
fragmentedMessageOpcode = packet.Opcode;
fragmentedMessageLength = 0;
fragmentedMessageBuffer.Read();
return AppendFragment(payload);
}
if (!fragmentedMessage)
return RejectProtocol("A WebSocket continuation frame arrived without an active fragmented message.");
if (!AppendFragment(payload))
return false;
if (!packet.FIN)
return true;
var message = fragmentedMessageBuffer.Read() ?? Array.Empty<byte>();
try
{
if (fragmentedMessageOpcode == WebsocketPacket.WSOpcode.TextFrame)
WebsocketPacket.ValidateTextPayload(message);
}
catch (InvalidDataException exception)
{
return RejectProtocol(exception.Message);
}
receiveNetworkBuffer.Write(message);
ResetFragmentedMessage();
return true;
}
private bool AppendFragment(byte[] payload)
{
var nextLength = fragmentedMessageLength + (ulong)payload.LongLength;
if (nextLength < fragmentedMessageLength
|| nextLength > int.MaxValue
|| (MaximumMessageLength > 0 && nextLength > MaximumMessageLength))
return RejectProtocol($"WebSocket fragmented message exceeds the {MaximumMessageLength}-byte limit.");
fragmentedMessageBuffer.Write(payload);
fragmentedMessageLength = nextLength;
return true;
}
private void DeliverReceivedData()
{
if (receiveNetworkBuffer != null && receiveNetworkBuffer.Available > 0)
Receiver?.NetworkReceive(this, receiveNetworkBuffer);
}
private bool RejectProtocol(string message)
{
Global.Log("WSocket", LogType.Warning, message);
ResetFragmentedMessage();
Close();
return false;
}
private void ResetFragmentedMessage()
{
fragmentedMessage = false;
fragmentedMessageLength = 0;
fragmentedMessageOpcode = default;
fragmentedMessageBuffer?.Read();
}
private void PrepareOutboundPacket(WebsocketPacket packet)
{
packet.Mask = !IsServer;
// RFC 6455 requires a fresh unpredictable masking key for every client
// frame. pkt_send is intentionally reused, so discard its previous key.
if (packet.Mask)
packet.MaskKey = null;
}
private bool TryParseFrame(byte[] message, uint offset, out long packetLength)
{
try
{
packetLength = pkt_receive.Parse(message, offset, (uint)message.Length);
return true;
}
catch (Exception exception) when (
exception is InvalidDataException ||
exception is ParserLimitException ||
exception is ArgumentException)
{
Global.Log(exception);
packetLength = 0;
Close();
return false;
}
}
public void NetworkConnect(ISocket sender)
{