Added AesPassword
This commit is contained in:
parent
bdf2d1b935
commit
9564e2002d
|
@ -1,27 +1,27 @@
|
|||
using EonaCat.Connections;
|
||||
using EonaCat.Connections.Models;
|
||||
using System.Text;
|
||||
|
||||
namespace EonaCat.Connections.Client.Example
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
private static NetworkClient _client;
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
await CreateClientAsync().ConfigureAwait(false);
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.Write("Enter message to send (or 'exit' to quit): ");
|
||||
var message = Console.ReadLine();
|
||||
if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await _client.DisconnectAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
using EonaCat.Connections;
|
||||
using EonaCat.Connections.Models;
|
||||
using System.Text;
|
||||
|
||||
namespace EonaCat.Connections.Client.Example
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
private static NetworkClient _client;
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
await CreateClientAsync().ConfigureAwait(false);
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.Write("Enter message to send (or 'exit' to quit): ");
|
||||
var message = Console.ReadLine();
|
||||
if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await _client.DisconnectAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
var jsonUrl = "https://microsoftedge.github.io/Demos/json-dummy-data/5MB-min.json";
|
||||
|
||||
try
|
||||
|
@ -35,46 +35,47 @@ namespace EonaCat.Connections.Client.Example
|
|||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to download large JSON file: {ex.Message}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
await _client.SendAsync(message).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task CreateClientAsync()
|
||||
{
|
||||
var config = new Configuration
|
||||
{
|
||||
Protocol = ProtocolType.TCP,
|
||||
Host = "127.0.0.1",
|
||||
Port = 1111,
|
||||
UseSsl = false,
|
||||
UseAesEncryption = false,
|
||||
//ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"),
|
||||
};
|
||||
|
||||
_client = new NetworkClient(config);
|
||||
|
||||
// Subscribe to events
|
||||
_client.OnConnected += (sender, e) =>
|
||||
Console.WriteLine($"Connected to server at {e.RemoteEndPoint}");
|
||||
|
||||
_client.OnDataReceived += (sender, e) =>
|
||||
Console.WriteLine($"Server says: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}");
|
||||
|
||||
_client.OnDisconnected += (sender, e) =>
|
||||
Console.WriteLine("Disconnected from server");
|
||||
|
||||
await _client.ConnectAsync();
|
||||
|
||||
// Send nickname
|
||||
await _client.SendNicknameAsync("TestUser");
|
||||
|
||||
// Send a message
|
||||
await _client.SendAsync("Hello server!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
await _client.SendAsync(message).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task CreateClientAsync()
|
||||
{
|
||||
var config = new Configuration
|
||||
{
|
||||
Protocol = ProtocolType.TCP,
|
||||
Host = "127.0.0.1",
|
||||
Port = 1111,
|
||||
UseSsl = false,
|
||||
UseAesEncryption = true,
|
||||
AesPassword = "p@ss",
|
||||
//ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"),
|
||||
};
|
||||
|
||||
_client = new NetworkClient(config);
|
||||
|
||||
// Subscribe to events
|
||||
_client.OnConnected += (sender, e) =>
|
||||
Console.WriteLine($"Connected to server at {e.RemoteEndPoint}");
|
||||
|
||||
_client.OnDataReceived += (sender, e) =>
|
||||
Console.WriteLine($"Server says: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}");
|
||||
|
||||
_client.OnDisconnected += (sender, e) =>
|
||||
Console.WriteLine("Disconnected from server");
|
||||
|
||||
await _client.ConnectAsync();
|
||||
|
||||
// Send nickname
|
||||
await _client.SendNicknameAsync("TestUser");
|
||||
|
||||
// Send a message
|
||||
await _client.SendAsync("Hello server!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,8 +37,9 @@ namespace EonaCat.Connections.Server.Example
|
|||
Protocol = ProtocolType.TCP,
|
||||
Port = 1111,
|
||||
UseSsl = false,
|
||||
UseAesEncryption = false,
|
||||
UseAesEncryption = true,
|
||||
MaxConnections = 100000,
|
||||
AesPassword = "p@ss",
|
||||
//ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("server.pfx", "p@ss"),
|
||||
};
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace EonaCat.Connections.Helpers
|
|||
/// <param name="stream"></param>
|
||||
/// <param name="aes"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Aes> SendAesKeyAsync(Stream stream, Aes aes)
|
||||
public static async Task<Aes> SendAesKeyAsync(Stream stream, Aes aes, string password = null)
|
||||
{
|
||||
var rawKey = aes.Key;
|
||||
var iv = aes.IV;
|
||||
|
@ -29,8 +29,12 @@ namespace EonaCat.Connections.Helpers
|
|||
await WriteBytesWithLengthAsync(stream, salt);
|
||||
await stream.FlushAsync();
|
||||
|
||||
// Derive stronger key using PBKDF2-SHA256 + salt + pepper
|
||||
var derivedKey = PBKDF2_SHA256(Combine(rawKey, Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32);
|
||||
// Derive key using PBKDF2-SHA256 + salt + password + pepper
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
password = "EonaCat.Connections";
|
||||
}
|
||||
var derivedKey = PBKDF2_SHA256(Combine(Combine(rawKey, Encoding.UTF8.GetBytes(password)), Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32);
|
||||
aes.Key = derivedKey;
|
||||
|
||||
return aes;
|
||||
|
@ -41,13 +45,19 @@ namespace EonaCat.Connections.Helpers
|
|||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Aes> ReceiveAesKeyAsync(Stream stream)
|
||||
public static async Task<Aes> ReceiveAesKeyAsync(Stream stream, string password = null)
|
||||
{
|
||||
var rawKey = await ReadBytesWithLengthAsync(stream);
|
||||
var iv = await ReadBytesWithLengthAsync(stream);
|
||||
var salt = await ReadBytesWithLengthAsync(stream);
|
||||
|
||||
var derivedKey = PBKDF2_SHA256(Combine(rawKey, Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32);
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
password = "EonaCat.Connections";
|
||||
}
|
||||
|
||||
// Derived key using PBKDF2-SHA256 + salt + password + pepper
|
||||
var derivedKey = PBKDF2_SHA256(Combine(Combine(rawKey, Encoding.UTF8.GetBytes(password)), Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32);
|
||||
|
||||
Aes _aesEncryption = Aes.Create();
|
||||
_aesEncryption.Key = derivedKey;
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace EonaCat.Connections.Models
|
|||
public bool EnableNagle { get; set; } = false;
|
||||
|
||||
// For testing purposes, allow self-signed certificates
|
||||
public bool IsSelfSignedEnabled { get; set; } = true;
|
||||
public bool IsSelfSignedEnabled { get; set; } = true;
|
||||
public string AesPassword { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,148 +1,148 @@
|
|||
using EonaCat.Connections.EventArguments;
|
||||
using EonaCat.Connections.Helpers;
|
||||
using EonaCat.Connections.Models;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs;
|
||||
|
||||
namespace EonaCat.Connections
|
||||
{
|
||||
public class NetworkClient
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private TcpClient _tcpClient;
|
||||
private UdpClient _udpClient;
|
||||
private Stream _stream;
|
||||
private Aes _aesEncryption;
|
||||
private CancellationTokenSource _cancellation;
|
||||
private bool _isConnected;
|
||||
|
||||
public event EventHandler<ConnectionEventArgs> OnConnected;
|
||||
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
|
||||
public event EventHandler<ConnectionEventArgs> OnDisconnected;
|
||||
public event EventHandler<ErrorEventArgs> OnSslError;
|
||||
public event EventHandler<ErrorEventArgs> OnEncryptionError;
|
||||
public event EventHandler<ErrorEventArgs> OnGeneralError;
|
||||
|
||||
public NetworkClient(Configuration config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
_cancellation = new CancellationTokenSource();
|
||||
|
||||
if (_config.Protocol == ProtocolType.TCP)
|
||||
{
|
||||
await ConnectTcpAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ConnectUdp();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectTcpAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_tcpClient = new TcpClient();
|
||||
await _tcpClient.ConnectAsync(_config.Host, _config.Port);
|
||||
|
||||
Stream stream = _tcpClient.GetStream();
|
||||
|
||||
// Setup SSL if required
|
||||
if (_config.UseSsl)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback);
|
||||
await sslStream.AuthenticateAsClientAsync(_config.Host);
|
||||
stream = sslStream;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnSslError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "SSL authentication failed" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup AES encryption if required
|
||||
if (_config.UseAesEncryption)
|
||||
{
|
||||
try
|
||||
{
|
||||
_aesEncryption = await AesKeyExchange.ReceiveAesKeyAsync(stream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnEncryptionError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "AES setup failed" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_stream = stream;
|
||||
_isConnected = true;
|
||||
|
||||
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) });
|
||||
|
||||
// Start receiving data
|
||||
_ = Task.Run(() => ReceiveDataAsync(), _cancellation.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect" });
|
||||
}
|
||||
}
|
||||
|
||||
private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (_config.IsSelfSignedEnabled)
|
||||
{
|
||||
return true; // Accept self-signed certificates
|
||||
}
|
||||
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
{
|
||||
return true; // Certificate is valid
|
||||
}
|
||||
|
||||
// Log or handle the SSL error as needed
|
||||
OnSslError?.Invoke(this, new ErrorEventArgs
|
||||
{
|
||||
Exception = new AuthenticationException("SSL certificate validation failed"),
|
||||
Message = $"SSL Policy Errors: {sslPolicyErrors}"
|
||||
});
|
||||
return false; // Reject the certificate
|
||||
}
|
||||
|
||||
private async Task ConnectUdp()
|
||||
{
|
||||
try
|
||||
{
|
||||
_udpClient = new UdpClient();
|
||||
_udpClient.Connect(_config.Host, _config.Port);
|
||||
_isConnected = true;
|
||||
|
||||
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) });
|
||||
|
||||
// Start receiving data
|
||||
_ = Task.Run(() => ReceiveUdpDataAsync(), _cancellation.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect UDP" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
using EonaCat.Connections.EventArguments;
|
||||
using EonaCat.Connections.Helpers;
|
||||
using EonaCat.Connections.Models;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs;
|
||||
|
||||
namespace EonaCat.Connections
|
||||
{
|
||||
public class NetworkClient
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private TcpClient _tcpClient;
|
||||
private UdpClient _udpClient;
|
||||
private Stream _stream;
|
||||
private Aes _aesEncryption;
|
||||
private CancellationTokenSource _cancellation;
|
||||
private bool _isConnected;
|
||||
|
||||
public event EventHandler<ConnectionEventArgs> OnConnected;
|
||||
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
|
||||
public event EventHandler<ConnectionEventArgs> OnDisconnected;
|
||||
public event EventHandler<ErrorEventArgs> OnSslError;
|
||||
public event EventHandler<ErrorEventArgs> OnEncryptionError;
|
||||
public event EventHandler<ErrorEventArgs> OnGeneralError;
|
||||
|
||||
public NetworkClient(Configuration config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
_cancellation = new CancellationTokenSource();
|
||||
|
||||
if (_config.Protocol == ProtocolType.TCP)
|
||||
{
|
||||
await ConnectTcpAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ConnectUdp();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectTcpAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_tcpClient = new TcpClient();
|
||||
await _tcpClient.ConnectAsync(_config.Host, _config.Port);
|
||||
|
||||
Stream stream = _tcpClient.GetStream();
|
||||
|
||||
// Setup SSL if required
|
||||
if (_config.UseSsl)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback);
|
||||
await sslStream.AuthenticateAsClientAsync(_config.Host);
|
||||
stream = sslStream;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnSslError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "SSL authentication failed" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup AES encryption if required
|
||||
if (_config.UseAesEncryption)
|
||||
{
|
||||
try
|
||||
{
|
||||
_aesEncryption = await AesKeyExchange.ReceiveAesKeyAsync(stream, _config.AesPassword);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnEncryptionError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "AES setup failed" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_stream = stream;
|
||||
_isConnected = true;
|
||||
|
||||
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) });
|
||||
|
||||
// Start receiving data
|
||||
_ = Task.Run(() => ReceiveDataAsync(), _cancellation.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect" });
|
||||
}
|
||||
}
|
||||
|
||||
private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (_config.IsSelfSignedEnabled)
|
||||
{
|
||||
return true; // Accept self-signed certificates
|
||||
}
|
||||
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
{
|
||||
return true; // Certificate is valid
|
||||
}
|
||||
|
||||
// Log or handle the SSL error as needed
|
||||
OnSslError?.Invoke(this, new ErrorEventArgs
|
||||
{
|
||||
Exception = new AuthenticationException("SSL certificate validation failed"),
|
||||
Message = $"SSL Policy Errors: {sslPolicyErrors}"
|
||||
});
|
||||
return false; // Reject the certificate
|
||||
}
|
||||
|
||||
private async Task ConnectUdp()
|
||||
{
|
||||
try
|
||||
{
|
||||
_udpClient = new UdpClient();
|
||||
_udpClient.Connect(_config.Host, _config.Port);
|
||||
_isConnected = true;
|
||||
|
||||
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) });
|
||||
|
||||
// Start receiving data
|
||||
_ = Task.Run(() => ReceiveUdpDataAsync(), _cancellation.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect UDP" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task ReceiveDataAsync()
|
||||
{
|
||||
while (!_cancellation.Token.IsCancellationRequested && _isConnected)
|
||||
|
@ -209,29 +209,29 @@ namespace EonaCat.Connections
|
|||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
private async Task ReceiveUdpDataAsync()
|
||||
{
|
||||
while (!_cancellation.Token.IsCancellationRequested && _isConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _udpClient.ReceiveAsync();
|
||||
await ProcessReceivedDataAsync(result.Buffer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving data" });
|
||||
_isConnected = false;
|
||||
|
||||
// Start reconnect
|
||||
_ = Task.Run(() => AutoReconnectAsync());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task ReceiveUdpDataAsync()
|
||||
{
|
||||
while (!_cancellation.Token.IsCancellationRequested && _isConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _udpClient.ReceiveAsync();
|
||||
await ProcessReceivedDataAsync(result.Buffer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving data" });
|
||||
_isConnected = false;
|
||||
|
||||
// Start reconnect
|
||||
_ = Task.Run(() => AutoReconnectAsync());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessReceivedDataAsync(byte[] data)
|
||||
{
|
||||
try
|
||||
|
@ -269,8 +269,8 @@ namespace EonaCat.Connections
|
|||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error processing data" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public async Task SendAsync(byte[] data)
|
||||
{
|
||||
if (!_isConnected) return;
|
||||
|
@ -310,99 +310,99 @@ namespace EonaCat.Connections
|
|||
else
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error sending data" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task SendAsync(string message)
|
||||
{
|
||||
await SendAsync(Encoding.UTF8.GetBytes(message));
|
||||
}
|
||||
|
||||
public async Task SendNicknameAsync(string nickname)
|
||||
{
|
||||
await SendAsync($"NICKNAME:{nickname}");
|
||||
}
|
||||
|
||||
private async Task<byte[]> EncryptDataAsync(byte[] data, Aes aes)
|
||||
{
|
||||
using (var encryptor = aes.CreateEncryptor())
|
||||
using (var ms = new MemoryStream())
|
||||
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
|
||||
{
|
||||
await cs.WriteAsync(data, 0, data.Length);
|
||||
cs.FlushFinalBlock();
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<byte[]> DecryptDataAsync(byte[] data, Aes aes)
|
||||
{
|
||||
using (var decryptor = aes.CreateDecryptor())
|
||||
using (var ms = new MemoryStream(data))
|
||||
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
|
||||
using (var result = new MemoryStream())
|
||||
{
|
||||
await cs.CopyToAsync(result);
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AutoReconnectAsync()
|
||||
{
|
||||
if (!_config.EnableAutoReconnect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int attempt = 0;
|
||||
|
||||
while (!_isConnected && (_config.MaxReconnectAttempts == 0 || attempt < _config.MaxReconnectAttempts))
|
||||
{
|
||||
attempt++;
|
||||
try
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Attempting to reconnect (Attempt {attempt})" });
|
||||
await ConnectAsync();
|
||||
|
||||
if (_isConnected)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Reconnected successfully after {attempt} attempt(s)" });
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore exceptions, we'll retry
|
||||
}
|
||||
|
||||
await Task.Delay(_config.ReconnectDelayMs);
|
||||
}
|
||||
|
||||
if (!_isConnected)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = "Failed to reconnect" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
_isConnected = false;
|
||||
_cancellation?.Cancel();
|
||||
_tcpClient?.Close();
|
||||
_udpClient?.Close();
|
||||
_stream?.Dispose();
|
||||
_aesEncryption?.Dispose();
|
||||
|
||||
OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self" });
|
||||
|
||||
_ = Task.Run(() => AutoReconnectAsync());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisconnectAsync().Wait();
|
||||
_cancellation?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task SendAsync(string message)
|
||||
{
|
||||
await SendAsync(Encoding.UTF8.GetBytes(message));
|
||||
}
|
||||
|
||||
public async Task SendNicknameAsync(string nickname)
|
||||
{
|
||||
await SendAsync($"NICKNAME:{nickname}");
|
||||
}
|
||||
|
||||
private async Task<byte[]> EncryptDataAsync(byte[] data, Aes aes)
|
||||
{
|
||||
using (var encryptor = aes.CreateEncryptor())
|
||||
using (var ms = new MemoryStream())
|
||||
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
|
||||
{
|
||||
await cs.WriteAsync(data, 0, data.Length);
|
||||
cs.FlushFinalBlock();
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<byte[]> DecryptDataAsync(byte[] data, Aes aes)
|
||||
{
|
||||
using (var decryptor = aes.CreateDecryptor())
|
||||
using (var ms = new MemoryStream(data))
|
||||
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
|
||||
using (var result = new MemoryStream())
|
||||
{
|
||||
await cs.CopyToAsync(result);
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AutoReconnectAsync()
|
||||
{
|
||||
if (!_config.EnableAutoReconnect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int attempt = 0;
|
||||
|
||||
while (!_isConnected && (_config.MaxReconnectAttempts == 0 || attempt < _config.MaxReconnectAttempts))
|
||||
{
|
||||
attempt++;
|
||||
try
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Attempting to reconnect (Attempt {attempt})" });
|
||||
await ConnectAsync();
|
||||
|
||||
if (_isConnected)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Reconnected successfully after {attempt} attempt(s)" });
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore exceptions, we'll retry
|
||||
}
|
||||
|
||||
await Task.Delay(_config.ReconnectDelayMs);
|
||||
}
|
||||
|
||||
if (!_isConnected)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = "Failed to reconnect" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
_isConnected = false;
|
||||
_cancellation?.Cancel();
|
||||
_tcpClient?.Close();
|
||||
_udpClient?.Close();
|
||||
_stream?.Dispose();
|
||||
_aesEncryption?.Dispose();
|
||||
|
||||
OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self" });
|
||||
|
||||
_ = Task.Run(() => AutoReconnectAsync());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisconnectAsync().Wait();
|
||||
_cancellation?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,248 +1,248 @@
|
|||
using EonaCat.Connections.EventArguments;
|
||||
using EonaCat.Connections.Helpers;
|
||||
using EonaCat.Connections.Models;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs;
|
||||
|
||||
namespace EonaCat.Connections
|
||||
{
|
||||
public class NetworkServer
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private readonly Stats _stats;
|
||||
private readonly ConcurrentDictionary<string, Connection> _clients;
|
||||
private TcpListener _tcpListener;
|
||||
private UdpClient _udpListener;
|
||||
private CancellationTokenSource _serverCancellation;
|
||||
private readonly object _statsLock = new object();
|
||||
|
||||
public event EventHandler<ConnectionEventArgs> OnConnected;
|
||||
public event EventHandler<NicknameConnectionEventArgs> OnConnectedWithNickname;
|
||||
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
|
||||
public event EventHandler<ConnectionEventArgs> OnDisconnected;
|
||||
public event EventHandler<ErrorEventArgs> OnSslError;
|
||||
public event EventHandler<ErrorEventArgs> OnEncryptionError;
|
||||
public event EventHandler<ErrorEventArgs> OnGeneralError;
|
||||
|
||||
public NetworkServer(Configuration config)
|
||||
{
|
||||
_config = config;
|
||||
_stats = new Stats { StartTime = DateTime.UtcNow };
|
||||
_clients = new ConcurrentDictionary<string, Connection>();
|
||||
}
|
||||
|
||||
public Stats GetStats()
|
||||
{
|
||||
lock (_statsLock)
|
||||
{
|
||||
_stats.ActiveConnections = _clients.Count;
|
||||
return _stats;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
_serverCancellation = new CancellationTokenSource();
|
||||
|
||||
if (_config.Protocol == ProtocolType.TCP)
|
||||
{
|
||||
await StartTcpServerAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await StartUdpServerAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartTcpServerAsync()
|
||||
{
|
||||
_tcpListener = new TcpListener(IPAddress.Parse(_config.Host), _config.Port);
|
||||
_tcpListener.Start();
|
||||
|
||||
Console.WriteLine($"TCP Server started on {_config.Host}:{_config.Port}");
|
||||
|
||||
while (!_serverCancellation.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tcpClient = await _tcpListener.AcceptTcpClientAsync();
|
||||
_ = Task.Run(() => HandleTcpClientAsync(tcpClient), _serverCancellation.Token);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error accepting TCP client" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartUdpServerAsync()
|
||||
{
|
||||
_udpListener = new UdpClient(_config.Port);
|
||||
Console.WriteLine($"UDP Server started on {_config.Host}:{_config.Port}");
|
||||
|
||||
while (!_serverCancellation.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _udpListener.ReceiveAsync();
|
||||
_ = Task.Run(() => HandleUdpDataAsync(result), _serverCancellation.Token);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving UDP data" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleTcpClientAsync(TcpClient tcpClient)
|
||||
{
|
||||
var clientId = Guid.NewGuid().ToString();
|
||||
var client = new Connection
|
||||
{
|
||||
Id = clientId,
|
||||
TcpClient = tcpClient,
|
||||
RemoteEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint,
|
||||
ConnectedAt = DateTime.UtcNow,
|
||||
CancellationToken = new CancellationTokenSource()
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Configure TCP client
|
||||
tcpClient.NoDelay = !_config.EnableNagle;
|
||||
if (_config.EnableKeepAlive)
|
||||
{
|
||||
tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
|
||||
}
|
||||
|
||||
Stream stream = tcpClient.GetStream();
|
||||
|
||||
// Setup SSL if required
|
||||
if (_config.UseSsl)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback);
|
||||
await sslStream.AuthenticateAsServerAsync(_config.ServerCertificate, false, SslProtocols.Tls12, false);
|
||||
stream = sslStream;
|
||||
client.IsSecure = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnSslError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "SSL authentication failed" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup AES encryption if required
|
||||
if (_config.UseAesEncryption)
|
||||
{
|
||||
try
|
||||
{
|
||||
client.AesEncryption = Aes.Create();
|
||||
client.AesEncryption.GenerateKey();
|
||||
client.AesEncryption.GenerateIV();
|
||||
client.IsEncrypted = true;
|
||||
|
||||
// Securely send raw AES key + IV + salt
|
||||
await AesKeyExchange.SendAesKeyAsync(stream, client.AesEncryption);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnEncryptionError?.Invoke(this, new ErrorEventArgs
|
||||
{
|
||||
ClientId = clientId,
|
||||
Exception = ex,
|
||||
Message = "AES setup failed"
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
client.Stream = stream;
|
||||
_clients[clientId] = client;
|
||||
|
||||
lock (_statsLock)
|
||||
{
|
||||
_stats.TotalConnections++;
|
||||
}
|
||||
|
||||
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint });
|
||||
|
||||
// Handle client communication
|
||||
await HandleClientCommunicationAsync(client);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error handling TCP client" });
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DisconnectClientAsync(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (_config.IsSelfSignedEnabled)
|
||||
{
|
||||
return true; // Accept self-signed certificates
|
||||
}
|
||||
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
{
|
||||
return true; // Certificate is valid
|
||||
}
|
||||
|
||||
// Log or handle the SSL error as needed
|
||||
OnSslError?.Invoke(this, new ErrorEventArgs
|
||||
{
|
||||
Exception = new AuthenticationException("SSL certificate validation failed"),
|
||||
Message = $"SSL Policy Errors: {sslPolicyErrors}"
|
||||
});
|
||||
return false; // Reject the certificate
|
||||
}
|
||||
|
||||
private async Task HandleUdpDataAsync(UdpReceiveResult result)
|
||||
{
|
||||
var clientKey = result.RemoteEndPoint.ToString();
|
||||
|
||||
if (!_clients.TryGetValue(clientKey, out var client))
|
||||
{
|
||||
client = new Connection
|
||||
{
|
||||
Id = clientKey,
|
||||
RemoteEndPoint = result.RemoteEndPoint,
|
||||
ConnectedAt = DateTime.UtcNow
|
||||
};
|
||||
_clients[clientKey] = client;
|
||||
|
||||
lock (_statsLock)
|
||||
{
|
||||
_stats.TotalConnections++;
|
||||
}
|
||||
|
||||
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientKey, RemoteEndPoint = result.RemoteEndPoint });
|
||||
}
|
||||
|
||||
await ProcessReceivedDataAsync(client, result.Buffer);
|
||||
}
|
||||
|
||||
using EonaCat.Connections.EventArguments;
|
||||
using EonaCat.Connections.Helpers;
|
||||
using EonaCat.Connections.Models;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs;
|
||||
|
||||
namespace EonaCat.Connections
|
||||
{
|
||||
public class NetworkServer
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private readonly Stats _stats;
|
||||
private readonly ConcurrentDictionary<string, Connection> _clients;
|
||||
private TcpListener _tcpListener;
|
||||
private UdpClient _udpListener;
|
||||
private CancellationTokenSource _serverCancellation;
|
||||
private readonly object _statsLock = new object();
|
||||
|
||||
public event EventHandler<ConnectionEventArgs> OnConnected;
|
||||
public event EventHandler<NicknameConnectionEventArgs> OnConnectedWithNickname;
|
||||
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
|
||||
public event EventHandler<ConnectionEventArgs> OnDisconnected;
|
||||
public event EventHandler<ErrorEventArgs> OnSslError;
|
||||
public event EventHandler<ErrorEventArgs> OnEncryptionError;
|
||||
public event EventHandler<ErrorEventArgs> OnGeneralError;
|
||||
|
||||
public NetworkServer(Configuration config)
|
||||
{
|
||||
_config = config;
|
||||
_stats = new Stats { StartTime = DateTime.UtcNow };
|
||||
_clients = new ConcurrentDictionary<string, Connection>();
|
||||
}
|
||||
|
||||
public Stats GetStats()
|
||||
{
|
||||
lock (_statsLock)
|
||||
{
|
||||
_stats.ActiveConnections = _clients.Count;
|
||||
return _stats;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
_serverCancellation = new CancellationTokenSource();
|
||||
|
||||
if (_config.Protocol == ProtocolType.TCP)
|
||||
{
|
||||
await StartTcpServerAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await StartUdpServerAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartTcpServerAsync()
|
||||
{
|
||||
_tcpListener = new TcpListener(IPAddress.Parse(_config.Host), _config.Port);
|
||||
_tcpListener.Start();
|
||||
|
||||
Console.WriteLine($"TCP Server started on {_config.Host}:{_config.Port}");
|
||||
|
||||
while (!_serverCancellation.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tcpClient = await _tcpListener.AcceptTcpClientAsync();
|
||||
_ = Task.Run(() => HandleTcpClientAsync(tcpClient), _serverCancellation.Token);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error accepting TCP client" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartUdpServerAsync()
|
||||
{
|
||||
_udpListener = new UdpClient(_config.Port);
|
||||
Console.WriteLine($"UDP Server started on {_config.Host}:{_config.Port}");
|
||||
|
||||
while (!_serverCancellation.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _udpListener.ReceiveAsync();
|
||||
_ = Task.Run(() => HandleUdpDataAsync(result), _serverCancellation.Token);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving UDP data" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleTcpClientAsync(TcpClient tcpClient)
|
||||
{
|
||||
var clientId = Guid.NewGuid().ToString();
|
||||
var client = new Connection
|
||||
{
|
||||
Id = clientId,
|
||||
TcpClient = tcpClient,
|
||||
RemoteEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint,
|
||||
ConnectedAt = DateTime.UtcNow,
|
||||
CancellationToken = new CancellationTokenSource()
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Configure TCP client
|
||||
tcpClient.NoDelay = !_config.EnableNagle;
|
||||
if (_config.EnableKeepAlive)
|
||||
{
|
||||
tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
|
||||
}
|
||||
|
||||
Stream stream = tcpClient.GetStream();
|
||||
|
||||
// Setup SSL if required
|
||||
if (_config.UseSsl)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback);
|
||||
await sslStream.AuthenticateAsServerAsync(_config.ServerCertificate, false, SslProtocols.Tls12, false);
|
||||
stream = sslStream;
|
||||
client.IsSecure = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnSslError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "SSL authentication failed" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup AES encryption if required
|
||||
if (_config.UseAesEncryption)
|
||||
{
|
||||
try
|
||||
{
|
||||
client.AesEncryption = Aes.Create();
|
||||
client.AesEncryption.GenerateKey();
|
||||
client.AesEncryption.GenerateIV();
|
||||
client.IsEncrypted = true;
|
||||
|
||||
// Securely send raw AES key + IV + salt
|
||||
await AesKeyExchange.SendAesKeyAsync(stream, client.AesEncryption, _config.AesPassword);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnEncryptionError?.Invoke(this, new ErrorEventArgs
|
||||
{
|
||||
ClientId = clientId,
|
||||
Exception = ex,
|
||||
Message = "AES setup failed"
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
client.Stream = stream;
|
||||
_clients[clientId] = client;
|
||||
|
||||
lock (_statsLock)
|
||||
{
|
||||
_stats.TotalConnections++;
|
||||
}
|
||||
|
||||
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint });
|
||||
|
||||
// Handle client communication
|
||||
await HandleClientCommunicationAsync(client);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error handling TCP client" });
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DisconnectClientAsync(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (_config.IsSelfSignedEnabled)
|
||||
{
|
||||
return true; // Accept self-signed certificates
|
||||
}
|
||||
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
{
|
||||
return true; // Certificate is valid
|
||||
}
|
||||
|
||||
// Log or handle the SSL error as needed
|
||||
OnSslError?.Invoke(this, new ErrorEventArgs
|
||||
{
|
||||
Exception = new AuthenticationException("SSL certificate validation failed"),
|
||||
Message = $"SSL Policy Errors: {sslPolicyErrors}"
|
||||
});
|
||||
return false; // Reject the certificate
|
||||
}
|
||||
|
||||
private async Task HandleUdpDataAsync(UdpReceiveResult result)
|
||||
{
|
||||
var clientKey = result.RemoteEndPoint.ToString();
|
||||
|
||||
if (!_clients.TryGetValue(clientKey, out var client))
|
||||
{
|
||||
client = new Connection
|
||||
{
|
||||
Id = clientKey,
|
||||
RemoteEndPoint = result.RemoteEndPoint,
|
||||
ConnectedAt = DateTime.UtcNow
|
||||
};
|
||||
_clients[clientKey] = client;
|
||||
|
||||
lock (_statsLock)
|
||||
{
|
||||
_stats.TotalConnections++;
|
||||
}
|
||||
|
||||
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientKey, RemoteEndPoint = result.RemoteEndPoint });
|
||||
}
|
||||
|
||||
await ProcessReceivedDataAsync(client, result.Buffer);
|
||||
}
|
||||
|
||||
private async Task HandleClientCommunicationAsync(Connection client)
|
||||
{
|
||||
var lengthBuffer = new byte[4]; // length prefix
|
||||
|
@ -312,8 +312,8 @@ namespace EonaCat.Connections
|
|||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private async Task ProcessReceivedDataAsync(Connection client, byte[] data)
|
||||
{
|
||||
try
|
||||
|
@ -367,36 +367,36 @@ namespace EonaCat.Connections
|
|||
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error processing data" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task SendToClientAsync(string clientId, byte[] data)
|
||||
{
|
||||
if (_clients.TryGetValue(clientId, out var client))
|
||||
{
|
||||
await SendDataAsync(client, data);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendToClientAsync(string clientId, string message)
|
||||
{
|
||||
await SendToClientAsync(clientId, Encoding.UTF8.GetBytes(message));
|
||||
}
|
||||
|
||||
public async Task BroadcastAsync(byte[] data)
|
||||
{
|
||||
var tasks = new List<Task>();
|
||||
foreach (var client in _clients.Values)
|
||||
{
|
||||
tasks.Add(SendDataAsync(client, data));
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
public async Task BroadcastAsync(string message)
|
||||
{
|
||||
await BroadcastAsync(Encoding.UTF8.GetBytes(message));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task SendToClientAsync(string clientId, byte[] data)
|
||||
{
|
||||
if (_clients.TryGetValue(clientId, out var client))
|
||||
{
|
||||
await SendDataAsync(client, data);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendToClientAsync(string clientId, string message)
|
||||
{
|
||||
await SendToClientAsync(clientId, Encoding.UTF8.GetBytes(message));
|
||||
}
|
||||
|
||||
public async Task BroadcastAsync(byte[] data)
|
||||
{
|
||||
var tasks = new List<Task>();
|
||||
foreach (var client in _clients.Values)
|
||||
{
|
||||
tasks.Add(SendDataAsync(client, data));
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
public async Task BroadcastAsync(string message)
|
||||
{
|
||||
await BroadcastAsync(Encoding.UTF8.GetBytes(message));
|
||||
}
|
||||
|
||||
private async Task SendDataAsync(Connection client, byte[] data)
|
||||
{
|
||||
try
|
||||
|
@ -442,72 +442,72 @@ namespace EonaCat.Connections
|
|||
else
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error sending data" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task<byte[]> EncryptDataAsync(byte[] data, Aes aes)
|
||||
{
|
||||
using (var encryptor = aes.CreateEncryptor())
|
||||
using (var ms = new MemoryStream())
|
||||
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
|
||||
{
|
||||
await cs.WriteAsync(data, 0, data.Length);
|
||||
cs.FlushFinalBlock();
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<byte[]> DecryptDataAsync(byte[] data, Aes aes)
|
||||
{
|
||||
using (var decryptor = aes.CreateDecryptor())
|
||||
using (var ms = new MemoryStream(data))
|
||||
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
|
||||
using (var result = new MemoryStream())
|
||||
{
|
||||
await cs.CopyToAsync(result);
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DisconnectClientAsync(string clientId)
|
||||
{
|
||||
if (_clients.TryRemove(clientId, out var client))
|
||||
{
|
||||
try
|
||||
{
|
||||
client.CancellationToken?.Cancel();
|
||||
client.TcpClient?.Close();
|
||||
client.Stream?.Dispose();
|
||||
client.AesEncryption?.Dispose();
|
||||
|
||||
OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error disconnecting client" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_serverCancellation?.Cancel();
|
||||
_tcpListener?.Stop();
|
||||
_udpListener?.Close();
|
||||
|
||||
// Disconnect all clients
|
||||
var disconnectTasks = new List<Task>();
|
||||
foreach (var clientId in _clients.Keys.ToArray())
|
||||
{
|
||||
disconnectTasks.Add(DisconnectClientAsync(clientId));
|
||||
}
|
||||
Task.WaitAll(disconnectTasks.ToArray());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
_serverCancellation?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task<byte[]> EncryptDataAsync(byte[] data, Aes aes)
|
||||
{
|
||||
using (var encryptor = aes.CreateEncryptor())
|
||||
using (var ms = new MemoryStream())
|
||||
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
|
||||
{
|
||||
await cs.WriteAsync(data, 0, data.Length);
|
||||
cs.FlushFinalBlock();
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<byte[]> DecryptDataAsync(byte[] data, Aes aes)
|
||||
{
|
||||
using (var decryptor = aes.CreateDecryptor())
|
||||
using (var ms = new MemoryStream(data))
|
||||
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
|
||||
using (var result = new MemoryStream())
|
||||
{
|
||||
await cs.CopyToAsync(result);
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DisconnectClientAsync(string clientId)
|
||||
{
|
||||
if (_clients.TryRemove(clientId, out var client))
|
||||
{
|
||||
try
|
||||
{
|
||||
client.CancellationToken?.Cancel();
|
||||
client.TcpClient?.Close();
|
||||
client.Stream?.Dispose();
|
||||
client.AesEncryption?.Dispose();
|
||||
|
||||
OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error disconnecting client" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_serverCancellation?.Cancel();
|
||||
_tcpListener?.Stop();
|
||||
_udpListener?.Close();
|
||||
|
||||
// Disconnect all clients
|
||||
var disconnectTasks = new List<Task>();
|
||||
foreach (var clientId in _clients.Keys.ToArray())
|
||||
{
|
||||
disconnectTasks.Add(DisconnectClientAsync(clientId));
|
||||
}
|
||||
Task.WaitAll(disconnectTasks.ToArray());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
_serverCancellation?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue