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']

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

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">
<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/issues"><img src="https://img.shields.io/github/issues/oliverbooth/TcpDotNet" 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/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/vpre/TcpDotNet?label=nightly" 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/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?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?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&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&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?style=flat-square" alt="MIT License" title="MIT License"></a>
</p>
### 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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
using TcpDotNet.Protocol;
using TcpDotNet.Protocol;
namespace TcpDotNet.ClientIntegrationTest.PacketHandlers;
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}");
return Task.CompletedTask;

View File

@ -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<PingPacket, PongPacket>(ping);
var pong = await client.SendAndReceiveAsync<PongPacket>(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!");

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
using TcpDotNet.Protocol;
using TcpDotNet.Protocol;
namespace TcpDotNet.ListenerIntegrationTest.PacketHandlers;
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}");
return recipient.SendPacketAsync(new GoodbyePacket {Message = "Goodbye!"}, cancellationToken);

View File

@ -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<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)}");
var pong = new PongPacket(packet.CallbackId, packet.Payload);

View File

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

View File

@ -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

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.Sockets;
using System.Reflection;
using System.Runtime.Serialization;
using Chilkat;
using TcpDotNet.Protocol;
using Stream = System.IO.Stream;
@ -13,16 +12,16 @@ namespace TcpDotNet;
/// <summary>
/// Represents a client node.
/// </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<long, TaskCompletionSource<ResponsePacket>> _callbackCompletionSources = new();
private EndPoint? _remoteEP;
/// <summary>
/// Initializes a new instance of the <see cref="BaseClientNode" /> class.
/// Initializes a new instance of the <see cref="ClientNode" /> class.
/// </summary>
protected BaseClientNode()
protected ClientNode()
{
}
@ -219,53 +218,29 @@ public abstract class BaseClientNode : Node
/// <summary>
/// Sends a packet, and waits for a specific packet to be received.
/// </summary>
/// <param name="packetToSend">The packet to send.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
/// <typeparam name="TSend">The type of the packet to send.</typeparam>
/// <param name="packet">The packet to send.</param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the asynchronous operation.
/// </param>
/// <typeparam name="TReceive">The type of the packet to return.</typeparam>
/// <returns>The received packet.</returns>
/// <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>
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)
where TSend : Packet
where TReceive : Packet
where TReceive : ResponsePacket
{
var attribute = typeof(TReceive).GetCustomAttribute<PacketAttribute>();
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<ResponsePacket>();
if (!_callbackCompletionSources.TryAdd(packet.CallbackId, completionSource))
throw new InvalidOperationException("Duplicate packet sent");
var completionSource = new TaskCompletionSource<Packet>();
if (!_packetCompletionSources.TryGetValue(attribute.Id, out List<TaskCompletionSource<Packet>>? completionSources))
{
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;
await SendPacketAsync(packet, cancellationToken);
return (TReceive)await completionSource.Task;
}
/// <summary>
@ -284,17 +259,16 @@ public abstract class BaseClientNode : Node
return WaitForPacketAsync<TPacket>(completionSource, cancellationToken);
}
private async Task<TPacket> WaitForPacketAsync<TPacket>(
TaskCompletionSource<Packet> completionSource,
CancellationToken cancellationToken = default
)
private async Task<TPacket> WaitForPacketAsync<TPacket>(TaskCompletionSource<Packet> completionSource,
CancellationToken cancellationToken = default)
where TPacket : Packet
{
var attribute = typeof(TPacket).GetCustomAttribute<PacketAttribute>();
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<TaskCompletionSource<Packet>>? completionSources))
if (!_packetCompletionSources.TryGetValue(attribute.Id,
out List<TaskCompletionSource<Packet>>? completionSources))
{
completionSources = new List<TaskCompletionSource<Packet>>();
_packetCompletionSources.TryAdd(attribute.Id, completionSources);

View File

@ -1,7 +1,7 @@
namespace TcpDotNet;
namespace TcpDotNet;
/// <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>
public enum ClientState
{

View File

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

View File

@ -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
/// <value>The registered packets.</value>
public IReadOnlyDictionary<Type, IReadOnlyCollection<PacketHandler>> RegisteredPacketHandlers =>
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>
/// Closes the base socket connection and releases all associated resources.
@ -124,7 +125,8 @@ public abstract class Node : IDisposable
var attribute = packetType.GetCustomAttribute<PacketAttribute>();
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);
}

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
namespace TcpDotNet.Protocol;
namespace TcpDotNet.Protocol;
/// <summary>
/// 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="packet">The packet to handle.</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>
@ -27,7 +27,7 @@ public abstract class PacketHandler<T> : PacketHandler
public static readonly PacketHandler<T> Empty = new NullPacketHandler<T>();
/// <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);
return Task.CompletedTask;
@ -39,7 +39,7 @@ public abstract class PacketHandler<T> : PacketHandler
/// <param name="recipient">The recipient of the packet.</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>
public abstract Task HandleAsync(BaseClientNode recipient, T packet, CancellationToken cancellationToken = default);
public abstract Task HandleAsync(ClientNode recipient, T packet, CancellationToken cancellationToken = default);
}
/// <summary>
@ -50,7 +50,7 @@ internal sealed class NullPacketHandler<T> : PacketHandler<T>
where T : Packet
{
/// <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;
}

View File

@ -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<DisconnectPacket>
{
/// <inheritdoc />
public override Task HandleAsync(
BaseClientNode recipient,
ClientNode recipient,
DisconnectPacket packet,
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.ServerBound;
@ -11,7 +11,7 @@ internal sealed class EncryptionResponsePacketHandler : PacketHandler<Encryption
{
/// <inheritdoc />
public override async Task HandleAsync(
BaseClientNode recipient,
ClientNode recipient,
EncryptionResponsePacket packet,
CancellationToken cancellationToken = default
)

View File

@ -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<HandshakeReq
{
/// <inheritdoc />
public override async Task HandleAsync(
BaseClientNode recipient,
ClientNode recipient,
HandshakeRequestPacket packet,
CancellationToken cancellationToken = default
)
@ -22,13 +22,14 @@ internal sealed class HandshakeRequestPacketHandler : PacketHandler<HandshakeReq
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);
client.Close();
return;
}
response = new HandshakeResponsePacket(packet.ProtocolVersion, HandshakeResponse.Success);
response = new HandshakeResponsePacket(packet.CallbackId, packet.ProtocolVersion, HandshakeResponse.Success);
await client.SendPacketAsync(response, cancellationToken);
client.State = ClientState.Encrypting;

View File

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

View File

@ -1,4 +1,4 @@
using System.Security.Cryptography;
using System.Security.Cryptography;
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;
@ -6,14 +6,16 @@ namespace TcpDotNet.Protocol.Packets.ClientBound;
/// Represents a packet which responds to a <see cref="HandshakeRequestPacket" />.
/// </summary>
[Packet(0x7FFFFFE1)]
internal sealed class HandshakeResponsePacket : Packet
internal sealed class HandshakeResponsePacket : ResponsePacket
{
/// <summary>
/// Initializes a new instance of the <see cref="HandshakeResponsePacket" /> class.
/// </summary>
/// <param name="callbackId">The callback ID.</param>
/// <param name="protocolVersion">The requested protocol version.</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;
HandshakeResponse = handshakeResponse;

View File

@ -1,4 +1,4 @@
namespace TcpDotNet.Protocol.Packets.ClientBound;
namespace TcpDotNet.Protocol.Packets.ClientBound;
/// <summary>
/// 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)]
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;

View File

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

View File

@ -1,4 +1,4 @@
using System.Net;
using System.Net;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;

View File

@ -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<byte> 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<byte> 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);

View File

@ -1,15 +1,28 @@
namespace TcpDotNet.Protocol;
using System.Buffers.Binary;
using System.Security.Cryptography;
namespace TcpDotNet.Protocol;
/// <summary>
/// Represents a request packet, which forms a request/response packet pair.
/// </summary>
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>
/// Gets the request identifier.
/// </summary>
/// <value>The request identifier.</value>
public long CallbackId { get; internal set; }
public long CallbackId { get; private set; }
/// <inheritdoc />
protected internal override void Deserialize(ProtocolReader reader)

View File

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

View File

@ -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;
/// <summary>
/// Represents a client on the TcpDotNet protocol.
/// </summary>
public sealed class ProtocolClient : BaseClientNode
public sealed class ProtocolClient : ClientNode
{
/// <summary>
/// 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);
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<HandshakeRequestPacket, HandshakeResponsePacket>(handshakeRequest, cancellationToken);
var handshakeResponse = await SendAndReceiveAsync<HandshakeResponsePacket>(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);

View File

@ -8,7 +8,7 @@ public sealed partial class ProtocolListener
/// <summary>
/// Represents a client that is connected to a <see cref="ProtocolListener" />.
/// </summary>
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<PacketHandler>? handlers) in ParentListener.RegisteredPacketHandlers)
foreach ((Type packetType, IReadOnlyCollection<PacketHandler>? handlers) in ParentListener
.RegisteredPacketHandlers)
foreach (PacketHandler handler in handlers)
RegisterPacketHandler(packetType, handler);

View File

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

View File

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