1
0
mirror of https://github.com/oliverbooth/TcpDotNet synced 2024-11-10 04:55:41 +00:00
This commit is contained in:
Oliver Booth 2024-02-12 22:57:27 +00:00 committed by GitHub
commit 0db6fd2976
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 204 additions and 161 deletions

1
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
ko_fi: oliverbooth
custom: ['https://buymeacoffee.com/oliverbooth'] custom: ['https://buymeacoffee.com/oliverbooth']

View File

@ -2,9 +2,13 @@ name: .NET
on: on:
push: push:
branches: [ main, develop ] branches:
- '*'
- '*/*'
pull_request: pull_request:
branches: [ main, develop ] branches:
- '*'
- '*/*'
jobs: jobs:
build: build:
@ -16,9 +20,9 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v2 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Add NuGet source - 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" 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"

View File

@ -3,7 +3,8 @@ name: Publish Nightly
on: on:
push: push:
branches: branches:
- develop - main
workflow_dispatch:
jobs: jobs:
nightly: nightly:
@ -15,9 +16,9 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v2 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Add GitHub NuGet source - 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" 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"

View File

@ -15,9 +15,9 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v2 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Add GitHub NuGet source - 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" 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"

View File

@ -15,9 +15,9 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v2 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Add GitHub NuGet source - 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" 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"

View File

@ -1,15 +1,15 @@
<h1 align="center">TcpDotNet</h1> <h1 align="center"><img src="branding_Banner.png" alt="TCP.NET"></h1>
<p align="center"> <p align="center">
<a href="https://github.com/oliverbooth/TcpDotNet/actions/workflows/dotnet.yml"><img src="https://img.shields.io/github/actions/workflow/status/oliverbooth/TcpDotNet/dotnet.yml?branch=main" alt="GitHub Workflow Status" title="GitHub Workflow Status"></a> <a href="https://github.com/oliverbooth/TcpDotNet/actions/workflows/dotnet.yml"><img src="https://img.shields.io/github/actions/workflow/status/oliverbooth/TcpDotNet/dotnet.yml?branch=main&style=flat-square" alt="GitHub Workflow Status" title="GitHub Workflow Status"></a>
<a href="https://github.com/oliverbooth/TcpDotNet/issues"><img src="https://img.shields.io/github/issues/oliverbooth/TcpDotNet" alt="GitHub Issues" title="GitHub Issues"></a> <a href="https://github.com/oliverbooth/TcpDotNet/issues"><img src="https://img.shields.io/github/issues/oliverbooth/TcpDotNet?style=flat-square" alt="GitHub Issues" title="GitHub Issues"></a>
<a href="https://www.nuget.org/packages/TcpDotNet/"><img src="https://img.shields.io/nuget/dt/TcpDotNet" alt="NuGet Downloads" title="NuGet Downloads"></a> <a href="https://www.nuget.org/packages/TcpDotNet/"><img src="https://img.shields.io/nuget/dt/TcpDotNet?style=flat-square" alt="NuGet Downloads" title="NuGet Downloads"></a>
<a href="https://www.nuget.org/packages/TcpDotNet/"><img src="https://img.shields.io/nuget/v/TcpDotNet?label=stable" alt="Stable Version" title="Stable Version"></a> <a href="https://www.nuget.org/packages/TcpDotNet/"><img src="https://img.shields.io/nuget/v/TcpDotNet?label=stable&style=flat-square" alt="Stable Version" title="Stable Version"></a>
<a href="https://www.nuget.org/packages/TcpDotNet/"><img src="https://img.shields.io/nuget/vpre/TcpDotNet?label=nightly" alt="Nightly Version" title="Nightly Version"></a> <a href="https://www.nuget.org/packages/TcpDotNet/"><img src="https://img.shields.io/nuget/vpre/TcpDotNet?label=nightly&style=flat-square" alt="Nightly Version" title="Nightly Version"></a>
<a href="https://github.com/oliverbooth/TcpDotNet/blob/master/LICENSE.md"><img src="https://img.shields.io/github/license/oliverbooth/TcpDotNet" alt="MIT License" title="MIT License"></a> <a href="https://github.com/oliverbooth/TcpDotNet/blob/master/LICENSE.md"><img src="https://img.shields.io/github/license/oliverbooth/TcpDotNet?style=flat-square" alt="MIT License" title="MIT License"></a>
</p> </p>
### About ### 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. 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.)* *(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 ## Installation
### NuGet installation ### NuGet installation
```ps ```ps
Install-Package TcpDotNet -Version 0.1.0-nightly.1 Install-Package TcpDotNet -Version 1.0.0
``` ```
### Manual installation ### Manual installation

View File

@ -1,4 +1,4 @@
using TcpDotNet.Protocol; using TcpDotNet.Protocol;
namespace TcpDotNet.ClientIntegrationTest; namespace TcpDotNet.ClientIntegrationTest;

View File

@ -1,4 +1,4 @@
using TcpDotNet.Protocol; using TcpDotNet.Protocol;
namespace TcpDotNet.ClientIntegrationTest; namespace TcpDotNet.ClientIntegrationTest;

View File

@ -1,10 +1,10 @@
using TcpDotNet.Protocol; using TcpDotNet.Protocol;
namespace TcpDotNet.ClientIntegrationTest.PacketHandlers; namespace TcpDotNet.ClientIntegrationTest.PacketHandlers;
internal sealed class GoodbyePacketHandler : PacketHandler<GoodbyePacket> internal sealed class GoodbyePacketHandler : PacketHandler<GoodbyePacket>
{ {
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}"); Console.WriteLine($"Server sent {packet.Message}");
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -1,4 +1,4 @@
using System.Net; using System.Net;
using TcpDotNet; using TcpDotNet;
using TcpDotNet.ClientIntegrationTest; using TcpDotNet.ClientIntegrationTest;
using TcpDotNet.ClientIntegrationTest.PacketHandlers; using TcpDotNet.ClientIntegrationTest.PacketHandlers;
@ -17,7 +17,7 @@ Console.WriteLine($"Connected to {client.RemoteEndPoint}. My session is {client.
var ping = new PingPacket(); var ping = new PingPacket();
Console.WriteLine($"Sending ping packet with payload: {BitConverter.ToString(ping.Payload)}"); Console.WriteLine($"Sending ping packet with payload: {BitConverter.ToString(ping.Payload)}");
var pong = await client.SendAndReceiveAsync<PingPacket, PongPacket>(ping); var pong = await client.SendAndReceiveAsync<PongPacket>(ping);
Console.WriteLine($"Received pong packet with payload: {BitConverter.ToString(pong.Payload)}"); Console.WriteLine($"Received pong packet with payload: {BitConverter.ToString(pong.Payload)}");
Console.WriteLine(pong.Payload.SequenceEqual(ping.Payload) ? "Payload matches!" : "Payload does not match!"); Console.WriteLine(pong.Payload.SequenceEqual(ping.Payload) ? "Payload matches!" : "Payload does not match!");

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

View File

@ -1,4 +1,4 @@
using TcpDotNet.Protocol; using TcpDotNet.Protocol;
namespace TcpDotNet.ListenerIntegrationTest; namespace TcpDotNet.ListenerIntegrationTest;

View File

@ -1,4 +1,4 @@
using TcpDotNet.Protocol; using TcpDotNet.Protocol;
namespace TcpDotNet.ListenerIntegrationTest; namespace TcpDotNet.ListenerIntegrationTest;

View File

@ -1,10 +1,10 @@
using TcpDotNet.Protocol; using TcpDotNet.Protocol;
namespace TcpDotNet.ListenerIntegrationTest.PacketHandlers; namespace TcpDotNet.ListenerIntegrationTest.PacketHandlers;
internal sealed class HelloPacketHandler : PacketHandler<HelloPacket> internal sealed class HelloPacketHandler : PacketHandler<HelloPacket>
{ {
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}"); Console.WriteLine($"Client sent {packet.Message}");
return recipient.SendPacketAsync(new GoodbyePacket {Message = "Goodbye!"}, cancellationToken); return recipient.SendPacketAsync(new GoodbyePacket {Message = "Goodbye!"}, cancellationToken);

View File

@ -1,4 +1,4 @@
using TcpDotNet; using TcpDotNet;
using TcpDotNet.ListenerIntegrationTest.PacketHandlers; using TcpDotNet.ListenerIntegrationTest.PacketHandlers;
using TcpDotNet.Protocol; using TcpDotNet.Protocol;
using TcpDotNet.Protocol.Packets.ClientBound; using TcpDotNet.Protocol.Packets.ClientBound;
@ -16,7 +16,7 @@ await Task.Delay(-1);
internal sealed class PingPacketHandler : PacketHandler<PingPacket> internal sealed class PingPacketHandler : PacketHandler<PingPacket>
{ {
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)}"); Console.WriteLine($"Client {recipient.SessionId} sent ping with payload {BitConverter.ToString(packet.Payload)}");
var pong = new PongPacket(packet.CallbackId, packet.Payload); var pong = new PongPacket(packet.CallbackId, packet.Payload);

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

View File

@ -6,6 +6,23 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TcpDotNet.ListenerIntegrati
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TcpDotNet.ClientIntegrationTest", "TcpDotNet.ClientIntegrationTest\TcpDotNet.ClientIntegrationTest.csproj", "{ED9C812F-9835-4268-9AFC-57CFAAC16162}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TcpDotNet.ClientIntegrationTest", "TcpDotNet.ClientIntegrationTest\TcpDotNet.ClientIntegrationTest.csproj", "{ED9C812F-9835-4268-9AFC-57CFAAC16162}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU

View File

@ -1 +1 @@
[assembly: CLSCompliant(true)] [assembly: CLSCompliant(true)]

View File

@ -1,8 +1,7 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Reflection; using System.Reflection;
using System.Runtime.Serialization;
using Chilkat; using Chilkat;
using TcpDotNet.Protocol; using TcpDotNet.Protocol;
using Stream = System.IO.Stream; using Stream = System.IO.Stream;
@ -13,16 +12,16 @@ namespace TcpDotNet;
/// <summary> /// <summary>
/// Represents a client node. /// Represents a client node.
/// </summary> /// </summary>
public abstract class BaseClientNode : Node public abstract class ClientNode : Node
{ {
private readonly ObjectIDGenerator _callbackIdGenerator = new();
private readonly ConcurrentDictionary<int, List<TaskCompletionSource<Packet>>> _packetCompletionSources = new(); private readonly ConcurrentDictionary<int, List<TaskCompletionSource<Packet>>> _packetCompletionSources = new();
private readonly ConcurrentDictionary<long, TaskCompletionSource<ResponsePacket>> _callbackCompletionSources = new();
private EndPoint? _remoteEP; private EndPoint? _remoteEP;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseClientNode" /> class. /// Initializes a new instance of the <see cref="ClientNode" /> class.
/// </summary> /// </summary>
protected BaseClientNode() protected ClientNode()
{ {
} }
@ -148,7 +147,7 @@ public abstract class BaseClientNode : Node
if (constructor is null) if (constructor is null)
return null; return null;
var packet = (Packet) constructor.Invoke(null); var packet = (Packet)constructor.Invoke(null);
packet.Deserialize(bufferReader); packet.Deserialize(bufferReader);
await targetStream.DisposeAsync(); await targetStream.DisposeAsync();
@ -198,7 +197,7 @@ public abstract class BaseClientNode : Node
buffer.Position = 0; buffer.Position = 0;
} }
var length = (int) buffer.Length; var length = (int)buffer.Length;
networkWriter.Write(length); networkWriter.Write(length);
try try
@ -219,53 +218,29 @@ public abstract class BaseClientNode : Node
/// <summary> /// <summary>
/// Sends a packet, and waits for a specific packet to be received. /// Sends a packet, and waits for a specific packet to be received.
/// </summary> /// </summary>
/// <param name="packetToSend">The packet to send.</param> /// <param name="packet">The packet to send.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param> /// <param name="cancellationToken">
/// <typeparam name="TSend">The type of the packet to send.</typeparam> /// A cancellation token that can be used to cancel the asynchronous operation.
/// </param>
/// <typeparam name="TReceive">The type of the packet to return.</typeparam> /// <typeparam name="TReceive">The type of the packet to return.</typeparam>
/// <returns>The received packet.</returns> /// <returns>The received packet.</returns>
/// <remarks> /// <remarks>
/// 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.
/// </remarks> /// </remarks>
public async Task<TReceive> SendAndReceiveAsync<TSend, TReceive>(TSend packetToSend, /// <exception cref="ArgumentNullException"><paramref name="packet" /> is <see langword="null" />.</exception>
public async Task<TReceive> SendAndReceiveAsync<TReceive>(RequestPacket packet,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
where TSend : Packet where TReceive : ResponsePacket
where TReceive : Packet
{ {
var attribute = typeof(TReceive).GetCustomAttribute<PacketAttribute>(); if (packet is null) throw new ArgumentNullException(nameof(packet));
if (attribute is null)
throw new ArgumentException($"The packet type {typeof(TReceive).Name} is not a valid packet.");
var requestPacket = packetToSend as RequestPacket; var completionSource = new TaskCompletionSource<ResponsePacket>();
if (requestPacket is not null) if (!_callbackCompletionSources.TryAdd(packet.CallbackId, completionSource))
requestPacket.CallbackId = _callbackIdGenerator.GetId(packetToSend, out _); throw new InvalidOperationException("Duplicate packet sent");
var completionSource = new TaskCompletionSource<Packet>(); await SendPacketAsync(packet, cancellationToken);
if (!_packetCompletionSources.TryGetValue(attribute.Id, out List<TaskCompletionSource<Packet>>? completionSources)) return (TReceive)await completionSource.Task;
{
completionSources = new List<TaskCompletionSource<Packet>>();
_packetCompletionSources.TryAdd(attribute.Id, completionSources);
}
lock (completionSources)
{
if (!completionSources.Contains(completionSource))
completionSources.Add(completionSource);
}
await SendPacketAsync(packetToSend, cancellationToken);
TReceive response;
do
{
response = await WaitForPacketAsync<TReceive>(completionSource, cancellationToken);
if (requestPacket is null)
break;
if (response is ResponsePacket responsePacket && responsePacket.CallbackId == requestPacket.CallbackId)
break;
} while (true);
return response;
} }
/// <summary> /// <summary>
@ -284,17 +259,16 @@ public abstract class BaseClientNode : Node
return WaitForPacketAsync<TPacket>(completionSource, cancellationToken); return WaitForPacketAsync<TPacket>(completionSource, cancellationToken);
} }
private async Task<TPacket> WaitForPacketAsync<TPacket>( private async Task<TPacket> WaitForPacketAsync<TPacket>(TaskCompletionSource<Packet> completionSource,
TaskCompletionSource<Packet> completionSource, CancellationToken cancellationToken = default)
CancellationToken cancellationToken = default
)
where TPacket : Packet where TPacket : Packet
{ {
var attribute = typeof(TPacket).GetCustomAttribute<PacketAttribute>(); var attribute = typeof(TPacket).GetCustomAttribute<PacketAttribute>();
if (attribute is null) if (attribute is null)
throw new ArgumentException($"The packet type {typeof(TPacket).Name} is not a valid packet."); throw new ArgumentException($"The packet type {typeof(TPacket).Name} is not a valid packet.");
if (!_packetCompletionSources.TryGetValue(attribute.Id, out List<TaskCompletionSource<Packet>>? completionSources)) if (!_packetCompletionSources.TryGetValue(attribute.Id,
out List<TaskCompletionSource<Packet>>? completionSources))
{ {
completionSources = new List<TaskCompletionSource<Packet>>(); completionSources = new List<TaskCompletionSource<Packet>>();
_packetCompletionSources.TryAdd(attribute.Id, completionSources); _packetCompletionSources.TryAdd(attribute.Id, completionSources);
@ -328,7 +302,7 @@ public abstract class BaseClientNode : Node
completionSource.SetCanceled(); completionSource.SetCanceled();
}, cancellationToken); }, 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)) if (_packetCompletionSources.TryGetValue(attribute.Id, out completionSources))
{ {
lock (completionSources) lock (completionSources)

View File

@ -1,7 +1,7 @@
namespace TcpDotNet; namespace TcpDotNet;
/// <summary> /// <summary>
/// An enumeration of states for a <see cref="BaseClientNode" /> to be in. /// An enumeration of states for a <see cref="ClientNode" /> to be in.
/// </summary> /// </summary>
public enum ClientState public enum ClientState
{ {

View File

@ -1,4 +1,4 @@
using Chilkat; using Chilkat;
namespace TcpDotNet; namespace TcpDotNet;

View File

@ -1,4 +1,4 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Net.Sockets; using System.Net.Sockets;
using System.Reflection; using System.Reflection;
@ -38,7 +38,8 @@ public abstract class Node : IDisposable
/// <value>The registered packets.</value> /// <value>The registered packets.</value>
public IReadOnlyDictionary<Type, IReadOnlyCollection<PacketHandler>> RegisteredPacketHandlers => public IReadOnlyDictionary<Type, IReadOnlyCollection<PacketHandler>> RegisteredPacketHandlers =>
new ReadOnlyDictionary<Type, IReadOnlyCollection<PacketHandler>>( new ReadOnlyDictionary<Type, IReadOnlyCollection<PacketHandler>>(
_registeredPacketHandlers.ToDictionary(p => p.Key, p => (IReadOnlyCollection<PacketHandler>) p.Value.AsReadOnly())); _registeredPacketHandlers.ToDictionary(p => p.Key,
p => (IReadOnlyCollection<PacketHandler>)p.Value.AsReadOnly()));
/// <summary> /// <summary>
/// Closes the base socket connection and releases all associated resources. /// Closes the base socket connection and releases all associated resources.
@ -124,7 +125,8 @@ public abstract class Node : IDisposable
var attribute = packetType.GetCustomAttribute<PacketAttribute>(); var attribute = packetType.GetCustomAttribute<PacketAttribute>();
if (attribute is null) throw new ArgumentException($"{packetType.Name} is not a valid packet."); if (attribute is null) throw new ArgumentException($"{packetType.Name} is not a valid packet.");
if (_registeredPackets.TryGetValue(attribute.Id, out Type? registeredPacket)) 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); _registeredPackets.TryAdd(attribute.Id, packetType);
} }

View File

@ -1,4 +1,4 @@
namespace TcpDotNet.Protocol; namespace TcpDotNet.Protocol;
/// <summary> /// <summary>
/// An enumeration of handshake responses. /// An enumeration of handshake responses.

View File

@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
namespace TcpDotNet.Protocol; namespace TcpDotNet.Protocol;

View File

@ -1,4 +1,4 @@
namespace TcpDotNet.Protocol; namespace TcpDotNet.Protocol;
/// <summary> /// <summary>
/// Specifies metadata for a <see cref="Packet" />. /// Specifies metadata for a <see cref="Packet" />.

View File

@ -1,4 +1,4 @@
namespace TcpDotNet.Protocol; namespace TcpDotNet.Protocol;
/// <summary> /// <summary>
/// Represents the base class for a packet handler. /// Represents the base class for a packet handler.
@ -12,7 +12,7 @@ public abstract class PacketHandler
/// <param name="recipient">The recipient of the packet.</param> /// <param name="recipient">The recipient of the packet.</param>
/// <param name="packet">The packet to handle.</param> /// <param name="packet">The packet to handle.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param> /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
public abstract Task HandleAsync(BaseClientNode recipient, Packet packet, CancellationToken cancellationToken = default); public abstract Task HandleAsync(ClientNode recipient, Packet packet, CancellationToken cancellationToken = default);
} }
/// <summary> /// <summary>
@ -27,7 +27,7 @@ public abstract class PacketHandler<T> : PacketHandler
public static readonly PacketHandler<T> Empty = new NullPacketHandler<T>(); public static readonly PacketHandler<T> Empty = new NullPacketHandler<T>();
/// <inheritdoc /> /// <inheritdoc />
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); if (packet is T actual) return HandleAsync(recipient, actual, cancellationToken);
return Task.CompletedTask; return Task.CompletedTask;
@ -39,7 +39,7 @@ public abstract class PacketHandler<T> : PacketHandler
/// <param name="recipient">The recipient of the packet.</param> /// <param name="recipient">The recipient of the packet.</param>
/// <param name="packet">The packet to handle.</param> /// <param name="packet">The packet to handle.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param> /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
public abstract Task HandleAsync(BaseClientNode recipient, T packet, CancellationToken cancellationToken = default); public abstract Task HandleAsync(ClientNode recipient, T packet, CancellationToken cancellationToken = default);
} }
/// <summary> /// <summary>
@ -50,7 +50,7 @@ internal sealed class NullPacketHandler<T> : PacketHandler<T>
where T : Packet where T : Packet
{ {
/// <inheritdoc /> /// <inheritdoc />
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; return Task.CompletedTask;
} }

View File

@ -1,4 +1,4 @@
using TcpDotNet.Protocol.Packets.ClientBound; using TcpDotNet.Protocol.Packets.ClientBound;
namespace TcpDotNet.Protocol.PacketHandlers; namespace TcpDotNet.Protocol.PacketHandlers;
@ -6,7 +6,7 @@ internal sealed class DisconnectPacketHandler : PacketHandler<DisconnectPacket>
{ {
/// <inheritdoc /> /// <inheritdoc />
public override Task HandleAsync( public override Task HandleAsync(
BaseClientNode recipient, ClientNode recipient,
DisconnectPacket packet, DisconnectPacket packet,
CancellationToken cancellationToken = default CancellationToken cancellationToken = default
) )

View File

@ -1,4 +1,4 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using TcpDotNet.Protocol.Packets.ClientBound; using TcpDotNet.Protocol.Packets.ClientBound;
using TcpDotNet.Protocol.Packets.ServerBound; using TcpDotNet.Protocol.Packets.ServerBound;
@ -11,7 +11,7 @@ internal sealed class EncryptionResponsePacketHandler : PacketHandler<Encryption
{ {
/// <inheritdoc /> /// <inheritdoc />
public override async Task HandleAsync( public override async Task HandleAsync(
BaseClientNode recipient, ClientNode recipient,
EncryptionResponsePacket packet, EncryptionResponsePacket packet,
CancellationToken cancellationToken = default CancellationToken cancellationToken = default
) )

View File

@ -1,4 +1,4 @@
using TcpDotNet.Protocol.Packets.ClientBound; using TcpDotNet.Protocol.Packets.ClientBound;
using TcpDotNet.Protocol.Packets.ServerBound; using TcpDotNet.Protocol.Packets.ServerBound;
namespace TcpDotNet.Protocol.PacketHandlers; namespace TcpDotNet.Protocol.PacketHandlers;
@ -10,7 +10,7 @@ internal sealed class HandshakeRequestPacketHandler : PacketHandler<HandshakeReq
{ {
/// <inheritdoc /> /// <inheritdoc />
public override async Task HandleAsync( public override async Task HandleAsync(
BaseClientNode recipient, ClientNode recipient,
HandshakeRequestPacket packet, HandshakeRequestPacket packet,
CancellationToken cancellationToken = default CancellationToken cancellationToken = default
) )
@ -22,13 +22,14 @@ internal sealed class HandshakeRequestPacketHandler : PacketHandler<HandshakeReq
if (packet.ProtocolVersion != Node.ProtocolVersion) if (packet.ProtocolVersion != Node.ProtocolVersion)
{ {
response = new HandshakeResponsePacket(packet.ProtocolVersion, HandshakeResponse.UnsupportedProtocolVersion); const HandshakeResponse responseCode = HandshakeResponse.UnsupportedProtocolVersion;
response = new HandshakeResponsePacket(packet.CallbackId, packet.ProtocolVersion, responseCode);
await client.SendPacketAsync(response, cancellationToken); await client.SendPacketAsync(response, cancellationToken);
client.Close(); client.Close();
return; return;
} }
response = new HandshakeResponsePacket(packet.ProtocolVersion, HandshakeResponse.Success); response = new HandshakeResponsePacket(packet.CallbackId, packet.ProtocolVersion, HandshakeResponse.Success);
await client.SendPacketAsync(response, cancellationToken); await client.SendPacketAsync(response, cancellationToken);
client.State = ClientState.Encrypting; client.State = ClientState.Encrypting;

View File

@ -1,4 +1,4 @@
namespace TcpDotNet.Protocol.Packets.ClientBound; namespace TcpDotNet.Protocol.Packets.ClientBound;
[Packet(0x7FFFFFFF)] [Packet(0x7FFFFFFF)]
internal sealed class DisconnectPacket : Packet internal sealed class DisconnectPacket : Packet
@ -24,12 +24,12 @@ internal sealed class DisconnectPacket : Packet
/// <inheritdoc /> /// <inheritdoc />
protected internal override void Deserialize(ProtocolReader reader) protected internal override void Deserialize(ProtocolReader reader)
{ {
Reason = (DisconnectReason) reader.ReadByte(); Reason = (DisconnectReason)reader.ReadByte();
} }
/// <inheritdoc /> /// <inheritdoc />
protected internal override void Serialize(ProtocolWriter writer) protected internal override void Serialize(ProtocolWriter writer)
{ {
writer.Write((byte) Reason); writer.Write((byte)Reason);
} }
} }

View File

@ -1,4 +1,4 @@
using System.Security.Cryptography; using System.Security.Cryptography;
namespace TcpDotNet.Protocol.Packets.ClientBound; namespace TcpDotNet.Protocol.Packets.ClientBound;

View File

@ -1,4 +1,4 @@
using TcpDotNet.Protocol.Packets.ServerBound; using TcpDotNet.Protocol.Packets.ServerBound;
namespace TcpDotNet.Protocol.Packets.ClientBound; namespace TcpDotNet.Protocol.Packets.ClientBound;
@ -6,14 +6,16 @@ namespace TcpDotNet.Protocol.Packets.ClientBound;
/// Represents a packet which responds to a <see cref="HandshakeRequestPacket" />. /// Represents a packet which responds to a <see cref="HandshakeRequestPacket" />.
/// </summary> /// </summary>
[Packet(0x7FFFFFE1)] [Packet(0x7FFFFFE1)]
internal sealed class HandshakeResponsePacket : Packet internal sealed class HandshakeResponsePacket : ResponsePacket
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HandshakeResponsePacket" /> class. /// Initializes a new instance of the <see cref="HandshakeResponsePacket" /> class.
/// </summary> /// </summary>
/// <param name="callbackId">The callback ID.</param>
/// <param name="protocolVersion">The requested protocol version.</param> /// <param name="protocolVersion">The requested protocol version.</param>
/// <param name="handshakeResponse">The handshake response.</param> /// <param name="handshakeResponse">The handshake response.</param>
public HandshakeResponsePacket(int protocolVersion, HandshakeResponse handshakeResponse) public HandshakeResponsePacket(long callbackId, int protocolVersion, HandshakeResponse handshakeResponse)
: base(callbackId)
{ {
ProtocolVersion = protocolVersion; ProtocolVersion = protocolVersion;
HandshakeResponse = handshakeResponse; HandshakeResponse = handshakeResponse;
@ -38,14 +40,14 @@ internal sealed class HandshakeResponsePacket : Packet
/// <inheritdoc /> /// <inheritdoc />
protected internal override void Deserialize(ProtocolReader reader) protected internal override void Deserialize(ProtocolReader reader)
{ {
HandshakeResponse = (HandshakeResponse) reader.ReadByte(); HandshakeResponse = (HandshakeResponse)reader.ReadByte();
ProtocolVersion = reader.ReadInt32(); ProtocolVersion = reader.ReadInt32();
} }
/// <inheritdoc /> /// <inheritdoc />
protected internal override void Serialize(ProtocolWriter writer) protected internal override void Serialize(ProtocolWriter writer)
{ {
writer.Write((byte) HandshakeResponse); writer.Write((byte)HandshakeResponse);
writer.Write(ProtocolVersion); writer.Write(ProtocolVersion);
} }
} }

View File

@ -1,4 +1,4 @@
namespace TcpDotNet.Protocol.Packets.ClientBound; namespace TcpDotNet.Protocol.Packets.ClientBound;
/// <summary> /// <summary>
/// Represents a packet which performs a heartbeat response. /// Represents a packet which performs a heartbeat response.

View File

@ -1,4 +1,4 @@
namespace TcpDotNet.Protocol.Packets.ClientBound; namespace TcpDotNet.Protocol.Packets.ClientBound;
[Packet(0x7FFFFFE4)] [Packet(0x7FFFFFE4)]
internal sealed class SessionExchangePacket : Packet internal sealed class SessionExchangePacket : Packet

View File

@ -1,4 +1,4 @@
using TcpDotNet.Protocol.Packets.ClientBound; using TcpDotNet.Protocol.Packets.ClientBound;
namespace TcpDotNet.Protocol.Packets.ServerBound; namespace TcpDotNet.Protocol.Packets.ServerBound;

View File

@ -1,10 +1,10 @@
namespace TcpDotNet.Protocol.Packets.ServerBound; namespace TcpDotNet.Protocol.Packets.ServerBound;
/// <summary> /// <summary>
/// Represents a packet which requests a handshake with a <see cref="ProtocolListener" />. /// Represents a packet which requests a handshake with a <see cref="ProtocolListener" />.
/// </summary> /// </summary>
[Packet(0x7FFFFFE0)] [Packet(0x7FFFFFE0)]
internal sealed class HandshakeRequestPacket : Packet internal sealed class HandshakeRequestPacket : RequestPacket
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HandshakeRequestPacket" /> class. /// Initializes a new instance of the <see cref="HandshakeRequestPacket" /> class.

View File

@ -1,4 +1,4 @@
using System.Security.Cryptography; using System.Security.Cryptography;
namespace TcpDotNet.Protocol.Packets.ServerBound; namespace TcpDotNet.Protocol.Packets.ServerBound;

View File

@ -1,4 +1,4 @@
using System.Net; using System.Net;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -66,7 +66,7 @@ public sealed class ProtocolReader : BinaryReader
result |= (byteReadJustNow & 0x7Fu) << shift; result |= (byteReadJustNow & 0x7Fu) << shift;
if (byteReadJustNow <= 0x7Fu) if (byteReadJustNow <= 0x7Fu)
return (int) result; // early exit return (int)result; // early exit
} }
// Read the 5th byte. Since we already read 28 bits, // Read the 5th byte. Since we already read 28 bits,
@ -77,8 +77,8 @@ public sealed class ProtocolReader : BinaryReader
if (byteReadJustNow > 0b_1111u) if (byteReadJustNow > 0b_1111u)
throw new FormatException(); throw new FormatException();
result |= (uint) byteReadJustNow << (maxBytesWithoutOverflow * 7); result |= (uint)byteReadJustNow << (maxBytesWithoutOverflow * 7);
return (int) result; return (int)result;
} }
/// <summary> /// <summary>
@ -107,7 +107,7 @@ public sealed class ProtocolReader : BinaryReader
result |= (byteReadJustNow & 0x7Ful) << shift; result |= (byteReadJustNow & 0x7Ful) << shift;
if (byteReadJustNow <= 0x7Fu) if (byteReadJustNow <= 0x7Fu)
return (long) result; // early exit return (long)result; // early exit
} }
// Read the 10th byte. Since we already read 63 bits, // Read the 10th byte. Since we already read 63 bits,
@ -118,8 +118,8 @@ public sealed class ProtocolReader : BinaryReader
if (byteReadJustNow > 0b_1u) if (byteReadJustNow > 0b_1u)
throw new FormatException(); throw new FormatException();
result |= (ulong) byteReadJustNow << (maxBytesWithoutOverflow * 7); result |= (ulong)byteReadJustNow << (maxBytesWithoutOverflow * 7);
return (long) result; return (long)result;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -196,21 +196,21 @@ public sealed class ProtocolReader : BinaryReader
[CLSCompliant(false)] [CLSCompliant(false)]
public override ushort ReadUInt16() public override ushort ReadUInt16()
{ {
return (ushort) IPAddress.NetworkToHostOrder((short) base.ReadUInt16()); return (ushort)IPAddress.NetworkToHostOrder((short)base.ReadUInt16());
} }
/// <inheritdoc /> /// <inheritdoc />
[CLSCompliant(false)] [CLSCompliant(false)]
public override uint ReadUInt32() public override uint ReadUInt32()
{ {
return (uint) IPAddress.NetworkToHostOrder((int) base.ReadUInt32()); return (uint)IPAddress.NetworkToHostOrder((int)base.ReadUInt32());
} }
/// <inheritdoc /> /// <inheritdoc />
[CLSCompliant(false)] [CLSCompliant(false)]
public override ulong ReadUInt64() public override ulong ReadUInt64()
{ {
return (ulong) IPAddress.NetworkToHostOrder((long) base.ReadUInt64()); return (ulong)IPAddress.NetworkToHostOrder((long)base.ReadUInt64());
} }
/// <summary> /// <summary>

View File

@ -1,4 +1,4 @@
using System.Net; using System.Net;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -38,7 +38,11 @@ public sealed class ProtocolWriter : BinaryWriter
public override void Write(double value) public override void Write(double value)
{ {
Span<byte> buffer = stackalloc byte[8]; Span<byte> buffer = stackalloc byte[8];
#if NET8_0_OR_GREATER
MemoryMarshal.TryWrite(buffer, in value);
#else
MemoryMarshal.TryWrite(buffer, ref value); MemoryMarshal.TryWrite(buffer, ref value);
#endif
if (BitConverter.IsLittleEndian) buffer.Reverse(); if (BitConverter.IsLittleEndian) buffer.Reverse();
Write(buffer); Write(buffer);
@ -89,7 +93,11 @@ public sealed class ProtocolWriter : BinaryWriter
public override void Write(float value) public override void Write(float value)
{ {
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
#if NET8_0_OR_GREATER
MemoryMarshal.TryWrite(buffer, in value);
#else
MemoryMarshal.TryWrite(buffer, ref value); MemoryMarshal.TryWrite(buffer, ref value);
#endif
if (BitConverter.IsLittleEndian) buffer.Reverse(); if (BitConverter.IsLittleEndian) buffer.Reverse();
Write(buffer); Write(buffer);
@ -99,21 +107,21 @@ public sealed class ProtocolWriter : BinaryWriter
[CLSCompliant(false)] [CLSCompliant(false)]
public override void Write(ushort value) public override void Write(ushort value)
{ {
base.Write((ushort) IPAddress.HostToNetworkOrder((short) value)); base.Write((ushort)IPAddress.HostToNetworkOrder((short)value));
} }
/// <inheritdoc /> /// <inheritdoc />
[CLSCompliant(false)] [CLSCompliant(false)]
public override void Write(uint value) public override void Write(uint value)
{ {
base.Write((uint) IPAddress.HostToNetworkOrder((int) value)); base.Write((uint)IPAddress.HostToNetworkOrder((int)value));
} }
/// <inheritdoc /> /// <inheritdoc />
[CLSCompliant(false)] [CLSCompliant(false)]
public override void Write(ulong value) public override void Write(ulong value)
{ {
base.Write((ulong) IPAddress.HostToNetworkOrder((long) value)); base.Write((ulong)IPAddress.HostToNetworkOrder((long)value));
} }
/// <summary> /// <summary>
@ -155,7 +163,7 @@ public sealed class ProtocolWriter : BinaryWriter
/// <param name="value">The 32-bit integer to be written.</param> /// <param name="value">The 32-bit integer to be written.</param>
public void Write7BitEncodedInt32(int value) 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, // Write out an int 7 bits at a time. The high bit of the byte,
// when on, tells reader to continue reading more bytes. // when on, tells reader to continue reading more bytes.
@ -165,11 +173,11 @@ public sealed class ProtocolWriter : BinaryWriter
while (uValue > 0x7Fu) while (uValue > 0x7Fu)
{ {
Write((byte) (uValue | ~0x7Fu)); Write((byte)(uValue | ~0x7Fu));
uValue >>= 7; uValue >>= 7;
} }
Write((byte) uValue); Write((byte)uValue);
} }
/// <summary> /// <summary>
@ -178,7 +186,7 @@ public sealed class ProtocolWriter : BinaryWriter
/// <param name="value">The 64-bit integer to be written.</param> /// <param name="value">The 64-bit integer to be written.</param>
public void Write7BitEncodedInt64(long value) 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, // Write out an int 7 bits at a time. The high bit of the byte,
// when on, tells reader to continue reading more bytes. // when on, tells reader to continue reading more bytes.
@ -188,10 +196,10 @@ public sealed class ProtocolWriter : BinaryWriter
while (uValue > 0x7Fu) while (uValue > 0x7Fu)
{ {
Write((byte) ((uint) uValue | ~0x7Fu)); Write((byte)((uint)uValue | ~0x7Fu));
uValue >>= 7; uValue >>= 7;
} }
Write((byte) uValue); Write((byte)uValue);
} }
} }

View File

@ -1,15 +1,28 @@
namespace TcpDotNet.Protocol; using System.Buffers.Binary;
using System.Security.Cryptography;
namespace TcpDotNet.Protocol;
/// <summary> /// <summary>
/// Represents a request packet, which forms a request/response packet pair. /// Represents a request packet, which forms a request/response packet pair.
/// </summary> /// </summary>
public abstract class RequestPacket : Packet public abstract class RequestPacket : Packet
{ {
/// <summary>
/// Initializes a new instance of the <see cref="RequestPacket" /> class.
/// </summary>
protected RequestPacket()
{
Span<byte> buffer = stackalloc byte[8];
RandomNumberGenerator.Fill(buffer);
CallbackId = BinaryPrimitives.ReadInt64BigEndian(buffer);
}
/// <summary> /// <summary>
/// Gets the request identifier. /// Gets the request identifier.
/// </summary> /// </summary>
/// <value>The request identifier.</value> /// <value>The request identifier.</value>
public long CallbackId { get; internal set; } public long CallbackId { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
protected internal override void Deserialize(ProtocolReader reader) protected internal override void Deserialize(ProtocolReader reader)

View File

@ -1,4 +1,4 @@
namespace TcpDotNet.Protocol; namespace TcpDotNet.Protocol;
/// <summary> /// <summary>
/// Represents a response packet, which forms a request/response packet pair. /// Represents a response packet, which forms a request/response packet pair.
@ -14,6 +14,10 @@ public abstract class ResponsePacket : Packet
CallbackId = callbackId; CallbackId = callbackId;
} }
internal ResponsePacket()
{
}
/// <summary> /// <summary>
/// Gets the response identifier. /// Gets the response identifier.
/// </summary> /// </summary>

View File

@ -1,4 +1,4 @@
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography; using System.Security.Cryptography;
using TcpDotNet.EventData; using TcpDotNet.EventData;
@ -14,7 +14,7 @@ namespace TcpDotNet;
/// <summary> /// <summary>
/// Represents a client on the TcpDotNet protocol. /// Represents a client on the TcpDotNet protocol.
/// </summary> /// </summary>
public sealed class ProtocolClient : BaseClientNode public sealed class ProtocolClient : ClientNode
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProtocolClient" /> class. /// Initializes a new instance of the <see cref="ProtocolClient" /> class.
@ -82,7 +82,7 @@ public sealed class ProtocolClient : BaseClientNode
BaseSocket = new Socket(remoteEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); BaseSocket = new Socket(remoteEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try try
{ {
await Task.Run(() => BaseSocket.ConnectAsync(remoteEP), cancellationToken); await BaseSocket.ConnectAsync(remoteEP, cancellationToken);
} }
catch catch
{ {
@ -95,15 +95,15 @@ public sealed class ProtocolClient : BaseClientNode
State = ClientState.Handshaking; State = ClientState.Handshaking;
var handshakeRequest = new HandshakeRequestPacket(ProtocolVersion); var handshakeRequest = new HandshakeRequestPacket(ProtocolVersion);
var handshakeResponse = var handshakeResponse = await SendAndReceiveAsync<HandshakeResponsePacket>(handshakeRequest, cancellationToken);
await SendAndReceiveAsync<HandshakeRequestPacket, HandshakeResponsePacket>(handshakeRequest, cancellationToken);
if (handshakeResponse.HandshakeResponse != HandshakeResponse.Success) if (handshakeResponse.HandshakeResponse != HandshakeResponse.Success)
{ {
Close(); Close();
IsConnected = false; 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; State = ClientState.Encrypting;
@ -113,8 +113,7 @@ public sealed class ProtocolClient : BaseClientNode
byte[] encryptedPayload = rsa.Encrypt(encryptionRequest.Payload, true); byte[] encryptedPayload = rsa.Encrypt(encryptionRequest.Payload, true);
var key = new byte[128]; var key = new byte[128];
using var rng = new RNGCryptoServiceProvider(); RandomNumberGenerator.Fill(key);
rng.GetBytes(key);
Aes = CryptographyUtils.GenerateAes(key); Aes = CryptographyUtils.GenerateAes(key);
byte[] aesKey = rsa.Encrypt(key, true); byte[] aesKey = rsa.Encrypt(key, true);

View File

@ -8,7 +8,7 @@ public sealed partial class ProtocolListener
/// <summary> /// <summary>
/// Represents a client that is connected to a <see cref="ProtocolListener" />. /// Represents a client that is connected to a <see cref="ProtocolListener" />.
/// </summary> /// </summary>
public sealed class Client : BaseClientNode public sealed class Client : ClientNode
{ {
internal Client(ProtocolListener listener, Socket socket) internal Client(ProtocolListener listener, Socket socket)
{ {
@ -35,7 +35,8 @@ public sealed partial class ProtocolListener
foreach (Type packetType in ParentListener.RegisteredPackets.Values) foreach (Type packetType in ParentListener.RegisteredPackets.Values)
RegisterPacket(packetType); RegisterPacket(packetType);
foreach ((Type packetType, IReadOnlyCollection<PacketHandler>? handlers) in ParentListener.RegisteredPacketHandlers) foreach ((Type packetType, IReadOnlyCollection<PacketHandler>? handlers) in ParentListener
.RegisteredPacketHandlers)
foreach (PacketHandler handler in handlers) foreach (PacketHandler handler in handlers)
RegisterPacketHandler(packetType, handler); RegisterPacketHandler(packetType, handler);

View File

@ -1,4 +1,4 @@
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography; using System.Security.Cryptography;
using TcpDotNet.EventData; using TcpDotNet.EventData;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>10</LangVersion> <LangVersion>10</LangVersion>
@ -12,6 +12,7 @@
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<Description>A TCP library for .NET with support for AES encryption.</Description> <Description>A TCP library for .NET with support for AES encryption.</Description>
<PackageLicenseFile>LICENSE.md</PackageLicenseFile> <PackageLicenseFile>LICENSE.md</PackageLicenseFile>
<PackageIcon>branding_Icon.png</PackageIcon>
<PackageTags>dotnet networking encryption tcp</PackageTags> <PackageTags>dotnet networking encryption tcp</PackageTags>
<VersionPrefix>0.1.0</VersionPrefix> <VersionPrefix>0.1.0</VersionPrefix>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
@ -40,10 +41,18 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\branding_Icon.png">
<Pack>True</Pack>
<PackagePath/>
</None>
<None Include="..\LICENSE.md"> <None Include="..\LICENSE.md">
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath/> <PackagePath/>
</None> </None>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath/>
</None>
</ItemGroup> </ItemGroup>
</Project> </Project>

BIN
branding_Banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
branding_Icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

7
global.json Normal file
View File

@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestMajor",
"allowPrerelease": false
}
}