more rcon tweaks, and starting on unit tests for commands bleh

This commit is contained in:
RaidMax 2018-09-29 21:49:12 -05:00
parent d45729d7e1
commit 7fa0b52543
9 changed files with 123 additions and 102 deletions

View File

@ -268,7 +268,7 @@ namespace IW4MAdmin.Application
Running = true; Running = true;
#region DATABASE #region DATABASE
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString, GetApplicationSettings().Configuration().DatabaseProvider)) using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString, GetApplicationSettings().Configuration()?.DatabaseProvider))
{ {
await new ContextSeed(db).Seed(); await new ContextSeed(db).Seed();
} }

View File

@ -16,42 +16,6 @@ namespace IW4MAdmin.Application.RconParsers
{ {
public class T6MRConParser : IRConParser public class T6MRConParser : IRConParser
{ {
class T6MResponse
{
public class SInfo
{
public short Com_maxclients { get; set; }
public string Game { get; set; }
public string Gametype { get; set; }
public string Mapname { get; set; }
public short NumBots { get; set; }
public short NumClients { get; set; }
public short Round { get; set; }
public string Sv_hostname { get; set; }
}
public class PInfo
{
public short Assists { get; set; }
public string Clan { get; set; }
public short Deaths { get; set; }
public short Downs { get; set; }
public short Headshots { get; set; }
public short Id { get; set; }
public bool IsBot { get; set; }
public short Kills { get; set; }
public string Name { get; set; }
public short Ping { get; set; }
public short Revives { get; set; }
public int Score { get; set; }
public long Xuid { get; set; }
public string Ip { get; set; }
}
public SInfo Info { get; set; }
public PInfo[] Players { get; set; }
}
private static readonly CommandPrefix Prefixes = new CommandPrefix() private static readonly CommandPrefix Prefixes = new CommandPrefix()
{ {
Tell = "tell {0} {1}", Tell = "tell {0} {1}",
@ -73,7 +37,6 @@ namespace IW4MAdmin.Application.RconParsers
{ {
string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"get {dvarName}"); string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"get {dvarName}");
if (LineSplit.Length < 2) if (LineSplit.Length < 2)
{ {
var e = new DvarException($"DVAR \"{dvarName}\" does not exist"); var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
@ -103,8 +66,6 @@ namespace IW4MAdmin.Application.RconParsers
{ {
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status"); string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
return ClientsFromStatus(response); return ClientsFromStatus(response);
//return ClientsFromResponse(connection);
} }
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue) public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
@ -114,41 +75,6 @@ namespace IW4MAdmin.Application.RconParsers
return true; return true;
} }
private async Task<List<Player>> ClientsFromResponse(Connection conn)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri($"http://{conn.Endpoint.Address}:{conn.Endpoint.Port}/");
try
{
var parameters = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("rcon_password", conn.RConPassword)
});
var serverResponse = await client.PostAsync("/info", parameters);
var serverResponseObject = Newtonsoft.Json.JsonConvert.DeserializeObject<T6MResponse>(await serverResponse.Content.ReadAsStringAsync());
return serverResponseObject.Players.Select(p => new Player()
{
Name = p.Name,
NetworkId = p.Xuid,
ClientNumber = p.Id,
IPAddress = p.Ip.Split(':')[0].ConvertToIP(),
Ping = p.Ping,
Score = p.Score,
IsBot = p.IsBot,
}).ToList();
}
catch (HttpRequestException e)
{
throw new NetworkException(e.Message);
}
}
}
private List<Player> ClientsFromStatus(string[] status) private List<Player> ClientsFromStatus(string[] status)
{ {
List<Player> StatusPlayers = new List<Player>(); List<Player> StatusPlayers = new List<Player>();
@ -174,9 +100,6 @@ namespace IW4MAdmin.Application.RconParsers
#endif #endif
int ipAddress = regex.Value.Split(':')[0].ConvertToIP(); int ipAddress = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+"); regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+");
int score = 0;
// todo: fix this when T6M score is valid ;)
//int score = Int32.Parse(playerInfo[1]);
var p = new Player() var p = new Player()
{ {
Name = name, Name = name,
@ -184,7 +107,7 @@ namespace IW4MAdmin.Application.RconParsers
ClientNumber = clientId, ClientNumber = clientId,
IPAddress = ipAddress, IPAddress = ipAddress,
Ping = Ping, Ping = Ping,
Score = score, Score = 0,
State = Player.ClientState.Connecting, State = Player.ClientState.Connecting,
IsBot = networkId == 0 IsBot = networkId == 0
}; };

View File

@ -855,7 +855,6 @@ namespace IW4MAdmin
return; return;
} }
Target.Warnings++;
String message = $"^1{loc["SERVER_WARNING"]} ^7[^3{Target.Warnings}^7]: ^3{Target.Name}^7, {Reason}"; String message = $"^1{loc["SERVER_WARNING"]} ^7[^3{Target.Warnings}^7]: ^3{Target.Name}^7, {Reason}";
Target.CurrentServer.Broadcast(message); Target.CurrentServer.Broadcast(message);
} }

View File

@ -1,13 +1,26 @@
using SharedLibraryCore.Objects; using IW4MAdmin.Application;
using SharedLibraryCore;
using SharedLibraryCore.Objects;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using Xunit; using Xunit;
namespace Tests namespace Tests
{ {
[Collection("ManagerCollection")]
public class ClientTests public class ClientTests
{ {
readonly ApplicationManager Manager;
const int TestTimeout = 5000;
public ClientTests(ManagerFixture fixture)
{
Manager = fixture.Manager;
}
[Fact] [Fact]
public void SetAdditionalPropertyShouldSucceed() public void SetAdditionalPropertyShouldSucceed()
{ {
@ -25,5 +38,68 @@ namespace Tests
Assert.True(client.GetAdditionalProperty<int>("NewProp") == 5, "added property does not match retrieved property"); Assert.True(client.GetAdditionalProperty<int>("NewProp") == 5, "added property does not match retrieved property");
} }
[Fact]
public void WarnPlayerShouldSucceed()
{
while (!Manager.IsInitialized)
{
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
Assert.False(client == null, "no client found to warn");
var warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Console });
warnEvent.OnProcessed.Wait(TestTimeout);
Assert.True(client.Warnings == 1 ||
warnEvent.Failed, "warning did not get applied");
warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Banned });
warnEvent.OnProcessed.Wait(TestTimeout);
Assert.True(warnEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.Warnings == 1, "warning was applied without proper permissions");
}
[Fact]
public void ReportPlayerShouldSucceed()
{
while (!Manager.IsInitialized)
{
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
Assert.False(client == null, "no client found to report");
// succeed
var reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console });
reportEvent.OnProcessed.Wait(TestTimeout);
Assert.True(!reportEvent.Failed &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was not applied [{reportEvent.FailReason.ToString()}]");
// fail
reportEvent = client.Report("test report", new Player() { ClientId = 2, Level = Player.Permission.Banned });
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was applied without proper permission");
// fail
reportEvent = client.Report("test report", client);
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Invalid &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was applied to self");
// fail
reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console});
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Exception &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"duplicate report was applied");
}
} }
} }

View File

@ -16,7 +16,6 @@ namespace Tests
public ManagerFixture() public ManagerFixture()
{ {
File.WriteAllText("test_mp.log", "TEST_LOG_FILE"); File.WriteAllText("test_mp.log", "TEST_LOG_FILE");
Manager = Program.ServerManager; Manager = Program.ServerManager;
@ -32,7 +31,7 @@ namespace Tests
Password = "test", Password = "test",
Port = 28963, Port = 28963,
Rules = new List<string>(), Rules = new List<string>(),
ManualLogPath = "https://raidmax.org/IW4MAdmin/getlog.php" ManualLogPath = "http://google.com"
} }
}, },
AutoMessages = new List<string>(), AutoMessages = new List<string>(),

View File

@ -69,12 +69,14 @@ namespace SharedLibraryCore.Commands
}) })
{ } { }
public override async Task ExecuteAsync(GameEvent E) public override Task ExecuteAsync(GameEvent E)
{ {
if (!await E.Target.Warn(E.Data, E.Origin).WaitAsync()) if (E.Target.Warn(E.Data, E.Origin).Failed)
{ {
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_FAIL"]} {E.Target.Name}"); E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_FAIL"]} {E.Target.Name}");
} }
return Task.CompletedTask;
} }
} }
@ -91,12 +93,14 @@ namespace SharedLibraryCore.Commands
}) })
{ } { }
public override async Task ExecuteAsync(GameEvent E) public override Task ExecuteAsync(GameEvent E)
{ {
if (await E.Target.WarnClear(E.Origin).WaitAsync()) if (!E.Target.WarnClear(E.Origin).Failed)
{ {
E.Owner.Broadcast($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_SUCCESS"]} {E.Target.Name}"); E.Owner.Broadcast($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_SUCCESS"]} {E.Target.Name}");
} }
return Task.CompletedTask;
} }
} }
@ -839,8 +843,6 @@ namespace SharedLibraryCore.Commands
else else
{ {
commandEvent.Owner.Reports.Add(new Report(commandEvent.Target, commandEvent.Origin, commandEvent.Data));
Penalty newReport = new Penalty() Penalty newReport = new Penalty()
{ {
Type = Penalty.PenaltyType.Report, Type = Penalty.PenaltyType.Report,

View File

@ -128,6 +128,8 @@ namespace SharedLibraryCore.Objects
return e; return e;
} }
this.Warnings++;
CurrentServer.Manager.GetEventHandler().AddEvent(e); CurrentServer.Manager.GetEventHandler().AddEvent(e);
return e; return e;
} }
@ -150,7 +152,7 @@ namespace SharedLibraryCore.Objects
Owner = this.CurrentServer Owner = this.CurrentServer
}; };
if (this.Level < sender.Level) if (this.Level > sender.Level)
{ {
e.FailReason = GameEvent.EventFailReason.Permission; e.FailReason = GameEvent.EventFailReason.Permission;
return e; return e;
@ -162,13 +164,14 @@ namespace SharedLibraryCore.Objects
return e; return e;
} }
if (CurrentServer.Reports.Count(rep => (rep.Origin == sender && if (CurrentServer.Reports.Count(rep => (rep.Origin.NetworkId == sender.NetworkId &&
rep.Target.NetworkId == this.NetworkId)) > 0) rep.Target.NetworkId == this.NetworkId)) > 0)
{ {
e.FailReason = GameEvent.EventFailReason.Exception; e.FailReason = GameEvent.EventFailReason.Exception;
return e; return e;
} }
CurrentServer.Reports.Add(new Report(this, sender, reportReason));
CurrentServer.Manager.GetEventHandler().AddEvent(e); CurrentServer.Manager.GetEventHandler().AddEvent(e);
return e; return e;
} }

View File

@ -17,6 +17,7 @@ namespace SharedLibraryCore.RCon
const int BufferSize = 4096; const int BufferSize = 4096;
public readonly byte[] ReceiveBuffer = new byte[BufferSize]; public readonly byte[] ReceiveBuffer = new byte[BufferSize];
public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1); public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1);
public DateTime LastQuery { get; set; } = DateTime.Now;
} }
public class Connection public class Connection
@ -42,9 +43,19 @@ namespace SharedLibraryCore.RCon
var connectionState = ActiveQueries[this.Endpoint]; var connectionState = ActiveQueries[this.Endpoint];
var timeLeft = (DateTime.Now - connectionState.LastQuery).TotalMilliseconds;
if (timeLeft > 0)
{
await Task.Delay((int)timeLeft);
}
connectionState.LastQuery = DateTime.Now;
#if DEBUG == true #if DEBUG == true
Log.WriteDebug($"Waiting for semaphore to be released [${this.Endpoint}]"); Log.WriteDebug($"Waiting for semaphore to be released [${this.Endpoint}]");
#endif #endif
// enter the semaphore so only one query is sent at a time per server.
await connectionState.OnComplete.WaitAsync(); await connectionState.OnComplete.WaitAsync();
#if DEBUG == true #if DEBUG == true
@ -73,40 +84,41 @@ namespace SharedLibraryCore.RCon
byte[] response = null; byte[] response = null;
retrySend: retrySend:
#if DEBUG == true #if DEBUG == true
Log.WriteDebug($"Sending {payload.Length} bytes to [${this.Endpoint}] ({connectionState.ConnectionAttempts++}/{StaticHelpers.AllowedConnectionFails})"); Log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts++}/{StaticHelpers.AllowedConnectionFails})");
#endif #endif
try try
{ {
response = await SendPayloadAsync(payload); response = await SendPayloadAsync(payload);
connectionState.OnComplete.Release(1);
} }
catch (Exception ex) catch (Exception ex)
{ {
if(connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails) if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
{ {
connectionState.ConnectionAttempts++; connectionState.ConnectionAttempts++;
Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts++}/{StaticHelpers.AllowedConnectionFails})"); Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts++}/{StaticHelpers.AllowedConnectionFails})");
await Task.Delay(StaticHelpers.SocketTimeout.Milliseconds); await Task.Delay(StaticHelpers.FloodProtectionInterval);
goto retrySend; goto retrySend;
} }
ActiveQueries.TryRemove(this.Endpoint, out _); // the next thread can go ahead and enter
connectionState.OnComplete.Release(1);
Log.WriteDebug(ex.GetExceptionInfo()); Log.WriteDebug(ex.GetExceptionInfo());
throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]"); throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]");
} }
ActiveQueries.TryRemove(this.Endpoint, out _); connectionState.ConnectionAttempts = 0;
string responseString = Utilities.EncodingType.GetString(response, 0, response.Length).TrimEnd('\0') + '\n'; string responseString = Utilities.EncodingType.GetString(response, 0, response.Length).TrimEnd('\0') + '\n';
if (responseString.Contains("Invalid password")) if (responseString.Contains("Invalid password"))
{ {
// todo: localize this throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]);
throw new NetworkException("RCON password is invalid");
} }
if (responseString.ToString().Contains("rcon_password")) if (responseString.ToString().Contains("rcon_password"))
{ {
throw new NetworkException("RCON password has not been set"); throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_NOTSET"]);
} }
string[] splitResponse = responseString.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries) string[] splitResponse = responseString.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
@ -147,6 +159,10 @@ namespace SharedLibraryCore.RCon
if (!await connectionState.OnComplete.WaitAsync(StaticHelpers.SocketTimeout.Milliseconds)) if (!await connectionState.OnComplete.WaitAsync(StaticHelpers.SocketTimeout.Milliseconds))
{ {
// we no longer care about the data because the server is being too slow
incomingDataArgs.Completed -= OnDataReceived;
// the next thread can go ahead and make a query
connectionState.OnComplete.Release(1);
throw new NetworkException("Timed out waiting for response", rconSocket); throw new NetworkException("Timed out waiting for response", rconSocket);
} }
@ -159,7 +175,10 @@ namespace SharedLibraryCore.RCon
#if DEBUG == true #if DEBUG == true
Log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}"); Log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}");
#endif #endif
ActiveQueries[this.Endpoint].OnComplete.Release(1); if (ActiveQueries[this.Endpoint].OnComplete.CurrentCount == 0)
{
ActiveQueries[this.Endpoint].OnComplete.Release(1);
}
} }
private void OnDataSent(object sender, SocketAsyncEventArgs e) private void OnDataSent(object sender, SocketAsyncEventArgs e)

View File

@ -39,7 +39,7 @@ namespace SharedLibraryCore.RCon
/// <summary> /// <summary>
/// timeout in seconds to wait for a socket send or receive before giving up /// timeout in seconds to wait for a socket send or receive before giving up
/// </summary> /// </summary>
public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 0, 0, 150); public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 0, 0,150);
/// <summary> /// <summary>
/// interval in milliseconds to wait before sending the next RCon request /// interval in milliseconds to wait before sending the next RCon request
/// </summary> /// </summary>