diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 835b96d..86dc0fe 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ +ko_fi: oliverbooth custom: ['https://buymeacoffee.com/oliverbooth'] diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 41cf44d..ae53f18 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -2,9 +2,13 @@ name: .NET on: push: - branches: [ main, develop ] + branches: + - '*' + - '*/*' pull_request: - branches: [ main, develop ] + branches: + - '*' + - '*/*' jobs: build: @@ -16,9 +20,9 @@ jobs: uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Add NuGet source run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ca5ca69..913a55b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -3,7 +3,8 @@ name: Publish Nightly on: push: branches: - - develop + - main + workflow_dispatch: jobs: nightly: @@ -15,9 +16,9 @@ jobs: uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Add GitHub NuGet source run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 7f089d9..06245fd 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -15,9 +15,9 @@ jobs: uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Add GitHub NuGet source run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b234990..910cb8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,9 +15,9 @@ jobs: uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Add GitHub NuGet source run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" diff --git a/README.md b/README.md index c08825b..5a5c582 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -

TcpDotNet

+

TCP.NET

-GitHub Workflow Status -GitHub Issues -NuGet Downloads -Stable Version -Nightly Version -MIT License +GitHub Workflow Status +GitHub Issues +NuGet Downloads +Stable Version +Nightly Version +MIT License

### About -TcpDotNet is a .NET Standard 2.1 package that enables you to perform server/client communications over the TCP protocol. +TcpDotNet is a .NET package that enables you to perform server/client communications over the TCP protocol. This package is a work-in-progress and not deemed ready for production use. However, it is available on NuGet as a very crude and early development build. This package will be maintained as I see fit. *(I'm also [dogfooding](https://www.pcmag.com/encyclopedia/term/dogfooding) this library, so there's that.)* @@ -17,7 +17,7 @@ This package is a work-in-progress and not deemed ready for production use. Howe ## Installation ### NuGet installation ```ps -Install-Package TcpDotNet -Version 0.1.0-nightly.1 +Install-Package TcpDotNet -Version 1.0.0 ``` ### Manual installation diff --git a/TcpDotNet.ClientIntegrationTest/GoodbyePacket.cs b/TcpDotNet.ClientIntegrationTest/GoodbyePacket.cs index d79d97c..9892853 100644 --- a/TcpDotNet.ClientIntegrationTest/GoodbyePacket.cs +++ b/TcpDotNet.ClientIntegrationTest/GoodbyePacket.cs @@ -1,4 +1,4 @@ -using TcpDotNet.Protocol; +using TcpDotNet.Protocol; namespace TcpDotNet.ClientIntegrationTest; diff --git a/TcpDotNet.ClientIntegrationTest/HelloPacket.cs b/TcpDotNet.ClientIntegrationTest/HelloPacket.cs index 0b4f357..0cc4e9f 100644 --- a/TcpDotNet.ClientIntegrationTest/HelloPacket.cs +++ b/TcpDotNet.ClientIntegrationTest/HelloPacket.cs @@ -1,4 +1,4 @@ -using TcpDotNet.Protocol; +using TcpDotNet.Protocol; namespace TcpDotNet.ClientIntegrationTest; diff --git a/TcpDotNet.ClientIntegrationTest/PacketHandlers/GoodbyePacketHandler.cs b/TcpDotNet.ClientIntegrationTest/PacketHandlers/GoodbyePacketHandler.cs index 243aa78..58fec4a 100644 --- a/TcpDotNet.ClientIntegrationTest/PacketHandlers/GoodbyePacketHandler.cs +++ b/TcpDotNet.ClientIntegrationTest/PacketHandlers/GoodbyePacketHandler.cs @@ -1,10 +1,10 @@ -using TcpDotNet.Protocol; +using TcpDotNet.Protocol; namespace TcpDotNet.ClientIntegrationTest.PacketHandlers; internal sealed class GoodbyePacketHandler : PacketHandler { - public override Task HandleAsync(BaseClientNode recipient, GoodbyePacket packet, CancellationToken cancellationToken = default) + public override Task HandleAsync(ClientNode recipient, GoodbyePacket packet, CancellationToken cancellationToken = default) { Console.WriteLine($"Server sent {packet.Message}"); return Task.CompletedTask; diff --git a/TcpDotNet.ClientIntegrationTest/Program.cs b/TcpDotNet.ClientIntegrationTest/Program.cs index ada9729..0ecd968 100644 --- a/TcpDotNet.ClientIntegrationTest/Program.cs +++ b/TcpDotNet.ClientIntegrationTest/Program.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using TcpDotNet; using TcpDotNet.ClientIntegrationTest; using TcpDotNet.ClientIntegrationTest.PacketHandlers; @@ -17,7 +17,7 @@ Console.WriteLine($"Connected to {client.RemoteEndPoint}. My session is {client. var ping = new PingPacket(); Console.WriteLine($"Sending ping packet with payload: {BitConverter.ToString(ping.Payload)}"); -var pong = await client.SendAndReceiveAsync(ping); +var pong = await client.SendAndReceiveAsync(ping); Console.WriteLine($"Received pong packet with payload: {BitConverter.ToString(pong.Payload)}"); Console.WriteLine(pong.Payload.SequenceEqual(ping.Payload) ? "Payload matches!" : "Payload does not match!"); diff --git a/TcpDotNet.ClientIntegrationTest/TcpDotNet.ClientIntegrationTest.csproj b/TcpDotNet.ClientIntegrationTest/TcpDotNet.ClientIntegrationTest.csproj index ed854e8..8532d3c 100644 --- a/TcpDotNet.ClientIntegrationTest/TcpDotNet.ClientIntegrationTest.csproj +++ b/TcpDotNet.ClientIntegrationTest/TcpDotNet.ClientIntegrationTest.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/TcpDotNet.ListenerIntegrationTest/GoodbyePacket.cs b/TcpDotNet.ListenerIntegrationTest/GoodbyePacket.cs index 02baa10..40e03c1 100644 --- a/TcpDotNet.ListenerIntegrationTest/GoodbyePacket.cs +++ b/TcpDotNet.ListenerIntegrationTest/GoodbyePacket.cs @@ -1,4 +1,4 @@ -using TcpDotNet.Protocol; +using TcpDotNet.Protocol; namespace TcpDotNet.ListenerIntegrationTest; diff --git a/TcpDotNet.ListenerIntegrationTest/HelloPacket.cs b/TcpDotNet.ListenerIntegrationTest/HelloPacket.cs index 80a958b..6adb687 100644 --- a/TcpDotNet.ListenerIntegrationTest/HelloPacket.cs +++ b/TcpDotNet.ListenerIntegrationTest/HelloPacket.cs @@ -1,4 +1,4 @@ -using TcpDotNet.Protocol; +using TcpDotNet.Protocol; namespace TcpDotNet.ListenerIntegrationTest; diff --git a/TcpDotNet.ListenerIntegrationTest/PacketHandlers/HelloPacketHandler.cs b/TcpDotNet.ListenerIntegrationTest/PacketHandlers/HelloPacketHandler.cs index f181fea..f138f55 100644 --- a/TcpDotNet.ListenerIntegrationTest/PacketHandlers/HelloPacketHandler.cs +++ b/TcpDotNet.ListenerIntegrationTest/PacketHandlers/HelloPacketHandler.cs @@ -1,10 +1,10 @@ -using TcpDotNet.Protocol; +using TcpDotNet.Protocol; namespace TcpDotNet.ListenerIntegrationTest.PacketHandlers; internal sealed class HelloPacketHandler : PacketHandler { - public override Task HandleAsync(BaseClientNode recipient, HelloPacket packet, CancellationToken cancellationToken = default) + public override Task HandleAsync(ClientNode recipient, HelloPacket packet, CancellationToken cancellationToken = default) { Console.WriteLine($"Client sent {packet.Message}"); return recipient.SendPacketAsync(new GoodbyePacket {Message = "Goodbye!"}, cancellationToken); diff --git a/TcpDotNet.ListenerIntegrationTest/Program.cs b/TcpDotNet.ListenerIntegrationTest/Program.cs index 8f88848..611cae2 100644 --- a/TcpDotNet.ListenerIntegrationTest/Program.cs +++ b/TcpDotNet.ListenerIntegrationTest/Program.cs @@ -1,4 +1,4 @@ -using TcpDotNet; +using TcpDotNet; using TcpDotNet.ListenerIntegrationTest.PacketHandlers; using TcpDotNet.Protocol; using TcpDotNet.Protocol.Packets.ClientBound; @@ -16,7 +16,7 @@ await Task.Delay(-1); internal sealed class PingPacketHandler : PacketHandler { - public override async Task HandleAsync(BaseClientNode recipient, PingPacket packet, CancellationToken cancellationToken = default) + public override async Task HandleAsync(ClientNode recipient, PingPacket packet, CancellationToken cancellationToken = default) { Console.WriteLine($"Client {recipient.SessionId} sent ping with payload {BitConverter.ToString(packet.Payload)}"); var pong = new PongPacket(packet.CallbackId, packet.Payload); diff --git a/TcpDotNet.ListenerIntegrationTest/TcpDotNet.ListenerIntegrationTest.csproj b/TcpDotNet.ListenerIntegrationTest/TcpDotNet.ListenerIntegrationTest.csproj index ed854e8..8532d3c 100644 --- a/TcpDotNet.ListenerIntegrationTest/TcpDotNet.ListenerIntegrationTest.csproj +++ b/TcpDotNet.ListenerIntegrationTest/TcpDotNet.ListenerIntegrationTest.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/TcpDotNet.sln b/TcpDotNet.sln index 1a518a6..dd1fca7 100644 --- a/TcpDotNet.sln +++ b/TcpDotNet.sln @@ -6,6 +6,23 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TcpDotNet.ListenerIntegrati EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TcpDotNet.ClientIntegrationTest", "TcpDotNet.ClientIntegrationTest\TcpDotNet.ClientIntegrationTest.csproj", "{ED9C812F-9835-4268-9AFC-57CFAAC16162}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D55FB87A-1066-489D-B0CC-F2C09B611751}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + LICENSE.md = LICENSE.md + README.md = README.md + branding_Banner.png = branding_Banner.png + branding_Icon.png = branding_Icon.png + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflows", "Workflows", "{0FAB77D5-AAE4-4722-A5F4-88A239858D65}" + ProjectSection(SolutionItems) = preProject + .github\workflows\dotnet.yml = .github\workflows\dotnet.yml + .github\workflows\nightly.yml = .github\workflows\nightly.yml + .github\workflows\prerelease.yml = .github\workflows\prerelease.yml + .github\workflows\release.yml = .github\workflows\release.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/TcpDotNet/Assembly.cs b/TcpDotNet/Assembly.cs index f547610..c0d560e 100644 --- a/TcpDotNet/Assembly.cs +++ b/TcpDotNet/Assembly.cs @@ -1 +1 @@ -[assembly: CLSCompliant(true)] +[assembly: CLSCompliant(true)] diff --git a/TcpDotNet/BaseClientNode.cs b/TcpDotNet/ClientNode.cs similarity index 79% rename from TcpDotNet/BaseClientNode.cs rename to TcpDotNet/ClientNode.cs index 9670bb0..bc8ebd9 100644 --- a/TcpDotNet/BaseClientNode.cs +++ b/TcpDotNet/ClientNode.cs @@ -1,8 +1,7 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; using System.Reflection; -using System.Runtime.Serialization; using Chilkat; using TcpDotNet.Protocol; using Stream = System.IO.Stream; @@ -13,16 +12,16 @@ namespace TcpDotNet; /// /// Represents a client node. /// -public abstract class BaseClientNode : Node +public abstract class ClientNode : Node { - private readonly ObjectIDGenerator _callbackIdGenerator = new(); private readonly ConcurrentDictionary>> _packetCompletionSources = new(); + private readonly ConcurrentDictionary> _callbackCompletionSources = new(); private EndPoint? _remoteEP; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - protected BaseClientNode() + protected ClientNode() { } @@ -148,7 +147,7 @@ public abstract class BaseClientNode : Node if (constructor is null) return null; - var packet = (Packet) constructor.Invoke(null); + var packet = (Packet)constructor.Invoke(null); packet.Deserialize(bufferReader); await targetStream.DisposeAsync(); @@ -198,7 +197,7 @@ public abstract class BaseClientNode : Node buffer.Position = 0; } - var length = (int) buffer.Length; + var length = (int)buffer.Length; networkWriter.Write(length); try @@ -219,53 +218,29 @@ public abstract class BaseClientNode : Node /// /// Sends a packet, and waits for a specific packet to be received. /// - /// The packet to send. - /// A cancellation token that can be used to cancel the asynchronous operation. - /// The type of the packet to send. + /// The packet to send. + /// + /// A cancellation token that can be used to cancel the asynchronous operation. + /// /// The type of the packet to return. /// The received packet. /// - /// This method will consume all incoming packets, raising their associated handlers if such packets are recognised. + /// This method will consume all incoming packets, raising their associated handlers if such packets are + /// recognised. /// - public async Task SendAndReceiveAsync(TSend packetToSend, + /// is . + public async Task SendAndReceiveAsync(RequestPacket packet, CancellationToken cancellationToken = default) - where TSend : Packet - where TReceive : Packet + where TReceive : ResponsePacket { - var attribute = typeof(TReceive).GetCustomAttribute(); - if (attribute is null) - throw new ArgumentException($"The packet type {typeof(TReceive).Name} is not a valid packet."); + if (packet is null) throw new ArgumentNullException(nameof(packet)); - var requestPacket = packetToSend as RequestPacket; - if (requestPacket is not null) - requestPacket.CallbackId = _callbackIdGenerator.GetId(packetToSend, out _); + var completionSource = new TaskCompletionSource(); + if (!_callbackCompletionSources.TryAdd(packet.CallbackId, completionSource)) + throw new InvalidOperationException("Duplicate packet sent"); - var completionSource = new TaskCompletionSource(); - if (!_packetCompletionSources.TryGetValue(attribute.Id, out List>? completionSources)) - { - completionSources = new List>(); - _packetCompletionSources.TryAdd(attribute.Id, completionSources); - } - - lock (completionSources) - { - if (!completionSources.Contains(completionSource)) - completionSources.Add(completionSource); - } - - await SendPacketAsync(packetToSend, cancellationToken); - TReceive response; - do - { - response = await WaitForPacketAsync(completionSource, cancellationToken); - if (requestPacket is null) - break; - - if (response is ResponsePacket responsePacket && responsePacket.CallbackId == requestPacket.CallbackId) - break; - } while (true); - - return response; + await SendPacketAsync(packet, cancellationToken); + return (TReceive)await completionSource.Task; } /// @@ -284,17 +259,16 @@ public abstract class BaseClientNode : Node return WaitForPacketAsync(completionSource, cancellationToken); } - private async Task WaitForPacketAsync( - TaskCompletionSource completionSource, - CancellationToken cancellationToken = default - ) + private async Task WaitForPacketAsync(TaskCompletionSource completionSource, + CancellationToken cancellationToken = default) where TPacket : Packet { var attribute = typeof(TPacket).GetCustomAttribute(); if (attribute is null) throw new ArgumentException($"The packet type {typeof(TPacket).Name} is not a valid packet."); - if (!_packetCompletionSources.TryGetValue(attribute.Id, out List>? completionSources)) + if (!_packetCompletionSources.TryGetValue(attribute.Id, + out List>? completionSources)) { completionSources = new List>(); _packetCompletionSources.TryAdd(attribute.Id, completionSources); @@ -328,7 +302,7 @@ public abstract class BaseClientNode : Node completionSource.SetCanceled(); }, cancellationToken); - var packet = (TPacket) await Task.Run(() => completionSource.Task, cancellationToken); + var packet = (TPacket)await Task.Run(() => completionSource.Task, cancellationToken); if (_packetCompletionSources.TryGetValue(attribute.Id, out completionSources)) { lock (completionSources) diff --git a/TcpDotNet/ClientState.cs b/TcpDotNet/ClientState.cs index 01532ba..fec2980 100644 --- a/TcpDotNet/ClientState.cs +++ b/TcpDotNet/ClientState.cs @@ -1,7 +1,7 @@ -namespace TcpDotNet; +namespace TcpDotNet; /// -/// An enumeration of states for a to be in. +/// An enumeration of states for a to be in. /// public enum ClientState { diff --git a/TcpDotNet/CryptographyUtils.cs b/TcpDotNet/CryptographyUtils.cs index 2907bb9..f1b287a 100644 --- a/TcpDotNet/CryptographyUtils.cs +++ b/TcpDotNet/CryptographyUtils.cs @@ -1,4 +1,4 @@ -using Chilkat; +using Chilkat; namespace TcpDotNet; diff --git a/TcpDotNet/Node.cs b/TcpDotNet/Node.cs index bdcf759..7af3a01 100644 --- a/TcpDotNet/Node.cs +++ b/TcpDotNet/Node.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Net.Sockets; using System.Reflection; @@ -38,7 +38,8 @@ public abstract class Node : IDisposable /// The registered packets. public IReadOnlyDictionary> RegisteredPacketHandlers => new ReadOnlyDictionary>( - _registeredPacketHandlers.ToDictionary(p => p.Key, p => (IReadOnlyCollection) p.Value.AsReadOnly())); + _registeredPacketHandlers.ToDictionary(p => p.Key, + p => (IReadOnlyCollection)p.Value.AsReadOnly())); /// /// Closes the base socket connection and releases all associated resources. @@ -124,7 +125,8 @@ public abstract class Node : IDisposable var attribute = packetType.GetCustomAttribute(); if (attribute is null) throw new ArgumentException($"{packetType.Name} is not a valid packet."); if (_registeredPackets.TryGetValue(attribute.Id, out Type? registeredPacket)) - throw new ArgumentException($"The packet type {attribute.Id:X8} is already registered to {registeredPacket.Name}."); + throw new ArgumentException( + $"The packet type {attribute.Id:X8} is already registered to {registeredPacket.Name}."); _registeredPackets.TryAdd(attribute.Id, packetType); } diff --git a/TcpDotNet/Protocol/HandshakeResponse.cs b/TcpDotNet/Protocol/HandshakeResponse.cs index a0a5a30..4534962 100644 --- a/TcpDotNet/Protocol/HandshakeResponse.cs +++ b/TcpDotNet/Protocol/HandshakeResponse.cs @@ -1,4 +1,4 @@ -namespace TcpDotNet.Protocol; +namespace TcpDotNet.Protocol; /// /// An enumeration of handshake responses. diff --git a/TcpDotNet/Protocol/Packet.cs b/TcpDotNet/Protocol/Packet.cs index e984dbc..c1c79ce 100644 --- a/TcpDotNet/Protocol/Packet.cs +++ b/TcpDotNet/Protocol/Packet.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; namespace TcpDotNet.Protocol; diff --git a/TcpDotNet/Protocol/PacketAttribute.cs b/TcpDotNet/Protocol/PacketAttribute.cs index 8bda278..a613d81 100644 --- a/TcpDotNet/Protocol/PacketAttribute.cs +++ b/TcpDotNet/Protocol/PacketAttribute.cs @@ -1,4 +1,4 @@ -namespace TcpDotNet.Protocol; +namespace TcpDotNet.Protocol; /// /// Specifies metadata for a . diff --git a/TcpDotNet/Protocol/PacketHandler.cs b/TcpDotNet/Protocol/PacketHandler.cs index c303e27..9431b8a 100644 --- a/TcpDotNet/Protocol/PacketHandler.cs +++ b/TcpDotNet/Protocol/PacketHandler.cs @@ -1,4 +1,4 @@ -namespace TcpDotNet.Protocol; +namespace TcpDotNet.Protocol; /// /// Represents the base class for a packet handler. @@ -12,7 +12,7 @@ public abstract class PacketHandler /// The recipient of the packet. /// The packet to handle. /// A cancellation token that can be used to cancel the asynchronous operation. - public abstract Task HandleAsync(BaseClientNode recipient, Packet packet, CancellationToken cancellationToken = default); + public abstract Task HandleAsync(ClientNode recipient, Packet packet, CancellationToken cancellationToken = default); } /// @@ -27,7 +27,7 @@ public abstract class PacketHandler : PacketHandler public static readonly PacketHandler Empty = new NullPacketHandler(); /// - public override Task HandleAsync(BaseClientNode recipient, Packet packet, CancellationToken cancellationToken = default) + public override Task HandleAsync(ClientNode recipient, Packet packet, CancellationToken cancellationToken = default) { if (packet is T actual) return HandleAsync(recipient, actual, cancellationToken); return Task.CompletedTask; @@ -39,7 +39,7 @@ public abstract class PacketHandler : PacketHandler /// The recipient of the packet. /// The packet to handle. /// A cancellation token that can be used to cancel the asynchronous operation. - public abstract Task HandleAsync(BaseClientNode recipient, T packet, CancellationToken cancellationToken = default); + public abstract Task HandleAsync(ClientNode recipient, T packet, CancellationToken cancellationToken = default); } /// @@ -50,7 +50,7 @@ internal sealed class NullPacketHandler : PacketHandler where T : Packet { /// - public override Task HandleAsync(BaseClientNode recipient, T packet, CancellationToken cancellationToken = default) + public override Task HandleAsync(ClientNode recipient, T packet, CancellationToken cancellationToken = default) { return Task.CompletedTask; } diff --git a/TcpDotNet/Protocol/PacketHandlers/DisconnectPacketHandler.cs b/TcpDotNet/Protocol/PacketHandlers/DisconnectPacketHandler.cs index 0107ba9..b36f755 100644 --- a/TcpDotNet/Protocol/PacketHandlers/DisconnectPacketHandler.cs +++ b/TcpDotNet/Protocol/PacketHandlers/DisconnectPacketHandler.cs @@ -1,4 +1,4 @@ -using TcpDotNet.Protocol.Packets.ClientBound; +using TcpDotNet.Protocol.Packets.ClientBound; namespace TcpDotNet.Protocol.PacketHandlers; @@ -6,7 +6,7 @@ internal sealed class DisconnectPacketHandler : PacketHandler { /// public override Task HandleAsync( - BaseClientNode recipient, + ClientNode recipient, DisconnectPacket packet, CancellationToken cancellationToken = default ) diff --git a/TcpDotNet/Protocol/PacketHandlers/EncryptionResponsePacketHandler.cs b/TcpDotNet/Protocol/PacketHandlers/EncryptionResponsePacketHandler.cs index c79914a..16dc115 100644 --- a/TcpDotNet/Protocol/PacketHandlers/EncryptionResponsePacketHandler.cs +++ b/TcpDotNet/Protocol/PacketHandlers/EncryptionResponsePacketHandler.cs @@ -1,4 +1,4 @@ -using System.Security.Cryptography; +using System.Security.Cryptography; using TcpDotNet.Protocol.Packets.ClientBound; using TcpDotNet.Protocol.Packets.ServerBound; @@ -11,7 +11,7 @@ internal sealed class EncryptionResponsePacketHandler : PacketHandler public override async Task HandleAsync( - BaseClientNode recipient, + ClientNode recipient, EncryptionResponsePacket packet, CancellationToken cancellationToken = default ) diff --git a/TcpDotNet/Protocol/PacketHandlers/HandshakeRequestPacketHandler.cs b/TcpDotNet/Protocol/PacketHandlers/HandshakeRequestPacketHandler.cs index 2df88d0..217f411 100644 --- a/TcpDotNet/Protocol/PacketHandlers/HandshakeRequestPacketHandler.cs +++ b/TcpDotNet/Protocol/PacketHandlers/HandshakeRequestPacketHandler.cs @@ -1,4 +1,4 @@ -using TcpDotNet.Protocol.Packets.ClientBound; +using TcpDotNet.Protocol.Packets.ClientBound; using TcpDotNet.Protocol.Packets.ServerBound; namespace TcpDotNet.Protocol.PacketHandlers; @@ -10,7 +10,7 @@ internal sealed class HandshakeRequestPacketHandler : PacketHandler public override async Task HandleAsync( - BaseClientNode recipient, + ClientNode recipient, HandshakeRequestPacket packet, CancellationToken cancellationToken = default ) @@ -22,13 +22,14 @@ internal sealed class HandshakeRequestPacketHandler : PacketHandler protected internal override void Deserialize(ProtocolReader reader) { - Reason = (DisconnectReason) reader.ReadByte(); + Reason = (DisconnectReason)reader.ReadByte(); } /// protected internal override void Serialize(ProtocolWriter writer) { - writer.Write((byte) Reason); + writer.Write((byte)Reason); } } diff --git a/TcpDotNet/Protocol/Packets/ClientBound/EncryptionRequestPacket.cs b/TcpDotNet/Protocol/Packets/ClientBound/EncryptionRequestPacket.cs index c3960f5..cbbef56 100644 --- a/TcpDotNet/Protocol/Packets/ClientBound/EncryptionRequestPacket.cs +++ b/TcpDotNet/Protocol/Packets/ClientBound/EncryptionRequestPacket.cs @@ -1,4 +1,4 @@ -using System.Security.Cryptography; +using System.Security.Cryptography; namespace TcpDotNet.Protocol.Packets.ClientBound; diff --git a/TcpDotNet/Protocol/Packets/ClientBound/HandshakeResponsePacket.cs b/TcpDotNet/Protocol/Packets/ClientBound/HandshakeResponsePacket.cs index 0cc020e..b0eb8e2 100644 --- a/TcpDotNet/Protocol/Packets/ClientBound/HandshakeResponsePacket.cs +++ b/TcpDotNet/Protocol/Packets/ClientBound/HandshakeResponsePacket.cs @@ -1,4 +1,4 @@ -using TcpDotNet.Protocol.Packets.ServerBound; +using TcpDotNet.Protocol.Packets.ServerBound; namespace TcpDotNet.Protocol.Packets.ClientBound; @@ -6,14 +6,16 @@ namespace TcpDotNet.Protocol.Packets.ClientBound; /// Represents a packet which responds to a . /// [Packet(0x7FFFFFE1)] -internal sealed class HandshakeResponsePacket : Packet +internal sealed class HandshakeResponsePacket : ResponsePacket { /// /// Initializes a new instance of the class. /// + /// The callback ID. /// The requested protocol version. /// The handshake response. - public HandshakeResponsePacket(int protocolVersion, HandshakeResponse handshakeResponse) + public HandshakeResponsePacket(long callbackId, int protocolVersion, HandshakeResponse handshakeResponse) + : base(callbackId) { ProtocolVersion = protocolVersion; HandshakeResponse = handshakeResponse; @@ -38,14 +40,14 @@ internal sealed class HandshakeResponsePacket : Packet /// protected internal override void Deserialize(ProtocolReader reader) { - HandshakeResponse = (HandshakeResponse) reader.ReadByte(); + HandshakeResponse = (HandshakeResponse)reader.ReadByte(); ProtocolVersion = reader.ReadInt32(); } /// protected internal override void Serialize(ProtocolWriter writer) { - writer.Write((byte) HandshakeResponse); + writer.Write((byte)HandshakeResponse); writer.Write(ProtocolVersion); } } diff --git a/TcpDotNet/Protocol/Packets/ClientBound/PongPacket.cs b/TcpDotNet/Protocol/Packets/ClientBound/PongPacket.cs index 7952016..7bc7a60 100644 --- a/TcpDotNet/Protocol/Packets/ClientBound/PongPacket.cs +++ b/TcpDotNet/Protocol/Packets/ClientBound/PongPacket.cs @@ -1,4 +1,4 @@ -namespace TcpDotNet.Protocol.Packets.ClientBound; +namespace TcpDotNet.Protocol.Packets.ClientBound; /// /// Represents a packet which performs a heartbeat response. diff --git a/TcpDotNet/Protocol/Packets/ClientBound/SessionExchangePacket.cs b/TcpDotNet/Protocol/Packets/ClientBound/SessionExchangePacket.cs index e7679ac..3301b50 100644 --- a/TcpDotNet/Protocol/Packets/ClientBound/SessionExchangePacket.cs +++ b/TcpDotNet/Protocol/Packets/ClientBound/SessionExchangePacket.cs @@ -1,4 +1,4 @@ -namespace TcpDotNet.Protocol.Packets.ClientBound; +namespace TcpDotNet.Protocol.Packets.ClientBound; [Packet(0x7FFFFFE4)] internal sealed class SessionExchangePacket : Packet diff --git a/TcpDotNet/Protocol/Packets/ServerBound/EncryptionResponsePacket.cs b/TcpDotNet/Protocol/Packets/ServerBound/EncryptionResponsePacket.cs index 935cca6..4107174 100644 --- a/TcpDotNet/Protocol/Packets/ServerBound/EncryptionResponsePacket.cs +++ b/TcpDotNet/Protocol/Packets/ServerBound/EncryptionResponsePacket.cs @@ -1,4 +1,4 @@ -using TcpDotNet.Protocol.Packets.ClientBound; +using TcpDotNet.Protocol.Packets.ClientBound; namespace TcpDotNet.Protocol.Packets.ServerBound; diff --git a/TcpDotNet/Protocol/Packets/ServerBound/HandshakeRequestPacket.cs b/TcpDotNet/Protocol/Packets/ServerBound/HandshakeRequestPacket.cs index 82e4ef3..afe4102 100644 --- a/TcpDotNet/Protocol/Packets/ServerBound/HandshakeRequestPacket.cs +++ b/TcpDotNet/Protocol/Packets/ServerBound/HandshakeRequestPacket.cs @@ -1,10 +1,10 @@ -namespace TcpDotNet.Protocol.Packets.ServerBound; +namespace TcpDotNet.Protocol.Packets.ServerBound; /// /// Represents a packet which requests a handshake with a . /// [Packet(0x7FFFFFE0)] -internal sealed class HandshakeRequestPacket : Packet +internal sealed class HandshakeRequestPacket : RequestPacket { /// /// Initializes a new instance of the class. diff --git a/TcpDotNet/Protocol/Packets/ServerBound/PingPacket.cs b/TcpDotNet/Protocol/Packets/ServerBound/PingPacket.cs index 06eb33c..1ce5e8e 100644 --- a/TcpDotNet/Protocol/Packets/ServerBound/PingPacket.cs +++ b/TcpDotNet/Protocol/Packets/ServerBound/PingPacket.cs @@ -1,4 +1,4 @@ -using System.Security.Cryptography; +using System.Security.Cryptography; namespace TcpDotNet.Protocol.Packets.ServerBound; diff --git a/TcpDotNet/Protocol/ProtocolReader.cs b/TcpDotNet/Protocol/ProtocolReader.cs index 810613d..e9b5e53 100644 --- a/TcpDotNet/Protocol/ProtocolReader.cs +++ b/TcpDotNet/Protocol/ProtocolReader.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using System.Numerics; using System.Runtime.InteropServices; using System.Text; @@ -66,7 +66,7 @@ public sealed class ProtocolReader : BinaryReader result |= (byteReadJustNow & 0x7Fu) << shift; if (byteReadJustNow <= 0x7Fu) - return (int) result; // early exit + return (int)result; // early exit } // Read the 5th byte. Since we already read 28 bits, @@ -77,8 +77,8 @@ public sealed class ProtocolReader : BinaryReader if (byteReadJustNow > 0b_1111u) throw new FormatException(); - result |= (uint) byteReadJustNow << (maxBytesWithoutOverflow * 7); - return (int) result; + result |= (uint)byteReadJustNow << (maxBytesWithoutOverflow * 7); + return (int)result; } /// @@ -107,7 +107,7 @@ public sealed class ProtocolReader : BinaryReader result |= (byteReadJustNow & 0x7Ful) << shift; if (byteReadJustNow <= 0x7Fu) - return (long) result; // early exit + return (long)result; // early exit } // Read the 10th byte. Since we already read 63 bits, @@ -118,8 +118,8 @@ public sealed class ProtocolReader : BinaryReader if (byteReadJustNow > 0b_1u) throw new FormatException(); - result |= (ulong) byteReadJustNow << (maxBytesWithoutOverflow * 7); - return (long) result; + result |= (ulong)byteReadJustNow << (maxBytesWithoutOverflow * 7); + return (long)result; } /// @@ -196,21 +196,21 @@ public sealed class ProtocolReader : BinaryReader [CLSCompliant(false)] public override ushort ReadUInt16() { - return (ushort) IPAddress.NetworkToHostOrder((short) base.ReadUInt16()); + return (ushort)IPAddress.NetworkToHostOrder((short)base.ReadUInt16()); } /// [CLSCompliant(false)] public override uint ReadUInt32() { - return (uint) IPAddress.NetworkToHostOrder((int) base.ReadUInt32()); + return (uint)IPAddress.NetworkToHostOrder((int)base.ReadUInt32()); } /// [CLSCompliant(false)] public override ulong ReadUInt64() { - return (ulong) IPAddress.NetworkToHostOrder((long) base.ReadUInt64()); + return (ulong)IPAddress.NetworkToHostOrder((long)base.ReadUInt64()); } /// diff --git a/TcpDotNet/Protocol/ProtocolWriter.cs b/TcpDotNet/Protocol/ProtocolWriter.cs index 76280fe..920b867 100644 --- a/TcpDotNet/Protocol/ProtocolWriter.cs +++ b/TcpDotNet/Protocol/ProtocolWriter.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using System.Numerics; using System.Runtime.InteropServices; using System.Text; @@ -38,7 +38,11 @@ public sealed class ProtocolWriter : BinaryWriter public override void Write(double value) { Span buffer = stackalloc byte[8]; +#if NET8_0_OR_GREATER + MemoryMarshal.TryWrite(buffer, in value); +#else MemoryMarshal.TryWrite(buffer, ref value); +#endif if (BitConverter.IsLittleEndian) buffer.Reverse(); Write(buffer); @@ -89,7 +93,11 @@ public sealed class ProtocolWriter : BinaryWriter public override void Write(float value) { Span buffer = stackalloc byte[4]; +#if NET8_0_OR_GREATER + MemoryMarshal.TryWrite(buffer, in value); +#else MemoryMarshal.TryWrite(buffer, ref value); +#endif if (BitConverter.IsLittleEndian) buffer.Reverse(); Write(buffer); @@ -99,21 +107,21 @@ public sealed class ProtocolWriter : BinaryWriter [CLSCompliant(false)] public override void Write(ushort value) { - base.Write((ushort) IPAddress.HostToNetworkOrder((short) value)); + base.Write((ushort)IPAddress.HostToNetworkOrder((short)value)); } /// [CLSCompliant(false)] public override void Write(uint value) { - base.Write((uint) IPAddress.HostToNetworkOrder((int) value)); + base.Write((uint)IPAddress.HostToNetworkOrder((int)value)); } /// [CLSCompliant(false)] public override void Write(ulong value) { - base.Write((ulong) IPAddress.HostToNetworkOrder((long) value)); + base.Write((ulong)IPAddress.HostToNetworkOrder((long)value)); } /// @@ -155,7 +163,7 @@ public sealed class ProtocolWriter : BinaryWriter /// The 32-bit integer to be written. public void Write7BitEncodedInt32(int value) { - var uValue = (uint) value; + var uValue = (uint)value; // Write out an int 7 bits at a time. The high bit of the byte, // when on, tells reader to continue reading more bytes. @@ -165,11 +173,11 @@ public sealed class ProtocolWriter : BinaryWriter while (uValue > 0x7Fu) { - Write((byte) (uValue | ~0x7Fu)); + Write((byte)(uValue | ~0x7Fu)); uValue >>= 7; } - Write((byte) uValue); + Write((byte)uValue); } /// @@ -178,7 +186,7 @@ public sealed class ProtocolWriter : BinaryWriter /// The 64-bit integer to be written. public void Write7BitEncodedInt64(long value) { - var uValue = (ulong) value; + var uValue = (ulong)value; // Write out an int 7 bits at a time. The high bit of the byte, // when on, tells reader to continue reading more bytes. @@ -188,10 +196,10 @@ public sealed class ProtocolWriter : BinaryWriter while (uValue > 0x7Fu) { - Write((byte) ((uint) uValue | ~0x7Fu)); + Write((byte)((uint)uValue | ~0x7Fu)); uValue >>= 7; } - Write((byte) uValue); + Write((byte)uValue); } } diff --git a/TcpDotNet/Protocol/RequestPacket.cs b/TcpDotNet/Protocol/RequestPacket.cs index 42f3bcf..21037d9 100644 --- a/TcpDotNet/Protocol/RequestPacket.cs +++ b/TcpDotNet/Protocol/RequestPacket.cs @@ -1,15 +1,28 @@ -namespace TcpDotNet.Protocol; +using System.Buffers.Binary; +using System.Security.Cryptography; + +namespace TcpDotNet.Protocol; /// /// Represents a request packet, which forms a request/response packet pair. /// public abstract class RequestPacket : Packet { + /// + /// Initializes a new instance of the class. + /// + protected RequestPacket() + { + Span buffer = stackalloc byte[8]; + RandomNumberGenerator.Fill(buffer); + CallbackId = BinaryPrimitives.ReadInt64BigEndian(buffer); + } + /// /// Gets the request identifier. /// /// The request identifier. - public long CallbackId { get; internal set; } + public long CallbackId { get; private set; } /// protected internal override void Deserialize(ProtocolReader reader) diff --git a/TcpDotNet/Protocol/ResponsePacket.cs b/TcpDotNet/Protocol/ResponsePacket.cs index 7aade8b..f162c92 100644 --- a/TcpDotNet/Protocol/ResponsePacket.cs +++ b/TcpDotNet/Protocol/ResponsePacket.cs @@ -1,4 +1,4 @@ -namespace TcpDotNet.Protocol; +namespace TcpDotNet.Protocol; /// /// Represents a response packet, which forms a request/response packet pair. @@ -14,6 +14,10 @@ public abstract class ResponsePacket : Packet CallbackId = callbackId; } + internal ResponsePacket() + { + } + /// /// Gets the response identifier. /// diff --git a/TcpDotNet/ProtocolClient.cs b/TcpDotNet/ProtocolClient.cs index 1294eb8..de0bb89 100644 --- a/TcpDotNet/ProtocolClient.cs +++ b/TcpDotNet/ProtocolClient.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using System.Net.Sockets; using System.Security.Cryptography; using TcpDotNet.EventData; @@ -14,7 +14,7 @@ namespace TcpDotNet; /// /// Represents a client on the TcpDotNet protocol. /// -public sealed class ProtocolClient : BaseClientNode +public sealed class ProtocolClient : ClientNode { /// /// Initializes a new instance of the class. @@ -82,7 +82,7 @@ public sealed class ProtocolClient : BaseClientNode BaseSocket = new Socket(remoteEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); try { - await Task.Run(() => BaseSocket.ConnectAsync(remoteEP), cancellationToken); + await BaseSocket.ConnectAsync(remoteEP, cancellationToken); } catch { @@ -95,15 +95,15 @@ public sealed class ProtocolClient : BaseClientNode State = ClientState.Handshaking; var handshakeRequest = new HandshakeRequestPacket(ProtocolVersion); - var handshakeResponse = - await SendAndReceiveAsync(handshakeRequest, cancellationToken); + var handshakeResponse = await SendAndReceiveAsync(handshakeRequest, cancellationToken); if (handshakeResponse.HandshakeResponse != HandshakeResponse.Success) { Close(); IsConnected = false; - throw new InvalidOperationException("Handshake failed. " + - $"Server responded with {handshakeResponse.HandshakeResponse:D}"); + + var message = $"Handshake failed. Server responded with {handshakeResponse.HandshakeResponse:D}"; + throw new InvalidOperationException(message); } State = ClientState.Encrypting; @@ -113,8 +113,7 @@ public sealed class ProtocolClient : BaseClientNode byte[] encryptedPayload = rsa.Encrypt(encryptionRequest.Payload, true); var key = new byte[128]; - using var rng = new RNGCryptoServiceProvider(); - rng.GetBytes(key); + RandomNumberGenerator.Fill(key); Aes = CryptographyUtils.GenerateAes(key); byte[] aesKey = rsa.Encrypt(key, true); diff --git a/TcpDotNet/ProtocolListener.Client.cs b/TcpDotNet/ProtocolListener.Client.cs index 2755a31..2364d1b 100644 --- a/TcpDotNet/ProtocolListener.Client.cs +++ b/TcpDotNet/ProtocolListener.Client.cs @@ -8,7 +8,7 @@ public sealed partial class ProtocolListener /// /// Represents a client that is connected to a . /// - public sealed class Client : BaseClientNode + public sealed class Client : ClientNode { internal Client(ProtocolListener listener, Socket socket) { @@ -35,7 +35,8 @@ public sealed partial class ProtocolListener foreach (Type packetType in ParentListener.RegisteredPackets.Values) RegisterPacket(packetType); - foreach ((Type packetType, IReadOnlyCollection? handlers) in ParentListener.RegisteredPacketHandlers) + foreach ((Type packetType, IReadOnlyCollection? handlers) in ParentListener + .RegisteredPacketHandlers) foreach (PacketHandler handler in handlers) RegisterPacketHandler(packetType, handler); diff --git a/TcpDotNet/ProtocolListener.cs b/TcpDotNet/ProtocolListener.cs index 335d313..4787449 100644 --- a/TcpDotNet/ProtocolListener.cs +++ b/TcpDotNet/ProtocolListener.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using System.Net.Sockets; using System.Security.Cryptography; using TcpDotNet.EventData; diff --git a/TcpDotNet/TcpDotNet.csproj b/TcpDotNet/TcpDotNet.csproj index 3e8b637..515cbd5 100644 --- a/TcpDotNet/TcpDotNet.csproj +++ b/TcpDotNet/TcpDotNet.csproj @@ -1,7 +1,7 @@ - netstandard2.1;net6.0;net7.0 + net6.0;net7.0;net8.0 enable enable 10 @@ -12,6 +12,7 @@ git A TCP library for .NET with support for AES encryption. LICENSE.md + branding_Icon.png dotnet networking encryption tcp 0.1.0 true @@ -40,10 +41,18 @@ + + True + + True + + True + + diff --git a/branding_Banner.png b/branding_Banner.png new file mode 100644 index 0000000..50ae366 Binary files /dev/null and b/branding_Banner.png differ diff --git a/branding_Icon.png b/branding_Icon.png new file mode 100644 index 0000000..781d411 Binary files /dev/null and b/branding_Icon.png differ diff --git a/global.json b/global.json new file mode 100644 index 0000000..b5b37b6 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.0", + "rollForward": "latestMajor", + "allowPrerelease": false + } +} \ No newline at end of file