IW4M-Admin/SharedLibrary/RCon/Connection.cs

297 lines
9.7 KiB
C#
Raw Normal View History

using SharedLibrary.Exceptions;
using SharedLibrary.Interfaces;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SharedLibrary.RCon
{
class ConnectionState
{
public Socket Client { get; private set; }
public int BufferSize { get; private set; }
public byte[] Buffer { get; private set; }
private readonly StringBuilder sb;
public StringBuilder ResponseString
{
get => sb;
}
public ConnectionState(Socket cl)
{
BufferSize = 8192;
Buffer = new byte[BufferSize];
Client = cl;
sb = new StringBuilder();
}
}
public class Connection
{
IPEndPoint Endpoint;
string RConPassword;
Socket ServerConnection;
ILogger Log;
int FailedSends;
int FailedReceives;
DateTime LastQuery;
string response;
ManualResetEvent OnConnected;
ManualResetEvent OnSent;
ManualResetEvent OnReceived;
public Connection(string ipAddress, int port, string password, ILogger log)
{
Endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
RConPassword = password;
Log = log;
OnConnected = new ManualResetEvent(false);
OnSent = new ManualResetEvent(false);
OnReceived = new ManualResetEvent(false);
try
{
ServerConnection = new Socket(Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
ServerConnection.BeginConnect(Endpoint, new AsyncCallback(OnConnectedCallback), ServerConnection);
if (!OnConnected.WaitOne(StaticHelpers.SocketTimeout))
throw new SocketException((int)SocketError.TimedOut);
FailedSends = 0;
}
catch (SocketException e)
{
throw new NetworkException(e.Message);
}
}
~Connection()
{
ServerConnection.Shutdown(SocketShutdown.Both);
ServerConnection.Close();
ServerConnection.Dispose();
}
private void OnConnectedCallback(IAsyncResult ar)
{
var serverSocket = (Socket)ar.AsyncState;
try
{
serverSocket.EndConnect(ar);
#if DEBUG
Log.WriteDebug($"Successfully initialized socket to {serverSocket.RemoteEndPoint}");
#endif
OnConnected.Set();
}
catch (SocketException e)
{
throw new NetworkException($"Could not initialize socket for RCon - {e.Message}");
}
}
private void OnSentCallback(IAsyncResult ar)
{
Socket serverConnection = (Socket)ar.AsyncState;
try
{
int sentByteNum = serverConnection.EndSend(ar);
#if DEBUG
Log.WriteDebug($"Sent {sentByteNum} bytes to {ServerConnection.RemoteEndPoint}");
#endif
OnSent.Set();
}
catch (SocketException)
{
}
}
private void OnReceivedCallback(IAsyncResult ar)
{
var connectionState = (ConnectionState)ar.AsyncState;
var serverConnection = connectionState.Client;
try
{
int bytesRead = serverConnection.EndReceive(ar);
if (bytesRead > 0)
{
#if DEBUG
Log.WriteDebug($"Received {bytesRead} bytes from {ServerConnection.RemoteEndPoint}");
#endif
connectionState.ResponseString.Append(Encoding.UTF7.GetString(connectionState.Buffer, 0, bytesRead).TrimEnd('\0'));
if (serverConnection.Available > 0)
{
ServerConnection.BeginReceive(connectionState.Buffer, 0, connectionState.Buffer.Length, 0,
new AsyncCallback(OnReceivedCallback), connectionState);
}
else
{
response = connectionState.ResponseString.ToString();
OnReceived.Set();
}
}
else
{
response = connectionState.ResponseString.ToString();
OnReceived.Set();
}
}
catch (SocketException)
{
}
}
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "")
{
// will this really prevent flooding?
if ((DateTime.Now - LastQuery).TotalMilliseconds < 150)
{
await Task.Delay(150);
}
LastQuery = DateTime.Now;
OnSent.Reset();
OnReceived.Reset();
string queryString = "";
switch (type)
{
case StaticHelpers.QueryType.DVAR:
case StaticHelpers.QueryType.COMMAND:
queryString = $"ÿÿÿÿrcon {RConPassword} {parameters}";
break;
case StaticHelpers.QueryType.GET_STATUS:
queryString = "ÿÿÿÿgetstatus";
break;
}
byte[] payload = queryString.Select(Convert.ToByte).ToArray();
retrySend:
try
{
ServerConnection.BeginSend(payload, 0, payload.Length, 0, new AsyncCallback(OnSentCallback), ServerConnection);
bool success = await Task.FromResult(OnSent.WaitOne(StaticHelpers.SocketTimeout));
if (!success)
{
FailedSends++;
#if DEBUG
Log.WriteDebug($"{FailedSends} failed sends to {ServerConnection.RemoteEndPoint.ToString()}");
#endif
if (FailedSends < 4)
goto retrySend;
else if (FailedSends == 4)
Log.WriteError($"Failed to send data to {ServerConnection.RemoteEndPoint}");
}
else
{
if (FailedSends >= 4)
{
Log.WriteVerbose($"Resumed send RCon connection with {ServerConnection.RemoteEndPoint}");
FailedSends = 0;
}
}
}
catch (SocketException e)
{
// this result is normal if the server is not listening
if (e.NativeErrorCode != (int)SocketError.ConnectionReset &&
e.NativeErrorCode != (int)SocketError.TimedOut)
throw new NetworkException($"Unexpected error while sending data to server - {e.Message}");
}
var connectionState = new ConnectionState(ServerConnection);
retryReceive:
try
{
ServerConnection.BeginReceive(connectionState.Buffer, 0, connectionState.Buffer.Length, 0,
new AsyncCallback(OnReceivedCallback), connectionState);
bool success = await Task.FromResult(OnReceived.WaitOne(StaticHelpers.SocketTimeout));
if (!success)
{
FailedReceives++;
#if DEBUG
Log.WriteDebug($"{FailedReceives} failed receives from {ServerConnection.RemoteEndPoint.ToString()}");
#endif
if (FailedReceives < 4)
goto retrySend;
else if (FailedReceives == 4)
{
Log.WriteError($"Failed to receive data from {ServerConnection.RemoteEndPoint} after {FailedReceives} tries");
}
if (FailedReceives >= 4)
{
throw new NetworkException($"Could not receive data from the {ServerConnection.RemoteEndPoint}");
}
}
else
{
if (FailedReceives >= 4)
{
Log.WriteVerbose($"Resumed receive RCon connection from {ServerConnection.RemoteEndPoint}");
FailedReceives = 0;
}
}
}
catch (SocketException e)
{
// this result is normal if the server is not listening
if (e.NativeErrorCode != (int)SocketError.ConnectionReset &&
e.NativeErrorCode != (int)SocketError.TimedOut)
throw new NetworkException($"Unexpected error while receiving data from server - {e.Message}");
else if (FailedReceives < 4)
{
goto retryReceive;
}
else if (FailedReceives == 4)
{
Log.WriteError($"Failed to receive data from {ServerConnection.RemoteEndPoint} after {FailedReceives} tries");
}
if (FailedReceives >= 4)
{
throw new NetworkException(e.Message);
}
}
string queryResponse = response;
if (queryResponse.Contains("Invalid password"))
throw new NetworkException("RCON password is invalid");
if (queryResponse.ToString().Contains("rcon_password"))
throw new NetworkException("RCON password has not been set");
string[] splitResponse = queryResponse.Split(new char[]
{
StaticHelpers.SeperatorChar
}, StringSplitOptions.RemoveEmptyEntries);
return splitResponse;
}
}
}