diff --git a/server/Entrypoint.cs b/server/Entrypoint.cs
index eeeeaf1..0f44180 100644
--- a/server/Entrypoint.cs
+++ b/server/Entrypoint.cs
@@ -18,6 +18,9 @@ namespace CircleScape {
// logic processing loop
}
+
+ server.Stop();
+ PoolManager.Dispose();
}
}
}
diff --git a/server/Libraries/Kneesocks/Connection.cs b/server/Libraries/Kneesocks/Connection.cs
index bf43b2e..92c3e3c 100644
--- a/server/Libraries/Kneesocks/Connection.cs
+++ b/server/Libraries/Kneesocks/Connection.cs
@@ -9,7 +9,9 @@ using System.IO;
namespace Kneesocks {
public class Connection {
- public UInt64? _Id = null;
+ private bool Initialized = false;
+
+ private UInt64? _Id = null;
public UInt64 Id {
get {
if(_Id == null)
@@ -22,9 +24,14 @@ namespace Kneesocks {
_Id = value;
}
}
+ internal bool IsIdNull {
+ get {
+ return _Id == null;
+ }
+ }
- private TcpClient Socket;
- private NetworkStream Stream;
+ private TcpClient Socket = null;
+ private NetworkStream Stream = null;
ReadBuffer Buffer;
private byte[] FirstTwoBytes = null;
@@ -32,27 +39,54 @@ namespace Kneesocks {
private byte[] FrameHeader = null;
private List ReceiveFrameBuffer = new List();
private List SendFrameBuffer = new List();
+ private const int MaximumSendFrameSize = 0xFFFFF;
+ private Random Random = new Random();
+
+ protected const int PingInterval = 30;
+ protected const int TimeoutInterval = 120;
+ private byte[] PingData = Encoding.ASCII.GetBytes("woomy!");
private DateTime LastPing;
+ private bool AwaitingPingResponse = false;
+ private TimeSpan TimeSinceLastPing {
+ get {
+ return DateTime.UtcNow - LastPing;
+ }
+ }
+ internal bool OutsidePool = false;
public bool Disconnected { get; private set; } = false;
public string DisconnectReason { get; private set; } = null;
public bool Handshaked { get; private set; } = false;
public Handshake ClientHandshake { get; private set; } = null;
- public Connection(TcpClient sock) {
+ public void Initialize(TcpClient sock) {
+ if(Initialized)
+ return;
+
Socket = sock;
Socket.ReceiveTimeout = 1;
Stream = sock.GetStream();
Buffer = new ReadBuffer(Stream);
+
+ Initialized = true;
}
- public Connection(UInt64 id, TcpClient sock) : this(sock) {
+ public void Initialize(UInt64 id, TcpClient sock) {
+ if(Initialized)
+ return;
+
+ Initialize(sock);
Id = id;
+
+ Initialized = true;
}
- public Connection(Connection conn, bool preserveId = true) {
+ public void Initialize(Connection conn, bool preserveId = false) {
+ if(Initialized)
+ return;
+
if(preserveId)
_Id = conn._Id;
@@ -65,22 +99,25 @@ namespace Kneesocks {
FrameHeader = conn.FrameHeader;
ReceiveFrameBuffer = conn.ReceiveFrameBuffer;
+ LastPing = conn.LastPing;
+
Disconnected = conn.Disconnected;
DisconnectReason = conn.DisconnectReason;
Handshaked = conn.Handshaked;
ClientHandshake = conn.ClientHandshake;
+
+ Initialized = true;
}
- private const int MaximumFrameSize = 0xFFFFF;
private void _Send(byte[] message, bool isFinal = true, bool singleFrame = false, bool first = false) {
- int frameCount = singleFrame ? 0 : (message.Length / MaximumFrameSize);
+ int frameCount = singleFrame ? 0 : (message.Length / MaximumSendFrameSize);
for(var i = 0; i <= frameCount; ++i) {
SendFrameBuffer.Add(new Frame {
IsFinal = (i == frameCount && isFinal) ? true : false,
IsMasked = false,
Opcode = (i == 0 || (singleFrame && first)) ? Frame.kOpcode.BinaryFrame : Frame.kOpcode.Continuation,
- Content = message.Subset(i * (MaximumFrameSize + 1), MaximumFrameSize)
+ Content = message.Subset(i * (MaximumSendFrameSize + 1), MaximumSendFrameSize)
});
}
}
@@ -96,15 +133,15 @@ namespace Kneesocks {
return;
bool firstRead = true;
- byte[] byteBuffer = new byte[MaximumFrameSize];
+ byte[] byteBuffer = new byte[MaximumSendFrameSize];
while(true) {
- var bytesRead = stream.Read(byteBuffer, 0, MaximumFrameSize);
+ var bytesRead = stream.Read(byteBuffer, 0, MaximumSendFrameSize);
if(stream.Position == stream.Length) {
- _Send(bytesRead == MaximumFrameSize ? byteBuffer : byteBuffer.Take(bytesRead).ToArray(), true, true, firstRead);
+ _Send(bytesRead == MaximumSendFrameSize ? byteBuffer : byteBuffer.Take(bytesRead).ToArray(), true, true, firstRead);
return;
} else {
- _Send(bytesRead == MaximumFrameSize ? byteBuffer : byteBuffer.Take(bytesRead).ToArray(), false, true, firstRead);
+ _Send(bytesRead == MaximumSendFrameSize ? byteBuffer : byteBuffer.Take(bytesRead).ToArray(), false, true, firstRead);
}
firstRead = false;
@@ -133,9 +170,25 @@ namespace Kneesocks {
buffer = buffer == null ? Buffer.AttemptRead(terminator)
: buffer;
}
-
- public void Parse() {
+
+ internal void Parse() {
if(Handshaked) {
+ if(!Buffer.IsReading) {
+ if(TimeSinceLastPing.Seconds > TimeoutInterval) {
+ Disconnect(Frame.kClosingReason.Normal, "Ping response timed out.");
+ } else if(TimeSinceLastPing.Seconds > PingInterval && !AwaitingPingResponse) {
+ var frameBytes = new Frame {
+ IsFinal = true,
+ IsMasked = false,
+ Opcode = Frame.kOpcode.Ping,
+ Content = PingData
+ }.GetBytes();
+
+ Stream.Write(frameBytes, 0, frameBytes.Length);
+ AwaitingPingResponse = true;
+ }
+ }
+
lock(SendFrameBuffer) {
if(SendFrameBuffer.Count > 0) {
foreach(var frame in SendFrameBuffer) {
@@ -172,6 +225,7 @@ namespace Kneesocks {
Stream.Write(response, 0, response.Length);
ClientHandshake = request;
Handshaked = true;
+
LastPing = DateTime.UtcNow;
} catch(Exception e) {
Disconnect(Frame.kClosingReason.ProtocolError, e.Message);
@@ -238,7 +292,8 @@ namespace Kneesocks {
if(tempFrame.IsFinal) {
switch(tempFrame.Opcode) {
case Frame.kOpcode.Ping:
- LastPing = DateTime.Now;
+ LastPing = DateTime.UtcNow;
+ AwaitingPingResponse = false;
tempFrame.Opcode = Frame.kOpcode.Pong;
var pingBuffer = tempFrame.GetBytes();
@@ -246,24 +301,33 @@ namespace Kneesocks {
break;
case Frame.kOpcode.Pong:
- LastPing = DateTime.Now;
+ LastPing = DateTime.UtcNow;
+ AwaitingPingResponse = false;
break;
case Frame.kOpcode.Close:
Disconnect(Frame.kClosingReason.Normal, "Connection closed.");
break;
+
+ case Frame.kOpcode.BinaryFrame:
+ case Frame.kOpcode.TextFrame:
+ case Frame.kOpcode.Continuation:
+ byte[] byteBuffer = new byte[0];
+ foreach(var frame in ReceiveFrameBuffer)
+ byteBuffer = byteBuffer.Concat(frame.Content).ToArray();
+
+ ReceiveFrameBuffer = new List();
+ OnReceive(byteBuffer);
+ break;
}
-
- byte[] byteBuffer = new byte[0];
- foreach(var frame in ReceiveFrameBuffer)
- byteBuffer = byteBuffer.Concat(frame.Content).ToArray();
-
- ReceiveFrameBuffer = new List();
- OnReceive(byteBuffer);
}
}
}
+ public void RemoveFromPool() {
+ OutsidePool = true;
+ }
+
public void Disconnect(string reason = null) {
Disconnect(Frame.kClosingReason.Normal, reason);
}
diff --git a/server/Libraries/Kneesocks/Pool.cs b/server/Libraries/Kneesocks/Pool.cs
index a400715..9f0d9ca 100644
--- a/server/Libraries/Kneesocks/Pool.cs
+++ b/server/Libraries/Kneesocks/Pool.cs
@@ -26,6 +26,7 @@ namespace Kneesocks {
// 0 means never redistribute
public int Tolerance { get; set; } = 0;
+ private bool Disposed = false;
private int _fullThreadCount;
private volatile bool updateFullThreadCount = true;
@@ -41,42 +42,59 @@ namespace Kneesocks {
CreateThread(runWithNoClients: true);
}
- private void IndexConnection(UInt64 id, T connection) {
- lock(Connections) {
- if(id == 0)
- id = (ulong)Interlocked.Increment(ref InternalCounter);
-
- connection.Id = id;
- Connections.Add(id, connection);
+ public T this[UInt64 id] {
+ get {
+ if(HasConnection(id))
+ return Connections[id];
+ else return null;
}
}
- public void InvalidateConnection(UInt64 id) {
+ public bool HasConnection(UInt64 id) {
+ return Connections.ContainsKey(id);
+ }
+
+ private void IndexConnection(T connection) {
+ lock(Connections) {
+ if(connection.IsIdNull)
+ connection.Id = (ulong)Interlocked.Increment(ref InternalCounter);
+
+ Connections.Add(connection.Id, connection);
+ }
+ }
+
+ internal void InvalidateConnection(UInt64 id) {
lock(Connections) {
Connections.Remove(id);
}
}
public void Broadcast(byte[] message) {
+ if(Disposed)
+ return;
+
lock(Connections) {
foreach(var conn in Connections)
conn.Value.Send(message);
}
}
- public bool AddConnection(T connection, UInt64 id = 0) {
+ public bool AddConnection(T connection) {
+ if(Disposed)
+ return false;
+
lock(Threads) {
foreach(var thread in Threads) {
if(thread.Stack.Count < FullThreadSize) {
thread.Stack.AddClient(connection);
- IndexConnection(id, connection);
+ IndexConnection(connection);
return true;
}
}
if(MaxCount == 0 || Threads.Count < MaxCount) {
CreateThread(connection);
- IndexConnection(id, connection);
+ IndexConnection(connection);
return true;
}
}
@@ -84,7 +102,7 @@ namespace Kneesocks {
return false;
}
- public void InvalidateThread(Stack stackRef) {
+ internal void InvalidateThread(Stack stackRef) {
lock(Threads) {
var ctx = Threads.FirstOrDefault(x => Object.ReferenceEquals(x.Stack, stackRef));
if(ctx != null) {
@@ -120,6 +138,37 @@ namespace Kneesocks {
}
}
+ public void Dispose() {
+ if(Disposed)
+ return;
+
+ Disposed = true;
+
+ lock(Threads) {
+ foreach(var thread in Threads)
+ thread.Stack.StopThread();
+ }
+
+ while(true) {
+ Thread.Sleep(100);
+
+ lock(Threads) {
+ if(Threads.Count == 0)
+ break;
+ }
+ }
+
+ lock(Connections) {
+ foreach(var conn in Connections) {
+ conn.Value.Disconnect(Frame.kClosingReason.Normal, "Server shutting down.");
+ }
+ }
+ }
+
+ ~Pool() {
+ Dispose();
+ }
+
class ThreadContext {
public Thread Thread { get; set; }
public Stack Stack { get; set; }
diff --git a/server/Libraries/Kneesocks/Server.cs b/server/Libraries/Kneesocks/Server.cs
index b7ebc10..0c929c6 100644
--- a/server/Libraries/Kneesocks/Server.cs
+++ b/server/Libraries/Kneesocks/Server.cs
@@ -7,11 +7,11 @@ using System.Net.Sockets;
using System.Net;
namespace Kneesocks {
- public class Server where T : Connection {
+ public class Server where T : Connection, new() {
private TcpListener Socket;
private Thread Listener = null;
private Pool ConnectionPool = null;
- private bool Started = false;
+ public bool Started { get; private set; } = false;
public UInt16 Port { get; private set; }
public Server(UInt16 port, Pool pool) {
@@ -39,8 +39,11 @@ namespace Kneesocks {
Socket.Start();
while(Started) {
- if(Socket.Pending())
- ConnectionPool.AddConnection((T)new Connection(Socket.AcceptTcpClient()));
+ if(Socket.Pending()) {
+ var templatedConnection = new T();
+ templatedConnection.Initialize(Socket.AcceptTcpClient());
+ ConnectionPool.AddConnection(templatedConnection);
+ }
Thread.Sleep(100);
}
diff --git a/server/Libraries/Kneesocks/Stack.cs b/server/Libraries/Kneesocks/Stack.cs
index e01e47f..7575a38 100644
--- a/server/Libraries/Kneesocks/Stack.cs
+++ b/server/Libraries/Kneesocks/Stack.cs
@@ -6,12 +6,14 @@ using System.Text;
using System.Threading;
namespace Kneesocks {
- public class Stack where T : Connection {
+ internal class Stack where T : Connection {
private Pool PoolRef = null;
private List Clients = new List();
private bool RunWithNoClients = false;
private bool Running = true;
+ public bool Finished { get; private set; } = false;
+
public Stack(Pool poolRef, T initialConnection = null) {
PoolRef = poolRef;
if(initialConnection != null)
@@ -36,31 +38,26 @@ namespace Kneesocks {
}
}
- public void StopThread() {
+ internal void StopThread() {
Running = false;
}
- public bool Finished { get; private set; }
-
- public bool UnlistConnection(Connection connection) {
- lock(Clients) {
- foreach(var conn in Clients) {
-
- }
- }
+ private bool CheckIfConnected(T client) {
+ return !client.Disconnected && !client.OutsidePool;
}
-
- // USED FOR THREADING -- DO NOT CALL
+
public void ManageStack() {
while(Running && (Count > 0 || RunWithNoClients)) {
lock(Clients) {
for(var i = Count - 1; i >= 0 && Running; --i) {
var client = Clients[i];
- var connected = !client.Disconnected;
+ var connected = CheckIfConnected(client);
if(connected) {
try {
client.Parse();
+ if(CheckIfConnected(client))
+ connected = false;
} catch {
connected = false;
}
diff --git a/server/Libraries/Square/ArrayExtensions.cs b/server/Libraries/Square/ArrayExtensions.cs
index 481a49e..0a33d78 100644
--- a/server/Libraries/Square/ArrayExtensions.cs
+++ b/server/Libraries/Square/ArrayExtensions.cs
@@ -31,7 +31,7 @@ namespace Square {
return BitConverter.ToString(bytes).Replace("-", "");
}
- public static string ToString(this byte[] bytes, bool isUtf8 = true) {
+ public static string GetString(this byte[] bytes, bool isUtf8 = true) {
return isUtf8 ? Encoding.UTF8.GetString(bytes)
: Encoding.ASCII.GetString(bytes);
}
diff --git a/server/Socks/ActiveConnection.cs b/server/Socks/ActiveConnection.cs
index 6c56e91..c3b475a 100644
--- a/server/Socks/ActiveConnection.cs
+++ b/server/Socks/ActiveConnection.cs
@@ -7,7 +7,6 @@ using System.Threading.Tasks;
namespace CircleScape {
class ActiveConnection : Kneesocks.Connection {
- public ActiveConnection(UInt32 id, TcpClient sock) : base(id, sock) { }
- public ActiveConnection(PendingConnection conn) : base(conn) { }
+
}
}
diff --git a/server/Socks/PendingConnection.cs b/server/Socks/PendingConnection.cs
index 3a6375a..fa929da 100644
--- a/server/Socks/PendingConnection.cs
+++ b/server/Socks/PendingConnection.cs
@@ -4,10 +4,25 @@ using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
+using Square;
+using Kneesocks;
namespace CircleScape {
- class PendingConnection : Kneesocks.Connection {
- public PendingConnection(UInt32 id, TcpClient sock) : base(id, sock) { }
-
+ class PendingConnection : Connection {
+ private DateTime ConnectionOpened;
+
+ protected override void OnOpen() {
+ ConnectionOpened = DateTime.UtcNow;
+ }
+
+ protected override void OnParse() {
+ if((DateTime.UtcNow - ConnectionOpened).Seconds > 60) {
+ Disconnect(Frame.kClosingReason.ProtocolError, "Logon request timed out.");
+ }
+ }
+
+ protected override void OnReceive(byte[] data) {
+ Console.WriteLine(Id + " says " + data.GetString());
+ }
}
}
diff --git a/server/Socks/PoolManager.cs b/server/Socks/PoolManager.cs
index fa406da..ebc6dc7 100644
--- a/server/Socks/PoolManager.cs
+++ b/server/Socks/PoolManager.cs
@@ -32,5 +32,10 @@ namespace CircleScape {
ActiveConnectionsPool = new Pool();
}
+
+ public static void Dispose() {
+ PendingConnectionsPool.Dispose();
+ ActiveConnectionsPool.Dispose();
+ }
}
}