From f4ac815d0719dc003fdea8be158ae64770ef43f6 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Tue, 2 Oct 2018 12:39:08 -0500 Subject: [PATCH] hopefully finished with RCon changes. added more tests. fixed issues from event changes (there's most definitely still issues related to that) --- Application/Manager.cs | 12 ++ Application/Server.cs | 82 +++++++-- Plugins/Tests/ClientTests.cs | 169 ++++++++++++++++-- Plugins/Tests/ManagerFixture.cs | 4 +- SharedLibraryCore/Commands/NativeCommands.cs | 44 ++--- SharedLibraryCore/Database/DatabaseContext.cs | 18 +- SharedLibraryCore/Events/GameEvent.cs | 6 +- SharedLibraryCore/Objects/Player.cs | 133 +++++++------- SharedLibraryCore/Objects/Report.cs | 13 +- SharedLibraryCore/RCon/Connection.cs | 128 +++++++------ SharedLibraryCore/RCon/StaticHelpers.cs | 4 +- SharedLibraryCore/Server.cs | 6 +- SharedLibraryCore/Utilities.cs | 14 +- WebfrontCore/Controllers/ActionController.cs | 12 +- WebfrontCore/Controllers/ConsoleController.cs | 2 +- 15 files changed, 428 insertions(+), 219 deletions(-) diff --git a/Application/Manager.cs b/Application/Manager.cs index 04c914c79..fccaf38de 100644 --- a/Application/Manager.cs +++ b/Application/Manager.cs @@ -76,6 +76,12 @@ namespace IW4MAdmin.Application var newEvent = args.Event; + // the event has failed already + if (newEvent.Failed) + { + goto skip; + } + try { // if the origin client is not in an authorized state (detected by RCon) don't execute the event @@ -161,26 +167,32 @@ namespace IW4MAdmin.Application // this happens if a plugin requires login catch (AuthorizationException ex) { + newEvent.FailReason = GameEvent.EventFailReason.Permission; newEvent.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMAND_NOTAUTHORIZED"]} - {ex.Message}"); } catch (NetworkException ex) { + newEvent.FailReason = GameEvent.EventFailReason.Exception; Logger.WriteError(ex.Message); Logger.WriteDebug(ex.GetExceptionInfo()); } catch (ServerException ex) { + newEvent.FailReason = GameEvent.EventFailReason.Exception; Logger.WriteWarning(ex.Message); } catch (Exception ex) { + newEvent.FailReason = GameEvent.EventFailReason.Exception; Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EXCEPTION"]} {newEvent.Owner}"); Logger.WriteDebug(ex.GetExceptionInfo()); } + skip: + // tell anyone waiting for the output that we're done newEvent.OnProcessed.Set(); } diff --git a/Application/Server.cs b/Application/Server.cs index a1025ffa4..d520eada5 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -201,7 +201,7 @@ namespace IW4MAdmin { player.Level = Player.Permission.User; } - +#if DEBUG == false if (currentBan != null) { Logger.WriteInfo($"Banned client {player} trying to connect..."); @@ -243,16 +243,17 @@ namespace IW4MAdmin Players[player.ClientNumber] = null; return false; } +#endif player.State = Player.ClientState.Connected; return true; } - catch (Exception E) + catch (Exception ex) { Manager.GetLogger().WriteError($"{loc["SERVER_ERROR_ADDPLAYER"]} {polledPlayer.Name}::{polledPlayer.NetworkId}"); - Manager.GetLogger().WriteDebug(E.Message); - Manager.GetLogger().WriteDebug(E.StackTrace); + Manager.GetLogger().WriteDebug(ex.Message); + Manager.GetLogger().WriteDebug(ex.StackTrace); return false; } } @@ -376,6 +377,61 @@ namespace IW4MAdmin await OnPlayerJoined(E.Origin); } + else if (E.Type == GameEvent.EventType.Flag) + { + Penalty newPenalty = new Penalty() + { + Type = Penalty.PenaltyType.Flag, + Expires = DateTime.UtcNow, + Offender = E.Target, + Offense = E.Data, + Punisher = E.Origin, + Active = true, + When = DateTime.UtcNow, + Link = E.Target.AliasLink + }; + + var addedPenalty = await Manager.GetPenaltyService().Create(newPenalty); + E.Target.ReceivedPenalties.Add(addedPenalty); + + await Manager.GetClientService().Update(E.Target); + } + + else if (E.Type == GameEvent.EventType.Unflag) + { + await Manager.GetClientService().Update(E.Target); + } + + else if (E.Type == GameEvent.EventType.Report) + { + this.Reports.Add(new Report() + { + Origin = E.Origin, + Target = E.Target, + Reason = E.Data + }); + } + + else if (E.Type == GameEvent.EventType.TempBan) + { + await TempBan(E.Data, (TimeSpan)E.Extra, E.Target, E.Origin); ; + } + + else if (E.Type == GameEvent.EventType.Ban) + { + await Ban(E.Data, E.Target, E.Origin); + } + + else if (E.Type == GameEvent.EventType.Unban) + { + await Unban(E.Data, E.Target, E.Origin); + } + + else if (E.Type == GameEvent.EventType.Kick) + { + await Kick(E.Data, E.Target, E.Origin); + } + else if (E.Type == GameEvent.EventType.Quit) { var origin = Players.FirstOrDefault(p => p != null && p.NetworkId == E.Origin.NetworkId); @@ -874,7 +930,7 @@ namespace IW4MAdmin await Manager.GetPenaltyService().Create(newPenalty); } - public override async Task Kick(String Reason, Player Target, Player Origin) + protected override async Task Kick(String Reason, Player Target, Player Origin) { // ensure player gets kicked if command not performed on them in game if (Target.ClientNumber < 0) @@ -915,7 +971,7 @@ namespace IW4MAdmin await Manager.GetPenaltyService().Create(newPenalty); } - public override async Task TempBan(String Reason, TimeSpan length, Player Target, Player Origin) + protected override async Task TempBan(String Reason, TimeSpan length, Player Target, Player Origin) { // ensure player gets banned if command not performed on them in game if (Target.ClientNumber < 0) @@ -954,7 +1010,7 @@ namespace IW4MAdmin await Manager.GetPenaltyService().Create(newPenalty); } - override public async Task Ban(String Message, Player Target, Player Origin) + override protected async Task Ban(String Message, Player Target, Player Origin) { // ensure player gets banned if command not performed on them in game if (Target.ClientNumber < 0) @@ -978,18 +1034,6 @@ namespace IW4MAdmin // this is set only because they're still in the server. Target.Level = Player.Permission.Banned; - var e = new GameEvent() - { - Type = GameEvent.EventType.Ban, - Data = Message, - Origin = Origin, - Target = Target, - Owner = this - }; - - // let the api know that a ban occured - Manager.GetEventHandler().AddEvent(e); - #if !DEBUG string formattedString = String.Format(RconParser.GetCommandPrefixes().Kick, Target.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{Message} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7"); await Target.CurrentServer.ExecuteCommandAsync(formattedString); diff --git a/Plugins/Tests/ClientTests.cs b/Plugins/Tests/ClientTests.cs index 17c0ed70e..30737a704 100644 --- a/Plugins/Tests/ClientTests.cs +++ b/Plugins/Tests/ClientTests.cs @@ -1,5 +1,6 @@ using IW4MAdmin.Application; using SharedLibraryCore; +using SharedLibraryCore.Commands; using SharedLibraryCore.Objects; using System; using System.Collections.Generic; @@ -14,7 +15,7 @@ namespace Tests public class ClientTests { readonly ApplicationManager Manager; - const int TestTimeout = 5000; + const int TestTimeout = 10000; public ClientTests(ManagerFixture fixture) { @@ -40,7 +41,7 @@ namespace Tests } [Fact] - public void WarnPlayerShouldSucceed() + public void WarnClientShouldSucceed() { while (!Manager.IsInitialized) { @@ -51,21 +52,31 @@ namespace Tests Assert.False(client == null, "no client found to warn"); - var warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Console }); + var warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); warnEvent.OnProcessed.Wait(TestTimeout); - Assert.True(client.Warnings == 1 || + 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 = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); warnEvent.OnProcessed.Wait(TestTimeout); Assert.True(warnEvent.FailReason == GameEvent.EventFailReason.Permission && client.Warnings == 1, "warning was applied without proper permissions"); + + // warn clear + var warnClearEvent = client.WarnClear(new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); + + Assert.True(warnClearEvent.FailReason == GameEvent.EventFailReason.Permission && + client.Warnings == 1, "warning was removed without proper permissions"); + + warnClearEvent = client.WarnClear(new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); + + Assert.True(!warnClearEvent.Failed && client.Warnings == 0, "warning was not cleared"); } [Fact] - public void ReportPlayerShouldSucceed() + public void ReportClientShouldSucceed() { while (!Manager.IsInitialized) { @@ -73,21 +84,21 @@ namespace Tests } 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 }); + var reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); 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 }); + reportEvent = client.Report("test report", new Player() { ClientId = 1, NetworkId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Permission && - client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was applied without proper permission"); + client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, + $"report was applied without proper permission [{reportEvent.FailReason.ToString()},{ client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId)}]"); // fail reportEvent = client.Report("test report", client); @@ -96,10 +107,146 @@ namespace Tests 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}); + reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Exception && client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"duplicate report was applied"); } + + [Fact] + public void FlagClientShouldSucceed() + { + while (!Manager.IsInitialized) + { + Thread.Sleep(100); + } + + var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault(); + Assert.False(client == null, "no client found to flag"); + + var flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); + flagEvent.OnProcessed.Wait(); + + // succeed + Assert.True(!flagEvent.Failed && + client.Level == Player.Permission.Flagged, $"player is not flagged [{flagEvent.FailReason.ToString()}]"); + Assert.False(client.ReceivedPenalties.FirstOrDefault(p => p.Offense == "test flag") == null, "flag was not applied"); + + flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); + flagEvent.OnProcessed.Wait(); + + // fail + Assert.True(client.ReceivedPenalties.Count == 1, "flag was applied without permisions"); + + flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); + flagEvent.OnProcessed.Wait(); + + // fail + Assert.True(client.ReceivedPenalties.Count == 1, "duplicate flag was applied"); + + var unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); + unflagEvent.OnProcessed.Wait(); + + // fail + Assert.False(client.Level == Player.Permission.User, "user was unflagged without permissions"); + + unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); + unflagEvent.OnProcessed.Wait(); + + // succeed + Assert.True(client.Level == Player.Permission.User, "user was not unflagged"); + + unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); + unflagEvent.OnProcessed.Wait(); + + // succeed + Assert.True(unflagEvent.FailReason == GameEvent.EventFailReason.Invalid, "user was not flagged"); + } + + [Fact] + void KickClientShouldSucceed() + { + while (!Manager.IsInitialized) + { + Thread.Sleep(100); + } + + var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault(); + Assert.False(client == null, "no client found to kick"); + + var kickEvent = client.Kick("test kick", new Player() { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); + kickEvent.OnProcessed.Wait(); + + Assert.True(kickEvent.FailReason == GameEvent.EventFailReason.Permission, "client was kicked without permission"); + + kickEvent = client.Kick("test kick", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); + kickEvent.OnProcessed.Wait(); + + Assert.True(Manager.Servers.First().GetPlayersAsList().FirstOrDefault(c => c.NetworkId == client.NetworkId) == null, "client was not kicked"); + } + + [Fact] + void TempBanClientShouldSucceed() + { + while (!Manager.IsInitialized) + { + Thread.Sleep(100); + } + + var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault(); + Assert.False(client == null, "no client found to tempban"); + + var tbCommand = new CTempBan(); + tbCommand.ExecuteAsync(new GameEvent() + { + Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }, + Target = client, + Data = "5days test tempban", + Type = GameEvent.EventType.Command, + Owner = client.CurrentServer + }).Wait(); + + Assert.True(Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId).Result.Count(p => p.Type == Penalty.PenaltyType.TempBan) == 1, + "tempban was not added"); + } + + [Fact] + void BanUnbanClientShouldSucceed() + { + while (!Manager.IsInitialized) + { + Thread.Sleep(100); + } + + var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault(); + Assert.False(client == null, "no client found to ban"); + + var banCommand = new CBan(); + banCommand.ExecuteAsync(new GameEvent() + { + Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }, + Target = client, + Data = "test ban", + Type = GameEvent.EventType.Command, + Owner = client.CurrentServer + }).Wait(); + + Assert.True(Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId).Result.Count(p => p.Type == Penalty.PenaltyType.Ban) == 1, + "ban was not added"); + + var unbanCommand = new CUnban(); + unbanCommand.ExecuteAsync(new GameEvent() + { + Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }, + Target = Manager.GetClientService().Find(c => c.NetworkId == client.NetworkId).Result.First().AsPlayer(), + Data = "test unban", + Type = GameEvent.EventType.Command, + Owner = client.CurrentServer + }).Wait(); + + Assert.True(Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId).Result.Count(p => p.Type == Penalty.PenaltyType.Ban) == 0, + "ban was not removed"); + + } } } diff --git a/Plugins/Tests/ManagerFixture.cs b/Plugins/Tests/ManagerFixture.cs index 48d5f50cb..557d257a9 100644 --- a/Plugins/Tests/ManagerFixture.cs +++ b/Plugins/Tests/ManagerFixture.cs @@ -16,7 +16,7 @@ namespace Tests public ManagerFixture() { - File.WriteAllText("test_mp.log", "TEST_LOG_FILE"); + File.WriteAllText("test_mp.log", "test_log_file"); Manager = Program.ServerManager; @@ -39,7 +39,7 @@ namespace Tests Maps = new List(), RConPollRate = 10000 }; - Manager.ConfigHandler = new BaseConfigurationHandler("Test.json"); + Manager.ConfigHandler = new BaseConfigurationHandler("test.json"); Manager.ConfigHandler.Set(config); Manager.Init().Wait(); diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index 4dfb6ccb3..42f179dab 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -124,7 +124,7 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent E) { - var _ = await E.Target.Kick(E.Data, E.Origin).WaitAsync() ? + var _ = !(await E.Target.Kick(E.Data, E.Origin).WaitAsync()).Failed ? E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_SUCCESS"]}") : E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_FAIL"]} {E.Target.Name}"); } @@ -173,16 +173,20 @@ namespace SharedLibraryCore.Commands }) { } + private static readonly string TempBanRegex = @"([0-9]+\w+)\ (.+)"; + public override async Task ExecuteAsync(GameEvent E) { - String Message = Utilities.RemoveWords(E.Data, 1).Trim(); - var length = E.Data.Split(' ')[0].ToLower().ParseTimespan(); - if (length.TotalHours >= 1 && length.TotalHours < 2) - Message = E.Data.Replace("1h", "").Replace("1H", ""); + var match = Regex.Match(E.Data, TempBanRegex); + if (match.Success) + { + string tempbanReason = match.Groups[2].ToString(); + var length = match.Groups[1].ToString().ParseTimespan(); - var _ = await E.Target.TempBan(Message, length, E.Origin).WaitAsync() ? - E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}") : - E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}"); + var _ = !(await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync()).Failed ? + E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}") : + E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}"); + } } } @@ -206,7 +210,7 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent E) { - var _ = await E.Target.Ban(E.Data, E.Origin).WaitAsync() ? + var _ = !(await E.Target.Ban(E.Data, E.Origin).WaitAsync()).Failed ? E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_SUCCESS"]}") : E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_FAIL"]} {E.Target.Name}"); } @@ -721,11 +725,10 @@ namespace SharedLibraryCore.Commands }) { } - public override async Task ExecuteAsync(GameEvent E) + public override Task ExecuteAsync(GameEvent E) { var flagEvent = E.Target.Flag(E.Data, E.Origin); - if (E.FailReason == GameEvent.EventFailReason.Permission) { E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_FAIL"]} ^5{E.Target.Name}"); @@ -738,25 +741,11 @@ namespace SharedLibraryCore.Commands else { - E.Target.Level = Player.Permission.Flagged; - - Penalty newPenalty = new Penalty() - { - Type = Penalty.PenaltyType.Flag, - Expires = DateTime.UtcNow, - Offender = E.Target, - Offense = E.Data, - Punisher = E.Origin, - Active = true, - When = DateTime.UtcNow, - Link = E.Target.AliasLink - }; - - await E.Owner.Manager.GetPenaltyService().Create(newPenalty); - E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_SUCCESS"]} ^5{E.Target.Name}"); } + return Task.CompletedTask; + } } @@ -843,6 +832,7 @@ namespace SharedLibraryCore.Commands else { + // todo: move into server Penalty newReport = new Penalty() { Type = Penalty.PenaltyType.Report, diff --git a/SharedLibraryCore/Database/DatabaseContext.cs b/SharedLibraryCore/Database/DatabaseContext.cs index 4b104a069..ca63ae56b 100644 --- a/SharedLibraryCore/Database/DatabaseContext.cs +++ b/SharedLibraryCore/Database/DatabaseContext.cs @@ -24,11 +24,9 @@ namespace SharedLibraryCore.Database public DbSet EFMeta { get; set; } public DbSet EFChangeHistory { get; set; } - /// - /// this only works if there's one connection string - /// - private static string _ConnectionString; - private static string _provider; + + static string _ConnectionString; + static string _provider; public DatabaseContext(DbContextOptions opt) : base(opt) { } @@ -63,12 +61,12 @@ namespace SharedLibraryCore.Database var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = $"{currentPath}{Path.DirectorySeparatorChar}Database.db".Substring(6) }; var connectionString = connectionStringBuilder.ToString(); var connection = new SqliteConnection(connectionString); -#if DEBUG == true - optionsBuilder.UseMySql("UserId=root;Password=dev;Host=127.0.0.1;port=3306;Database=IW4MAdmin"); - // optionsBuilder.UseNpgsql("UserId=dev;Password=dev;Host=127.0.0.1;port=5432;Database=IW4MAdmin"); -#else + //#if DEBUG == true + //optionsBuilder.UseMySql("UserId=root;Password=dev;Host=127.0.0.1;port=3306;Database=IW4MAdmin"); + // optionsBuilder.UseNpgsql("UserId=dev;Password=dev;Host=127.0.0.1;port=5432;Database=IW4MAdmin"); + //#else optionsBuilder.UseSqlite(connection); -#endif + //#endif } else diff --git a/SharedLibraryCore/Events/GameEvent.cs b/SharedLibraryCore/Events/GameEvent.cs index fa150ef89..5edd1683c 100644 --- a/SharedLibraryCore/Events/GameEvent.cs +++ b/SharedLibraryCore/Events/GameEvent.cs @@ -181,10 +181,10 @@ namespace SharedLibraryCore /// asynchronously wait for GameEvent to be processed /// /// waitable task - public Task WaitAsync(int timeOut = int.MaxValue) => Task.Run(() => + public Task WaitAsync(int timeOut = int.MaxValue) => Task.Run(() => { OnProcessed.Wait(timeOut); - return !Failed; + return this; }); /// @@ -214,7 +214,7 @@ namespace SharedLibraryCore /// true if event should be delayed, false otherwise public static bool ShouldTargetEventBeDelayed(GameEvent queuedEvent) { - return queuedEvent.Target != null && + return (queuedEvent.Target != null && queuedEvent.Target.ClientNumber != -1) && (queuedEvent.Target.State != Player.ClientState.Connected && queuedEvent.Target.NetworkId != 0); } diff --git a/SharedLibraryCore/Objects/Player.cs b/SharedLibraryCore/Objects/Player.cs index 1b4f79868..43fb55e58 100644 --- a/SharedLibraryCore/Objects/Player.cs +++ b/SharedLibraryCore/Objects/Player.cs @@ -101,7 +101,7 @@ namespace SharedLibraryCore.Objects Data = message }; - CurrentServer.Manager.GetEventHandler().AddEvent(e); + this.CurrentServer?.Manager.GetEventHandler().AddEvent(e); return e; } @@ -118,7 +118,7 @@ namespace SharedLibraryCore.Objects Message = warnReason, Origin = sender, Target = this, - Owner = this.CurrentServer + Owner = sender.CurrentServer }; // enforce level restrictions @@ -130,7 +130,36 @@ namespace SharedLibraryCore.Objects this.Warnings++; - CurrentServer.Manager.GetEventHandler().AddEvent(e); + sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); + return e; + } + + + /// + /// clear all warnings for a client + /// + /// client performing the warn clear + /// + public GameEvent WarnClear(Player sender) + { + var e = new GameEvent() + { + Type = GameEvent.EventType.WarnClear, + Origin = sender, + Target = this, + Owner = sender.CurrentServer + }; + + // enforce level restrictions + if (sender.Level <= this.Level) + { + e.FailReason = GameEvent.EventFailReason.Permission; + return e; + } + + this.Warnings = 0; + + sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); return e; } @@ -149,58 +178,26 @@ namespace SharedLibraryCore.Objects Data = reportReason, Origin = sender, Target = this, - Owner = this.CurrentServer + Owner = sender.CurrentServer }; if (this.Level > sender.Level) { e.FailReason = GameEvent.EventFailReason.Permission; - return e; } - if (this == sender) + else if (this.Equals(sender)) { e.FailReason = GameEvent.EventFailReason.Invalid; - return e; } - if (CurrentServer.Reports.Count(rep => (rep.Origin.NetworkId == sender.NetworkId && - rep.Target.NetworkId == this.NetworkId)) > 0) + else if (CurrentServer.Reports.Count(report => (report.Origin.NetworkId == sender.NetworkId && + report.Target.NetworkId == this.NetworkId)) > 0) { e.FailReason = GameEvent.EventFailReason.Exception; - return e; } - CurrentServer.Reports.Add(new Report(this, sender, reportReason)); - CurrentServer.Manager.GetEventHandler().AddEvent(e); - return e; - } - - /// - /// clear all warnings for a client - /// - /// client performing the warn clear - /// - public GameEvent WarnClear(Player sender) - { - var e = new GameEvent() - { - Type = GameEvent.EventType.WarnClear, - Origin = sender, - Target = this, - Owner = this.CurrentServer - }; - - // enforce level restrictions - if (sender.Level <= this.Level) - { - e.FailReason = GameEvent.EventFailReason.Permission; - return e; - } - - this.Warnings = 0; - - CurrentServer.Manager.GetEventHandler().AddEvent(e); + sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); return e; } @@ -218,22 +215,26 @@ namespace SharedLibraryCore.Objects Origin = sender, Data = flagReason, Message = flagReason, - Owner = this.CurrentServer + Target = this, + Owner = sender.CurrentServer }; - if (sender.Level <= this.Level) + if (this.Level >= sender.Level) { e.FailReason = GameEvent.EventFailReason.Permission; - return e; } - if (this.Level == Player.Permission.Flagged) + else if (this.Level == Player.Permission.Flagged) { e.FailReason = GameEvent.EventFailReason.Invalid; - return e; } - CurrentServer.Manager.GetEventHandler().AddEvent(e); + else + { + this.Level = Player.Permission.Flagged; + } + + sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); return e; } @@ -249,26 +250,28 @@ namespace SharedLibraryCore.Objects { Type = GameEvent.EventType.Unflag, Origin = sender, + Target = this, Data = unflagReason, Message = unflagReason, - Owner = this.CurrentServer + Owner = sender.CurrentServer }; if (sender.Level <= this.Level) { e.FailReason = GameEvent.EventFailReason.Permission; - return e; } - if (this.Level != Player.Permission.Flagged) + else if (this.Level != Player.Permission.Flagged) { e.FailReason = GameEvent.EventFailReason.Invalid; - return e; } - this.Level = Permission.User; + else + { + this.Level = Permission.User; + } - CurrentServer.Manager.GetEventHandler().AddEvent(e); + sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); return e; } @@ -286,17 +289,16 @@ namespace SharedLibraryCore.Objects Target = this, Origin = sender, Data = kickReason, - Owner = this.CurrentServer + Owner = sender.CurrentServer }; // enforce level restrictions - if (sender.Level <= this.Level) + if (this.Level > sender.Level) { e.FailReason = GameEvent.EventFailReason.Permission; - return e; } - CurrentServer.Manager.GetEventHandler().AddEvent(e); + sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); return e; } @@ -312,10 +314,11 @@ namespace SharedLibraryCore.Objects { Type = GameEvent.EventType.TempBan, Message = tempbanReason, + Data = tempbanReason, Origin = sender, Target = this, Extra = banLength, - Owner = this.CurrentServer + Owner = sender.CurrentServer }; // enforce level restrictions @@ -325,7 +328,7 @@ namespace SharedLibraryCore.Objects return e; } - CurrentServer.Manager.GetEventHandler().AddEvent(e); + sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); return e; } @@ -340,9 +343,10 @@ namespace SharedLibraryCore.Objects { Type = GameEvent.EventType.Ban, Message = banReason, + Data = banReason, Origin = sender, Target = this, - Owner = this.CurrentServer + Owner = sender.CurrentServer }; // enforce level restrictions @@ -352,7 +356,7 @@ namespace SharedLibraryCore.Objects return e; } - CurrentServer.Manager.GetEventHandler().AddEvent(e); + sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); return e; } @@ -371,17 +375,16 @@ namespace SharedLibraryCore.Objects Data = unbanReason, Origin = sender, Target = this, - Owner = this.CurrentServer + Owner = sender.CurrentServer }; // enforce level restrictions - if (sender.Level <= this.Level) + if (this.Level > sender.Level) { e.FailReason = GameEvent.EventFailReason.Permission; - return e; } - CurrentServer.Manager.GetEventHandler().AddEvent(e); + sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); return e; } @@ -432,7 +435,7 @@ namespace SharedLibraryCore.Objects public override bool Equals(object obj) { - return ((Player)obj).NetworkId == NetworkId; + return ((Player)obj).NetworkId == this.NetworkId; } public override int GetHashCode() => (int)NetworkId; diff --git a/SharedLibraryCore/Objects/Report.cs b/SharedLibraryCore/Objects/Report.cs index 823f4e3df..a47f26ec9 100644 --- a/SharedLibraryCore/Objects/Report.cs +++ b/SharedLibraryCore/Objects/Report.cs @@ -8,15 +8,8 @@ namespace SharedLibraryCore.Objects { public class Report { - public Report(Player T, Player O, String R) - { - Target = T; - Origin = O; - Reason = R; - } - - public Player Target { get; private set; } - public Player Origin { get; private set; } - public String Reason { get; private set; } + public Player Target { get; set; } + public Player Origin { get; set; } + public String Reason { get; set; } } } diff --git a/SharedLibraryCore/RCon/Connection.cs b/SharedLibraryCore/RCon/Connection.cs index e721430a8..d362da7ae 100644 --- a/SharedLibraryCore/RCon/Connection.cs +++ b/SharedLibraryCore/RCon/Connection.cs @@ -17,6 +17,10 @@ namespace SharedLibraryCore.RCon const int BufferSize = 4096; public readonly byte[] ReceiveBuffer = new byte[BufferSize]; public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1); + public readonly ManualResetEventSlim OnSentData = new ManualResetEventSlim(false); + public readonly ManualResetEventSlim OnReceivedData = new ManualResetEventSlim(false); + public SocketAsyncEventArgs SendEventArgs { get; set; } = new SocketAsyncEventArgs(); + public SocketAsyncEventArgs ReceiveEventArgs { get; set; } = new SocketAsyncEventArgs(); public DateTime LastQuery { get; set; } = DateTime.Now; } @@ -43,23 +47,24 @@ namespace SharedLibraryCore.RCon var connectionState = ActiveQueries[this.Endpoint]; - var timeLeft = (DateTime.Now - connectionState.LastQuery).TotalMilliseconds; +#if DEBUG == true + Log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]"); +#endif + // enter the semaphore so only one query is sent at a time per server. + await connectionState.OnComplete.WaitAsync(); - if (timeLeft > 0) + var timeSinceLastQuery = (DateTime.Now - connectionState.LastQuery).TotalMilliseconds; + + if (timeSinceLastQuery < StaticHelpers.FloodProtectionInterval) { - await Task.Delay((int)timeLeft); + await Task.Delay(StaticHelpers.FloodProtectionInterval - (int)timeSinceLastQuery); } connectionState.LastQuery = DateTime.Now; #if DEBUG == true - Log.WriteDebug($"Waiting for semaphore to be released [${this.Endpoint}]"); -#endif - // enter the semaphore so only one query is sent at a time per server. - await connectionState.OnComplete.WaitAsync(); - -#if DEBUG == true - Log.WriteDebug($"Semaphore has been released [${this.Endpoint}]"); + Log.WriteDebug($"Semaphore has been released [{this.Endpoint}]"); + Log.WriteDebug($"Query [{this.Endpoint},{type.ToString()},{parameters}]"); #endif byte[] payload = null; @@ -82,33 +87,41 @@ namespace SharedLibraryCore.RCon } byte[] response = null; + retrySend: + connectionState.SendEventArgs.UserToken = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) + { + DontFragment = true, + Ttl = 42, + ExclusiveAddressUse = true, + }; + connectionState.OnSentData.Reset(); + connectionState.OnReceivedData.Reset(); + connectionState.ConnectionAttempts++; #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 try { response = await SendPayloadAsync(payload); connectionState.OnComplete.Release(1); + connectionState.ConnectionAttempts = 0; } catch (Exception ex) { if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails) { - 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.FloodProtectionInterval); goto retrySend; } - // the next thread can go ahead and enter - connectionState.OnComplete.Release(1); + connectionState.OnComplete.Release(1); Log.WriteDebug(ex.GetExceptionInfo()); throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]"); } - connectionState.ConnectionAttempts = 0; string responseString = Utilities.EncodingType.GetString(response, 0, response.Length).TrimEnd('\0') + '\n'; if (responseString.Contains("Invalid password")) @@ -128,45 +141,54 @@ namespace SharedLibraryCore.RCon private async Task SendPayloadAsync(byte[] payload) { - var rconSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) - { - DontFragment = true, - Ttl = 42, - ExclusiveAddressUse = true - }; - var connectionState = ActiveQueries[this.Endpoint]; + var rconSocket = (Socket)connectionState.SendEventArgs.UserToken; - var outgoingDataArgs = new SocketAsyncEventArgs() + if (connectionState.ReceiveEventArgs.RemoteEndPoint == null && + connectionState.SendEventArgs.RemoteEndPoint == null) { - RemoteEndPoint = this.Endpoint - }; - outgoingDataArgs.SetBuffer(payload); - outgoingDataArgs.Completed += OnDataSent; - - // send the data to the server - bool sendDataPending = rconSocket.SendToAsync(outgoingDataArgs); - - var incomingDataArgs = new SocketAsyncEventArgs() - { - RemoteEndPoint = this.Endpoint - }; - incomingDataArgs.SetBuffer(connectionState.ReceiveBuffer); - incomingDataArgs.Completed += OnDataReceived; - - // get our response back - rconSocket.ReceiveFromAsync(incomingDataArgs); - - 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); + // setup the event handlers only once because we're reusing the event args + connectionState.SendEventArgs.Completed += OnDataSent; + connectionState.ReceiveEventArgs.Completed += OnDataReceived; + connectionState.SendEventArgs.RemoteEndPoint = this.Endpoint; + connectionState.ReceiveEventArgs.RemoteEndPoint = this.Endpoint; + connectionState.ReceiveEventArgs.DisconnectReuseSocket = true; + connectionState.SendEventArgs.DisconnectReuseSocket = true; } - byte[] response = connectionState.ReceiveBuffer; + connectionState.SendEventArgs.SetBuffer(payload); + + // send the data to the server + bool sendDataPending = rconSocket.SendToAsync(connectionState.SendEventArgs); + + if (sendDataPending) + { + // the send has not been completed asyncronously + if (!await Task.Run(() => connectionState.OnSentData.Wait(StaticHelpers.SocketTimeout))) + { + rconSocket.Close(); + throw new NetworkException("Timed out sending data", rconSocket); + } + } + + connectionState.ReceiveEventArgs.SetBuffer(connectionState.ReceiveBuffer); + + // get our response back + bool receiveDataPending = rconSocket.ReceiveFromAsync(connectionState.ReceiveEventArgs); + + if (receiveDataPending) + { + if (!await Task.Run(() => connectionState.OnReceivedData.Wait(StaticHelpers.SocketTimeout))) + { + rconSocket.Close(); + throw new NetworkException("Timed out waiting for response", rconSocket); + } + } + + byte[] response = connectionState.ReceiveBuffer + .Take(connectionState.ReceiveEventArgs.BytesTransferred) + .ToArray(); + return response; } @@ -175,10 +197,7 @@ namespace SharedLibraryCore.RCon #if DEBUG == true Log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}"); #endif - if (ActiveQueries[this.Endpoint].OnComplete.CurrentCount == 0) - { - ActiveQueries[this.Endpoint].OnComplete.Release(1); - } + ActiveQueries[this.Endpoint].OnReceivedData.Set(); } private void OnDataSent(object sender, SocketAsyncEventArgs e) @@ -186,6 +205,7 @@ namespace SharedLibraryCore.RCon #if DEBUG == true Log.WriteDebug($"Sent {e.Buffer.Length} bytes to {e.ConnectSocket.RemoteEndPoint.ToString()}"); #endif + ActiveQueries[this.Endpoint].OnSentData.Set(); } } } diff --git a/SharedLibraryCore/RCon/StaticHelpers.cs b/SharedLibraryCore/RCon/StaticHelpers.cs index 378a5c9e4..2ac9cebd2 100644 --- a/SharedLibraryCore/RCon/StaticHelpers.cs +++ b/SharedLibraryCore/RCon/StaticHelpers.cs @@ -39,11 +39,11 @@ namespace SharedLibraryCore.RCon /// /// timeout in seconds to wait for a socket send or receive before giving up /// - public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 0, 0,150); + public static readonly int SocketTimeout = 1000; /// /// interval in milliseconds to wait before sending the next RCon request /// - public static readonly int FloodProtectionInterval = 350; + public static readonly int FloodProtectionInterval = 635; public static readonly int AllowedConnectionFails = 3; } } diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 25f800888..7c7fe04c5 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -192,14 +192,14 @@ namespace SharedLibraryCore /// /// Reason for kicking /// Player to kick - abstract public Task Kick(String Reason, Player Target, Player Origin); + abstract protected Task Kick(String Reason, Player Target, Player Origin); /// /// Temporarily ban a player ( default 1 hour ) from the server /// /// Reason for banning the player /// The player to ban - abstract public Task TempBan(String Reason, TimeSpan length, Player Target, Player Origin); + abstract protected Task TempBan(String Reason, TimeSpan length, Player Target, Player Origin); /// /// Perm ban a player from the server @@ -207,7 +207,7 @@ namespace SharedLibraryCore /// The reason for the ban /// The person to ban /// The person who banned the target - abstract public Task Ban(String Reason, Player Target, Player Origin); + abstract protected Task Ban(String Reason, Player Target, Player Origin); abstract public Task Warn(String Reason, Player Target, Player Origin); diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 38890b905..9655ea9f7 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -322,13 +322,13 @@ namespace SharedLibraryCore public static TimeSpan ParseTimespan(this string input) { - var expressionMatch = Regex.Match(input, @"[0-9]+.\b"); + var expressionMatch = Regex.Match(input, @"([0-9]+)(\w+)"); if (!expressionMatch.Success) // fallback to default tempban length of 1 hour return new TimeSpan(1, 0, 0); - char lengthDenote = expressionMatch.Value.ToLower()[expressionMatch.Value.Length - 1]; - int length = Int32.Parse(expressionMatch.Value.Substring(0, expressionMatch.Value.Length - 1)); + char lengthDenote = expressionMatch.Groups[2].ToString()[0]; + int length = Int32.Parse(expressionMatch.Groups[1].ToString()); var loc = CurrentLocalization.LocalizationIndex; @@ -337,22 +337,22 @@ namespace SharedLibraryCore return new TimeSpan(0, length, 0); } - if (lengthDenote == char.ToLower(loc["GLOBAL_TIME_HOURS"].First())) + if (lengthDenote == char.ToLower(loc["GLOBAL_TIME_HOURS"][0])) { return new TimeSpan(length, 0, 0); } - if (lengthDenote == char.ToLower(loc["GLOBAL_TIME_DAYS"].First())) + if (lengthDenote == char.ToLower(loc["GLOBAL_TIME_DAYS"][0])) { return new TimeSpan(length, 0, 0, 0); } - if (lengthDenote == char.ToLower(loc["GLOBAL_TIME_WEEKS"].First())) + if (lengthDenote == char.ToLower(loc["GLOBAL_TIME_WEEKS"][0])) { return new TimeSpan(length * 7, 0, 0, 0); } - if (lengthDenote == char.ToLower(loc["GLOBAL_TIME_YEARS"].First())) + if (lengthDenote == char.ToLower(loc["GLOBAL_TIME_YEARS"][0])) { return new TimeSpan(length * 365, 0, 0, 0); } diff --git a/WebfrontCore/Controllers/ActionController.cs b/WebfrontCore/Controllers/ActionController.cs index 69434bb92..b813d6d85 100644 --- a/WebfrontCore/Controllers/ActionController.cs +++ b/WebfrontCore/Controllers/ActionController.cs @@ -50,22 +50,24 @@ namespace WebfrontCore.Controllers { string duration = string.Empty; + var loc = Utilities.CurrentLocalization.LocalizationIndex; + switch (Duration) { case 1: - duration = "1h"; + duration = $"1{loc["GLOBAL_TIME_HOURS"][0]}"; break; case 2: - duration = "6h"; + duration = $"6{loc["GLOBAL_TIME_HOURS"][0]}"; break; case 3: - duration = "1d"; + duration = $"1{loc["GLOBAL_TIME_DAYS"][0]}"; break; case 4: - duration = "2d"; + duration = $"2{loc["GLOBAL_TIME_DAYS"][0]}"; break; case 5: - duration = "1w"; + duration = $"1{loc["GLOBAL_TIME_WEEKS"][0]}"; break; } diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs index 43bf35a9f..a5d7f4ae4 100644 --- a/WebfrontCore/Controllers/ConsoleController.cs +++ b/WebfrontCore/Controllers/ConsoleController.cs @@ -49,7 +49,7 @@ namespace WebfrontCore.Controllers Manager.GetEventHandler().AddEvent(remoteEvent); List response; // wait for the event to process - if (await remoteEvent.WaitAsync(60 * 1000)) + if (!(await remoteEvent.WaitAsync(60 * 1000)).Failed) { response = server.CommandResult.Where(c => c.ClientId == client.ClientId).ToList();