idk whats going on here
This commit is contained in:
parent
6de712910f
commit
acaf5fee14
8
.gitignore
vendored
8
.gitignore
vendored
@ -21,3 +21,11 @@
|
|||||||
|
|
||||||
#backups
|
#backups
|
||||||
.old*/
|
.old*/
|
||||||
|
|
||||||
|
#secrets
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.csr
|
||||||
|
*.crt
|
||||||
|
*.pfx
|
||||||
|
*.config.json
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ global using System.Threading;
|
|||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
global using Meum.Core;
|
global using Meum.Core;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Quic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using DTLib.Console;
|
using DTLib.Console;
|
||||||
using DTLib.Demystifier;
|
using DTLib.Demystifier;
|
||||||
@ -34,50 +35,69 @@ class Program
|
|||||||
Console.OutputEncoding = StringConverter.UTF8;
|
Console.OutputEncoding = StringConverter.UTF8;
|
||||||
Console.InputEncoding = StringConverter.UTF8;
|
Console.InputEncoding = StringConverter.UTF8;
|
||||||
ColoredConsole.ResetColor();
|
ColoredConsole.ResetColor();
|
||||||
var v = Assembly.GetExecutingAssembly().GetName().Version;
|
try
|
||||||
string title = $"Meum CLI v{v?.ToString(3) ?? "Null"}";
|
|
||||||
Console.Title = title;
|
|
||||||
ColoredConsole.WriteTitle(title, '=', fg: ConsoleColor.Cyan);
|
|
||||||
ColoredConsole.WriteLine(greeting_art, fg: ConsoleColor.Magenta);
|
|
||||||
ColoredConsole.WriteHLine('=', fg: ConsoleColor.Cyan);
|
|
||||||
|
|
||||||
UserAddress? userAddress = null;
|
|
||||||
DnsEndPoint? serverEndPoint = null;
|
|
||||||
string? serverAddress = null;
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
try
|
var v = Assembly.GetExecutingAssembly().GetName().Version;
|
||||||
{
|
string title = $"Meum CLI v{v?.ToString(3) ?? "Null"}";
|
||||||
if(userAddress == null)
|
Console.Title = title;
|
||||||
{
|
|
||||||
var addrstr = ColoredConsole.ReadLine("enter user address (name@server.xyz)", ConsoleColor.Blue);
|
|
||||||
if (string.IsNullOrEmpty(addrstr))
|
|
||||||
continue;
|
|
||||||
userAddress = new(addrstr);
|
|
||||||
}
|
|
||||||
Client client = new(userAddress);
|
|
||||||
if(serverEndPoint == null)
|
|
||||||
{
|
|
||||||
serverAddress = ColoredConsole.ReadLine("enter server address (server.xyz)", ConsoleColor.Blue);
|
|
||||||
if (string.IsNullOrEmpty(serverAddress))
|
|
||||||
{
|
|
||||||
ColoredConsole.WriteLine("null address", ConsoleColor.Red);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
serverEndPoint = Network.ParseDnsEndPoint(serverAddress);
|
|
||||||
ColoredConsole.WriteTitle(serverAddress, fg: ConsoleColor.Cyan);
|
|
||||||
}
|
|
||||||
|
|
||||||
ColoredConsole.WriteLine("connecting to the server...", ConsoleColor.Blue);
|
if (!QuicConnection.IsSupported)
|
||||||
var conn = await client.ConnectToServerAsync(serverEndPoint);
|
throw new Exception("Quic is not supported, check for presence of libmsquic and openssl");
|
||||||
await conn.PingAsync();
|
|
||||||
await Task.Delay(-1);
|
ColoredConsole.WriteTitle(title, '=', fg: ConsoleColor.Cyan);
|
||||||
}
|
ColoredConsole.WriteLine(greeting_art, fg: ConsoleColor.Magenta);
|
||||||
catch (Exception ex)
|
ColoredConsole.WriteHLine('=', fg: ConsoleColor.Cyan);
|
||||||
|
|
||||||
|
UserAddress? userAddress = null;
|
||||||
|
DnsEndPoint? serverEndPoint = null;
|
||||||
|
string? serverAddress = null;
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
ColoredConsole.WriteLine(ex.ToStringDemystified(), ConsoleColor.Red);
|
try
|
||||||
|
{
|
||||||
|
if (userAddress == null)
|
||||||
|
{
|
||||||
|
var addrstr =
|
||||||
|
ColoredConsole.ReadLine("enter user address (name@server.xyz)", ConsoleColor.Blue);
|
||||||
|
if (string.IsNullOrEmpty(addrstr))
|
||||||
|
continue;
|
||||||
|
userAddress = new(addrstr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Client client = new();
|
||||||
|
if (serverEndPoint == null)
|
||||||
|
{
|
||||||
|
serverAddress = ColoredConsole.ReadLine("enter server address (server.xyz)", ConsoleColor.Blue);
|
||||||
|
if (string.IsNullOrEmpty(serverAddress))
|
||||||
|
{
|
||||||
|
ColoredConsole.WriteLine("null address", ConsoleColor.Red);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
serverEndPoint = Functions.ParseDnsEndPoint(serverAddress);
|
||||||
|
ColoredConsole.WriteTitle(serverAddress, fg: ConsoleColor.Cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
ColoredConsole.WriteLine("connecting to the server...", ConsoleColor.Blue);
|
||||||
|
var conn = await client.ConnectToServerAsync(serverEndPoint);
|
||||||
|
ColoredConsole.WriteLine("Connected to server", ConsoleColor.Green);
|
||||||
|
|
||||||
|
await conn.PingAsync();
|
||||||
|
await Task.Delay(-1);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ColoredConsole.WriteLine(ex.ToStringDemystified(), ConsoleColor.Red);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ColoredConsole.ResetColor();
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ColoredConsole.WriteLine(ex.ToStringDemystified(), ConsoleColor.Red);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Console.ResetColor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +1,58 @@
|
|||||||
global using System;
|
global using System;
|
||||||
global using System.Collections.Generic;
|
global using System.Collections.Generic;
|
||||||
global using System.Threading;
|
|
||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
global using Meum.Core;
|
global using Meum.Core;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Quic;
|
||||||
|
using System.Net.Security;
|
||||||
|
using DTLib.Logging;
|
||||||
|
|
||||||
namespace Meum.Client;
|
namespace Meum.Client;
|
||||||
|
|
||||||
public class Client
|
public class Client
|
||||||
{
|
{
|
||||||
private readonly HashSet<ServerConnection> _connectedServers = new();
|
private readonly HashSet<ServerConnection> _connectedServers = new();
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public Client(ILogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
public IReadOnlySet<ServerConnection> ConnectedServers => _connectedServers;
|
public IReadOnlySet<ServerConnection> ConnectedServers => _connectedServers;
|
||||||
|
|
||||||
public UserAddress Address { get; }
|
public UserAddress? Address { get; private set; }
|
||||||
|
|
||||||
public Client(UserAddress address)
|
public Task RegisterAsync(UserAddress address)
|
||||||
{
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task LogInAsync(UserAddress address)
|
||||||
|
{
|
||||||
|
if(Address != null)
|
||||||
|
throw new InvalidOperationException("Already logged in");
|
||||||
Address = address;
|
Address = address;
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerConnection> ConnectToServerAsync(DnsEndPoint serverEndPoint)
|
public async Task<ServerConnection> ConnectToServerAsync(DnsEndPoint serverEndPoint)
|
||||||
{
|
{
|
||||||
var serv = new ServerConnection(serverEndPoint);
|
var quicConn = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions
|
||||||
await serv.ConnectAsync();
|
{
|
||||||
_connectedServers.Add(serv);
|
// TODO serverEndPoint
|
||||||
|
RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, Constants.ServerPortDefault),
|
||||||
|
DefaultStreamErrorCode = Constants.DefaultStreamErrorCode,
|
||||||
|
DefaultCloseErrorCode = Constants.DefaultCloseErrorCode,
|
||||||
|
ClientAuthenticationOptions = new SslClientAuthenticationOptions
|
||||||
|
{
|
||||||
|
ApplicationProtocols = Constants.ApplicationProtocols,
|
||||||
|
TargetHost = serverEndPoint.Host
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var serv = new ServerConnection(quicConn, serverEndPoint, _logger);
|
||||||
|
if(!_connectedServers.Add(serv))
|
||||||
|
throw new Exception($"Is already connected to server '{serverEndPoint.Host}'");
|
||||||
return serv;
|
return serv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DTLib" Version="1.6.0" />
|
<PackageReference Include="DTLib" Version="1.6.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -2,46 +2,33 @@
|
|||||||
using System.Net.Quic;
|
using System.Net.Quic;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
using DTLib.Extensions;
|
using DTLib.Extensions;
|
||||||
|
using DTLib.Logging;
|
||||||
|
|
||||||
namespace Meum.Client;
|
namespace Meum.Client;
|
||||||
|
|
||||||
public class ServerConnection : IDisposable
|
public class ServerConnection : IAsyncDisposable
|
||||||
{
|
{
|
||||||
|
private readonly QuicConnection _quicConnection;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public DnsEndPoint ServerEndPoint { get; }
|
public DnsEndPoint ServerEndPoint { get; }
|
||||||
|
|
||||||
private QuicConnection? _quicConnection;
|
|
||||||
|
|
||||||
public ServerConnection(DnsEndPoint serverEndPoint)
|
public ServerConnection(QuicConnection quicConnection, DnsEndPoint serverEndPoint, ILogger logger)
|
||||||
{
|
{
|
||||||
ServerEndPoint = serverEndPoint;
|
ServerEndPoint = serverEndPoint;
|
||||||
|
_quicConnection = quicConnection;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ConnectAsync()
|
|
||||||
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
_quicConnection = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions
|
return _quicConnection.RemoteEndPoint.GetHashCode();
|
||||||
{
|
|
||||||
RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, Network.ServerPortDefault),
|
|
||||||
DefaultStreamErrorCode = Network.DefaultStreamErrorCode,
|
|
||||||
DefaultCloseErrorCode = Network.DefaultCloseErrorCode,
|
|
||||||
ClientAuthenticationOptions = new SslClientAuthenticationOptions
|
|
||||||
{
|
|
||||||
ApplicationProtocols = Network.ApplicationProtocols.ToList(),
|
|
||||||
TargetHost = ServerEndPoint.Host
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PingAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
var stream = await _quicConnection!.OpenOutboundStreamAsync(QuicStreamType.Bidirectional);
|
await _quicConnection.DisposeAsync();
|
||||||
await stream.WriteAsync("Ping\n".ToBytes());
|
|
||||||
StreamReader reader = new StreamReader(stream);
|
|
||||||
string line = await reader.ReadLineAsync() ?? String.Empty;
|
|
||||||
Console.WriteLine(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_quicConnection?.DisposeAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
20
Meum.Core/Constants.cs
Normal file
20
Meum.Core/Constants.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
global using System;
|
||||||
|
global using System.Collections.Generic;
|
||||||
|
global using System.Threading;
|
||||||
|
global using System.Threading.Tasks;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Security;
|
||||||
|
|
||||||
|
namespace Meum.Core;
|
||||||
|
|
||||||
|
public static class Constants
|
||||||
|
{
|
||||||
|
public static readonly List<SslApplicationProtocol> ApplicationProtocols =
|
||||||
|
[
|
||||||
|
new("Meum-1")
|
||||||
|
];
|
||||||
|
|
||||||
|
public const int ServerPortDefault = 9320;
|
||||||
|
public const long DefaultStreamErrorCode = 0xA;
|
||||||
|
public const long DefaultCloseErrorCode = 0xB;
|
||||||
|
}
|
||||||
@ -1,24 +1,9 @@
|
|||||||
global using System;
|
using System.Net;
|
||||||
global using System.Collections.Generic;
|
|
||||||
global using System.Threading;
|
|
||||||
global using System.Threading.Tasks;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Security;
|
|
||||||
|
|
||||||
namespace Meum.Core;
|
namespace Meum.Core;
|
||||||
|
|
||||||
public static class Network
|
public static class Functions
|
||||||
{
|
{
|
||||||
public static readonly SslApplicationProtocol[] ApplicationProtocols =
|
|
||||||
[
|
|
||||||
new("Meum-1")
|
|
||||||
];
|
|
||||||
|
|
||||||
public const int ServerPortDefault = 9320;
|
|
||||||
public const long DefaultStreamErrorCode = 0xA;
|
|
||||||
public const long DefaultCloseErrorCode = 0xB;
|
|
||||||
|
|
||||||
|
|
||||||
public static bool IsValidDomainName(string name)
|
public static bool IsValidDomainName(string name)
|
||||||
{
|
{
|
||||||
return Uri.CheckHostName(name) != UriHostNameType.Unknown;
|
return Uri.CheckHostName(name) != UriHostNameType.Unknown;
|
||||||
@ -32,7 +17,7 @@ public static class Network
|
|||||||
if (colon_index == -1)
|
if (colon_index == -1)
|
||||||
{
|
{
|
||||||
host = address_str;
|
host = address_str;
|
||||||
port = ServerPortDefault;
|
port = Constants.ServerPortDefault;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
9
Meum.Core/Messages/AuthorizationRequest.cs
Normal file
9
Meum.Core/Messages/AuthorizationRequest.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Meum.Core.Messages;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public record struct AuthorizationRequest(byte[] hash)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
28
Meum.Core/Messages/CodeMessage.cs
Normal file
28
Meum.Core/Messages/CodeMessage.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Meum.Core.Messages;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct CodeMessage
|
||||||
|
{
|
||||||
|
// 0xb65d6d - meum in 6-bit ascii encoding
|
||||||
|
// 02 - CodeMessage
|
||||||
|
private const uint correct_magic = 0xb65d6d01;
|
||||||
|
|
||||||
|
/// warning: check with <see cref="ThrowIfInvalid"/>
|
||||||
|
public uint magic;
|
||||||
|
/// warning: can be any int
|
||||||
|
public MessageTypeCode type_code;
|
||||||
|
|
||||||
|
public CodeMessage(MessageTypeCode t)
|
||||||
|
{
|
||||||
|
magic = correct_magic;
|
||||||
|
type_code = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ThrowIfInvalid()
|
||||||
|
{
|
||||||
|
if(magic != correct_magic)
|
||||||
|
throw new Exception($"Invalid CodeMessage magic: {magic}");
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Meum.Core/Messages/DataMessageHeader.cs
Normal file
34
Meum.Core/Messages/DataMessageHeader.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Meum.Core.Messages;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct DataMessageHeader
|
||||||
|
{
|
||||||
|
// 0xb65d6d - meum in 6-bit ascii encoding
|
||||||
|
// 02 - DataMessageHeader
|
||||||
|
private const uint correct_magic = 0xb65d6d02;
|
||||||
|
|
||||||
|
/// warning: check with <see cref="ThrowIfInvalid"/>
|
||||||
|
public uint magic;
|
||||||
|
/// warning: can be any int
|
||||||
|
public MessageTypeCode type_code;
|
||||||
|
/// warning: check with <see cref="ThrowIfInvalid"/>
|
||||||
|
public int data_size;
|
||||||
|
|
||||||
|
|
||||||
|
public DataMessageHeader(MessageTypeCode t, int size)
|
||||||
|
{
|
||||||
|
magic = correct_magic;
|
||||||
|
type_code = t;
|
||||||
|
data_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ThrowIfInvalid()
|
||||||
|
{
|
||||||
|
if(magic != correct_magic)
|
||||||
|
throw new Exception($"Invalid DataMessageHeader magic: {magic}");
|
||||||
|
if(data_size < 1)
|
||||||
|
throw new Exception($"Invalid DataMessageHeader data size: {data_size}");
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Meum.Core/Messages/MessageTypeCode.cs
Normal file
8
Meum.Core/Messages/MessageTypeCode.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Meum.Core.Messages;
|
||||||
|
|
||||||
|
public enum MessageTypeCode
|
||||||
|
{
|
||||||
|
Ping, Pong,
|
||||||
|
RegistrationRequest, RegistrationResponse,
|
||||||
|
AuthorizationRequest, AuthorizationResponse,
|
||||||
|
}
|
||||||
@ -1,4 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Unofficial.MsQuic" Version="2.4.6" />
|
<PackageReference Include="Unofficial.MsQuic" Version="2.4.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
85
Meum.Core/QuicStreamWrapper.cs
Normal file
85
Meum.Core/QuicStreamWrapper.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
using System.Buffers;
|
||||||
|
using System.Net.Quic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Meum.Core.Messages;
|
||||||
|
|
||||||
|
namespace Meum.Core;
|
||||||
|
|
||||||
|
public class QuicStreamWrapper : IAsyncDisposable
|
||||||
|
{
|
||||||
|
private QuicStream _stream;
|
||||||
|
|
||||||
|
public QuicStreamWrapper(QuicStream stream)
|
||||||
|
{
|
||||||
|
_stream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<T> ReadStructAsync<T>(CancellationToken ct = default)
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(Marshal.SizeOf(typeof(T)));
|
||||||
|
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _stream.ReadExactlyAsync(buffer, ct);
|
||||||
|
return (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T))!;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
handle.Free();
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask WriteStructAsync<T>(T msg_struct, CancellationToken ct = default)
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(Marshal.SizeOf(typeof(T)));
|
||||||
|
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Marshal.StructureToPtr(msg_struct, handle.AddrOfPinnedObject(), false);
|
||||||
|
return _stream.WriteAsync(buffer, ct);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
handle.Free();
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken ct = default)
|
||||||
|
=> _stream.ReadAsync(buffer, ct);
|
||||||
|
|
||||||
|
public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken ct = default)
|
||||||
|
=> _stream.WriteAsync(buffer, ct);
|
||||||
|
|
||||||
|
public ValueTask WriteCodeMessageAsync(MessageTypeCode messageTypeCode, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
CodeMessage m = new CodeMessage(messageTypeCode);
|
||||||
|
return WriteStructAsync(m, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<MessageTypeCode> ReadCodeMessageAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
CodeMessage m = await ReadStructAsync<CodeMessage>(ct);
|
||||||
|
m.ThrowIfInvalid();
|
||||||
|
return m.type_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<DataMessageHeader> ReadDataMessageHeaderAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var m = await ReadStructAsync<DataMessageHeader>(ct);
|
||||||
|
m.ThrowIfInvalid();
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
// public async ValueTask<>
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
_stream.Close();
|
||||||
|
await _stream.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -26,7 +26,7 @@ public class UserAddress
|
|||||||
throw new FormatException($"Invalid user name '{Name}' in address '{addrstr}'");
|
throw new FormatException($"Invalid user name '{Name}' in address '{addrstr}'");
|
||||||
string serverstr = addrstr.Substring(at_index + 1);
|
string serverstr = addrstr.Substring(at_index + 1);
|
||||||
|
|
||||||
RegistrationServer = Network.ParseDnsEndPoint(serverstr);
|
RegistrationServer = Functions.ParseDnsEndPoint(serverstr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => Full;
|
public override string ToString() => Full;
|
||||||
|
|||||||
61
Meum.Server/ClientConnection.cs
Normal file
61
Meum.Server/ClientConnection.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Net.Quic;
|
||||||
|
using DTLib.Logging;
|
||||||
|
using Meum.Core.Messages;
|
||||||
|
|
||||||
|
namespace Meum.Server;
|
||||||
|
|
||||||
|
public class ClientConnection : IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly QuicConnection _quicConnection;
|
||||||
|
private QuicStreamWrapper? _systemStream;
|
||||||
|
private ILogger _logger;
|
||||||
|
|
||||||
|
public ClientConnection(QuicConnection quicConnection, ILogger logger)
|
||||||
|
{
|
||||||
|
_quicConnection = quicConnection;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<QuicStreamWrapper> AcceptStreamAsync(QuicStreamType streamType, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var s = await _quicConnection.AcceptInboundStreamAsync(ct);
|
||||||
|
if (s.Type != streamType)
|
||||||
|
throw new Exception($"Accepted stream type is invalid: {s.Type} instead of {streamType}");
|
||||||
|
var w = new QuicStreamWrapper(s);
|
||||||
|
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void HandleClientRequestsAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if(_systemStream != null)
|
||||||
|
throw new Exception("Already connected to client");
|
||||||
|
|
||||||
|
_systemStream = await AcceptStreamAsync(QuicStreamType.Bidirectional, ct);
|
||||||
|
|
||||||
|
// receive "Ping"
|
||||||
|
if(await _systemStream.ReadCodeMessageAsync(ct) != MessageTypeCode.Ping)
|
||||||
|
throw new Exception("Failed to test application protocol");
|
||||||
|
// send "Pong"
|
||||||
|
await _systemStream.WriteCodeMessageAsync(MessageTypeCode.Pong, ct);
|
||||||
|
|
||||||
|
DataMessageHeader header = await _systemStream.ReadDataMessageHeaderAsync(ct);
|
||||||
|
switch (header.type_code)
|
||||||
|
{
|
||||||
|
case MessageTypeCode.RegistrationRequest:
|
||||||
|
|
||||||
|
break;
|
||||||
|
case MessageTypeCode.AuthorizationRequest:
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception($"New connection sent unexpected message: {header.type_code}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await _quicConnection.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +0,0 @@
|
|||||||
using Meum.Core;
|
|
||||||
|
|
||||||
namespace Meum.Server;
|
|
||||||
|
|
||||||
public class Config
|
|
||||||
{
|
|
||||||
public string listener_ip { get; set; } = "127.0.0.1";
|
|
||||||
public int listener_port { get; set; } = Network.ServerPortDefault;
|
|
||||||
public string certificate_path { get; set; } = "self-signed.pem";
|
|
||||||
public string? key_path { get; set; } = "self-signed.key";
|
|
||||||
}
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@ -9,5 +10,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DTLib" Version="1.6.0" />
|
<PackageReference Include="DTLib" Version="1.6.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -8,57 +8,39 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Quic;
|
using System.Net.Quic;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using DTLib.Console;
|
using DTLib.Console;
|
||||||
using DTLib.Demystifier;
|
using DTLib.Demystifier;
|
||||||
using DTLib.Extensions;
|
using DTLib.Extensions;
|
||||||
|
using DTLib.Filesystem;
|
||||||
|
using DTLib.Logging;
|
||||||
|
|
||||||
namespace Meum.Server;
|
namespace Meum.Server;
|
||||||
|
|
||||||
class Program
|
static class Program
|
||||||
{
|
{
|
||||||
|
static readonly IOPath config_path = "Meum.Server.config.json";
|
||||||
|
|
||||||
static async Task Main(string[] args)
|
static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var config = new Config();
|
if (!QuicConnection.IsSupported)
|
||||||
var certificate = X509Certificate2.CreateFromPemFile(config.certificate_path, config.key_path);
|
throw new Exception("Quic is not supported, check for presence of libmsquic and openssl");
|
||||||
if(!certificate.Verify())
|
var config = ServerConfig.LoadOrCreate(config_path);
|
||||||
throw new Exception("Certificate is not valid");
|
var logger = new ConsoleLogger();
|
||||||
var serverConnectionOptions = new QuicServerConnectionOptions
|
var server = new Server(config, logger);
|
||||||
{
|
await server.ListenAsync();
|
||||||
DefaultStreamErrorCode = Network.DefaultStreamErrorCode,
|
|
||||||
DefaultCloseErrorCode = Network.DefaultCloseErrorCode,
|
|
||||||
ServerAuthenticationOptions = new SslServerAuthenticationOptions
|
|
||||||
{
|
|
||||||
ApplicationProtocols = Network.ApplicationProtocols.ToList(),
|
|
||||||
ServerCertificate = certificate,
|
|
||||||
ClientCertificateRequired = false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var listenerOptions = new QuicListenerOptions
|
|
||||||
{
|
|
||||||
ListenEndPoint = new IPEndPoint(IPAddress.Parse(config.listener_ip), config.listener_port),
|
|
||||||
ApplicationProtocols = Network.ApplicationProtocols.ToList(),
|
|
||||||
ConnectionOptionsCallback = (_, _, _) => ValueTask.FromResult(serverConnectionOptions)
|
|
||||||
};
|
|
||||||
var listener = await QuicListener.ListenAsync(listenerOptions);
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var conn = await listener.AcceptConnectionAsync();
|
var conn = await server.AcceptConnectionAsync();
|
||||||
var stream = await conn.AcceptInboundStreamAsync();
|
|
||||||
StreamReader reader = new(stream);
|
|
||||||
string line = await reader.ReadLineAsync() ?? "";
|
|
||||||
Console.WriteLine(line);
|
|
||||||
await stream.WriteAsync("Pong\n".ToBytes());
|
|
||||||
await conn.CloseAsync(Network.DefaultCloseErrorCode);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ColoredConsole.WriteLine(ex.ToStringDemystified(), ConsoleColor.Red);
|
logger.LogError("Main", ex.ToStringDemystified());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,5 +48,9 @@ class Program
|
|||||||
{
|
{
|
||||||
ColoredConsole.WriteLine(ex.ToStringDemystified(), ConsoleColor.Red);
|
ColoredConsole.WriteLine(ex.ToStringDemystified(), ConsoleColor.Red);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Console.ResetColor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
## Create self-signed certificate
|
## Create self-signed certificate
|
||||||
```sh
|
```sh
|
||||||
dotnet dev-certs https -ep bin/Debug/net8.0/self-signed.pfx --trust --format PEM --no-password
|
dotnet dev-certs https -ep bin/Debug/net8.0/self-signed.pem --trust --format PEM --no-password
|
||||||
```
|
```
|
||||||
62
Meum.Server/Server.cs
Normal file
62
Meum.Server/Server.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Quic;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using DTLib.Logging;
|
||||||
|
|
||||||
|
namespace Meum.Server;
|
||||||
|
|
||||||
|
public class Server
|
||||||
|
{
|
||||||
|
private readonly ServerConfig _config;
|
||||||
|
private readonly X509Certificate _certificate;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private QuicListener? _listener;
|
||||||
|
|
||||||
|
|
||||||
|
public Server(ServerConfig config, ILogger logger, X509Certificate? certificate = null)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
_logger = logger;
|
||||||
|
_certificate = certificate ??
|
||||||
|
X509Certificate2.CreateFromPemFile(config.certificate_path, config.key_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask ListenAsync()
|
||||||
|
{
|
||||||
|
var serverConnectionOptions = new QuicServerConnectionOptions
|
||||||
|
{
|
||||||
|
DefaultStreamErrorCode = Constants.DefaultStreamErrorCode,
|
||||||
|
DefaultCloseErrorCode = Constants.DefaultCloseErrorCode,
|
||||||
|
ServerAuthenticationOptions = new SslServerAuthenticationOptions
|
||||||
|
{
|
||||||
|
ApplicationProtocols = Constants.ApplicationProtocols,
|
||||||
|
ServerCertificate = _certificate,
|
||||||
|
ClientCertificateRequired = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var listenerOptions = new QuicListenerOptions
|
||||||
|
{
|
||||||
|
ListenEndPoint = new IPEndPoint(IPAddress.Parse(_config.listener_ip), _config.listener_port),
|
||||||
|
ApplicationProtocols = Constants.ApplicationProtocols,
|
||||||
|
ConnectionOptionsCallback = (_, _, _) => ValueTask.FromResult(serverConnectionOptions)
|
||||||
|
};
|
||||||
|
_listener = await QuicListener.ListenAsync(listenerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<ClientConnection> AcceptConnectionAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (_listener == null)
|
||||||
|
throw new Exception("Server is not listening");
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
var quicConnection = await _listener.AcceptConnectionAsync(ct);
|
||||||
|
var clientConnection = new ClientConnection(quicConnection, _logger);
|
||||||
|
await clientConnection.HandleClientRequestsAsync(ct);
|
||||||
|
return clientConnection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Meum.Server/ServerConfig.cs
Normal file
36
Meum.Server/ServerConfig.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using DTLib.Filesystem;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Meum.Server;
|
||||||
|
|
||||||
|
public class ServerConfig
|
||||||
|
{
|
||||||
|
public string listener_ip { get; set; } = "127.0.0.1";
|
||||||
|
public int listener_port { get; set; } = Constants.ServerPortDefault;
|
||||||
|
public string certificate_path { get; set; } = "self-signed.pem";
|
||||||
|
public string? key_path { get; set; } = "self-signed.key";
|
||||||
|
|
||||||
|
public void Save(IOPath file_path)
|
||||||
|
{
|
||||||
|
string serialized = JsonConvert.SerializeObject(this);
|
||||||
|
if (string.IsNullOrEmpty(serialized))
|
||||||
|
throw new Exception("can't serialize config");
|
||||||
|
File.WriteAllText(file_path, serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerConfig LoadOrCreate(IOPath file_path)
|
||||||
|
{
|
||||||
|
if(File.Exists(file_path))
|
||||||
|
{
|
||||||
|
string serialized = File.ReadAllText(file_path);
|
||||||
|
return JsonConvert.DeserializeObject<ServerConfig>(serialized)
|
||||||
|
?? throw new Exception("can't deserialize config");
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = new ServerConfig();
|
||||||
|
c.Save(file_path);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
Meum.sln
1
Meum.sln
@ -9,6 +9,7 @@ EndProject
|
|||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution_items", "solution_items", "{9375C0E5-DB78-4E77-869B-F9F7DC2651D1}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution_items", "solution_items", "{9375C0E5-DB78-4E77-869B-F9F7DC2651D1}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
Directory.Build.props = Directory.Build.props
|
Directory.Build.props = Directory.Build.props
|
||||||
|
.gitignore = .gitignore
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meum.Client", "Meum.Client\Meum.Client.csproj", "{6DADE8A1-B363-4888-BB9B-72282B9AC769}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meum.Client", "Meum.Client\Meum.Client.csproj", "{6DADE8A1-B363-4888-BB9B-72282B9AC769}"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user