diff --git a/.gitignore b/.gitignore index c25fee1..c6d6aae 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,12 @@ *.user #backups -.old*/ \ No newline at end of file +.old*/ + +#secrets +*.pem +*.key +*.csr +*.crt +*.pfx +*.config.json diff --git a/Meum.Client.CLI/Meum.Client.CLI.csproj b/Meum.Client.CLI/Meum.Client.CLI.csproj index 836657c..903d585 100644 --- a/Meum.Client.CLI/Meum.Client.CLI.csproj +++ b/Meum.Client.CLI/Meum.Client.CLI.csproj @@ -1,5 +1,6 @@  + net9.0 Exe 1.0.0 diff --git a/Meum.Client.CLI/Program.cs b/Meum.Client.CLI/Program.cs index f6b04ec..c4ede7c 100644 --- a/Meum.Client.CLI/Program.cs +++ b/Meum.Client.CLI/Program.cs @@ -4,6 +4,7 @@ global using System.Threading; global using System.Threading.Tasks; global using Meum.Core; using System.Net; +using System.Net.Quic; using System.Reflection; using DTLib.Console; using DTLib.Demystifier; @@ -34,50 +35,69 @@ class Program Console.OutputEncoding = StringConverter.UTF8; Console.InputEncoding = StringConverter.UTF8; ColoredConsole.ResetColor(); - var v = Assembly.GetExecutingAssembly().GetName().Version; - 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 { - try + var v = Assembly.GetExecutingAssembly().GetName().Version; + string title = $"Meum CLI v{v?.ToString(3) ?? "Null"}"; + Console.Title = title; + + if (!QuicConnection.IsSupported) + throw new Exception("Quic is not supported, check for presence of libmsquic and openssl"); + + 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) { - if(userAddress == null) + try { - 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)) + if (userAddress == null) { - ColoredConsole.WriteLine("null address", ConsoleColor.Red); - continue; + var addrstr = + ColoredConsole.ReadLine("enter user address (name@server.xyz)", ConsoleColor.Blue); + if (string.IsNullOrEmpty(addrstr)) + continue; + userAddress = new(addrstr); } - serverEndPoint = Network.ParseDnsEndPoint(serverAddress); - ColoredConsole.WriteTitle(serverAddress, fg: ConsoleColor.Cyan); + + 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.WriteLine("connecting to the server...", ConsoleColor.Blue); - var conn = await client.ConnectToServerAsync(serverEndPoint); - 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(); + } } } diff --git a/Meum.Client/Client.cs b/Meum.Client/Client.cs index 7589d66..ce186d4 100644 --- a/Meum.Client/Client.cs +++ b/Meum.Client/Client.cs @@ -1,29 +1,58 @@ global using System; global using System.Collections.Generic; -global using System.Threading; global using System.Threading.Tasks; global using Meum.Core; using System.Net; +using System.Net.Quic; +using System.Net.Security; +using DTLib.Logging; namespace Meum.Client; public class Client { private readonly HashSet _connectedServers = new(); + private readonly ILogger _logger; + + public Client(ILogger logger) + { + _logger = logger; + } + public IReadOnlySet ConnectedServers => _connectedServers; - public UserAddress Address { get; } - - public Client(UserAddress address) + public UserAddress? Address { get; private set; } + + public Task RegisterAsync(UserAddress address) { + return Task.CompletedTask; + } + + public Task LogInAsync(UserAddress address) + { + if(Address != null) + throw new InvalidOperationException("Already logged in"); Address = address; + return Task.CompletedTask; } public async Task ConnectToServerAsync(DnsEndPoint serverEndPoint) { - var serv = new ServerConnection(serverEndPoint); - await serv.ConnectAsync(); - _connectedServers.Add(serv); + var quicConn = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions + { + // 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; } } \ No newline at end of file diff --git a/Meum.Client/Meum.Client.csproj b/Meum.Client/Meum.Client.csproj index b511aa1..42a8226 100644 --- a/Meum.Client/Meum.Client.csproj +++ b/Meum.Client/Meum.Client.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable enable @@ -12,6 +12,7 @@ + diff --git a/Meum.Client/ServerConnection.cs b/Meum.Client/ServerConnection.cs index 9305677..51903a3 100644 --- a/Meum.Client/ServerConnection.cs +++ b/Meum.Client/ServerConnection.cs @@ -2,46 +2,33 @@ using System.Net.Quic; using System.Net.Security; using DTLib.Extensions; +using DTLib.Logging; namespace Meum.Client; -public class ServerConnection : IDisposable +public class ServerConnection : IAsyncDisposable { + private readonly QuicConnection _quicConnection; + private readonly ILogger _logger; + public DnsEndPoint ServerEndPoint { get; } - private QuicConnection? _quicConnection; - - public ServerConnection(DnsEndPoint serverEndPoint) + + public ServerConnection(QuicConnection quicConnection, DnsEndPoint serverEndPoint, ILogger logger) { ServerEndPoint = serverEndPoint; + _quicConnection = quicConnection; + _logger = logger; } - public async Task ConnectAsync() + + public override int GetHashCode() { - _quicConnection = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions - { - RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, Network.ServerPortDefault), - DefaultStreamErrorCode = Network.DefaultStreamErrorCode, - DefaultCloseErrorCode = Network.DefaultCloseErrorCode, - ClientAuthenticationOptions = new SslClientAuthenticationOptions - { - ApplicationProtocols = Network.ApplicationProtocols.ToList(), - TargetHost = ServerEndPoint.Host - } - }); + return _quicConnection.RemoteEndPoint.GetHashCode(); } - public async Task PingAsync() + public async ValueTask DisposeAsync() { - var stream = await _quicConnection!.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); - 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(); + await _quicConnection.DisposeAsync(); } } \ No newline at end of file diff --git a/Meum.Core/Constants.cs b/Meum.Core/Constants.cs new file mode 100644 index 0000000..f64031d --- /dev/null +++ b/Meum.Core/Constants.cs @@ -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 ApplicationProtocols = + [ + new("Meum-1") + ]; + + public const int ServerPortDefault = 9320; + public const long DefaultStreamErrorCode = 0xA; + public const long DefaultCloseErrorCode = 0xB; +} \ No newline at end of file diff --git a/Meum.Core/Network.cs b/Meum.Core/Functions.cs similarity index 57% rename from Meum.Core/Network.cs rename to Meum.Core/Functions.cs index c0a72c7..7fe6ab7 100644 --- a/Meum.Core/Network.cs +++ b/Meum.Core/Functions.cs @@ -1,24 +1,9 @@ -global using System; -global using System.Collections.Generic; -global using System.Threading; -global using System.Threading.Tasks; -using System.Net; -using System.Net.Security; +using System.Net; 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) { return Uri.CheckHostName(name) != UriHostNameType.Unknown; @@ -32,7 +17,7 @@ public static class Network if (colon_index == -1) { host = address_str; - port = ServerPortDefault; + port = Constants.ServerPortDefault; } else { diff --git a/Meum.Core/Messages/AuthorizationRequest.cs b/Meum.Core/Messages/AuthorizationRequest.cs new file mode 100644 index 0000000..658f24c --- /dev/null +++ b/Meum.Core/Messages/AuthorizationRequest.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Meum.Core.Messages; + +[StructLayout(LayoutKind.Sequential)] +public record struct AuthorizationRequest(byte[] hash) +{ + +} \ No newline at end of file diff --git a/Meum.Core/Messages/CodeMessage.cs b/Meum.Core/Messages/CodeMessage.cs new file mode 100644 index 0000000..3c56e9d --- /dev/null +++ b/Meum.Core/Messages/CodeMessage.cs @@ -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 + 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}"); + } +} diff --git a/Meum.Core/Messages/DataMessageHeader.cs b/Meum.Core/Messages/DataMessageHeader.cs new file mode 100644 index 0000000..8a5f0de --- /dev/null +++ b/Meum.Core/Messages/DataMessageHeader.cs @@ -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 + public uint magic; + /// warning: can be any int + public MessageTypeCode type_code; + /// warning: check with + 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}"); + } +} diff --git a/Meum.Core/Messages/MessageTypeCode.cs b/Meum.Core/Messages/MessageTypeCode.cs new file mode 100644 index 0000000..6d304fa --- /dev/null +++ b/Meum.Core/Messages/MessageTypeCode.cs @@ -0,0 +1,8 @@ +namespace Meum.Core.Messages; + +public enum MessageTypeCode +{ + Ping, Pong, + RegistrationRequest, RegistrationResponse, + AuthorizationRequest, AuthorizationResponse, +} \ No newline at end of file diff --git a/Meum.Core/Meum.Core.csproj b/Meum.Core/Meum.Core.csproj index 0d100d8..f835e5a 100644 --- a/Meum.Core/Meum.Core.csproj +++ b/Meum.Core/Meum.Core.csproj @@ -1,4 +1,8 @@  + + net9.0 + true + diff --git a/Meum.Core/QuicStreamWrapper.cs b/Meum.Core/QuicStreamWrapper.cs new file mode 100644 index 0000000..d6fce98 --- /dev/null +++ b/Meum.Core/QuicStreamWrapper.cs @@ -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 ReadStructAsync(CancellationToken ct = default) + where T : struct + { + byte[] buffer = ArrayPool.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.Shared.Return(buffer); + } + } + + public ValueTask WriteStructAsync(T msg_struct, CancellationToken ct = default) + where T : struct + { + byte[] buffer = ArrayPool.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.Shared.Return(buffer); + } + } + + public ValueTask ReadAsync(Memory buffer, CancellationToken ct = default) + => _stream.ReadAsync(buffer, ct); + + public ValueTask WriteAsync(ReadOnlyMemory 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 ReadCodeMessageAsync(CancellationToken ct = default) + { + CodeMessage m = await ReadStructAsync(ct); + m.ThrowIfInvalid(); + return m.type_code; + } + + public async ValueTask ReadDataMessageHeaderAsync(CancellationToken ct = default) + { + var m = await ReadStructAsync(ct); + m.ThrowIfInvalid(); + return m; + } + + //TODO + // public async ValueTask<> + + public async ValueTask DisposeAsync() + { + _stream.Close(); + await _stream.DisposeAsync(); + } +} \ No newline at end of file diff --git a/Meum.Core/UserAddress.cs b/Meum.Core/UserAddress.cs index e56434e..421251b 100644 --- a/Meum.Core/UserAddress.cs +++ b/Meum.Core/UserAddress.cs @@ -26,7 +26,7 @@ public class UserAddress throw new FormatException($"Invalid user name '{Name}' in address '{addrstr}'"); string serverstr = addrstr.Substring(at_index + 1); - RegistrationServer = Network.ParseDnsEndPoint(serverstr); + RegistrationServer = Functions.ParseDnsEndPoint(serverstr); } public override string ToString() => Full; diff --git a/Meum.Server/ClientConnection.cs b/Meum.Server/ClientConnection.cs new file mode 100644 index 0000000..e34de14 --- /dev/null +++ b/Meum.Server/ClientConnection.cs @@ -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 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(); + } +} \ No newline at end of file diff --git a/Meum.Server/Config.cs b/Meum.Server/Config.cs deleted file mode 100644 index dff99c2..0000000 --- a/Meum.Server/Config.cs +++ /dev/null @@ -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"; -} \ No newline at end of file diff --git a/Meum.Server/Meum.Server.csproj b/Meum.Server/Meum.Server.csproj index 34bd7d1..2dbf323 100644 --- a/Meum.Server/Meum.Server.csproj +++ b/Meum.Server/Meum.Server.csproj @@ -1,5 +1,6 @@  + net9.0 Exe @@ -9,5 +10,6 @@ + diff --git a/Meum.Server/Program.cs b/Meum.Server/Program.cs index 3d2f318..0da3f41 100644 --- a/Meum.Server/Program.cs +++ b/Meum.Server/Program.cs @@ -8,57 +8,39 @@ using System.Linq; using System.Net; using System.Net.Quic; using System.Net.Security; -using System.Runtime.Serialization; using System.Security.Cryptography.X509Certificates; using DTLib.Console; using DTLib.Demystifier; using DTLib.Extensions; +using DTLib.Filesystem; +using DTLib.Logging; namespace Meum.Server; -class Program +static class Program { + static readonly IOPath config_path = "Meum.Server.config.json"; + static async Task Main(string[] args) { try { - var config = new Config(); - var certificate = X509Certificate2.CreateFromPemFile(config.certificate_path, config.key_path); - if(!certificate.Verify()) - throw new Exception("Certificate is not valid"); - var serverConnectionOptions = new QuicServerConnectionOptions - { - 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); + if (!QuicConnection.IsSupported) + throw new Exception("Quic is not supported, check for presence of libmsquic and openssl"); + var config = ServerConfig.LoadOrCreate(config_path); + var logger = new ConsoleLogger(); + var server = new Server(config, logger); + await server.ListenAsync(); while (true) { try { - var conn = await listener.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); + var conn = await server.AcceptConnectionAsync(); + } 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); } + finally + { + Console.ResetColor(); + } } } \ No newline at end of file diff --git a/Meum.Server/README.md b/Meum.Server/README.md index f9885a0..5a0ecc0 100644 --- a/Meum.Server/README.md +++ b/Meum.Server/README.md @@ -1,4 +1,4 @@ ## Create self-signed certificate ```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 ``` \ No newline at end of file diff --git a/Meum.Server/Server.cs b/Meum.Server/Server.cs new file mode 100644 index 0000000..52c4e5e --- /dev/null +++ b/Meum.Server/Server.cs @@ -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 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; + } + } +} \ No newline at end of file diff --git a/Meum.Server/ServerConfig.cs b/Meum.Server/ServerConfig.cs new file mode 100644 index 0000000..83c8494 --- /dev/null +++ b/Meum.Server/ServerConfig.cs @@ -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(serialized) + ?? throw new Exception("can't deserialize config"); + } + + var c = new ServerConfig(); + c.Save(file_path); + return c; + } +} \ No newline at end of file diff --git a/Meum.sln b/Meum.sln index fffdd67..f89bef3 100644 --- a/Meum.sln +++ b/Meum.sln @@ -9,6 +9,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution_items", "solution_items", "{9375C0E5-DB78-4E77-869B-F9F7DC2651D1}" ProjectSection(SolutionItems) = preProject Directory.Build.props = Directory.Build.props + .gitignore = .gitignore EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meum.Client", "Meum.Client\Meum.Client.csproj", "{6DADE8A1-B363-4888-BB9B-72282B9AC769}"