Ping! Pong!
This commit is contained in:
commit
6de712910f
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# Build results
|
||||
[Bb]in/
|
||||
.bin/
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
[Pp]ublish/
|
||||
|
||||
# IDE files
|
||||
.vs/
|
||||
.vscode/
|
||||
.vshistory/
|
||||
.idea/
|
||||
с/
|
||||
.editorconfig
|
||||
*.user
|
||||
|
||||
#backups
|
||||
.old*/
|
||||
12
Directory.Build.props
Normal file
12
Directory.Build.props
Normal file
@ -0,0 +1,12 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<EnablePreviewFeatures>True</EnablePreviewFeatures>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<!-- System.Net.Quic not officially supported on some platforms -->
|
||||
<NoWarn>CA1416</NoWarn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
14
Meum.Client.CLI/Meum.Client.CLI.csproj
Normal file
14
Meum.Client.CLI/Meum.Client.CLI.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>1.0.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Meum.Client\Meum.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DTLib" Version="1.6.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
83
Meum.Client.CLI/Program.cs
Normal file
83
Meum.Client.CLI/Program.cs
Normal file
@ -0,0 +1,83 @@
|
||||
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.Reflection;
|
||||
using DTLib.Console;
|
||||
using DTLib.Demystifier;
|
||||
using DTLib.Extensions;
|
||||
|
||||
namespace Meum.Client.CLI;
|
||||
|
||||
class Program
|
||||
{
|
||||
private const string greeting_art =
|
||||
"""
|
||||
^,,^ ╱|
|
||||
( •·•) Meum! (o.o`7
|
||||
/ ` | Meum... |`˜ \
|
||||
\(_,J J L l`,)/
|
||||
""";
|
||||
|
||||
private const string farewell_art =
|
||||
"""
|
||||
^,,^ ╱|
|
||||
( -.-) (>,<`7
|
||||
/ ` | Goodbye! |`˜ \
|
||||
\(_,J J L l`,)/
|
||||
""";
|
||||
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
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
|
||||
{
|
||||
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(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);
|
||||
var conn = await client.ConnectToServerAsync(serverEndPoint);
|
||||
await conn.PingAsync();
|
||||
await Task.Delay(-1);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ColoredConsole.WriteLine(ex.ToStringDemystified(), ConsoleColor.Red);
|
||||
}
|
||||
}
|
||||
// ColoredConsole.ResetColor();
|
||||
}
|
||||
}
|
||||
29
Meum.Client/Client.cs
Normal file
29
Meum.Client/Client.cs
Normal file
@ -0,0 +1,29 @@
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
global using Meum.Core;
|
||||
using System.Net;
|
||||
|
||||
namespace Meum.Client;
|
||||
|
||||
public class Client
|
||||
{
|
||||
private readonly HashSet<ServerConnection> _connectedServers = new();
|
||||
public IReadOnlySet<ServerConnection> ConnectedServers => _connectedServers;
|
||||
|
||||
public UserAddress Address { get; }
|
||||
|
||||
public Client(UserAddress address)
|
||||
{
|
||||
Address = address;
|
||||
}
|
||||
|
||||
public async Task<ServerConnection> ConnectToServerAsync(DnsEndPoint serverEndPoint)
|
||||
{
|
||||
var serv = new ServerConnection(serverEndPoint);
|
||||
await serv.ConnectAsync();
|
||||
_connectedServers.Add(serv);
|
||||
return serv;
|
||||
}
|
||||
}
|
||||
17
Meum.Client/Meum.Client.csproj
Normal file
17
Meum.Client/Meum.Client.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Meum.Core\Meum.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DTLib" Version="1.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
47
Meum.Client/ServerConnection.cs
Normal file
47
Meum.Client/ServerConnection.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System.Net;
|
||||
using System.Net.Quic;
|
||||
using System.Net.Security;
|
||||
using DTLib.Extensions;
|
||||
|
||||
namespace Meum.Client;
|
||||
|
||||
public class ServerConnection : IDisposable
|
||||
{
|
||||
public DnsEndPoint ServerEndPoint { get; }
|
||||
|
||||
private QuicConnection? _quicConnection;
|
||||
|
||||
public ServerConnection(DnsEndPoint serverEndPoint)
|
||||
{
|
||||
ServerEndPoint = serverEndPoint;
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
_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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task PingAsync()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
5
Meum.Core/Meum.Core.csproj
Normal file
5
Meum.Core/Meum.Core.csproj
Normal file
@ -0,0 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Unofficial.MsQuic" Version="2.4.6" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
47
Meum.Core/Network.cs
Normal file
47
Meum.Core/Network.cs
Normal file
@ -0,0 +1,47 @@
|
||||
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 Network
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public static DnsEndPoint ParseDnsEndPoint(string address_str)
|
||||
{
|
||||
string host;
|
||||
int port;
|
||||
int colon_index = address_str.IndexOf(':');
|
||||
if (colon_index == -1)
|
||||
{
|
||||
host = address_str;
|
||||
port = ServerPortDefault;
|
||||
}
|
||||
else
|
||||
{
|
||||
host = address_str.Substring(0, colon_index);
|
||||
port = Convert.ToInt32(address_str.Substring(colon_index + 1));
|
||||
}
|
||||
if(!IsValidDomainName(host))
|
||||
throw new ArgumentException($"Invalid domain name '{host}'");
|
||||
|
||||
return new DnsEndPoint(host, port);
|
||||
}
|
||||
}
|
||||
48
Meum.Core/UserAddress.cs
Normal file
48
Meum.Core/UserAddress.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System.Net;
|
||||
|
||||
namespace Meum.Core;
|
||||
|
||||
public class UserAddress
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly DnsEndPoint RegistrationServer;
|
||||
public readonly string Full;
|
||||
|
||||
public UserAddress(string name, DnsEndPoint registrationServer)
|
||||
{
|
||||
Name = name;
|
||||
RegistrationServer = registrationServer;
|
||||
Full = $"{name}@{registrationServer}";
|
||||
}
|
||||
|
||||
public UserAddress(string addrstr)
|
||||
{
|
||||
int at_index = addrstr.IndexOf('@');
|
||||
if(at_index == -1)
|
||||
throw new FormatException($"Invalid user address format '{addrstr}'");
|
||||
Full = addrstr;
|
||||
Name = addrstr.Substring(0, at_index);
|
||||
if(!ValidateName(Name))
|
||||
throw new FormatException($"Invalid user name '{Name}' in address '{addrstr}'");
|
||||
string serverstr = addrstr.Substring(at_index + 1);
|
||||
|
||||
RegistrationServer = Network.ParseDnsEndPoint(serverstr);
|
||||
}
|
||||
|
||||
public override string ToString() => Full;
|
||||
|
||||
public override int GetHashCode() => ToString().GetHashCode();
|
||||
|
||||
private static bool ValidateName(string? name)
|
||||
{
|
||||
if(string.IsNullOrEmpty(name))
|
||||
return false;
|
||||
foreach (char c in name)
|
||||
{
|
||||
if(!char.IsAsciiLetterOrDigit(c) && c != '.' && c != '-' && c != '_')
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
11
Meum.Server/Config.cs
Normal file
11
Meum.Server/Config.cs
Normal file
@ -0,0 +1,11 @@
|
||||
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";
|
||||
}
|
||||
13
Meum.Server/Meum.Server.csproj
Normal file
13
Meum.Server/Meum.Server.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Meum.Core\Meum.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DTLib" Version="1.6.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
70
Meum.Server/Program.cs
Normal file
70
Meum.Server/Program.cs
Normal file
@ -0,0 +1,70 @@
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
global using Meum.Core;
|
||||
using System.IO;
|
||||
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;
|
||||
|
||||
namespace Meum.Server;
|
||||
|
||||
class Program
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ColoredConsole.WriteLine(ex.ToStringDemystified(), ConsoleColor.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ColoredConsole.WriteLine(ex.ToStringDemystified(), ConsoleColor.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
4
Meum.Server/README.md
Normal file
4
Meum.Server/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
## Create self-signed certificate
|
||||
```sh
|
||||
dotnet dev-certs https -ep bin/Debug/net8.0/self-signed.pfx --trust --format PEM --no-password
|
||||
```
|
||||
39
Meum.sln
Normal file
39
Meum.sln
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meum.Server", "Meum.Server\Meum.Server.csproj", "{769D657F-488E-4794-AC12-F7894037CB84}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meum.Client.CLI", "Meum.Client.CLI\Meum.Client.CLI.csproj", "{1DA062F3-F4F6-4A67-AC66-281EEA7427C6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meum.Core", "Meum.Core\Meum.Core.csproj", "{2A31C5C6-A8A8-4C7F-B913-1F6BBD906743}"
|
||||
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
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meum.Client", "Meum.Client\Meum.Client.csproj", "{6DADE8A1-B363-4888-BB9B-72282B9AC769}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{769D657F-488E-4794-AC12-F7894037CB84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{769D657F-488E-4794-AC12-F7894037CB84}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{769D657F-488E-4794-AC12-F7894037CB84}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{769D657F-488E-4794-AC12-F7894037CB84}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1DA062F3-F4F6-4A67-AC66-281EEA7427C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1DA062F3-F4F6-4A67-AC66-281EEA7427C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1DA062F3-F4F6-4A67-AC66-281EEA7427C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1DA062F3-F4F6-4A67-AC66-281EEA7427C6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2A31C5C6-A8A8-4C7F-B913-1F6BBD906743}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2A31C5C6-A8A8-4C7F-B913-1F6BBD906743}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2A31C5C6-A8A8-4C7F-B913-1F6BBD906743}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2A31C5C6-A8A8-4C7F-B913-1F6BBD906743}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6DADE8A1-B363-4888-BB9B-72282B9AC769}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6DADE8A1-B363-4888-BB9B-72282B9AC769}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6DADE8A1-B363-4888-BB9B-72282B9AC769}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6DADE8A1-B363-4888-BB9B-72282B9AC769}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Loading…
Reference in New Issue
Block a user