diff --git a/NosCore.Networking.sln.DotSettings b/NosCore.Networking.sln.DotSettings index 53bb462..6e70b1b 100644 --- a/NosCore.Networking.sln.DotSettings +++ b/NosCore.Networking.sln.DotSettings @@ -8,4 +8,5 @@ ----------------------------------- True False - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/src/NosCore.Networking/BroadcastableExtension.cs b/src/NosCore.Networking/BroadcastableExtension.cs index dd186c7..ee413a6 100644 --- a/src/NosCore.Networking/BroadcastableExtension.cs +++ b/src/NosCore.Networking/BroadcastableExtension.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using DotNetty.Transport.Channels.Groups; using NosCore.Networking.SessionGroup; using NosCore.Packets.Interfaces; @@ -34,9 +33,9 @@ public static Task SendPacketAsync(this IBroadcastable channelGroup, IPacket pac /// /// The broadcastable group to send the packet to. /// The packet to send. - /// The channel matcher to filter recipients. + /// The session matcher to filter recipients. /// A task representing the asynchronous send operation. - public static Task SendPacketAsync(this IBroadcastable channelGroup, IPacket packet, IChannelMatcher matcher) + public static Task SendPacketAsync(this IBroadcastable channelGroup, IPacket packet, ISessionMatcher matcher) { return channelGroup.SendPacketsAsync(new[] { packet }, matcher); } @@ -46,29 +45,39 @@ public static Task SendPacketAsync(this IBroadcastable channelGroup, IPacket pac /// /// The broadcastable group to send the packets to. /// The collection of packets to send. - /// The optional channel matcher to filter recipients. + /// The optional session matcher to filter recipients. /// A task representing the asynchronous send operation. public static async Task SendPacketsAsync(this IBroadcastable channelGroup, IEnumerable packets, - IChannelMatcher? matcher) + ISessionMatcher? matcher) { var packetDefinitions = (packets as IPacket[] ?? packets).Where(c => c != null).ToArray(); - if (packetDefinitions.Any()) + if (packetDefinitions.Length == 0) { - Parallel.ForEach(packets, packet => channelGroup.LastPackets.Enqueue(packet)); - Parallel.For(0, channelGroup.LastPackets.Count - channelGroup.MaxPacketsBuffer, (_, __) => channelGroup.LastPackets.TryDequeue(out var ___)); - if (channelGroup.Sessions == null!) - { - return; - } + return; + } + + foreach (var packet in packetDefinitions) + { + channelGroup.LastPackets.Enqueue(packet); + } + + while (channelGroup.LastPackets.Count > channelGroup.MaxPacketsBuffer) + { + channelGroup.LastPackets.TryDequeue(out _); + } - if (matcher == null) - { - await channelGroup.Sessions.Broadcast(packetDefinitions).ConfigureAwait(false); - } - else - { - await channelGroup.Sessions.Broadcast(packetDefinitions, matcher).ConfigureAwait(false); - } + if (channelGroup.Sessions == null!) + { + return; + } + + if (matcher == null) + { + await channelGroup.Sessions.Broadcast(packetDefinitions).ConfigureAwait(false); + } + else + { + await channelGroup.Sessions.Broadcast(packetDefinitions, matcher).ConfigureAwait(false); } } diff --git a/src/NosCore.Networking/Encoding/Filter/RequestFilter.cs b/src/NosCore.Networking/Encoding/Filter/RequestFilter.cs index d4d13ad..7827a28 100644 --- a/src/NosCore.Networking/Encoding/Filter/RequestFilter.cs +++ b/src/NosCore.Networking/Encoding/Filter/RequestFilter.cs @@ -1,43 +1,25 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- using System; -using System.Collections.Generic; -using DotNetty.Buffers; -using DotNetty.Codecs; -using DotNetty.Transport.Channels; +using System.Net; namespace NosCore.Networking.Encoding.Filter { /// - /// Abstract base class for request filters that process incoming byte data. + /// Defines a request filter that processes incoming byte data. /// - public abstract class RequestFilter : MessageToMessageDecoder + public interface IRequestFilter { /// /// Filters incoming request data. /// - /// The channel handler context. + /// The remote endpoint of the connection. /// The incoming message bytes. /// The filtered byte array, or null if the request should be blocked. - public abstract byte[]? Filter(IChannelHandlerContext context, Span message); - - /// - /// Decodes the incoming byte buffer through the filter. - /// - /// The channel handler context. - /// The byte buffer to decode. - /// The output list to add filtered results to. - protected override void Decode(IChannelHandlerContext context, IByteBuffer message, List output) - { - var result = Filter(context, ((Span)message.Array).Slice(message.ArrayOffset, message.ReadableBytes)); - if (result != null) - { - output.Add(Unpooled.WrappedBuffer(result)); - } - } + byte[]? Filter(EndPoint remoteEndPoint, Span message); } } diff --git a/src/NosCore.Networking/Encoding/Filter/SpamRequestFilter.cs b/src/NosCore.Networking/Encoding/Filter/SpamRequestFilter.cs index 9293b43..2bd97c6 100644 --- a/src/NosCore.Networking/Encoding/Filter/SpamRequestFilter.cs +++ b/src/NosCore.Networking/Encoding/Filter/SpamRequestFilter.cs @@ -1,4 +1,4 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Net; -using DotNetty.Transport.Channels; using Microsoft.Extensions.Logging; using NodaTime; using NosCore.Networking.Resource; @@ -18,7 +17,7 @@ namespace NosCore.Networking.Encoding.Filter /// /// Filters spam requests by rate-limiting connections from the same IP address. /// - public class SpamRequestFilter : RequestFilter + public class SpamRequestFilter : IRequestFilter { private readonly Dictionary _connectionsByIp = new(); private readonly TimeSpan _timeBetweenConnection = TimeSpan.FromMilliseconds(1000); @@ -26,11 +25,6 @@ public class SpamRequestFilter : RequestFilter private readonly ILogger _logger; private readonly ILogLanguageLocalizer _logLanguage; - /// - /// Gets a value indicating whether this handler can be shared across multiple channels. - /// - public override bool IsSharable => true; - /// /// Initializes a new instance of the class. /// @@ -47,21 +41,21 @@ public SpamRequestFilter(IClock clock, ILogger logger, ILogLa /// /// Filters incoming requests based on connection rate from the same IP address. /// - /// The channel handler context. + /// The remote endpoint of the connection. /// The incoming message bytes. /// The message bytes if allowed, or null if blocked by the spam filter. - public override byte[]? Filter(IChannelHandlerContext context, Span message) + public byte[]? Filter(EndPoint remoteEndPoint, Span message) { - if (_connectionsByIp.TryGetValue(context.Channel.RemoteAddress, out var date)) + if (_connectionsByIp.TryGetValue(remoteEndPoint, out var date)) { if (date.Plus(Duration.FromTimeSpan(_timeBetweenConnection)) > _clock.GetCurrentInstant()) { - _logger.LogWarning(_logLanguage[LogLanguageKey.BLOCKED_BY_SPAM_FILTER], context.Channel.RemoteAddress); + _logger.LogWarning(_logLanguage[LogLanguageKey.BLOCKED_BY_SPAM_FILTER], remoteEndPoint); return null; } } - _connectionsByIp[context.Channel.RemoteAddress] = _clock.GetCurrentInstant(); + _connectionsByIp[remoteEndPoint] = _clock.GetCurrentInstant(); return message.ToArray(); } } diff --git a/src/NosCore.Networking/Encoding/FrameDelimiter.cs b/src/NosCore.Networking/Encoding/FrameDelimiter.cs index f28d9b4..d1b4113 100644 --- a/src/NosCore.Networking/Encoding/FrameDelimiter.cs +++ b/src/NosCore.Networking/Encoding/FrameDelimiter.cs @@ -1,63 +1,16 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- -using System.Collections.Generic; -using System.Linq; -using DotNetty.Buffers; -using DotNetty.Codecs; -using DotNetty.Transport.Channels; -using NosCore.Networking.SessionRef; - namespace NosCore.Networking.Encoding; /// -/// Delimits incoming byte streams into frames based on session-specific delimiters. +/// Provides delimiter calculation for session-specific frame detection. /// -public class FrameDelimiter : ByteToMessageDecoder +public static class FrameDelimiter { - private readonly ISessionRefHolder _sessionRefHolder; - - /// - /// Initializes a new instance of the class. - /// - /// The session reference holder. - public FrameDelimiter(ISessionRefHolder sessionRefHolder) - { - _sessionRefHolder = sessionRefHolder; - } - - /// - /// Decodes the incoming byte buffer into frames based on delimiters. - /// - /// The channel handler context. - /// The input byte buffer. - /// The output list to add decoded frames to. - protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List output) - { - var sessionId = context.Channel.Id.AsLongText(); - var mapper = _sessionRefHolder[sessionId]; - - var currentDelimiter = GetDelimiter(mapper.SessionId, mapper.SessionId == 0); - - var startReaderIndex = input.ReaderIndex; - var endReaderIndex = startReaderIndex + input.ReadableBytes; - - for (var i = startReaderIndex; i < endReaderIndex; i++) - { - if (input.GetByte(i) == currentDelimiter) - { - var frameLength = i - startReaderIndex + 1; - var frame = input.Copy(startReaderIndex, frameLength); - output.Add(frame); - input.SetReaderIndex(i + 1); - break; - } - } - } - /// /// Gets the delimiter byte for a specific session. /// @@ -79,4 +32,4 @@ public static byte GetDelimiter(int session, bool isFirstPacket = false) _ => (0xff + 0xF) & 0xFF }); } -} \ No newline at end of file +} diff --git a/src/NosCore.Networking/Encoding/IDecoder.cs b/src/NosCore.Networking/Encoding/IDecoder.cs index d97c590..fd5687f 100644 --- a/src/NosCore.Networking/Encoding/IDecoder.cs +++ b/src/NosCore.Networking/Encoding/IDecoder.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; -using DotNetty.Transport.Channels; using NosCore.Packets.Interfaces; namespace NosCore.Networking.Encoding @@ -14,7 +13,7 @@ namespace NosCore.Networking.Encoding /// /// Defines a packet decoder that converts byte data into packets. /// - public interface IDecoder : IChannelHandler + public interface IDecoder { /// /// Decodes a byte span into a collection of packets. diff --git a/src/NosCore.Networking/Encoding/IEncoder.cs b/src/NosCore.Networking/Encoding/IEncoder.cs index 4fe4e76..2bba3f6 100644 --- a/src/NosCore.Networking/Encoding/IEncoder.cs +++ b/src/NosCore.Networking/Encoding/IEncoder.cs @@ -4,9 +4,7 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- -using System; using System.Collections.Generic; -using DotNetty.Transport.Channels; using NosCore.Packets.Interfaces; namespace NosCore.Networking.Encoding @@ -14,7 +12,7 @@ namespace NosCore.Networking.Encoding /// /// Defines a packet encoder that converts packets to byte arrays for transmission. /// - public interface IEncoder : IChannelHandler + public interface IEncoder { /// /// Encodes a collection of packets into a byte array. diff --git a/src/NosCore.Networking/Encoding/LoginDecoder.cs b/src/NosCore.Networking/Encoding/LoginDecoder.cs index b4059a0..3b1dced 100644 --- a/src/NosCore.Networking/Encoding/LoginDecoder.cs +++ b/src/NosCore.Networking/Encoding/LoginDecoder.cs @@ -1,4 +1,4 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| @@ -6,11 +6,7 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; -using DotNetty.Buffers; -using DotNetty.Codecs; -using DotNetty.Transport.Channels; using Microsoft.Extensions.Logging; using NosCore.Networking.Resource; using NosCore.Networking.SessionRef; @@ -22,7 +18,7 @@ namespace NosCore.Networking.Encoding /// /// Decodes packets from login server communication using region-specific decoding. /// - public class LoginDecoder : MessageToMessageDecoder, IDecoder + public class LoginDecoder : IDecoder { private readonly IDeserializer _deserializer; private readonly ILogger _logger; @@ -90,22 +86,5 @@ public IEnumerable Decode(string clientSessionId, Span message) return Array.Empty(); } - - /// - /// Decodes a byte buffer into packets. - /// - /// The channel handler context. - /// The byte buffer containing encoded packet data. - /// The output list to add decoded packets to. - protected override void Decode(IChannelHandlerContext context, IByteBuffer message, List output) - { - var packets = Decode(context.Channel.Id.AsLongText(), - ((Span)message.Array).Slice(message.ArrayOffset, message.ReadableBytes)); - - if (packets.Any()) - { - output.Add(packets); - } - } } -} \ No newline at end of file +} diff --git a/src/NosCore.Networking/Encoding/LoginEncoder.cs b/src/NosCore.Networking/Encoding/LoginEncoder.cs index 813400c..9d2a029 100644 --- a/src/NosCore.Networking/Encoding/LoginEncoder.cs +++ b/src/NosCore.Networking/Encoding/LoginEncoder.cs @@ -1,4 +1,4 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| @@ -7,9 +7,6 @@ using System; using System.Collections.Generic; using System.Linq; -using DotNetty.Buffers; -using DotNetty.Codecs; -using DotNetty.Transport.Channels; using Microsoft.Extensions.Logging; using NosCore.Networking.Extensions; using NosCore.Networking.Resource; @@ -22,7 +19,7 @@ namespace NosCore.Networking.Encoding /// /// Encodes packets for login server communication using region-specific encoding. /// - public class LoginEncoder : MessageToMessageEncoder>, IEncoder + public class LoginEncoder : IEncoder { private readonly ILogger _logger; private readonly ISerializer _serializer; @@ -44,18 +41,6 @@ public LoginEncoder(ILogger logger, ISerializer serializer, ISessi _logLanguage = logLanguage; } - /// - /// Encodes packets into a byte buffer for transmission. - /// - /// The channel handler context. - /// The packets to encode. - /// The output list to add the encoded buffer to. - protected override void Encode(IChannelHandlerContext context, IEnumerable message, - List output) - { - output.Add(Unpooled.WrappedBuffer(Encode(context.Channel.Id.AsLongText(), message))); - } - /// /// Encodes a collection of packets into a byte array using login server encoding. /// @@ -89,4 +74,4 @@ public byte[] Encode(string clientSessionId, IEnumerable packets) return Array.Empty(); } } -} \ No newline at end of file +} diff --git a/src/NosCore.Networking/Encoding/WorldDecoder.cs b/src/NosCore.Networking/Encoding/WorldDecoder.cs index e59cd66..64a4a9a 100644 --- a/src/NosCore.Networking/Encoding/WorldDecoder.cs +++ b/src/NosCore.Networking/Encoding/WorldDecoder.cs @@ -8,9 +8,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using DotNetty.Buffers; -using DotNetty.Codecs; -using DotNetty.Transport.Channels; using Microsoft.Extensions.Logging; using NosCore.Networking.Extensions; using NosCore.Networking.Resource; @@ -25,7 +22,7 @@ namespace NosCore.Networking.Encoding /// /// Decodes packets from world server communication using region-specific decoding. /// - public class WorldDecoder : MessageToMessageDecoder, IDecoder + public class WorldDecoder : IDecoder { private readonly IDeserializer _deserializer; private readonly ILogger _logger; @@ -182,23 +179,6 @@ private static string DecryptCustomParameter(byte[] str, out byte[] endOfPacket) } } - /// - /// Decodes a byte buffer into packets. - /// - /// The channel handler context. - /// The byte buffer containing encoded packet data. - /// The output list to add decoded packets to. - protected override void Decode(IChannelHandlerContext context, IByteBuffer message, List output) - { - var packets = Decode(context.Channel.Id.AsLongText(), - ((Span)message.Array).Slice(message.ArrayOffset, message.ReadableBytes)); - - if (packets.Any()) - { - output.Add(packets); - } - } - /// /// Decodes a byte span into a collection of packets using world server decoding. /// diff --git a/src/NosCore.Networking/Encoding/WorldEncoder.cs b/src/NosCore.Networking/Encoding/WorldEncoder.cs index f79f8ac..adc0d7e 100644 --- a/src/NosCore.Networking/Encoding/WorldEncoder.cs +++ b/src/NosCore.Networking/Encoding/WorldEncoder.cs @@ -1,4 +1,4 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| @@ -7,9 +7,6 @@ using System; using System.Collections.Generic; using System.Linq; -using DotNetty.Buffers; -using DotNetty.Codecs; -using DotNetty.Transport.Channels; using NosCore.Networking.Extensions; using NosCore.Networking.SessionRef; using NosCore.Packets.Interfaces; @@ -19,7 +16,7 @@ namespace NosCore.Networking.Encoding /// /// Encodes packets for world server communication using region-specific encoding. /// - public class WorldEncoder : MessageToMessageEncoder>, IEncoder + public class WorldEncoder : IEncoder { private readonly ISerializer _serializer; private readonly ISessionRefHolder _sessionRefHolder; @@ -35,18 +32,6 @@ public WorldEncoder(ISerializer serializer, ISessionRefHolder sessionRefHolder) _sessionRefHolder = sessionRefHolder; } - /// - /// Encodes packets into a byte buffer for transmission. - /// - /// The channel handler context. - /// The packets to encode. - /// The output list to add the encoded buffer to. - protected override void Encode(IChannelHandlerContext context, IEnumerable message, - List output) - { - output.Add(Unpooled.WrappedBuffer(Encode(context.Channel.Id.AsLongText(), message))); - } - /// /// Encodes a collection of packets into a byte array using world server encoding. /// @@ -79,6 +64,5 @@ public byte[] Encode(string clientSessionId, IEnumerable packets) return encryptedData; }).ToArray(); } - } -} \ No newline at end of file +} diff --git a/src/NosCore.Networking/Filters/PipelineFilter.cs b/src/NosCore.Networking/Filters/PipelineFilter.cs new file mode 100644 index 0000000..2d45eb9 --- /dev/null +++ b/src/NosCore.Networking/Filters/PipelineFilter.cs @@ -0,0 +1,104 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using NosCore.Networking.Encoding; +using NosCore.Networking.Encoding.Filter; +using NosCore.Networking.SessionRef; +using SuperSocket.ProtoBase; +using SuperSocket.Server.Abstractions.Session; +using System; +using System.Buffers; +using System.Collections.Generic; + +namespace NosCore.Networking.Filters +{ + public class PipelineFilter : IPipelineFilter + { + private readonly IDecoder _decoder; + private readonly ISessionRefHolder _sessionRefHolder; + private readonly IEnumerable _requestFilters; + private readonly bool _useDelimiter; + + public IPackageDecoder? Decoder { get; set; } + public IPipelineFilter? NextFilter { get; private set; } + public object? Context { get; set; } + + public PipelineFilter( + IDecoder decoder, + ISessionRefHolder sessionRefHolder, + IEnumerable requestFilters, + bool useDelimiter) + { + _decoder = decoder; + _sessionRefHolder = sessionRefHolder; + _requestFilters = requestFilters; + _useDelimiter = useDelimiter; + } + + public NosPackageInfo Filter(ref SequenceReader reader) + { + var typedCtx = Context as IAppSession; + if (reader.Remaining == 0) + { + return default!; + } + + if (!_sessionRefHolder.TryGetValue(typedCtx!.SessionID, out var mapper)) + { + mapper = new RegionTypeMapping(0, NosCore.Shared.Enumerations.RegionType.EN); + _sessionRefHolder.Add(typedCtx.SessionID, mapper); + } + byte[] frameData; + + if (_useDelimiter) + { + var delimiter = FrameDelimiter.GetDelimiter(mapper.SessionId, mapper.SessionId == 0); + + if (!reader.TryReadTo(out ReadOnlySequence frame, delimiter, advancePastDelimiter: true)) + { + return default!; + } + + frameData = frame.ToArray(); + } + else + { + frameData = reader.UnreadSequence.ToArray(); + reader.Advance(reader.Remaining); + } + + var filteredData = ApplyFilters(frameData); + if (filteredData == null) + { + return default!; + } + + var packets = _decoder.Decode(typedCtx.SessionID, filteredData.AsSpan()); + + return new NosPackageInfo { Packets = packets }; + } + + private byte[]? ApplyFilters(byte[] data) + { + byte[]? currentData = data; + var typedCtx = Context as IAppSession; + foreach (var filter in _requestFilters) + { + if (currentData == null || typedCtx?.RemoteEndPoint == null) + { + return null; + } + currentData = filter.Filter(typedCtx.RemoteEndPoint, currentData.AsSpan()); + } + return currentData; + } + + public void Reset() + { + NextFilter = null; + } + } +} diff --git a/src/NosCore.Networking/IChannel.cs b/src/NosCore.Networking/IChannel.cs new file mode 100644 index 0000000..a1d7e15 --- /dev/null +++ b/src/NosCore.Networking/IChannel.cs @@ -0,0 +1,35 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using System; +using System.Threading.Tasks; + +namespace NosCore.Networking +{ + /// + /// Defines a communication channel for network operations. + /// + public interface IChannel + { + /// + /// Gets the unique identifier for this channel. + /// + string Id { get; } + + /// + /// Disconnects the channel asynchronously. + /// + /// A task representing the asynchronous disconnect operation. + Task DisconnectAsync(); + + /// + /// Sends data to the channel asynchronously. + /// + /// The data to send. + /// A task representing the asynchronous send operation. + ValueTask SendAsync(ReadOnlyMemory data); + } +} diff --git a/src/NosCore.Networking/INetworkClient.cs b/src/NosCore.Networking/INetworkClient.cs index 8cdbb8e..5db9ed2 100644 --- a/src/NosCore.Networking/INetworkClient.cs +++ b/src/NosCore.Networking/INetworkClient.cs @@ -1,4 +1,4 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| @@ -6,22 +6,36 @@ using System.Collections.Generic; using System.Threading.Tasks; -using DotNetty.Transport.Channels; -using DotNetty.Transport.Channels.Sockets; using NosCore.Packets.Interfaces; namespace NosCore.Networking { /// - /// Defines a network client that handles packet communication and channel management. + /// Defines a network client that handles packet communication and session management. /// - public interface INetworkClient : IChannelHandler + public interface INetworkClient { + /// + /// Gets the unique session key for this client. + /// + string SessionKey { get; } + /// /// Gets or sets the unique session identifier for this client. /// int SessionId { get; set; } + /// + /// Gets the communication channel associated with this client. + /// + IChannel? Channel { get; } + + /// + /// Registers a channel with this network client. + /// + /// The channel to register. + void RegisterChannel(IChannel channel); + /// /// Disconnects the client asynchronously. /// @@ -41,11 +55,5 @@ public interface INetworkClient : IChannelHandler /// The collection of packets to send. /// A task representing the asynchronous send operation. Task SendPacketsAsync(IEnumerable packets); - - /// - /// Registers a socket channel with this network client. - /// - /// The socket channel to register. - void RegisterChannel(ISocketChannel channel); } -} \ No newline at end of file +} diff --git a/src/NosCore.Networking/IPipelineConfiguration.cs b/src/NosCore.Networking/IPipelineConfiguration.cs index 2e44d00..6cfeccc 100644 --- a/src/NosCore.Networking/IPipelineConfiguration.cs +++ b/src/NosCore.Networking/IPipelineConfiguration.cs @@ -4,6 +4,8 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- +using NosCore.Shared.Enumerations; + namespace NosCore.Networking; /// @@ -15,4 +17,9 @@ public interface IPipelineConfiguration /// Gets or sets a value indicating whether to use frame delimiters in the pipeline. /// public bool UseDelimiter { get; set; } + + /// + /// Gets or sets the region type for encoding/decoding. + /// + public RegionType Language { get; set; } } \ No newline at end of file diff --git a/src/NosCore.Networking/IPipelineFactory.cs b/src/NosCore.Networking/IPipelineFactory.cs deleted file mode 100644 index 8048280..0000000 --- a/src/NosCore.Networking/IPipelineFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -// __ _ __ __ ___ __ ___ ___ -// | \| |/__\ /' _/ / _//__\| _ \ __| -// | | ' | \/ |`._`.| \_| \/ | v / _| -// |_|\__|\__/ |___/ \__/\__/|_|_\___| -// ----------------------------------- - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NosCore.Networking -{ - /// - /// Defines a factory for creating network channel pipelines. - /// - public interface IPipelineFactory - { - /// - /// Creates and configures the network channel pipeline with all required handlers. - /// - void CreatePipeline(); - } -} diff --git a/src/NosCore.Networking/NetworkChannel.cs b/src/NosCore.Networking/NetworkChannel.cs new file mode 100644 index 0000000..1e04c18 --- /dev/null +++ b/src/NosCore.Networking/NetworkChannel.cs @@ -0,0 +1,54 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using System; +using System.Threading.Tasks; +using SuperSocket.Connection; +using SuperSocket.Server.Abstractions.Session; + +namespace NosCore.Networking +{ + /// + /// Represents a communication channel that wraps a SuperSocket session. + /// + public class NetworkChannel : IChannel + { + private readonly IAppSession _session; + + /// + /// Initializes a new instance of the class. + /// + /// The SuperSocket session to wrap. + public NetworkChannel(IAppSession session) + { + _session = session; + } + + /// + /// Gets the unique identifier for this channel. + /// + public string Id => _session.SessionID; + + /// + /// Disconnects the channel asynchronously. + /// + /// A task representing the asynchronous disconnect operation. + public async Task DisconnectAsync() + { + await _session.CloseAsync(CloseReason.LocalClosing); + } + + /// + /// Sends data to the channel asynchronously. + /// + /// The data to send. + /// A task representing the asynchronous send operation. + public ValueTask SendAsync(ReadOnlyMemory data) + { + return _session.SendAsync(data); + } + } +} diff --git a/src/NosCore.Networking/NetworkClient.cs b/src/NosCore.Networking/NetworkClient.cs index 2451ac3..44416d0 100644 --- a/src/NosCore.Networking/NetworkClient.cs +++ b/src/NosCore.Networking/NetworkClient.cs @@ -1,4 +1,4 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| @@ -8,10 +8,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Net.Sockets; using System.Threading.Tasks; -using DotNetty.Transport.Channels; -using DotNetty.Transport.Channels.Sockets; +using NosCore.Networking.Encoding; using NosCore.Networking.Resource; using NosCore.Packets.Interfaces; using NosCore.Shared.I18N; @@ -20,30 +18,35 @@ namespace NosCore.Networking { /// - /// Represents a network client that manages packet communication and handles channel events. + /// Represents a network client that manages packet communication and handles session events. /// - public class NetworkClient : ChannelHandlerAdapter, INetworkClient + public class NetworkClient : INetworkClient { private const short MaxPacketsBuffer = 50; private readonly ILogger _logger; private readonly ILogLanguageLocalizer _logLanguage; + private readonly IEncoder _encoder; + private IChannel? _channel; + private string _sessionKey = string.Empty; /// /// Initializes a new instance of the class. /// /// The logger instance. /// The localized log language provider. - public NetworkClient(ILogger logger, ILogLanguageLocalizer logLanguage) + /// The packet encoder. + public NetworkClient(ILogger logger, ILogLanguageLocalizer logLanguage, IEncoder encoder) { _logger = logger; - LastPackets = new ConcurrentQueue(); _logLanguage = logLanguage; + _encoder = encoder; + LastPackets = new ConcurrentQueue(); } /// - /// Gets the communication channel associated with this client. + /// Gets the unique session key for this client (used for encoding/decoding). /// - public IChannel? Channel { get; private set; } + public string SessionKey => _sessionKey; /// /// Gets or sets a value indicating whether the client has selected a character. @@ -65,17 +68,33 @@ public NetworkClient(ILogger logger, ILogLanguageLocalizer logLa /// public ConcurrentQueue LastPackets { get; } + /// + /// Gets the communication channel associated with this client. + /// + public IChannel? Channel => _channel; + + /// + /// Registers a channel with this network client. + /// + /// The channel to register. + public void RegisterChannel(IChannel channel) + { + _channel = channel; + _sessionKey = channel.Id; + NetworkClientRegistry.Register(this); + } + /// /// Disconnects the client asynchronously. /// /// A task representing the asynchronous disconnect operation. public async Task DisconnectAsync() { - _logger.Information(_logLanguage[LogLanguageKey.FORCED_DISCONNECTION], - SessionId); - if (Channel != null) + _logger.Information(_logLanguage[LogLanguageKey.FORCED_DISCONNECTION], SessionId); + NetworkClientRegistry.Unregister(this); + if (_channel != null) { - await Channel.DisconnectAsync(); + await _channel.DisconnectAsync(); } } @@ -96,74 +115,40 @@ public Task SendPacketAsync(IPacket? packet) /// A task representing the asynchronous send operation. public async Task SendPacketsAsync(IEnumerable packets) { - var packetlist = packets.ToList(); - var packetDefinitions = (packets as IPacket?[] ?? packetlist.ToArray()).Where(c => c != null); - if (!packetDefinitions.Any()) + var packetList = packets.Where(c => c != null).Cast().ToList(); + if (packetList.Count == 0) { return; } - - await Task.WhenAll(packetlist.Select(packet => Task.Run(() => + foreach (var packet in packetList) { if (packet?.IsValid == false) { _logger.Error(_logLanguage[LogLanguageKey.SENDING_INVALID_PACKET], packet.Header, packet.ValidationResult); } LastPackets.Enqueue(packet); - }))).ConfigureAwait(false); - Parallel.For(0, LastPackets.Count - MaxPacketsBuffer, (_, __) => LastPackets.TryDequeue(out var ___)); - if (Channel == null) - { - return; } - await Channel.WriteAndFlushAsync(packetDefinitions).ConfigureAwait(false); - } - /// - /// Registers a socket channel with this network client. - /// - /// The socket channel to register. - public void RegisterChannel(ISocketChannel? channel) - { - Channel = channel; - } + while (LastPackets.Count > MaxPacketsBuffer) + { + LastPackets.TryDequeue(out _); + } - /// - /// Handles exceptions that occur during channel operations. - /// - /// The channel handler context. - /// The exception that occurred. -#pragma warning disable VSTHRD100 // Avoid async void methods - public override async void ExceptionCaught(IChannelHandlerContext context, Exception exception) -#pragma warning restore VSTHRD100 // Avoid async void methods - { - if ((exception == null) || (context == null)) + if (_channel == null) { - throw new ArgumentNullException(nameof(exception)); + return; } - if (exception is SocketException sockException) + try { - switch (sockException.SocketErrorCode) - { - case SocketError.ConnectionReset: - _logger.Information( - "Client disconnected. ClientId = {SessionId}", - SessionId); - break; - default: - _logger.Fatal(exception.StackTrace ?? ""); - break; - } + var encoded = _encoder.Encode(SessionKey, packetList); + await _channel.SendAsync(new ReadOnlyMemory(encoded)); } - else + catch (Exception ex) { - _logger.Fatal(exception.StackTrace ?? ""); + _logger.Warning(ex, _logLanguage[LogLanguageKey.ENCODE_ERROR], SessionId); } - - // ReSharper disable once AsyncConverter.AsyncAwaitMayBeElidedHighlighting - await context.CloseAsync().ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/src/NosCore.Networking/NetworkClientRegistry.cs b/src/NosCore.Networking/NetworkClientRegistry.cs new file mode 100644 index 0000000..45ecf95 --- /dev/null +++ b/src/NosCore.Networking/NetworkClientRegistry.cs @@ -0,0 +1,64 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; + +namespace NosCore.Networking; + +/// +/// Global registry for network clients, allowing lookup by session key. +/// +public static class NetworkClientRegistry +{ + private static readonly ConcurrentDictionary _clients = new(); + + /// + /// Registers a client in the registry. + /// + /// The client to register. + public static void Register(INetworkClient client) + { + _clients.TryAdd(client.SessionKey, client); + } + + /// + /// Unregisters a client from the registry. + /// + /// The client to unregister. + public static void Unregister(INetworkClient client) + { + _clients.TryRemove(client.SessionKey, out _); + } + + /// + /// Unregisters a client from the registry by session key. + /// + /// The session key of the client to unregister. + public static void Unregister(string sessionKey) + { + _clients.TryRemove(sessionKey, out _); + } + + /// + /// Tries to get a client from the registry by session key. + /// + /// The session key to look up. + /// The client if found; otherwise, null. + /// True if the client was found; otherwise, false. + public static bool TryGetClient(string sessionKey, [NotNullWhen(true)] out INetworkClient? client) + { + return _clients.TryGetValue(sessionKey, out client); + } + + /// + /// Clears all clients from the registry. Used for testing and shutdown scenarios. + /// + public static void Clear() + { + _clients.Clear(); + } +} diff --git a/src/NosCore.Networking/NetworkManager.cs b/src/NosCore.Networking/NetworkManager.cs index 9d675fd..c58a2e8 100644 --- a/src/NosCore.Networking/NetworkManager.cs +++ b/src/NosCore.Networking/NetworkManager.cs @@ -5,16 +5,21 @@ // ----------------------------------- using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using DotNetty.Transport.Bootstrapping; -using DotNetty.Transport.Channels; -using DotNetty.Transport.Channels.Sockets; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using NosCore.Networking.Filters; using NosCore.Networking.Resource; using NosCore.Shared.Configuration; using NosCore.Shared.I18N; +using Serilog; +using SuperSocket.ProtoBase; +using SuperSocket.Server.Abstractions; +using SuperSocket.Server.Host; namespace NosCore.Networking { @@ -25,17 +30,20 @@ public class NetworkManager { private readonly IOptions _configuration; private readonly ILogger _logger; - private readonly Func _pipelineFactory; + private readonly ILogLanguageLocalizer _logLanguage; + private readonly PipelineFactory _pipelineFactory; + + private static readonly AutoResetEvent ClosingEvent = new AutoResetEvent(false); /// /// Initializes a new instance of the class. /// /// The server configuration options. - /// Factory function for creating pipeline instances per channel. + /// Factory for creating pipeline filters. /// The logger instance. /// The localized log language provider. public NetworkManager(IOptions configuration, - Func pipelineFactory, ILogger logger, ILogLanguageLocalizer logLanguage) + PipelineFactory pipelineFactory, ILogger logger, ILogLanguageLocalizer logLanguage) { _configuration = configuration; _pipelineFactory = pipelineFactory; @@ -43,45 +51,68 @@ public NetworkManager(IOptions configuration, _logLanguage = logLanguage; } - private static readonly AutoResetEvent ClosingEvent = new AutoResetEvent(false); - private readonly ILogLanguageLocalizer _logLanguage; - /// /// Starts and runs the network server, listening for incoming client connections. /// /// A task representing the asynchronous server operation. public async Task RunServerAsync() { - var bossGroup = new MultithreadEventLoopGroup(1); - var workerGroup = new MultithreadEventLoopGroup(); - try { - var bootstrap = new ServerBootstrap(); - bootstrap - .Group(bossGroup, workerGroup) - .Channel() - .ChildHandler(new ActionChannelInitializer(channel => - _pipelineFactory(channel).CreatePipeline())); + var host = SuperSocketHostBuilder.Create() + .UsePackageHandler(async (session, package) => + { + _pipelineFactory.HandlePackage(session, package); + await Task.CompletedTask.ConfigureAwait(false); + }) + .UseSessionHandler( + async session => + { + _pipelineFactory.OnSessionConnected(session); + await Task.CompletedTask.ConfigureAwait(false); + }, + async (session, e) => + { + _pipelineFactory.OnSessionClosed(session); + await Task.CompletedTask.ConfigureAwait(false); + }) + .ConfigureSuperSocket(options => + { + options.Name = "NosCore"; + options.Listeners = new List + { + new ListenOptions + { + Ip = "Any", + Port = _configuration.Value.Port + } + }; + }) + .ConfigureLogging((context, logging) => + { + logging.ClearProviders(); + logging.AddSerilog(); + }) + .ConfigureServices((context, services) => + { + services.AddSingleton>(_pipelineFactory); + }) + .Build(); _logger.LogInformation(_logLanguage[LogLanguageKey.LISTENING_PORT], _configuration.Value.Port); - var bootstrapChannel = await bootstrap.BindAsync(_configuration.Value.Port).ConfigureAwait(false); + await host.StartAsync().ConfigureAwait(false); Console.CancelKeyPress += ((s, a) => { ClosingEvent.Set(); }); ClosingEvent.WaitOne(); - await bootstrapChannel.CloseAsync().ConfigureAwait(false); + await host.StopAsync().ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, ex.Message); } - finally - { - await Task.WhenAll(bossGroup.ShutdownGracefullyAsync(), workerGroup.ShutdownGracefullyAsync()).ConfigureAwait(false); - } } } } \ No newline at end of file diff --git a/src/NosCore.Networking/NosCore.Networking.csproj b/src/NosCore.Networking/NosCore.Networking.csproj index 26d93da..0aa050f 100644 --- a/src/NosCore.Networking/NosCore.Networking.csproj +++ b/src/NosCore.Networking/NosCore.Networking.csproj @@ -12,7 +12,7 @@ https://github.com/NosCoreIO/NosCore.Networking.git nostale, noscore, nostale private server source, nostale emulator - 6.0.0 + 7.0.0 false NosCore Networking MIT @@ -25,14 +25,14 @@ - - - - + + + + diff --git a/src/NosCore.Networking/NosPackageInfo.cs b/src/NosCore.Networking/NosPackageInfo.cs new file mode 100644 index 0000000..329c612 --- /dev/null +++ b/src/NosCore.Networking/NosPackageInfo.cs @@ -0,0 +1,16 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using System.Collections.Generic; +using NosCore.Packets.Interfaces; + +namespace NosCore.Networking +{ + public class NosPackageInfo + { + public IEnumerable Packets { get; set; } = []; + } +} diff --git a/src/NosCore.Networking/PipelineConfiguration.cs b/src/NosCore.Networking/PipelineConfiguration.cs index 9157f94..e7eac8c 100644 --- a/src/NosCore.Networking/PipelineConfiguration.cs +++ b/src/NosCore.Networking/PipelineConfiguration.cs @@ -4,6 +4,8 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- +using NosCore.Shared.Enumerations; + namespace NosCore.Networking; /// @@ -15,4 +17,9 @@ public class PipelineConfiguration : IPipelineConfiguration /// Gets or sets a value indicating whether to use frame delimiters in the pipeline. /// public bool UseDelimiter { get; set; } + + /// + /// Gets or sets the region type for encoding/decoding. + /// + public RegionType Language { get; set; } = RegionType.EN; } \ No newline at end of file diff --git a/src/NosCore.Networking/PipelineFactory.cs b/src/NosCore.Networking/PipelineFactory.cs index b476a01..bc0fdc0 100644 --- a/src/NosCore.Networking/PipelineFactory.cs +++ b/src/NosCore.Networking/PipelineFactory.cs @@ -4,80 +4,131 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- +using System; +using System.Collections.Concurrent; using System.Collections.Generic; -using DotNetty.Buffers; -using DotNetty.Codecs; -using DotNetty.Transport.Channels.Sockets; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; using NosCore.Networking.Encoding; using NosCore.Networking.Encoding.Filter; +using NosCore.Networking.Filters; +using NosCore.Networking.Resource; using NosCore.Networking.SessionRef; -using NosCore.Packets.Interfaces; -using NosCore.Shared.Configuration; +using NosCore.Shared.Enumerations; +using NosCore.Shared.I18N; +using SuperSocket.ProtoBase; +using SuperSocket.Server.Abstractions.Session; namespace NosCore.Networking { /// /// Factory class responsible for creating and configuring network channel pipelines. /// - public class PipelineFactory : IPipelineFactory + public class PipelineFactory : IPipelineFilterFactory { - private readonly ISocketChannel _channel; - private readonly INetworkClient _clientSession; - private readonly IOptions _configuration; private readonly IDecoder _decoder; - private readonly IEncoder _encoder; private readonly ISessionRefHolder _sessionRefHolder; - private readonly IEnumerable _requestFilters; + private readonly IEnumerable _requestFilters; private readonly IPipelineConfiguration _pipelineConfiguration; + private readonly Func _clientFactory; + private readonly Action _packetHandler; + private readonly Action? _disconnectHandler; + private readonly ILogger? _logger; + private readonly ILogLanguageLocalizer? _logLanguage; + + private readonly ConcurrentDictionary _filtersByConnection = new(); + private readonly ConcurrentDictionary _clientsBySession = new(); /// /// Initializes a new instance of the class. /// - /// The socket channel to configure. /// The decoder for incoming packets. - /// The encoder for outgoing packets. - /// The network client session handler. - /// The server configuration options. /// The session reference holder. /// The collection of request filters to apply. /// The pipeline configuration settings. - public PipelineFactory(ISocketChannel channel, IDecoder decoder, - IEncoder encoder, INetworkClient clientSession, - IOptions configuration, ISessionRefHolder sessionRefHolder, IEnumerable requestFilters, IPipelineConfiguration pipelineConfiguration) + /// Factory function to create network client instances. + /// Handler for processing received packets. + /// Handler for processing client disconnections. + /// The logger instance. + /// The localized log language provider. + public PipelineFactory(IDecoder decoder, ISessionRefHolder sessionRefHolder, + IEnumerable requestFilters, IPipelineConfiguration pipelineConfiguration, + Func clientFactory, Action packetHandler, + Action? disconnectHandler = null, + ILogger? logger = null, ILogLanguageLocalizer? logLanguage = null) { - _channel = channel; _decoder = decoder; - _encoder = encoder; - _clientSession = clientSession; - _configuration = configuration; _sessionRefHolder = sessionRefHolder; _requestFilters = requestFilters; _pipelineConfiguration = pipelineConfiguration; + _clientFactory = clientFactory; + _packetHandler = packetHandler; + _disconnectHandler = disconnectHandler; + _logger = logger; + _logLanguage = logLanguage; + } + + /// + /// Creates a pipeline filter for a new connection. + /// + /// The connection object. + /// A configured pipeline filter. + public IPipelineFilter Create(object connection) + { + var filter = new PipelineFilter(_decoder, _sessionRefHolder, _requestFilters, _pipelineConfiguration.UseDelimiter); + var typedContext = filter.Context as IAppSession; + _sessionRefHolder[typedContext!.SessionID] = new RegionTypeMapping(0, _pipelineConfiguration.Language); + _filtersByConnection[connection] = filter; + return filter; } /// - /// Creates and configures the network channel pipeline with filters, decoder, encoder, and client handlers. + /// Creates a pipeline filter. /// - public void CreatePipeline() + /// A configured pipeline filter. + IPipelineFilter IPipelineFilterFactory.Create() { - _sessionRefHolder[_channel.Id.AsLongText()] = - new RegionTypeMapping(0, _configuration.Value.Language); - var pipeline = _channel.Pipeline; - foreach (var filter in _requestFilters) + return new PipelineFilter(_decoder, _sessionRefHolder, _requestFilters, _pipelineConfiguration.UseDelimiter); + } + + /// + /// Handles a session connection event. + /// + /// The connected session. + public void OnSessionConnected(IAppSession session) + { + var client = _clientFactory(); + client.RegisterChannel(new NetworkChannel(session)); + _clientsBySession[session.SessionID] = client; + } + + /// + /// Handles a session disconnection event. + /// + /// The disconnected session. + public void OnSessionClosed(IAppSession session) + { + _filtersByConnection.TryRemove(session.Connection, out _); + if (_clientsBySession.TryRemove(session.SessionID, out var client)) { - pipeline.AddLast(filter); + _disconnectHandler?.Invoke(client); } + } - if (_pipelineConfiguration.UseDelimiter) + /// + /// Handles an incoming packet. + /// + /// The session that received the packet. + /// The received package. + public void HandlePackage(IAppSession session, NosPackageInfo package) + { + if (_clientsBySession.TryGetValue(session.SessionID, out var client)) { - pipeline.AddLast(new FrameDelimiter(_sessionRefHolder)); + _packetHandler(package, client); + } + else if (_logger != null && _logLanguage != null) + { + _logger.LogWarning(_logLanguage[LogLanguageKey.ERROR_SESSIONID], session.SessionID); } - - pipeline.AddLast(_decoder); - _clientSession.RegisterChannel(_channel); - pipeline.AddLast(_clientSession); - pipeline.AddLast(_encoder); } } } \ No newline at end of file diff --git a/src/NosCore.Networking/Resource/LocalizedResources.Designer.cs b/src/NosCore.Networking/Resource/LocalizedResources.Designer.cs index 4d01933..b8c29e4 100644 --- a/src/NosCore.Networking/Resource/LocalizedResources.Designer.cs +++ b/src/NosCore.Networking/Resource/LocalizedResources.Designer.cs @@ -19,7 +19,7 @@ namespace NosCore.Networking.Resource { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class LocalizedResources { @@ -78,6 +78,15 @@ public static string CLIENT_CONNECTED { } } + /// + /// Looks up a localized string similar to Corrupted packet received: {0} - {1}. + /// + public static string CORRUPTED_PACKET { + get { + return ResourceManager.GetString("CORRUPTED_PACKET", resourceCulture); + } + } + /// /// Looks up a localized string similar to Encode Error: {0}. /// @@ -96,6 +105,15 @@ public static string ERROR_DECODING { } } + /// + /// Looks up a localized string similar to Invalid session ID: {0}. + /// + public static string ERROR_SESSIONID { + get { + return ResourceManager.GetString("ERROR_SESSIONID", resourceCulture); + } + } + /// /// Looks up a localized string similar to Forced disconnection of the client {0}.. /// @@ -122,5 +140,14 @@ public static string SENDING_INVALID_PACKET { return ResourceManager.GetString("SENDING_INVALID_PACKET", resourceCulture); } } + + /// + /// Looks up a localized string similar to Failed to broadcast to session {0}. + /// + public static string BROADCAST_ERROR { + get { + return ResourceManager.GetString("BROADCAST_ERROR", resourceCulture); + } + } } } diff --git a/src/NosCore.Networking/Resource/LocalizedResources.cs.resx b/src/NosCore.Networking/Resource/LocalizedResources.cs.resx index ad60c93..8e74fb0 100644 --- a/src/NosCore.Networking/Resource/LocalizedResources.cs.resx +++ b/src/NosCore.Networking/Resource/LocalizedResources.cs.resx @@ -132,4 +132,10 @@ Poslech Portu {0} + + Neplatné ID relace: {0} + + + Poškozený paket: {0} - {1} + \ No newline at end of file diff --git a/src/NosCore.Networking/Resource/LocalizedResources.de.resx b/src/NosCore.Networking/Resource/LocalizedResources.de.resx index 5f8a488..ebfbdfe 100644 --- a/src/NosCore.Networking/Resource/LocalizedResources.de.resx +++ b/src/NosCore.Networking/Resource/LocalizedResources.de.resx @@ -132,4 +132,7 @@ Zwangsabmeldung vom Client {0}, zu viele bestehende Verbindungen. + + Beschädigtes Paket: {0} - {1} + \ No newline at end of file diff --git a/src/NosCore.Networking/Resource/LocalizedResources.es.resx b/src/NosCore.Networking/Resource/LocalizedResources.es.resx index 3b4841f..51673f3 100644 --- a/src/NosCore.Networking/Resource/LocalizedResources.es.resx +++ b/src/NosCore.Networking/Resource/LocalizedResources.es.resx @@ -132,4 +132,10 @@ Escuchando el puerto: {0} + + ID de sesión inválido: {0} + + + Paquete corrupto: {0} - {1} + \ No newline at end of file diff --git a/src/NosCore.Networking/Resource/LocalizedResources.fr.resx b/src/NosCore.Networking/Resource/LocalizedResources.fr.resx index f594755..5771e30 100644 --- a/src/NosCore.Networking/Resource/LocalizedResources.fr.resx +++ b/src/NosCore.Networking/Resource/LocalizedResources.fr.resx @@ -138,4 +138,7 @@ Envoi d'un packet invalide: {PacketHeader} {ValidationResult} + + Paquet corrompu: {0} - {1} + \ No newline at end of file diff --git a/src/NosCore.Networking/Resource/LocalizedResources.it.resx b/src/NosCore.Networking/Resource/LocalizedResources.it.resx index 374f498..3a60f2e 100644 --- a/src/NosCore.Networking/Resource/LocalizedResources.it.resx +++ b/src/NosCore.Networking/Resource/LocalizedResources.it.resx @@ -135,4 +135,7 @@ Ascolto della porta {0} + + Pacchetto corrotto: {0} - {1} + \ No newline at end of file diff --git a/src/NosCore.Networking/Resource/LocalizedResources.pl.resx b/src/NosCore.Networking/Resource/LocalizedResources.pl.resx index dbd32b7..18efb67 100644 --- a/src/NosCore.Networking/Resource/LocalizedResources.pl.resx +++ b/src/NosCore.Networking/Resource/LocalizedResources.pl.resx @@ -132,4 +132,10 @@ Port nasłuchu: {0} + + Nieprawidłowy identyfikator sesji: {0} + + + Uszkodzony pakiet: {0} - {1} + \ No newline at end of file diff --git a/src/NosCore.Networking/Resource/LocalizedResources.resx b/src/NosCore.Networking/Resource/LocalizedResources.resx index 608988a..9316c23 100644 --- a/src/NosCore.Networking/Resource/LocalizedResources.resx +++ b/src/NosCore.Networking/Resource/LocalizedResources.resx @@ -138,4 +138,13 @@ Sending an invalid packet: {PacketHeader} {ValidationResult} + + Invalid session ID: {0} + + + Corrupted packet received: {0} - {1} + + + Failed to broadcast to session {0} + \ No newline at end of file diff --git a/src/NosCore.Networking/Resource/LocalizedResources.ru.resx b/src/NosCore.Networking/Resource/LocalizedResources.ru.resx index 13ac489..44c0c22 100644 --- a/src/NosCore.Networking/Resource/LocalizedResources.ru.resx +++ b/src/NosCore.Networking/Resource/LocalizedResources.ru.resx @@ -135,4 +135,7 @@ Слушаю порт {0} + + Поврежденный пакет: {0} - {1} + \ No newline at end of file diff --git a/src/NosCore.Networking/Resource/LocalizedResources.tr.resx b/src/NosCore.Networking/Resource/LocalizedResources.tr.resx index c9c13f8..25c40c9 100644 --- a/src/NosCore.Networking/Resource/LocalizedResources.tr.resx +++ b/src/NosCore.Networking/Resource/LocalizedResources.tr.resx @@ -132,4 +132,10 @@ {0} Portu Dinleniyor + + Geçersiz oturum kimliği: {0} + + + Bozuk paket: {0} - {1} + \ No newline at end of file diff --git a/src/NosCore.Networking/Resource/LogLanguageKey.cs b/src/NosCore.Networking/Resource/LogLanguageKey.cs index c731950..02738e5 100644 --- a/src/NosCore.Networking/Resource/LogLanguageKey.cs +++ b/src/NosCore.Networking/Resource/LogLanguageKey.cs @@ -54,6 +54,11 @@ public enum LogLanguageKey /// /// Log message when attempting to send an invalid packet. /// - SENDING_INVALID_PACKET + SENDING_INVALID_PACKET, + + /// + /// Log message when broadcasting to a session fails. + /// + BROADCAST_ERROR } } \ No newline at end of file diff --git a/src/NosCore.Networking/SessionGroup/ChannelMatcher/EveryoneBut.cs b/src/NosCore.Networking/SessionGroup/ChannelMatcher/EveryoneBut.cs index 4051912..d36d58c 100644 --- a/src/NosCore.Networking/SessionGroup/ChannelMatcher/EveryoneBut.cs +++ b/src/NosCore.Networking/SessionGroup/ChannelMatcher/EveryoneBut.cs @@ -1,38 +1,35 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- -using DotNetty.Transport.Channels; -using DotNetty.Transport.Channels.Groups; - namespace NosCore.Networking.SessionGroup.ChannelMatcher { /// - /// A channel matcher that matches all channels except a specific one. + /// A session matcher that matches all sessions except a specific one. /// - public class EveryoneBut : IChannelMatcher + public class EveryoneBut : ISessionMatcher { - private readonly IChannelId _id; + private readonly string _id; /// /// Initializes a new instance of the class. /// - /// The channel identifier to exclude from matching. - public EveryoneBut(IChannelId id) + /// The session identifier to exclude from matching. + public EveryoneBut(string id) { _id = id; } /// - /// Determines whether the specified channel matches (is not the excluded channel). + /// Determines whether the specified session matches (is not the excluded session). /// - /// The channel to test. - /// True if the channel is not the excluded channel; otherwise, false. - public bool Matches(IChannel channel) + /// The session to test. + /// True if the session is not the excluded session; otherwise, false. + public bool Matches(string sessionId) { - return channel.Id != _id; + return sessionId != _id; } } -} \ No newline at end of file +} diff --git a/src/NosCore.Networking/SessionGroup/ChannelMatcher/Only.cs b/src/NosCore.Networking/SessionGroup/ChannelMatcher/Only.cs index 288a696..617acdb 100644 --- a/src/NosCore.Networking/SessionGroup/ChannelMatcher/Only.cs +++ b/src/NosCore.Networking/SessionGroup/ChannelMatcher/Only.cs @@ -1,38 +1,35 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- -using DotNetty.Transport.Channels; -using DotNetty.Transport.Channels.Groups; - namespace NosCore.Networking.SessionGroup.ChannelMatcher { /// - /// A channel matcher that matches only a specific channel by its identifier. + /// A session matcher that matches only a specific session by its identifier. /// - public class Only : IChannelMatcher + public class Only : ISessionMatcher { - private readonly IChannelId _id; + private readonly string _id; /// /// Initializes a new instance of the class. /// - /// The channel identifier to match. - public Only(IChannelId id) + /// The session identifier to match. + public Only(string id) { _id = id; } /// - /// Determines whether the specified channel matches the target channel. + /// Determines whether the specified session matches the target session. /// - /// The channel to test. - /// True if the channel matches; otherwise, false. - public bool Matches(IChannel channel) + /// The session to test. + /// True if the session matches; otherwise, false. + public bool Matches(string sessionId) { - return channel.Id == _id; + return sessionId == _id; } } -} \ No newline at end of file +} diff --git a/src/NosCore.Networking/SessionGroup/ISessionGroup.cs b/src/NosCore.Networking/SessionGroup/ISessionGroup.cs index 7902b6e..10bb664 100644 --- a/src/NosCore.Networking/SessionGroup/ISessionGroup.cs +++ b/src/NosCore.Networking/SessionGroup/ISessionGroup.cs @@ -1,13 +1,10 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- -using System.Collections.Generic; using System.Threading.Tasks; -using DotNetty.Transport.Channels; -using DotNetty.Transport.Channels.Groups; using NosCore.Packets.Interfaces; namespace NosCore.Networking.SessionGroup; @@ -28,26 +25,54 @@ public interface ISessionGroup /// Broadcasts packets to matching sessions in the group. /// /// The array of packets to broadcast. - /// The matcher to filter which channels receive the packets. + /// The matcher to filter which sessions receive the packets. /// A task representing the asynchronous broadcast operation. - Task Broadcast(IPacket[] packetDefinitions, IChannelMatcher channelMatcher); + Task Broadcast(IPacket[] packetDefinitions, ISessionMatcher sessionMatcher); /// - /// Adds a channel to the session group. + /// Adds a client to the session group. /// - /// The channel to add. - /// True if the channel was added; otherwise, false. + /// The client to add. + /// True if the client was added; otherwise, false. + bool Add(INetworkClient client); + + /// + /// Adds a client to the session group by channel. + /// + /// The channel of the client to add. + /// True if the client was added; otherwise, false. bool Add(IChannel channel); /// - /// Removes a channel from the session group. + /// Adds a client to the session group by session key. + /// + /// The session key of the client to add. + /// True if the client was added; otherwise, false. + bool Add(string sessionKey); + + /// + /// Removes a client from the session group. /// - /// The channel to remove. - /// True if the channel was removed; otherwise, false. + /// The client to remove. + /// True if the client was removed; otherwise, false. + bool Remove(INetworkClient client); + + /// + /// Removes a client from the session group by channel. + /// + /// The channel of the client to remove. + /// True if the client was removed; otherwise, false. bool Remove(IChannel channel); /// - /// Gets the number of channels in the session group. + /// Removes a client from the session group by session key. + /// + /// The session key of the client to remove. + /// True if the client was removed; otherwise, false. + bool Remove(string sessionKey); + + /// + /// Gets the number of clients in the session group. /// int Count { get; } -} \ No newline at end of file +} diff --git a/src/NosCore.Networking/SessionGroup/ISessionGroupFactory.cs b/src/NosCore.Networking/SessionGroup/ISessionGroupFactory.cs new file mode 100644 index 0000000..c852886 --- /dev/null +++ b/src/NosCore.Networking/SessionGroup/ISessionGroupFactory.cs @@ -0,0 +1,19 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Networking.SessionGroup; + +/// +/// Factory for creating session groups with proper dependency injection. +/// +public interface ISessionGroupFactory +{ + /// + /// Creates a new session group instance. + /// + /// A new session group. + ISessionGroup Create(); +} diff --git a/src/NosCore.Networking/SessionGroup/ISessionMatcher.cs b/src/NosCore.Networking/SessionGroup/ISessionMatcher.cs new file mode 100644 index 0000000..a1e885a --- /dev/null +++ b/src/NosCore.Networking/SessionGroup/ISessionMatcher.cs @@ -0,0 +1,21 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +namespace NosCore.Networking.SessionGroup +{ + /// + /// Defines a matcher for filtering sessions by their identifier. + /// + public interface ISessionMatcher + { + /// + /// Determines whether the specified session matches the filter criteria. + /// + /// The session identifier to test. + /// True if the session matches; otherwise, false. + bool Matches(string sessionId); + } +} diff --git a/src/NosCore.Networking/SessionGroup/SessionGroup.cs b/src/NosCore.Networking/SessionGroup/SessionGroup.cs index fdc2cb2..e9939ea 100644 --- a/src/NosCore.Networking/SessionGroup/SessionGroup.cs +++ b/src/NosCore.Networking/SessionGroup/SessionGroup.cs @@ -1,14 +1,17 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- +using System; +using System.Collections.Concurrent; +using System.Linq; using System.Threading.Tasks; -using DotNetty.Common.Concurrency; -using DotNetty.Transport.Channels; -using DotNetty.Transport.Channels.Groups; +using Microsoft.Extensions.Logging; +using NosCore.Networking.Resource; using NosCore.Packets.Interfaces; +using NosCore.Shared.I18N; namespace NosCore.Networking.SessionGroup; @@ -17,15 +20,19 @@ namespace NosCore.Networking.SessionGroup; /// public class SessionGroup : ISessionGroup { - private readonly DefaultChannelGroup _channelGroup; + private readonly ConcurrentDictionary _sessions = new(); + private readonly ILogger _logger; + private readonly ILogLanguageLocalizer _logLanguage; /// /// Initializes a new instance of the class. /// - public SessionGroup() + /// The logger instance. + /// The log language localizer. + public SessionGroup(ILogger logger, ILogLanguageLocalizer logLanguage) { - ExecutionEnvironment.TryGetCurrentExecutor(out var executor); - _channelGroup = new DefaultChannelGroup(executor); + _logger = logger; + _logLanguage = logLanguage; } /// @@ -33,44 +40,111 @@ public SessionGroup() /// /// The array of packets to broadcast. /// A task representing the asynchronous broadcast operation. - public Task Broadcast(IPacket[] packetDefinitions) + public async Task Broadcast(IPacket[] packetDefinitions) { - return _channelGroup.WriteAndFlushAsync(packetDefinitions); + var tasks = _sessions.Select(async kvp => + { + try + { + await kvp.Value.SendPacketsAsync(packetDefinitions); + } + catch (Exception ex) + { + _logger.LogWarning(ex, _logLanguage[LogLanguageKey.BROADCAST_ERROR], kvp.Key); + } + }); + await Task.WhenAll(tasks); } /// /// Broadcasts packets to matching sessions in the group. /// /// The array of packets to broadcast. - /// The matcher to filter which channels receive the packets. + /// The matcher to filter which sessions receive the packets. /// A task representing the asynchronous broadcast operation. - public Task Broadcast(IPacket[] packetDefinitions, IChannelMatcher channelMatcher) + public async Task Broadcast(IPacket[] packetDefinitions, ISessionMatcher sessionMatcher) { - return _channelGroup.WriteAndFlushAsync(packetDefinitions, channelMatcher); + var matchedSessions = _sessions.Where(kvp => sessionMatcher.Matches(kvp.Key)); + var tasks = matchedSessions.Select(async kvp => + { + try + { + await kvp.Value.SendPacketsAsync(packetDefinitions); + } + catch (Exception ex) + { + _logger.LogWarning(ex, _logLanguage[LogLanguageKey.BROADCAST_ERROR], kvp.Key); + } + }); + await Task.WhenAll(tasks); } /// - /// Adds a channel to the session group. + /// Adds a client to the session group. /// - /// The channel to add. - /// True if the channel was added; otherwise, false. + /// The client to add. + /// True if the client was added; otherwise, false. + public bool Add(INetworkClient client) + { + return _sessions.TryAdd(client.SessionKey, client); + } + + /// + /// Adds a client to the session group by channel. + /// + /// The channel of the client to add. + /// True if the client was added; otherwise, false. public bool Add(IChannel channel) { - return _channelGroup.Add(channel); + return Add(channel.Id); + } + + /// + /// Adds a client to the session group by session key. + /// + /// The session key of the client to add. + /// True if the client was added; otherwise, false. + public bool Add(string sessionKey) + { + if (NetworkClientRegistry.TryGetClient(sessionKey, out var client)) + { + return _sessions.TryAdd(sessionKey, client!); + } + return false; } /// - /// Removes a channel from the session group. + /// Removes a client from the session group. /// - /// The channel to remove. - /// True if the channel was removed; otherwise, false. + /// The client to remove. + /// True if the client was removed; otherwise, false. + public bool Remove(INetworkClient client) + { + return _sessions.TryRemove(client.SessionKey, out _); + } + + /// + /// Removes a client from the session group by channel. + /// + /// The channel of the client to remove. + /// True if the client was removed; otherwise, false. public bool Remove(IChannel channel) { - return _channelGroup.Remove(channel); + return Remove(channel.Id); + } + + /// + /// Removes a client from the session group by session key. + /// + /// The session key of the client to remove. + /// True if the client was removed; otherwise, false. + public bool Remove(string sessionKey) + { + return _sessions.TryRemove(sessionKey, out _); } /// - /// Gets the number of channels in the session group. + /// Gets the number of clients in the session group. /// - public int Count => _channelGroup.Count; -} \ No newline at end of file + public int Count => _sessions.Count; +} diff --git a/src/NosCore.Networking/SessionGroup/SessionGroupFactory.cs b/src/NosCore.Networking/SessionGroup/SessionGroupFactory.cs new file mode 100644 index 0000000..6fce7a6 --- /dev/null +++ b/src/NosCore.Networking/SessionGroup/SessionGroupFactory.cs @@ -0,0 +1,40 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// ----------------------------------- + +using Microsoft.Extensions.Logging; +using NosCore.Networking.Resource; +using NosCore.Shared.I18N; + +namespace NosCore.Networking.SessionGroup; + +/// +/// Factory for creating session groups with proper dependency injection. +/// +public class SessionGroupFactory : ISessionGroupFactory +{ + private readonly ILogger _logger; + private readonly ILogLanguageLocalizer _logLanguage; + + /// + /// Initializes a new instance of the class. + /// + /// The logger instance. + /// The log language localizer. + public SessionGroupFactory(ILogger logger, ILogLanguageLocalizer logLanguage) + { + _logger = logger; + _logLanguage = logLanguage; + } + + /// + /// Creates a new session group instance. + /// + /// A new session group with injected dependencies. + public ISessionGroup Create() + { + return new SessionGroup(_logger, _logLanguage); + } +} diff --git a/src/NosCore.Networking/SessionRef/ISessionRefHolder.cs b/src/NosCore.Networking/SessionRef/ISessionRefHolder.cs index 72bd4f8..6e703eb 100644 --- a/src/NosCore.Networking/SessionRef/ISessionRefHolder.cs +++ b/src/NosCore.Networking/SessionRef/ISessionRefHolder.cs @@ -18,5 +18,13 @@ public interface ISessionRefHolder : IDictionary /// /// A new session identifier. public int GenerateSessionId(); + + /// + /// Attempts to remove and return the value with the specified key. + /// + /// The key of the element to remove. + /// The removed value, if found. + /// true if the element was removed successfully; otherwise, false. + public bool TryRemove(string key, out RegionTypeMapping? value); } } diff --git a/src/NosCore.Networking/SessionRef/SessionRefHolder.cs b/src/NosCore.Networking/SessionRef/SessionRefHolder.cs index 16ff612..f51c9bc 100644 --- a/src/NosCore.Networking/SessionRef/SessionRefHolder.cs +++ b/src/NosCore.Networking/SessionRef/SessionRefHolder.cs @@ -5,6 +5,7 @@ // ----------------------------------- using System.Collections.Concurrent; +using System.Threading; namespace NosCore.Networking.SessionRef; @@ -21,7 +22,19 @@ public class SessionRefHolder : ConcurrentDictionary, /// A new session identifier incremented by 2. public int GenerateSessionId() { - _sessionCounter += 2; - return _sessionCounter; + return Interlocked.Add(ref _sessionCounter, 2); + } + + /// + /// Attempts to remove and return the value with the specified key. + /// + /// The key of the element to remove. + /// The removed value, if found. + /// true if the element was removed successfully; otherwise, false. + public new bool TryRemove(string key, out RegionTypeMapping? value) + { + var result = base.TryRemove(key, out var baseValue); + value = baseValue; + return result; } } \ No newline at end of file diff --git a/test/NosCore.Networking.Tests/SpanRequestFilterTest.cs b/test/NosCore.Networking.Tests/SpanRequestFilterTest.cs index 7433218..8e50364 100644 --- a/test/NosCore.Networking.Tests/SpanRequestFilterTest.cs +++ b/test/NosCore.Networking.Tests/SpanRequestFilterTest.cs @@ -1,11 +1,10 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- using System.Net; -using DotNetty.Transport.Channels; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -23,55 +22,49 @@ public class SpanRequestFilterTest [TestMethod] public void FirstRequestNeverFilterOut() { - var ctx = new Mock(); - ctx.SetupGet(x => x.Channel.RemoteAddress).Returns(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 123)); + var remoteEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 123); var clock = new FakeClock(Instant.FromUtc(2022, 01, 01, 01, 01, 1)); var spamFilter = new SpamRequestFilter(clock, new Mock>().Object, new Mock>().Object); - var output = spamFilter.Filter(ctx.Object, new byte[] { 1, 2, 3 }); + var output = spamFilter.Filter(remoteEndPoint, new byte[] { 1, 2, 3 }); CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, output); } [TestMethod] public void DifferentIpFirstRequestNeverFilterOut() { - var ctx = new Mock(); - ctx.SetupGet(x => x.Channel.RemoteAddress).Returns(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 123)); - - var ctx2 = new Mock(); - ctx2.SetupGet(x => x.Channel.RemoteAddress).Returns(new IPEndPoint(IPAddress.Parse("127.0.0.2"), 123)); + var remoteEndPoint1 = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 123); + var remoteEndPoint2 = new IPEndPoint(IPAddress.Parse("127.0.0.2"), 123); var clock = new FakeClock(Instant.FromUtc(2022, 01, 01, 01, 01, 1)); var spamFilter = new SpamRequestFilter(clock, new Mock>().Object, new Mock>().Object); - spamFilter.Filter(ctx.Object, new byte[] { 1, 2, 3 }); - var output = spamFilter.Filter(ctx2.Object, new byte[] { 1, 2, 3 }); + spamFilter.Filter(remoteEndPoint1, new byte[] { 1, 2, 3 }); + var output = spamFilter.Filter(remoteEndPoint2, new byte[] { 1, 2, 3 }); CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, output); } [TestMethod] public void SameIpIsFiltering() { - var ctx = new Mock(); - ctx.SetupGet(x => x.Channel.RemoteAddress).Returns(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 123)); + var remoteEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 123); var clock = new FakeClock(Instant.FromUtc(2022, 01, 01, 01, 01, 1)); var spamFilter = new SpamRequestFilter(clock, new Mock>().Object, new Mock>().Object); - spamFilter.Filter(ctx.Object, new byte[] { 1, 2, 3 }); + spamFilter.Filter(remoteEndPoint, new byte[] { 1, 2, 3 }); - var output = spamFilter.Filter(ctx.Object, new byte[] { 1, 2, 3 }); + var output = spamFilter.Filter(remoteEndPoint, new byte[] { 1, 2, 3 }); Assert.IsNull(output); } [TestMethod] public void SameIpAfterOneSecondIsNotFiltering() { - var ctx = new Mock(); - ctx.SetupGet(x => x.Channel.RemoteAddress).Returns(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 123)); + var remoteEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 123); var clock = new FakeClock(Instant.FromUtc(2022, 01, 01, 01, 01, 1)); var spamFilter = new SpamRequestFilter(clock, new Mock>().Object, new Mock>().Object); - spamFilter.Filter(ctx.Object, new byte[] { 1, 2, 3 }); + spamFilter.Filter(remoteEndPoint, new byte[] { 1, 2, 3 }); clock.AdvanceSeconds(1); - var output = spamFilter.Filter(ctx.Object, new byte[] { 1, 2, 3 }); + var output = spamFilter.Filter(remoteEndPoint, new byte[] { 1, 2, 3 }); CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, output); } }