diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index b434448..1fd44aa 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -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 @@ -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 }} diff --git a/src/NosCore.Networking/BroadcastableExtension.cs b/src/NosCore.Networking/BroadcastableExtension.cs index 59767bd..dd186c7 100644 --- a/src/NosCore.Networking/BroadcastableExtension.cs +++ b/src/NosCore.Networking/BroadcastableExtension.cs @@ -13,19 +13,41 @@ namespace NosCore.Networking { + /// + /// Provides extension methods for broadcasting packets to instances. + /// public static class IBroadcastableExtension { + /// + /// Sends a single packet to all sessions in the broadcastable group. + /// + /// The broadcastable group to send the packet to. + /// The packet to send. + /// A task representing the asynchronous send operation. public static Task SendPacketAsync(this IBroadcastable channelGroup, IPacket packet) { return channelGroup.SendPacketsAsync(new[] { packet }); } + /// + /// Sends a single packet to matching sessions in the broadcastable group. + /// + /// The broadcastable group to send the packet to. + /// The packet to send. + /// The channel matcher to filter recipients. + /// A task representing the asynchronous send operation. public static Task SendPacketAsync(this IBroadcastable channelGroup, IPacket packet, IChannelMatcher matcher) { return channelGroup.SendPacketsAsync(new[] { packet }, matcher); } - + /// + /// Sends multiple packets to matching sessions in the broadcastable group. + /// + /// The broadcastable group to send the packets to. + /// The collection of packets to send. + /// The optional channel matcher to filter recipients. + /// A task representing the asynchronous send operation. public static async Task SendPacketsAsync(this IBroadcastable channelGroup, IEnumerable packets, IChannelMatcher? matcher) { @@ -51,6 +73,12 @@ public static async Task SendPacketsAsync(this IBroadcastable channelGroup, IEnu } + /// + /// Sends multiple packets to all sessions in the broadcastable group. + /// + /// The broadcastable group to send the packets to. + /// The collection of packets to send. + /// A task representing the asynchronous send operation. public static Task SendPacketsAsync(this IBroadcastable channelGroup, IEnumerable packets) { return channelGroup.SendPacketsAsync(packets, null); diff --git a/src/NosCore.Networking/Encoding/Filter/RequestFilter.cs b/src/NosCore.Networking/Encoding/Filter/RequestFilter.cs index 7037ab2..d4d13ad 100644 --- a/src/NosCore.Networking/Encoding/Filter/RequestFilter.cs +++ b/src/NosCore.Networking/Encoding/Filter/RequestFilter.cs @@ -12,10 +12,25 @@ namespace NosCore.Networking.Encoding.Filter { + /// + /// Abstract base class for request filters that process incoming byte data. + /// public abstract class RequestFilter : MessageToMessageDecoder { + /// + /// Filters incoming request data. + /// + /// The channel handler context. + /// 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)); diff --git a/src/NosCore.Networking/Encoding/Filter/SpamRequestFilter.cs b/src/NosCore.Networking/Encoding/Filter/SpamRequestFilter.cs index ebf2e6a..9293b43 100644 --- a/src/NosCore.Networking/Encoding/Filter/SpamRequestFilter.cs +++ b/src/NosCore.Networking/Encoding/Filter/SpamRequestFilter.cs @@ -15,6 +15,9 @@ namespace NosCore.Networking.Encoding.Filter { + /// + /// Filters spam requests by rate-limiting connections from the same IP address. + /// public class SpamRequestFilter : RequestFilter { private readonly Dictionary _connectionsByIp = new(); @@ -22,8 +25,18 @@ public class SpamRequestFilter : RequestFilter private readonly IClock _clock; 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. + /// + /// The clock instance for time tracking. + /// The logger instance. + /// The localized log language provider. public SpamRequestFilter(IClock clock, ILogger logger, ILogLanguageLocalizer logLanguage) { _clock = clock; @@ -31,6 +44,12 @@ public SpamRequestFilter(IClock clock, ILogger logger, ILogLa _logLanguage = logLanguage; } + /// + /// Filters incoming requests based on connection rate from the same IP address. + /// + /// The channel handler context. + /// 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) { if (_connectionsByIp.TryGetValue(context.Channel.RemoteAddress, out var date)) diff --git a/src/NosCore.Networking/Encoding/FrameDelimiter.cs b/src/NosCore.Networking/Encoding/FrameDelimiter.cs index f5000b9..f28d9b4 100644 --- a/src/NosCore.Networking/Encoding/FrameDelimiter.cs +++ b/src/NosCore.Networking/Encoding/FrameDelimiter.cs @@ -13,15 +13,28 @@ namespace NosCore.Networking.Encoding; +/// +/// Delimits incoming byte streams into frames based on session-specific delimiters. +/// public class FrameDelimiter : ByteToMessageDecoder { 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(); @@ -45,6 +58,12 @@ protected override void Decode(IChannelHandlerContext context, IByteBuffer input } } + /// + /// Gets the delimiter byte for a specific session. + /// + /// The session identifier. + /// Whether this is the first packet in the session. + /// The delimiter byte for the session. public static byte GetDelimiter(int session, bool isFirstPacket = false) { int stype = !isFirstPacket ? (session >> 6) & 3 : -1; diff --git a/src/NosCore.Networking/Encoding/IDecoder.cs b/src/NosCore.Networking/Encoding/IDecoder.cs index ee903f3..d97c590 100644 --- a/src/NosCore.Networking/Encoding/IDecoder.cs +++ b/src/NosCore.Networking/Encoding/IDecoder.cs @@ -11,8 +11,17 @@ namespace NosCore.Networking.Encoding { + /// + /// Defines a packet decoder that converts byte data into packets. + /// public interface IDecoder : IChannelHandler { + /// + /// Decodes a byte span into a collection of packets. + /// + /// The client session identifier. + /// The byte span containing the encoded packet data. + /// A collection of decoded packets. IEnumerable Decode(string clientSessionId, Span message); } } \ No newline at end of file diff --git a/src/NosCore.Networking/Encoding/IEncoder.cs b/src/NosCore.Networking/Encoding/IEncoder.cs index aac3309..4fe4e76 100644 --- a/src/NosCore.Networking/Encoding/IEncoder.cs +++ b/src/NosCore.Networking/Encoding/IEncoder.cs @@ -11,8 +11,17 @@ namespace NosCore.Networking.Encoding { + /// + /// Defines a packet encoder that converts packets to byte arrays for transmission. + /// public interface IEncoder : IChannelHandler { + /// + /// Encodes a collection of packets into a byte array. + /// + /// The client session identifier. + /// The packets to encode. + /// A byte array containing the encoded packet data. byte[] Encode(string clientSessionId, IEnumerable packets); } } \ No newline at end of file diff --git a/src/NosCore.Networking/Encoding/LoginDecoder.cs b/src/NosCore.Networking/Encoding/LoginDecoder.cs index 7806e1a..b4059a0 100644 --- a/src/NosCore.Networking/Encoding/LoginDecoder.cs +++ b/src/NosCore.Networking/Encoding/LoginDecoder.cs @@ -19,6 +19,9 @@ namespace NosCore.Networking.Encoding { + /// + /// Decodes packets from login server communication using region-specific decoding. + /// public class LoginDecoder : MessageToMessageDecoder, IDecoder { private readonly IDeserializer _deserializer; @@ -26,6 +29,13 @@ public class LoginDecoder : MessageToMessageDecoder, IDecoder private readonly ISessionRefHolder _sessionRefHolder; private readonly ILogLanguageLocalizer _logLanguage; + /// + /// Initializes a new instance of the class. + /// + /// The logger instance. + /// The packet deserializer. + /// The session reference holder. + /// The localized log language provider. public LoginDecoder(ILogger logger, IDeserializer deserializer, ISessionRefHolder sessionRefHolder, ILogLanguageLocalizer logLanguage) { _logger = logger; @@ -34,6 +44,12 @@ public LoginDecoder(ILogger logger, IDeserializer deserializer, IS _logLanguage = logLanguage; } + /// + /// Decodes a byte span into a collection of packets using login server decoding. + /// + /// The client session identifier. + /// The byte span containing the encoded packet data. + /// A collection of decoded packets. public IEnumerable Decode(string clientSessionId, Span message) { try @@ -75,6 +91,12 @@ 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(), diff --git a/src/NosCore.Networking/Encoding/LoginEncoder.cs b/src/NosCore.Networking/Encoding/LoginEncoder.cs index 6320c23..813400c 100644 --- a/src/NosCore.Networking/Encoding/LoginEncoder.cs +++ b/src/NosCore.Networking/Encoding/LoginEncoder.cs @@ -19,6 +19,9 @@ namespace NosCore.Networking.Encoding { + /// + /// Encodes packets for login server communication using region-specific encoding. + /// public class LoginEncoder : MessageToMessageEncoder>, IEncoder { private readonly ILogger _logger; @@ -26,6 +29,13 @@ public class LoginEncoder : MessageToMessageEncoder>, IEnco private readonly ISessionRefHolder _sessionRefHolder; private readonly ILogLanguageLocalizer _logLanguage; + /// + /// Initializes a new instance of the class. + /// + /// The logger instance. + /// The packet serializer. + /// The session reference holder. + /// The localized log language provider. public LoginEncoder(ILogger logger, ISerializer serializer, ISessionRefHolder sessionRefHolder, ILogLanguageLocalizer logLanguage) { _logger = logger; @@ -34,12 +44,24 @@ 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. + /// + /// The client session identifier. + /// The packets to encode. + /// A byte array containing the encoded packet data. public byte[] Encode(string clientSessionId, IEnumerable packets) { try diff --git a/src/NosCore.Networking/Encoding/WorldDecoder.cs b/src/NosCore.Networking/Encoding/WorldDecoder.cs index b0c1733..e59cd66 100644 --- a/src/NosCore.Networking/Encoding/WorldDecoder.cs +++ b/src/NosCore.Networking/Encoding/WorldDecoder.cs @@ -22,6 +22,9 @@ namespace NosCore.Networking.Encoding { + /// + /// Decodes packets from world server communication using region-specific decoding. + /// public class WorldDecoder : MessageToMessageDecoder, IDecoder { private readonly IDeserializer _deserializer; @@ -31,6 +34,13 @@ public class WorldDecoder : MessageToMessageDecoder, IDecoder private readonly ISessionRefHolder _sessionRefHolder; private readonly ILogLanguageLocalizer _logLanguage; + /// + /// Initializes a new instance of the class. + /// + /// The packet deserializer. + /// The logger instance. + /// The session reference holder. + /// The localized log language provider. public WorldDecoder(IDeserializer deserializer, ILogger logger, ISessionRefHolder sessionRefHolder, ILogLanguageLocalizer logLanguage) { _deserializer = deserializer; @@ -172,17 +182,29 @@ 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. + /// + /// The client session identifier. + /// The byte span containing the encoded packet data. + /// A collection of decoded packets. public IEnumerable Decode(string clientSessionId, Span message) { var continueToDecode = true; diff --git a/src/NosCore.Networking/Encoding/WorldEncoder.cs b/src/NosCore.Networking/Encoding/WorldEncoder.cs index 6a78e8a..f79f8ac 100644 --- a/src/NosCore.Networking/Encoding/WorldEncoder.cs +++ b/src/NosCore.Networking/Encoding/WorldEncoder.cs @@ -16,23 +16,43 @@ namespace NosCore.Networking.Encoding { + /// + /// Encodes packets for world server communication using region-specific encoding. + /// public class WorldEncoder : MessageToMessageEncoder>, IEncoder { private readonly ISerializer _serializer; private readonly ISessionRefHolder _sessionRefHolder; + /// + /// Initializes a new instance of the class. + /// + /// The packet serializer. + /// The session reference holder. public WorldEncoder(ISerializer serializer, ISessionRefHolder sessionRefHolder) { _serializer = serializer; _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. + /// + /// The client session identifier. + /// The packets to encode. + /// A byte array containing the encoded packet data. public byte[] Encode(string clientSessionId, IEnumerable packets) { return packets.SelectMany(packet => diff --git a/src/NosCore.Networking/Extensions/RegionTypeExtension.cs b/src/NosCore.Networking/Extensions/RegionTypeExtension.cs index 9cbd366..4f55930 100644 --- a/src/NosCore.Networking/Extensions/RegionTypeExtension.cs +++ b/src/NosCore.Networking/Extensions/RegionTypeExtension.cs @@ -9,8 +9,16 @@ namespace NosCore.Networking.Extensions { + /// + /// Provides extension methods for . + /// public static class RegionTypeExtension { + /// + /// Gets the text encoding associated with a specific region type. + /// + /// The region type. + /// The encoding for the specified region, or the default encoding if the region is not recognized. public static System.Text.Encoding? GetEncoding(this RegionType region) { return region switch diff --git a/src/NosCore.Networking/INetworkClient.cs b/src/NosCore.Networking/INetworkClient.cs index 3e25dd5..8cdbb8e 100644 --- a/src/NosCore.Networking/INetworkClient.cs +++ b/src/NosCore.Networking/INetworkClient.cs @@ -12,16 +12,40 @@ namespace NosCore.Networking { + /// + /// Defines a network client that handles packet communication and channel management. + /// public interface INetworkClient : IChannelHandler { + /// + /// Gets or sets the unique session identifier for this client. + /// int SessionId { get; set; } + /// + /// Disconnects the client asynchronously. + /// + /// A task representing the asynchronous disconnect operation. Task DisconnectAsync(); + /// + /// Sends a single packet to the client asynchronously. + /// + /// The packet to send. + /// A task representing the asynchronous send operation. Task SendPacketAsync(IPacket packet); + /// + /// Sends multiple packets to the client asynchronously. + /// + /// 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 be27786..2e44d00 100644 --- a/src/NosCore.Networking/IPipelineConfiguration.cs +++ b/src/NosCore.Networking/IPipelineConfiguration.cs @@ -6,7 +6,13 @@ namespace NosCore.Networking; +/// +/// Defines configuration settings for the network pipeline. +/// public interface IPipelineConfiguration { + /// + /// Gets or sets a value indicating whether to use frame delimiters in the pipeline. + /// public bool UseDelimiter { get; set; } } \ No newline at end of file diff --git a/src/NosCore.Networking/IPipelineFactory.cs b/src/NosCore.Networking/IPipelineFactory.cs index ab6b362..8048280 100644 --- a/src/NosCore.Networking/IPipelineFactory.cs +++ b/src/NosCore.Networking/IPipelineFactory.cs @@ -12,8 +12,14 @@ 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/NetworkClient.cs b/src/NosCore.Networking/NetworkClient.cs index 449c4d5..2451ac3 100644 --- a/src/NosCore.Networking/NetworkClient.cs +++ b/src/NosCore.Networking/NetworkClient.cs @@ -19,12 +19,20 @@ namespace NosCore.Networking { + /// + /// Represents a network client that manages packet communication and handles channel events. + /// public class NetworkClient : ChannelHandlerAdapter, INetworkClient { private const short MaxPacketsBuffer = 50; private readonly ILogger _logger; private readonly ILogLanguageLocalizer _logLanguage; + /// + /// Initializes a new instance of the class. + /// + /// The logger instance. + /// The localized log language provider. public NetworkClient(ILogger logger, ILogLanguageLocalizer logLanguage) { _logger = logger; @@ -32,15 +40,35 @@ public NetworkClient(ILogger logger, ILogLanguageLocalizer logLa _logLanguage = logLanguage; } + /// + /// Gets the communication channel associated with this client. + /// public IChannel? Channel { get; private set; } + /// + /// Gets or sets a value indicating whether the client has selected a character. + /// public bool HasSelectedCharacter { get; set; } + /// + /// Gets or sets a value indicating whether the client is authenticated. + /// public bool IsAuthenticated { get; set; } + /// + /// Gets or sets the unique session identifier for this client. + /// public int SessionId { get; set; } + + /// + /// Gets the queue of recently sent packets. + /// public ConcurrentQueue LastPackets { get; } + /// + /// Disconnects the client asynchronously. + /// + /// A task representing the asynchronous disconnect operation. public async Task DisconnectAsync() { _logger.Information(_logLanguage[LogLanguageKey.FORCED_DISCONNECTION], @@ -51,11 +79,21 @@ public async Task DisconnectAsync() } } + /// + /// Sends a single packet to the client asynchronously. + /// + /// The packet to send. + /// A task representing the asynchronous send operation. public Task SendPacketAsync(IPacket? packet) { return SendPacketsAsync(new[] { packet }); } + /// + /// Sends multiple packets to the client asynchronously. + /// + /// The collection of packets to send. + /// A task representing the asynchronous send operation. public async Task SendPacketsAsync(IEnumerable packets) { var packetlist = packets.ToList(); @@ -82,11 +120,20 @@ await Task.WhenAll(packetlist.Select(packet => Task.Run(() => 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; } + /// + /// 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 diff --git a/src/NosCore.Networking/NetworkManager.cs b/src/NosCore.Networking/NetworkManager.cs index e0cb3b6..9d675fd 100644 --- a/src/NosCore.Networking/NetworkManager.cs +++ b/src/NosCore.Networking/NetworkManager.cs @@ -18,12 +18,22 @@ namespace NosCore.Networking { + /// + /// Manages the network server lifecycle and handles incoming client connections. + /// public class NetworkManager { private readonly IOptions _configuration; private readonly ILogger _logger; private readonly Func _pipelineFactory; + /// + /// Initializes a new instance of the class. + /// + /// The server configuration options. + /// Factory function for creating pipeline instances per channel. + /// The logger instance. + /// The localized log language provider. public NetworkManager(IOptions configuration, Func pipelineFactory, ILogger logger, ILogLanguageLocalizer logLanguage) { @@ -36,6 +46,10 @@ public NetworkManager(IOptions configuration, 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); diff --git a/src/NosCore.Networking/NosCore.Networking.csproj b/src/NosCore.Networking/NosCore.Networking.csproj index 3a4f297..26d93da 100644 --- a/src/NosCore.Networking/NosCore.Networking.csproj +++ b/src/NosCore.Networking/NosCore.Networking.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 latest favicon.ico true @@ -25,12 +25,14 @@ - - - - - + + + + + + + diff --git a/src/NosCore.Networking/PipelineConfiguration.cs b/src/NosCore.Networking/PipelineConfiguration.cs index 5940ba9..9157f94 100644 --- a/src/NosCore.Networking/PipelineConfiguration.cs +++ b/src/NosCore.Networking/PipelineConfiguration.cs @@ -6,7 +6,13 @@ namespace NosCore.Networking; +/// +/// Provides configuration settings for the network pipeline. +/// public class PipelineConfiguration : IPipelineConfiguration { + /// + /// Gets or sets a value indicating whether to use frame delimiters in the pipeline. + /// public bool UseDelimiter { get; set; } } \ No newline at end of file diff --git a/src/NosCore.Networking/PipelineFactory.cs b/src/NosCore.Networking/PipelineFactory.cs index f6d51de..b476a01 100644 --- a/src/NosCore.Networking/PipelineFactory.cs +++ b/src/NosCore.Networking/PipelineFactory.cs @@ -17,6 +17,9 @@ namespace NosCore.Networking { + /// + /// Factory class responsible for creating and configuring network channel pipelines. + /// public class PipelineFactory : IPipelineFactory { private readonly ISocketChannel _channel; @@ -28,6 +31,17 @@ public class PipelineFactory : IPipelineFactory private readonly IEnumerable _requestFilters; private readonly IPipelineConfiguration _pipelineConfiguration; + /// + /// 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) @@ -42,6 +56,9 @@ public PipelineFactory(ISocketChannel channel, IDecoder decoder, _pipelineConfiguration = pipelineConfiguration; } + /// + /// Creates and configures the network channel pipeline with filters, decoder, encoder, and client handlers. + /// public void CreatePipeline() { _sessionRefHolder[_channel.Id.AsLongText()] = diff --git a/src/NosCore.Networking/RegionTypeMapping.cs b/src/NosCore.Networking/RegionTypeMapping.cs index 6d9c2b9..166edf7 100644 --- a/src/NosCore.Networking/RegionTypeMapping.cs +++ b/src/NosCore.Networking/RegionTypeMapping.cs @@ -8,15 +8,30 @@ namespace NosCore.Networking { + /// + /// Represents a mapping between a session identifier and its associated region type. + /// public class RegionTypeMapping { + /// + /// Initializes a new instance of the class. + /// + /// The unique session identifier. + /// The region type associated with the session. public RegionTypeMapping(int sessionId, RegionType regionType) { SessionId = sessionId; RegionType = regionType; } + /// + /// Gets or sets the unique session identifier. + /// public int SessionId { get; set; } + + /// + /// Gets or sets the region type associated with the session. + /// public RegionType RegionType { get; set; } } } \ No newline at end of file diff --git a/src/NosCore.Networking/Resource/LogLanguageKey.cs b/src/NosCore.Networking/Resource/LogLanguageKey.cs index 347b372..c731950 100644 --- a/src/NosCore.Networking/Resource/LogLanguageKey.cs +++ b/src/NosCore.Networking/Resource/LogLanguageKey.cs @@ -6,16 +6,54 @@ namespace NosCore.Networking.Resource { + /// + /// Defines localization keys for networking-related log messages. + /// public enum LogLanguageKey { + /// + /// Log message when a client connects. + /// CLIENT_CONNECTED, + + /// + /// Log message indicating the port the server is listening on. + /// LISTENING_PORT, + + /// + /// Log message for encoding errors. + /// ENCODE_ERROR, + + /// + /// Log message for decoding errors. + /// ERROR_DECODING, + + /// + /// Log message for session ID errors. + /// ERROR_SESSIONID, + + /// + /// Log message when a client is forcefully disconnected. + /// FORCED_DISCONNECTION, + + /// + /// Log message when a connection is blocked by the spam filter. + /// BLOCKED_BY_SPAM_FILTER, + + /// + /// Log message for corrupted packet detection. + /// CORRUPTED_PACKET, + + /// + /// Log message when attempting to send an invalid packet. + /// SENDING_INVALID_PACKET } } \ 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 0c01aff..4051912 100644 --- a/src/NosCore.Networking/SessionGroup/ChannelMatcher/EveryoneBut.cs +++ b/src/NosCore.Networking/SessionGroup/ChannelMatcher/EveryoneBut.cs @@ -9,15 +9,27 @@ namespace NosCore.Networking.SessionGroup.ChannelMatcher { + /// + /// A channel matcher that matches all channels except a specific one. + /// public class EveryoneBut : IChannelMatcher { private readonly IChannelId _id; + /// + /// Initializes a new instance of the class. + /// + /// The channel identifier to exclude from matching. public EveryoneBut(IChannelId id) { _id = id; } + /// + /// Determines whether the specified channel matches (is not the excluded channel). + /// + /// The channel to test. + /// True if the channel is not the excluded channel; otherwise, false. public bool Matches(IChannel channel) { return channel.Id != _id; diff --git a/src/NosCore.Networking/SessionGroup/ChannelMatcher/Only.cs b/src/NosCore.Networking/SessionGroup/ChannelMatcher/Only.cs index 341d8f3..288a696 100644 --- a/src/NosCore.Networking/SessionGroup/ChannelMatcher/Only.cs +++ b/src/NosCore.Networking/SessionGroup/ChannelMatcher/Only.cs @@ -9,15 +9,27 @@ namespace NosCore.Networking.SessionGroup.ChannelMatcher { + /// + /// A channel matcher that matches only a specific channel by its identifier. + /// public class Only : IChannelMatcher { private readonly IChannelId _id; + /// + /// Initializes a new instance of the class. + /// + /// The channel identifier to match. public Only(IChannelId id) { _id = id; } + /// + /// Determines whether the specified channel matches the target channel. + /// + /// The channel to test. + /// True if the channel matches; otherwise, false. public bool Matches(IChannel channel) { return channel.Id == _id; diff --git a/src/NosCore.Networking/SessionGroup/IBroadcastable.cs b/src/NosCore.Networking/SessionGroup/IBroadcastable.cs index 93e8fd8..41e5fb6 100644 --- a/src/NosCore.Networking/SessionGroup/IBroadcastable.cs +++ b/src/NosCore.Networking/SessionGroup/IBroadcastable.cs @@ -9,10 +9,24 @@ namespace NosCore.Networking.SessionGroup { + /// + /// Defines an entity that can broadcast packets to multiple sessions. + /// public interface IBroadcastable { + /// + /// Gets or sets the session group for broadcasting packets. + /// ISessionGroup Sessions { get; set; } + + /// + /// Gets the queue of recently broadcast packets. + /// ConcurrentQueue LastPackets { get; } + + /// + /// Gets the maximum number of packets to retain in the buffer. + /// short MaxPacketsBuffer { get; } } } \ No newline at end of file diff --git a/src/NosCore.Networking/SessionGroup/ISessionGroup.cs b/src/NosCore.Networking/SessionGroup/ISessionGroup.cs index 2e5d2a8..7902b6e 100644 --- a/src/NosCore.Networking/SessionGroup/ISessionGroup.cs +++ b/src/NosCore.Networking/SessionGroup/ISessionGroup.cs @@ -12,14 +12,42 @@ namespace NosCore.Networking.SessionGroup; +/// +/// Defines a group of network sessions that can be managed collectively. +/// public interface ISessionGroup { + /// + /// Broadcasts packets to all sessions in the group. + /// + /// The array of packets to broadcast. + /// A task representing the asynchronous broadcast operation. Task Broadcast(IPacket[] packetDefinitions); + + /// + /// Broadcasts packets to matching sessions in the group. + /// + /// The array of packets to broadcast. + /// The matcher to filter which channels receive the packets. + /// A task representing the asynchronous broadcast operation. Task Broadcast(IPacket[] packetDefinitions, IChannelMatcher channelMatcher); + /// + /// Adds a channel to the session group. + /// + /// The channel to add. + /// True if the channel was added; otherwise, false. bool Add(IChannel channel); + /// + /// Removes a channel from the session group. + /// + /// The channel to remove. + /// True if the channel was removed; otherwise, false. bool Remove(IChannel channel); + /// + /// Gets the number of channels in the session group. + /// int Count { get; } } \ No newline at end of file diff --git a/src/NosCore.Networking/SessionGroup/SessionGroup.cs b/src/NosCore.Networking/SessionGroup/SessionGroup.cs index cbfc5b7..fdc2cb2 100644 --- a/src/NosCore.Networking/SessionGroup/SessionGroup.cs +++ b/src/NosCore.Networking/SessionGroup/SessionGroup.cs @@ -12,35 +12,65 @@ namespace NosCore.Networking.SessionGroup; +/// +/// Manages a group of network sessions and provides broadcasting capabilities. +/// public class SessionGroup : ISessionGroup { private readonly DefaultChannelGroup _channelGroup; + /// + /// Initializes a new instance of the class. + /// public SessionGroup() { ExecutionEnvironment.TryGetCurrentExecutor(out var executor); _channelGroup = new DefaultChannelGroup(executor); } + /// + /// Broadcasts packets to all sessions in the group. + /// + /// The array of packets to broadcast. + /// A task representing the asynchronous broadcast operation. public Task Broadcast(IPacket[] packetDefinitions) { return _channelGroup.WriteAndFlushAsync(packetDefinitions); } + /// + /// Broadcasts packets to matching sessions in the group. + /// + /// The array of packets to broadcast. + /// The matcher to filter which channels receive the packets. + /// A task representing the asynchronous broadcast operation. public Task Broadcast(IPacket[] packetDefinitions, IChannelMatcher channelMatcher) { return _channelGroup.WriteAndFlushAsync(packetDefinitions, channelMatcher); } + /// + /// Adds a channel to the session group. + /// + /// The channel to add. + /// True if the channel was added; otherwise, false. public bool Add(IChannel channel) { return _channelGroup.Add(channel); } + /// + /// Removes a channel from the session group. + /// + /// The channel to remove. + /// True if the channel was removed; otherwise, false. public bool Remove(IChannel channel) { return _channelGroup.Remove(channel); } + /// + /// Gets the number of channels in the session group. + /// public int Count => _channelGroup.Count; } \ No newline at end of file diff --git a/src/NosCore.Networking/SessionRef/ISessionRefHolder.cs b/src/NosCore.Networking/SessionRef/ISessionRefHolder.cs index c4c48e8..72bd4f8 100644 --- a/src/NosCore.Networking/SessionRef/ISessionRefHolder.cs +++ b/src/NosCore.Networking/SessionRef/ISessionRefHolder.cs @@ -8,8 +8,15 @@ namespace NosCore.Networking.SessionRef { + /// + /// Defines a holder for managing session references and generating session identifiers. + /// public interface ISessionRefHolder : IDictionary { + /// + /// Generates a new unique session identifier. + /// + /// A new session identifier. public int GenerateSessionId(); } } diff --git a/src/NosCore.Networking/SessionRef/SessionRefHolder.cs b/src/NosCore.Networking/SessionRef/SessionRefHolder.cs index 3c8a090..16ff612 100644 --- a/src/NosCore.Networking/SessionRef/SessionRefHolder.cs +++ b/src/NosCore.Networking/SessionRef/SessionRefHolder.cs @@ -8,10 +8,17 @@ namespace NosCore.Networking.SessionRef; +/// +/// Manages session references and generates unique session identifiers in a thread-safe manner. +/// public class SessionRefHolder : ConcurrentDictionary, ISessionRefHolder { private int _sessionCounter; - + + /// + /// Generates a new unique session identifier. + /// + /// A new session identifier incremented by 2. public int GenerateSessionId() { _sessionCounter += 2; diff --git a/test/NosCore.Networking.Tests/AssemblyInfo.cs b/test/NosCore.Networking.Tests/AssemblyInfo.cs new file mode 100644 index 0000000..ae411c7 --- /dev/null +++ b/test/NosCore.Networking.Tests/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/test/NosCore.Networking.Tests/LogLanguageTests.cs b/test/NosCore.Networking.Tests/LogLanguageTests.cs index 9f8d45a..7d6d882 100644 --- a/test/NosCore.Networking.Tests/LogLanguageTests.cs +++ b/test/NosCore.Networking.Tests/LogLanguageTests.cs @@ -27,7 +27,7 @@ public LogLanguageTests() } [TestCategory("OPTIONAL-TEST")] - [DataTestMethod] + [TestMethod] [DataRow(RegionType.EN)] [DataRow(RegionType.CS)] [DataRow(RegionType.DE)] @@ -51,7 +51,7 @@ public void CheckEveryLanguageValueSet(RegionType type) } } - [DataTestMethod] + [TestMethod] [DataRow(RegionType.EN)] [DataRow(RegionType.CS)] [DataRow(RegionType.DE)] diff --git a/test/NosCore.Networking.Tests/NosCore.Networking.Tests.csproj b/test/NosCore.Networking.Tests/NosCore.Networking.Tests.csproj index 9ebc645..bfdc60a 100644 --- a/test/NosCore.Networking.Tests/NosCore.Networking.Tests.csproj +++ b/test/NosCore.Networking.Tests/NosCore.Networking.Tests.csproj @@ -1,10 +1,10 @@  - net8.0 + net10.0 enable false - true + true @@ -19,17 +19,19 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + + + diff --git a/test/NosCore.Networking.Tests/SpanRequestFilterTest.cs b/test/NosCore.Networking.Tests/SpanRequestFilterTest.cs index b0a1439..7433218 100644 --- a/test/NosCore.Networking.Tests/SpanRequestFilterTest.cs +++ b/test/NosCore.Networking.Tests/SpanRequestFilterTest.cs @@ -4,9 +4,7 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // ----------------------------------- -using System.Linq; using System.Net; -using Castle.Core.Logging; using DotNetty.Transport.Channels; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -30,7 +28,7 @@ public void FirstRequestNeverFilterOut() 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 }); - Assert.IsTrue(new byte[] { 1, 2, 3 }.SequenceEqual(output)); + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, output); } [TestMethod] @@ -46,7 +44,7 @@ public void DifferentIpFirstRequestNeverFilterOut() 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 }); - Assert.IsTrue(new byte[] { 1, 2, 3 }.SequenceEqual(output)); + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, output); } [TestMethod] @@ -74,7 +72,7 @@ public void SameIpAfterOneSecondIsNotFiltering() spamFilter.Filter(ctx.Object, new byte[] { 1, 2, 3 }); clock.AdvanceSeconds(1); var output = spamFilter.Filter(ctx.Object, new byte[] { 1, 2, 3 }); - Assert.IsTrue(new byte[] { 1, 2, 3 }.SequenceEqual(output)); + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, output); } } }