diff --git a/SharpChat/ChatContext.cs b/SharpChat/ChatContext.cs index 54d16ed..469d480 100644 --- a/SharpChat/ChatContext.cs +++ b/SharpChat/ChatContext.cs @@ -80,15 +80,6 @@ namespace SharpChat { return Users.Where(u => ids.Contains(u.UserId)).ToArray(); } - public void DebugPrintChannelUsers() { - lock(ChannelUsersAccess) { - Logger.Write("DebugPrintChannelUsers()"); - foreach(ChannelUserAssoc cua in ChannelUsers) - Logger.Write(cua); - Logger.Write(string.Empty); - } - } - public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) { if(duration > TimeSpan.Zero) SendTo(user, new ForceDisconnectPacket(ForceDisconnectReason.Banned, DateTimeOffset.Now + duration)); @@ -143,7 +134,6 @@ namespace SharpChat { foreach(ChatChannel chan in channels) { ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name)); - DebugPrintChannelUsers(); SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason)); Events.AddEvent(new UserDisconnectEvent(DateTimeOffset.Now, user, chan, reason)); diff --git a/SharpChat/ChatRateLimiter.cs b/SharpChat/ChatRateLimiter.cs deleted file mode 100644 index 2a3af13..0000000 --- a/SharpChat/ChatRateLimiter.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace SharpChat { - public enum ChatRateLimitState { - None, - Warning, - Kick, - } - - public class ChatRateLimiter { - private const int FLOOD_PROTECTION_AMOUNT = 30; - private const int FLOOD_PROTECTION_THRESHOLD = 10; - - private readonly Queue TimePoints = new(); - - public ChatRateLimitState State { - get { - if(TimePoints.Count == FLOOD_PROTECTION_AMOUNT) { - if((TimePoints.Last() - TimePoints.First()).TotalSeconds <= FLOOD_PROTECTION_THRESHOLD) - return ChatRateLimitState.Kick; - - if((TimePoints.Last() - TimePoints.Skip(5).First()).TotalSeconds <= FLOOD_PROTECTION_THRESHOLD) - return ChatRateLimitState.Warning; - } - - return ChatRateLimitState.None; - } - } - - public void AddTimePoint(DateTimeOffset? dto = null) { - if(!dto.HasValue) - dto = DateTimeOffset.Now; - - if(TimePoints.Count >= FLOOD_PROTECTION_AMOUNT) - TimePoints.Dequeue(); - - TimePoints.Enqueue(dto.Value); - } - } -} diff --git a/SharpChat/ChatUser.cs b/SharpChat/ChatUser.cs index 813b878..2fcca03 100644 --- a/SharpChat/ChatUser.cs +++ b/SharpChat/ChatUser.cs @@ -3,7 +3,7 @@ using System; using System.Text; namespace SharpChat { - public class BasicUser : IEquatable { + public class ChatUser : IEquatable { private const int RANK_NO_FLOOD = 9; public long UserId { get; set; } @@ -18,6 +18,11 @@ namespace SharpChat { public bool HasFloodProtection => Rank < RANK_NO_FLOOD; + public readonly RateLimiter RateLimiter = new(); + + // This needs to be a session thing + public ChatChannel CurrentChannel { get; set; } + public string DisplayName { get { StringBuilder sb = new(); @@ -36,6 +41,24 @@ namespace SharpChat { } } + public ChatUser() { } + + public ChatUser(MisuzuAuthInfo auth) { + UserId = auth.UserId; + ApplyAuth(auth); + } + + public void ApplyAuth(MisuzuAuthInfo auth) { + Username = auth.Username; + + if(Status == ChatUserStatus.Offline) + Status = ChatUserStatus.Online; + + Colour = ChatColour.FromMisuzu(auth.ColourRaw); + Rank = auth.Rank; + Permissions = auth.Permissions; + } + public bool Can(ChatUserPermissions perm, bool strict = false) { ChatUserPermissions perms = Permissions & perm; return strict ? perms == perm : perms > 0; @@ -63,56 +86,23 @@ namespace SharpChat { return sb.ToString(); } + public bool NameEquals(string name) { + return string.Equals(name, Username, StringComparison.InvariantCultureIgnoreCase) + || string.Equals(name, Nickname, StringComparison.InvariantCultureIgnoreCase) + || string.Equals(name, DisplayName, StringComparison.InvariantCultureIgnoreCase); + } + public override int GetHashCode() { return UserId.GetHashCode(); } public override bool Equals(object obj) { - return Equals(obj as BasicUser); + return Equals(obj as ChatUser); } - public bool Equals(BasicUser other) { + public bool Equals(ChatUser other) { return UserId == other?.UserId; } - } - - public class ChatUser : BasicUser { - public DateTimeOffset SilencedUntil { get; set; } - - public readonly ChatRateLimiter RateLimiter = new(); - - // This needs to be a session thing - public ChatChannel CurrentChannel { get; set; } - - public bool IsSilenced - => DateTimeOffset.UtcNow - SilencedUntil <= TimeSpan.Zero; - - public ChatUser() {} - - public ChatUser(MisuzuAuthInfo auth) { - UserId = auth.UserId; - ApplyAuth(auth, true); - } - - public void ApplyAuth(MisuzuAuthInfo auth, bool invalidateRestrictions = false) { - Username = auth.Username; - - if(Status == ChatUserStatus.Offline) - Status = ChatUserStatus.Online; - - Colour = ChatColour.FromMisuzu(auth.ColourRaw); - Rank = auth.Rank; - Permissions = auth.Permissions; - - if(invalidateRestrictions || !IsSilenced) - SilencedUntil = auth.SilencedUntil; - } - - public bool NameEquals(string name) { - return string.Equals(name, Username, StringComparison.InvariantCultureIgnoreCase) - || string.Equals(name, Nickname, StringComparison.InvariantCultureIgnoreCase) - || string.Equals(name, DisplayName, StringComparison.InvariantCultureIgnoreCase); - } public static string GetDMChannelName(ChatUser user1, ChatUser user2) { return user1.UserId < user2.UserId diff --git a/SharpChat/Commands/SilenceApplyCommand.cs b/SharpChat/Commands/SilenceApplyCommand.cs index 64bdcf6..b14f713 100644 --- a/SharpChat/Commands/SilenceApplyCommand.cs +++ b/SharpChat/Commands/SilenceApplyCommand.cs @@ -33,7 +33,7 @@ namespace SharpChat.Commands { return; } - if(silUser.IsSilenced) { + if(RNG.Next() > 1 /*silUser.IsSilenced*/) { ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.SILENCE_ALREADY)); return; } @@ -49,7 +49,7 @@ namespace SharpChat.Commands { silenceUntil = DateTimeOffset.UtcNow.AddSeconds(silenceSeconds); } - silUser.SilencedUntil = silenceUntil; + //silUser.SilencedUntil = silenceUntil; ctx.Chat.SendTo(silUser, new LegacyCommandResponse(LCR.SILENCED, false)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.TARGET_SILENCED, false, silUser.DisplayName)); } diff --git a/SharpChat/Commands/SilenceRevokeCommand.cs b/SharpChat/Commands/SilenceRevokeCommand.cs index f0ee1e3..a2f55c9 100644 --- a/SharpChat/Commands/SilenceRevokeCommand.cs +++ b/SharpChat/Commands/SilenceRevokeCommand.cs @@ -28,12 +28,12 @@ namespace SharpChat.Commands { return; } - if(!unsilUser.IsSilenced) { + if(RNG.Next() > 1 /*!unsilUser.IsSilenced*/) { ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.NOT_SILENCED)); return; } - unsilUser.SilencedUntil = DateTimeOffset.MinValue; + //unsilUser.SilencedUntil = DateTimeOffset.MinValue; ctx.Chat.SendTo(unsilUser, new LegacyCommandResponse(LCR.UNSILENCED, false)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.TARGET_UNSILENCED, false, unsilUser.DisplayName)); } diff --git a/SharpChat/EventStorage/MariaDBEventStorage.cs b/SharpChat/EventStorage/MariaDBEventStorage.cs index 978139f..5046034 100644 --- a/SharpChat/EventStorage/MariaDBEventStorage.cs +++ b/SharpChat/EventStorage/MariaDBEventStorage.cs @@ -72,7 +72,7 @@ namespace SharpChat.EventStorage { evt.DateTime = DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")); if(!reader.IsDBNull(reader.GetOrdinal("event_sender"))) { - evt.Sender = new BasicUser { + evt.Sender = new ChatUser { UserId = reader.GetInt64("event_sender"), Username = reader.GetString("event_sender_name"), Colour = ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")), diff --git a/SharpChat/Events/ChatMessage.cs b/SharpChat/Events/ChatMessage.cs index f1c9069..5f4963f 100644 --- a/SharpChat/Events/ChatMessage.cs +++ b/SharpChat/Events/ChatMessage.cs @@ -4,7 +4,7 @@ using System.Text.Json.Serialization; namespace SharpChat.Events { public class ChatMessage : IChatMessage { [JsonIgnore] - public BasicUser Sender { get; set; } + public ChatUser Sender { get; set; } [JsonIgnore] public string ChannelName { get; set; } diff --git a/SharpChat/Events/IChatEvent.cs b/SharpChat/Events/IChatEvent.cs index 4fc1924..f4b684d 100644 --- a/SharpChat/Events/IChatEvent.cs +++ b/SharpChat/Events/IChatEvent.cs @@ -12,7 +12,7 @@ namespace SharpChat.Events { public interface IChatEvent { DateTimeOffset DateTime { get; set; } - BasicUser Sender { get; set; } + ChatUser Sender { get; set; } string ChannelName { get; set; } ChatMessageFlags Flags { get; set; } long SequenceId { get; set; } diff --git a/SharpChat/Events/UserChannelJoinEvent.cs b/SharpChat/Events/UserChannelJoinEvent.cs index 9789542..0664537 100644 --- a/SharpChat/Events/UserChannelJoinEvent.cs +++ b/SharpChat/Events/UserChannelJoinEvent.cs @@ -7,7 +7,7 @@ namespace SharpChat.Events { public DateTimeOffset DateTime { get; set; } [JsonIgnore] - public BasicUser Sender { get; set; } + public ChatUser Sender { get; set; } [JsonIgnore] public string ChannelName { get; set; } @@ -19,7 +19,7 @@ namespace SharpChat.Events { public long SequenceId { get; set; } public UserChannelJoinEvent() { } - public UserChannelJoinEvent(DateTimeOffset joined, BasicUser user, ChatChannel channel) { + public UserChannelJoinEvent(DateTimeOffset joined, ChatUser user, ChatChannel channel) { DateTime = joined; Sender = user; ChannelName = channel.Name; diff --git a/SharpChat/Events/UserChannelLeaveEvent.cs b/SharpChat/Events/UserChannelLeaveEvent.cs index 229b128..cd590c8 100644 --- a/SharpChat/Events/UserChannelLeaveEvent.cs +++ b/SharpChat/Events/UserChannelLeaveEvent.cs @@ -7,7 +7,7 @@ namespace SharpChat.Events { public DateTimeOffset DateTime { get; set; } [JsonIgnore] - public BasicUser Sender { get; set; } + public ChatUser Sender { get; set; } [JsonIgnore] public string ChannelName { get; set; } @@ -19,7 +19,7 @@ namespace SharpChat.Events { public long SequenceId { get; set; } public UserChannelLeaveEvent() { } - public UserChannelLeaveEvent(DateTimeOffset parted, BasicUser user, ChatChannel channel) { + public UserChannelLeaveEvent(DateTimeOffset parted, ChatUser user, ChatChannel channel) { DateTime = parted; Sender = user; ChannelName = channel.Name; diff --git a/SharpChat/Events/UserConnectEvent.cs b/SharpChat/Events/UserConnectEvent.cs index ee7a44b..28db92b 100644 --- a/SharpChat/Events/UserConnectEvent.cs +++ b/SharpChat/Events/UserConnectEvent.cs @@ -7,7 +7,7 @@ namespace SharpChat.Events { public DateTimeOffset DateTime { get; set; } [JsonIgnore] - public BasicUser Sender { get; set; } + public ChatUser Sender { get; set; } [JsonIgnore] public string ChannelName { get; set; } @@ -19,7 +19,7 @@ namespace SharpChat.Events { public long SequenceId { get; set; } public UserConnectEvent() { } - public UserConnectEvent(DateTimeOffset joined, BasicUser user, ChatChannel channel) { + public UserConnectEvent(DateTimeOffset joined, ChatUser user, ChatChannel channel) { DateTime = joined; Sender = user; ChannelName = channel.Name; diff --git a/SharpChat/Events/UserDisconnectEvent.cs b/SharpChat/Events/UserDisconnectEvent.cs index ca19b96..20b4c4a 100644 --- a/SharpChat/Events/UserDisconnectEvent.cs +++ b/SharpChat/Events/UserDisconnectEvent.cs @@ -9,7 +9,7 @@ namespace SharpChat.Events { public DateTimeOffset DateTime { get; set; } [JsonIgnore] - public BasicUser Sender { get; set; } + public ChatUser Sender { get; set; } [JsonIgnore] public string ChannelName { get; set; } @@ -24,7 +24,7 @@ namespace SharpChat.Events { public UserDisconnectReason Reason { get; set; } public UserDisconnectEvent() { } - public UserDisconnectEvent(DateTimeOffset parted, BasicUser user, ChatChannel channel, UserDisconnectReason reason) { + public UserDisconnectEvent(DateTimeOffset parted, ChatUser user, ChatChannel channel, UserDisconnectReason reason) { DateTime = parted; Sender = user; ChannelName = channel.Name; diff --git a/SharpChat/PacketHandlers/SendMessageHandler.cs b/SharpChat/PacketHandlers/SendMessageHandler.cs index 188e280..7460cc1 100644 --- a/SharpChat/PacketHandlers/SendMessageHandler.cs +++ b/SharpChat/PacketHandlers/SendMessageHandler.cs @@ -46,7 +46,7 @@ namespace SharpChat.PacketHandlers { if(channel == null || !ctx.Chat.IsInChannel(user, channel) - || (user.IsSilenced && !user.Can(ChatUserPermissions.SilenceUser))) + /*|| (user.IsSilenced && !user.Can(ChatUserPermissions.SilenceUser))*/) return; if(user.Status != ChatUserStatus.Online) { diff --git a/SharpChat/RateLimiter.cs b/SharpChat/RateLimiter.cs new file mode 100644 index 0000000..7b95967 --- /dev/null +++ b/SharpChat/RateLimiter.cs @@ -0,0 +1,45 @@ +using System; + +namespace SharpChat { + public class RateLimiter { + public const int DEFAULT_SIZE = 30; + public const int DEFAULT_MINIMUM_DELAY = 10000; + public const int DEFAULT_RISKY_OFFSET = 5; + + private readonly int Size; + private readonly int MinimumDelay; + private readonly int RiskyOffset; + private readonly long[] TimePoints; + + public RateLimiter( + int size = DEFAULT_SIZE, + int minDelay = DEFAULT_MINIMUM_DELAY, + int riskyOffset = DEFAULT_RISKY_OFFSET + ) { + if(size < 2) + throw new ArgumentException("Size is too small.", nameof(size)); + if(minDelay < 1000) + throw new ArgumentException("Minimum delay is inhuman.", nameof(minDelay)); + if(riskyOffset != 0) { + if(riskyOffset >= size) + throw new ArgumentException("Risky offset may not be greater or equal to the size.", nameof(riskyOffset)); + else if(riskyOffset < 0) + throw new ArgumentException("Risky offset may not be negative.", nameof(riskyOffset)); + } + + Size = size; + MinimumDelay = minDelay; + RiskyOffset = riskyOffset; + TimePoints = new long[Size]; + } + + public bool IsRisky => TimePoints[RiskyOffset] != 0 && TimePoints[RiskyOffset + 1] != 0 && TimePoints[RiskyOffset] + MinimumDelay >= TimePoints[Size - 1]; + public bool IsExceeded => TimePoints[0] != 0 && TimePoints[1] != 0 && TimePoints[0] + MinimumDelay >= TimePoints[Size - 1]; + + public void Update() { + for(int i = 1; i < Size; ++i) + TimePoints[i - 1] = TimePoints[i]; + TimePoints[Size - 1] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + } + } +} diff --git a/SharpChat/SockChatServer.cs b/SharpChat/SockChatServer.cs index ab51041..f74c62f 100644 --- a/SharpChat/SockChatServer.cs +++ b/SharpChat/SockChatServer.cs @@ -101,8 +101,8 @@ namespace SharpChat { new PardonUserCommand(msz), new PardonAddressCommand(msz), new BanListCommand(msz), - new SilenceApplyCommand(), - new SilenceRevokeCommand(), + //new SilenceApplyCommand(), + //new SilenceRevokeCommand(), new RemoteAddressCommand(), }); @@ -182,9 +182,9 @@ namespace SharpChat { // this doesn't affect non-authed connections????? if(conn.User is not null && conn.User.HasFloodProtection) { - conn.User.RateLimiter.AddTimePoint(); + conn.User.RateLimiter.Update(); - if(conn.User.RateLimiter.State == ChatRateLimitState.Kick) { + if(conn.User.RateLimiter.IsExceeded) { Task.Run(async () => { TimeSpan duration = TimeSpan.FromSeconds(FloodKickLength); @@ -198,7 +198,7 @@ namespace SharpChat { Context.BanUser(conn.User, duration, UserDisconnectReason.Flood); }).Wait(); return; - } else if(conn.User.RateLimiter.State == ChatRateLimitState.Warning) + } else if(conn.User.RateLimiter.IsRisky) Context.SendTo(conn.User, new FloodWarningPacket()); }