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
40 changes: 19 additions & 21 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,25 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v3
- uses: actions/checkout@v4
- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: '10.0.0'
continue-on-error: true
- name: Fallback - Install .NET 10 manually
if: failure()
run: |
wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh
chmod +x dotnet-install.sh
./dotnet-install.sh --channel 10.0 --install-dir $HOME/.dotnet
echo "$HOME/.dotnet" >> $GITHUB_PATH

- name: Check Tag
id: check-tag
run: |
if [[ v${{ github.event.ref }} =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo ::set-output name=match::true
echo "match=true" >> $GITHUB_OUTPUT
fi

- name: Run Unit Tests
Expand All @@ -39,23 +47,13 @@ jobs:
dotnet build -c Release
dotnet pack -c Release -o /tmp/nupkgs -v m -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
dotnet nuget push /tmp/nupkgs/NosCore.Networking.${{github.event.ref}}.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}}
echo ::set-output name=ARTIFACT_PATH::/tmp/nupkgs/NosCore.Networking.${{github.event.ref}}.nupkg
echo ::set-output name=ARTIFACT_NAME::NosCore.Networking.${{github.event.ref}}.nupkg

- name: Gets Latest Release
if: steps.check-tag.outputs.match == 'true'
id: latest_release_info
uses: jossef/action-latest-release-info@v1.1.0
env:
GITHUB_TOKEN: ${{ github.token }}
echo "ARTIFACT_PATH=/tmp/nupkgs/NosCore.Networking.${{github.event.ref}}.nupkg" >> $GITHUB_OUTPUT
echo "ARTIFACT_NAME=NosCore.Networking.${{github.event.ref}}.nupkg" >> $GITHUB_OUTPUT

- name: Upload Release Asset
if: steps.check-tag.outputs.match == 'true'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ github.token }}
uses: softprops/action-gh-release@v2
with:
upload_url: ${{ steps.latest_release_info.outputs.upload_url }}
asset_path: ${{ steps.build_artifact.outputs.ARTIFACT_PATH }}
asset_name: ${{ steps.build_artifact.outputs.ARTIFACT_NAME }}
asset_content_type: application/zip
files: ${{ steps.build_artifact.outputs.ARTIFACT_PATH }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 changes: 29 additions & 1 deletion src/NosCore.Networking/BroadcastableExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,41 @@

namespace NosCore.Networking
{
/// <summary>
/// Provides extension methods for broadcasting packets to <see cref="IBroadcastable"/> instances.
/// </summary>
public static class IBroadcastableExtension
{
/// <summary>
/// Sends a single packet to all sessions in the broadcastable group.
/// </summary>
/// <param name="channelGroup">The broadcastable group to send the packet to.</param>
/// <param name="packet">The packet to send.</param>
/// <returns>A task representing the asynchronous send operation.</returns>
public static Task SendPacketAsync(this IBroadcastable channelGroup, IPacket packet)
{
return channelGroup.SendPacketsAsync(new[] { packet });
}

/// <summary>
/// Sends a single packet to matching sessions in the broadcastable group.
/// </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>
/// <returns>A task representing the asynchronous send operation.</returns>
public static Task SendPacketAsync(this IBroadcastable channelGroup, IPacket packet, IChannelMatcher matcher)
{
return channelGroup.SendPacketsAsync(new[] { packet }, matcher);
}


/// <summary>
/// Sends multiple packets to matching sessions in the broadcastable group.
/// </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>
/// <returns>A task representing the asynchronous send operation.</returns>
public static async Task SendPacketsAsync(this IBroadcastable channelGroup, IEnumerable<IPacket> packets,
IChannelMatcher? matcher)
{
Expand All @@ -51,6 +73,12 @@ public static async Task SendPacketsAsync(this IBroadcastable channelGroup, IEnu
}


/// <summary>
/// Sends multiple packets to all sessions in the broadcastable group.
/// </summary>
/// <param name="channelGroup">The broadcastable group to send the packets to.</param>
/// <param name="packets">The collection of packets to send.</param>
/// <returns>A task representing the asynchronous send operation.</returns>
public static Task SendPacketsAsync(this IBroadcastable channelGroup, IEnumerable<IPacket> packets)
{
return channelGroup.SendPacketsAsync(packets, null);
Expand Down
15 changes: 15 additions & 0 deletions src/NosCore.Networking/Encoding/Filter/RequestFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,25 @@

namespace NosCore.Networking.Encoding.Filter
{
/// <summary>
/// Abstract base class for request filters that process incoming byte data.
/// </summary>
public abstract class RequestFilter : MessageToMessageDecoder<IByteBuffer>
{
/// <summary>
/// Filters incoming request data.
/// </summary>
/// <param name="context">The channel handler context.</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));
Expand Down
19 changes: 19 additions & 0 deletions src/NosCore.Networking/Encoding/Filter/SpamRequestFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,41 @@

namespace NosCore.Networking.Encoding.Filter
{
/// <summary>
/// Filters spam requests by rate-limiting connections from the same IP address.
/// </summary>
public class SpamRequestFilter : RequestFilter
{
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>
/// <param name="clock">The clock instance for time tracking.</param>
/// <param name="logger">The logger instance.</param>
/// <param name="logLanguage">The localized log language provider.</param>
public SpamRequestFilter(IClock clock, ILogger<SpamRequestFilter> logger, ILogLanguageLocalizer<LogLanguageKey> logLanguage)
{
_clock = clock;
_logger = logger;
_logLanguage = logLanguage;
}

/// <summary>
/// Filters incoming requests based on connection rate from the same IP address.
/// </summary>
/// <param name="context">The channel handler context.</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)
{
if (_connectionsByIp.TryGetValue(context.Channel.RemoteAddress, out var date))
Expand Down
21 changes: 20 additions & 1 deletion src/NosCore.Networking/Encoding/FrameDelimiter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,28 @@

namespace NosCore.Networking.Encoding;

/// <summary>
/// Delimits incoming byte streams into frames based on session-specific delimiters.
/// </summary>
public class FrameDelimiter : ByteToMessageDecoder
{
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();
Expand All @@ -45,6 +58,12 @@ protected override void Decode(IChannelHandlerContext context, IByteBuffer input
}
}

/// <summary>
/// Gets the delimiter byte for a specific session.
/// </summary>
/// <param name="session">The session identifier.</param>
/// <param name="isFirstPacket">Whether this is the first packet in the session.</param>
/// <returns>The delimiter byte for the session.</returns>
public static byte GetDelimiter(int session, bool isFirstPacket = false)
{
int stype = !isFirstPacket ? (session >> 6) & 3 : -1;
Expand Down
9 changes: 9 additions & 0 deletions src/NosCore.Networking/Encoding/IDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@

namespace NosCore.Networking.Encoding
{
/// <summary>
/// Defines a packet decoder that converts byte data into packets.
/// </summary>
public interface IDecoder : IChannelHandler
{
/// <summary>
/// Decodes a byte span into a collection of packets.
/// </summary>
/// <param name="clientSessionId">The client session identifier.</param>
/// <param name="message">The byte span containing the encoded packet data.</param>
/// <returns>A collection of decoded packets.</returns>
IEnumerable<IPacket> Decode(string clientSessionId, Span<byte> message);
}
}
9 changes: 9 additions & 0 deletions src/NosCore.Networking/Encoding/IEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@

namespace NosCore.Networking.Encoding
{
/// <summary>
/// Defines a packet encoder that converts packets to byte arrays for transmission.
/// </summary>
public interface IEncoder : IChannelHandler
{
/// <summary>
/// Encodes a collection of packets into a byte array.
/// </summary>
/// <param name="clientSessionId">The client session identifier.</param>
/// <param name="packets">The packets to encode.</param>
/// <returns>A byte array containing the encoded packet data.</returns>
byte[] Encode(string clientSessionId, IEnumerable<IPacket> packets);
}
}
22 changes: 22 additions & 0 deletions src/NosCore.Networking/Encoding/LoginDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,23 @@

namespace NosCore.Networking.Encoding
{
/// <summary>
/// Decodes packets from login server communication using region-specific decoding.
/// </summary>
public class LoginDecoder : MessageToMessageDecoder<IByteBuffer>, IDecoder
{
private readonly IDeserializer _deserializer;
private readonly ILogger<LoginDecoder> _logger;
private readonly ISessionRefHolder _sessionRefHolder;
private readonly ILogLanguageLocalizer<LogLanguageKey> _logLanguage;

/// <summary>
/// Initializes a new instance of the <see cref="LoginDecoder"/> class.
/// </summary>
/// <param name="logger">The logger instance.</param>
/// <param name="deserializer">The packet deserializer.</param>
/// <param name="sessionRefHolder">The session reference holder.</param>
/// <param name="logLanguage">The localized log language provider.</param>
public LoginDecoder(ILogger<LoginDecoder> logger, IDeserializer deserializer, ISessionRefHolder sessionRefHolder, ILogLanguageLocalizer<LogLanguageKey> logLanguage)
{
_logger = logger;
Expand All @@ -34,6 +44,12 @@ public LoginDecoder(ILogger<LoginDecoder> logger, IDeserializer deserializer, IS
_logLanguage = logLanguage;
}

/// <summary>
/// Decodes a byte span into a collection of packets using login server decoding.
/// </summary>
/// <param name="clientSessionId">The client session identifier.</param>
/// <param name="message">The byte span containing the encoded packet data.</param>
/// <returns>A collection of decoded packets.</returns>
public IEnumerable<IPacket> Decode(string clientSessionId, Span<byte> message)
{
try
Expand Down Expand Up @@ -75,6 +91,12 @@ 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(),
Expand Down
22 changes: 22 additions & 0 deletions src/NosCore.Networking/Encoding/LoginEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,23 @@

namespace NosCore.Networking.Encoding
{
/// <summary>
/// Encodes packets for login server communication using region-specific encoding.
/// </summary>
public class LoginEncoder : MessageToMessageEncoder<IEnumerable<IPacket>>, IEncoder
{
private readonly ILogger<LoginEncoder> _logger;
private readonly ISerializer _serializer;
private readonly ISessionRefHolder _sessionRefHolder;
private readonly ILogLanguageLocalizer<LogLanguageKey> _logLanguage;

/// <summary>
/// Initializes a new instance of the <see cref="LoginEncoder"/> class.
/// </summary>
/// <param name="logger">The logger instance.</param>
/// <param name="serializer">The packet serializer.</param>
/// <param name="sessionRefHolder">The session reference holder.</param>
/// <param name="logLanguage">The localized log language provider.</param>
public LoginEncoder(ILogger<LoginEncoder> logger, ISerializer serializer, ISessionRefHolder sessionRefHolder, ILogLanguageLocalizer<LogLanguageKey> logLanguage)
{
_logger = logger;
Expand All @@ -34,12 +44,24 @@ public LoginEncoder(ILogger<LoginEncoder> logger, ISerializer serializer, ISessi
_logLanguage = logLanguage;
}

/// <summary>
/// Encodes packets into a byte buffer for transmission.
/// </summary>
/// <param name="context">The channel handler context.</param>
/// <param name="message">The packets to encode.</param>
/// <param name="output">The output list to add the encoded buffer to.</param>
protected override void Encode(IChannelHandlerContext context, IEnumerable<IPacket> message,
List<object> output)
{
output.Add(Unpooled.WrappedBuffer(Encode(context.Channel.Id.AsLongText(), message)));
}

/// <summary>
/// Encodes a collection of packets into a byte array using login server encoding.
/// </summary>
/// <param name="clientSessionId">The client session identifier.</param>
/// <param name="packets">The packets to encode.</param>
/// <returns>A byte array containing the encoded packet data.</returns>
public byte[] Encode(string clientSessionId, IEnumerable<IPacket> packets)
{
try
Expand Down
Loading