Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion NosCore.Networking.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
-----------------------------------</s:String>
<s:Boolean x:Key="/Default/CodeStyle/Generate/=Implementations/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Generate/=Implementations/Options/=Async/@EntryIndexedValue">False</s:String>
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/IsNotificationDisabled/@EntryValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/IsNotificationDisabled/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
49 changes: 29 additions & 20 deletions src/NosCore.Networking/BroadcastableExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -34,9 +33,9 @@ public static Task SendPacketAsync(this IBroadcastable channelGroup, IPacket pac
/// </summary>
/// <param name="channelGroup">The broadcastable group to send the packet to.</param>
/// <param name="packet">The packet to send.</param>
/// <param name="matcher">The channel matcher to filter recipients.</param>
/// <param name="matcher">The session matcher to filter recipients.</param>
/// <returns>A task representing the asynchronous send operation.</returns>
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);
}
Expand All @@ -46,29 +45,39 @@ public static Task SendPacketAsync(this IBroadcastable channelGroup, IPacket pac
/// </summary>
/// <param name="channelGroup">The broadcastable group to send the packets to.</param>
/// <param name="packets">The collection of packets to send.</param>
/// <param name="matcher">The optional channel matcher to filter recipients.</param>
/// <param name="matcher">The optional session matcher to filter recipients.</param>
/// <returns>A task representing the asynchronous send operation.</returns>
public static async Task SendPacketsAsync(this IBroadcastable channelGroup, IEnumerable<IPacket> 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);
}
}

Expand Down
30 changes: 6 additions & 24 deletions src/NosCore.Networking/Encoding/Filter/RequestFilter.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Abstract base class for request filters that process incoming byte data.
/// Defines a request filter that processes incoming byte data.
/// </summary>
public abstract class RequestFilter : MessageToMessageDecoder<IByteBuffer>
public interface IRequestFilter
{
/// <summary>
/// Filters incoming request data.
/// </summary>
/// <param name="context">The channel handler context.</param>
/// <param name="remoteEndPoint">The remote endpoint of the connection.</param>
/// <param name="message">The incoming message bytes.</param>
/// <returns>The filtered byte array, or null if the request should be blocked.</returns>
public abstract byte[]? Filter(IChannelHandlerContext context, Span<byte> message);

/// <summary>
/// Decodes the incoming byte buffer through the filter.
/// </summary>
/// <param name="context">The channel handler context.</param>
/// <param name="message">The byte buffer to decode.</param>
/// <param name="output">The output list to add filtered results to.</param>
protected override void Decode(IChannelHandlerContext context, IByteBuffer message, List<object> output)
{
var result = Filter(context, ((Span<byte>)message.Array).Slice(message.ArrayOffset, message.ReadableBytes));
if (result != null)
{
output.Add(Unpooled.WrappedBuffer(result));
}
}
byte[]? Filter(EndPoint remoteEndPoint, Span<byte> message);
}
}
20 changes: 7 additions & 13 deletions src/NosCore.Networking/Encoding/Filter/SpamRequestFilter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// __ _ __ __ ___ __ ___ ___
// __ _ __ __ ___ __ ___ ___
// | \| |/__\ /' _/ / _//__\| _ \ __|
// | | ' | \/ |`._`.| \_| \/ | v / _|
// |_|\__|\__/ |___/ \__/\__/|_|_\___|
Expand All @@ -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;
Expand All @@ -18,19 +17,14 @@ namespace NosCore.Networking.Encoding.Filter
/// <summary>
/// Filters spam requests by rate-limiting connections from the same IP address.
/// </summary>
public class SpamRequestFilter : RequestFilter
public class SpamRequestFilter : IRequestFilter
{
private readonly Dictionary<EndPoint, Instant> _connectionsByIp = new();
private readonly TimeSpan _timeBetweenConnection = TimeSpan.FromMilliseconds(1000);
private readonly IClock _clock;
private readonly ILogger<SpamRequestFilter> _logger;
private readonly ILogLanguageLocalizer<LogLanguageKey> _logLanguage;

/// <summary>
/// Gets a value indicating whether this handler can be shared across multiple channels.
/// </summary>
public override bool IsSharable => true;

/// <summary>
/// Initializes a new instance of the <see cref="SpamRequestFilter"/> class.
/// </summary>
Expand All @@ -47,21 +41,21 @@ public SpamRequestFilter(IClock clock, ILogger<SpamRequestFilter> logger, ILogLa
/// <summary>
/// Filters incoming requests based on connection rate from the same IP address.
/// </summary>
/// <param name="context">The channel handler context.</param>
/// <param name="remoteEndPoint">The remote endpoint of the connection.</param>
/// <param name="message">The incoming message bytes.</param>
/// <returns>The message bytes if allowed, or null if blocked by the spam filter.</returns>
public override byte[]? Filter(IChannelHandlerContext context, Span<byte> message)
public byte[]? Filter(EndPoint remoteEndPoint, Span<byte> 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();
}
}
Expand Down
55 changes: 4 additions & 51 deletions src/NosCore.Networking/Encoding/FrameDelimiter.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Delimits incoming byte streams into frames based on session-specific delimiters.
/// Provides delimiter calculation for session-specific frame detection.
/// </summary>
public class FrameDelimiter : ByteToMessageDecoder
public static class FrameDelimiter
{
private readonly ISessionRefHolder _sessionRefHolder;

/// <summary>
/// Initializes a new instance of the <see cref="FrameDelimiter"/> class.
/// </summary>
/// <param name="sessionRefHolder">The session reference holder.</param>
public FrameDelimiter(ISessionRefHolder sessionRefHolder)
{
_sessionRefHolder = sessionRefHolder;
}

/// <summary>
/// Decodes the incoming byte buffer into frames based on delimiters.
/// </summary>
/// <param name="context">The channel handler context.</param>
/// <param name="input">The input byte buffer.</param>
/// <param name="output">The output list to add decoded frames to.</param>
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> 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;
}
}
}

/// <summary>
/// Gets the delimiter byte for a specific session.
/// </summary>
Expand All @@ -79,4 +32,4 @@ public static byte GetDelimiter(int session, bool isFirstPacket = false)
_ => (0xff + 0xF) & 0xFF
});
}
}
}
3 changes: 1 addition & 2 deletions src/NosCore.Networking/Encoding/IDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@

using System;
using System.Collections.Generic;
using DotNetty.Transport.Channels;
using NosCore.Packets.Interfaces;

namespace NosCore.Networking.Encoding
{
/// <summary>
/// Defines a packet decoder that converts byte data into packets.
/// </summary>
public interface IDecoder : IChannelHandler
public interface IDecoder
{
/// <summary>
/// Decodes a byte span into a collection of packets.
Expand Down
4 changes: 1 addition & 3 deletions src/NosCore.Networking/Encoding/IEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@
// |_|\__|\__/ |___/ \__/\__/|_|_\___|
// -----------------------------------

using System;
using System.Collections.Generic;
using DotNetty.Transport.Channels;
using NosCore.Packets.Interfaces;

namespace NosCore.Networking.Encoding
{
/// <summary>
/// Defines a packet encoder that converts packets to byte arrays for transmission.
/// </summary>
public interface IEncoder : IChannelHandler
public interface IEncoder
{
/// <summary>
/// Encodes a collection of packets into a byte array.
Expand Down
27 changes: 3 additions & 24 deletions src/NosCore.Networking/Encoding/LoginDecoder.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
// __ _ __ __ ___ __ ___ ___
// __ _ __ __ ___ __ ___ ___
// | \| |/__\ /' _/ / _//__\| _ \ __|
// | | ' | \/ |`._`.| \_| \/ | v / _|
// |_|\__|\__/ |___/ \__/\__/|_|_\___|
// -----------------------------------

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;
Expand All @@ -22,7 +18,7 @@ namespace NosCore.Networking.Encoding
/// <summary>
/// Decodes packets from login server communication using region-specific decoding.
/// </summary>
public class LoginDecoder : MessageToMessageDecoder<IByteBuffer>, IDecoder
public class LoginDecoder : IDecoder
{
private readonly IDeserializer _deserializer;
private readonly ILogger<LoginDecoder> _logger;
Expand Down Expand Up @@ -90,22 +86,5 @@ public IEnumerable<IPacket> Decode(string clientSessionId, Span<byte> message)

return Array.Empty<IPacket>();
}

/// <summary>
/// Decodes a byte buffer into packets.
/// </summary>
/// <param name="context">The channel handler context.</param>
/// <param name="message">The byte buffer containing encoded packet data.</param>
/// <param name="output">The output list to add decoded packets to.</param>
protected override void Decode(IChannelHandlerContext context, IByteBuffer message, List<object> output)
{
var packets = Decode(context.Channel.Id.AsLongText(),
((Span<byte>)message.Array).Slice(message.ArrayOffset, message.ReadableBytes));

if (packets.Any())
{
output.Add(packets);
}
}
}
}
}
Loading
Loading