mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-07-31 01:40:42 +00:00
Permissions, RateControl and Auditing
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user