Skip to content

Commit

Permalink
Add janky connection stat tracking and UI labels.
Browse files Browse the repository at this point in the history
  • Loading branch information
Koi-3088 committed Jun 7, 2022
1 parent 85c6e6a commit 4ab1048
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 73 deletions.
29 changes: 22 additions & 7 deletions EtumrepMMO.Server/HostSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,36 @@

namespace EtumrepMMO.Server
{
public class HostSettings
public class ServerSettings
{
public override string ToString() => "Host Settings";
public override string ToString() => "Server Settings";
private const string Startup = nameof(Startup);

[Category(Startup), Description("Host port.")]
[Category(Startup), Description("Port.")]
public int Port { get; set; } = 80;

[Category(Startup), Description("Token for client authorization.")]
public string Token { get; set; } = string.Empty;

[Category(Startup), Description("Whitelisted clients' numerical Discord user IDs.")]
public List<string> HostWhitelist { get; set; } = new();
[Category(Startup), Description("Whitelisted clients (bot hosts).")]
public List<DiscordUser> HostWhitelist { get; set; } = new();

[Category(Startup), Description("Blacklisted users' numerical Discord IDs.")]
public List<string> UserBlacklist { get; set; } = new();
[Category(Startup), Description("Blacklisted users.")]
public List<DiscordUser> UserBlacklist { get; set; } = new();

[Category(Startup), Description("Connections accepted.")]
public int ConnectionsAccepted { get; set; }

[Category(Startup), Description("Users authenticated.")]
public int UsersAuthenticated { get; set; }

[Category(Startup), Description("EtumrepMMOs successfully run.")]
public int EtumrepsRun { get; set; }

public class DiscordUser
{
public string UserName { get; set; } = string.Empty;
public ulong ID { get; set; }
}
}
}
104 changes: 56 additions & 48 deletions EtumrepMMO.Server/ServerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,23 @@ namespace EtumrepMMO.Server
{
public class ServerConnection
{
private int Port { get; }
private string Token { get; }
private ServerSettings Settings { get; }
private TcpListener Listener { get; }
private bool IsStopped { get; set; }
private List<string> HostWhitelist { get; }
private List<string> UserBlacklist { get; }
private ConcurrentQueue<RemoteUser> UserQueue { get; set; } = new();
private static IProgress<(string, int, bool)> UserProgress { get; set; } = default!;
private static IProgress<(int, int, int)> Labels { get; set; } = default!;

private string CurrentSeedChecker { get; set; } = "Waiting for users...";
private bool IsStopped { get; set; }
private int Progress { get; set; }
private bool InQueue { get; set; }
private static IProgress<(string, int, bool)> UserProgress { get; set; } = default!;
private ConcurrentQueue<RemoteUser> UserQueue { get; set; } = new();

public ServerConnection(HostSettings settings, IProgress<(string, int, bool)> progress)
public ServerConnection(ServerSettings settings, IProgress<(string, int, bool)> progress, IProgress<(int, int, int)> labels)
{
Port = settings.Port;
Token = settings.Token;
HostWhitelist = settings.HostWhitelist;
UserBlacklist = settings.UserBlacklist;
Settings = settings;
UserProgress = progress;
Listener = new(IPAddress.Any, Port);
Labels = labels;
Listener = new(IPAddress.Any, settings.Port);
}

internal class RemoteUser
Expand All @@ -44,8 +41,7 @@ internal RemoteUser(TcpClient client, AuthenticatedStream stream)

public TcpClient Client { get; }
public AuthenticatedStream Stream { get; }
public string Name { get; set; } = string.Empty;
public string SeedCheckerName { get; set; } = string.Empty;
public UserAuth? UserAuth { get; set; } = new();
public bool IsAuthenticated { get; set; }
public byte[] Buffer { get; } = new byte[1504];

Expand All @@ -55,10 +51,10 @@ internal RemoteUser(TcpClient client, AuthenticatedStream stream)
internal class UserAuth
{
public string HostName { get; set; } = string.Empty;
public string HostID { get; set; } = string.Empty;
public ulong HostID { get; set; }
public string Token { get; set; } = string.Empty;
public string SeedCheckerName { get; set; } = string.Empty;
public string SeedCheckerID { get; set; } = string.Empty;
public ulong SeedCheckerID { get; set; }
}

public async Task Stop()
Expand All @@ -79,35 +75,38 @@ public async Task MainAsync(CancellationToken token)

_ = Task.Run(async () => await RemoteUserQueue(token).ConfigureAwait(false), token);
_ = Task.Run(async () => await ReportUserProgress(token).ConfigureAwait(false), token);
_ = Task.Run(async () => await UpdateLabels(token).ConfigureAwait(false), token);
LogUtil.Log("Server initialized, waiting for connections...", "[TCP Listener]");

while (!token.IsCancellationRequested)
{
if (!Listener.Pending() || UserQueue.Count >= 100)
if (!Listener.Pending())
{
await Task.Delay(0_250, token).ConfigureAwait(false);
continue;
}

LogUtil.Log("A user is attempting to connect, authenticating connection...", "[TCP Listener]");
TcpClient remoteClient = await Listener.AcceptTcpClientAsync(token).ConfigureAwait(false);
Settings.ConnectionsAccepted += 1;
LogUtil.Log("A user has connected, authenticating the user...", "[TCP Listener]");

RemoteUser user = await AuthenticateConnection(remoteClient).ConfigureAwait(false);
if (!user.IsAuthenticated)
RemoteUser? user = await AuthenticateConnection(remoteClient).ConfigureAwait(false);
if (user is null || !user.IsAuthenticated)
{
DisposeStream(user);
continue;
}

var userAuth = await AuthenticateUser(user, token).ConfigureAwait(false);
if (userAuth is null)
user.UserAuth = await AuthenticateUser(user, token).ConfigureAwait(false);
if (user.UserAuth is null)
{
DisposeStream(user);
continue;
}
Settings.UsersAuthenticated += 1;

LogUtil.Log($"{user.Name} was successfully authenticated, enqueueing...", "[TCP Listener]");
LogUtil.Log($"{user.UserAuth.HostName} {(user.UserAuth.HostID)} was successfully authenticated, enqueueing...", "[TCP Listener]");
UserQueue.Enqueue(user);
}
}
Expand All @@ -117,16 +116,16 @@ private async Task RemoteUserQueue(CancellationToken token)
while (!token.IsCancellationRequested)
{
UserQueue.TryDequeue(out var user);
if (user is not null)
if (user is not null && user.UserAuth is not null)
{
CurrentSeedChecker = user.SeedCheckerName;
CurrentSeedChecker = user.UserAuth.SeedCheckerName;
Progress = 10;
InQueue = true;

LogUtil.Log($"{user.Name}: Attempting to read PKM data from {user.SeedCheckerName}.", "[UserQueue]");
LogUtil.Log($"{user.UserAuth.HostName}: Attempting to read PKM data from {user.UserAuth.SeedCheckerName}.", "[User Queue]");
if (!user.Stream.CanRead)
{
LogUtil.Log($"{user.Name}: Unable to read stream.", "[UserQueue]");
LogUtil.Log($"{user.UserAuth.HostName}: Unable to read stream.", "[User Queue]");
DisposeStream(user);
continue;
}
Expand All @@ -136,33 +135,33 @@ private async Task RemoteUserQueue(CancellationToken token)

if (read is 0 || count is < 2)
{
LogUtil.Log($"{user.Name}: Received an incorrect amount of data from {user.SeedCheckerName}.", "[UserQueue]");
LogUtil.Log($"{user.UserAuth.HostName}: Received an incorrect amount of data from {user.UserAuth.SeedCheckerName}.", "[User Queue]");
DisposeStream(user);
continue;
}

Progress = 30;
LogUtil.Log($"{user.Name}: Beginning seed calculation for {user.SeedCheckerName}...", "[UserQueue]");
LogUtil.Log($"{user.UserAuth.HostName}: Beginning seed calculation for {user.UserAuth.SeedCheckerName}...", "[User Queue]");

var sw = new Stopwatch();
sw.Start();
var seed = EtumrepUtil.CalculateSeed(user.Buffer, count);
sw.Stop();

Progress = 80;
LogUtil.Log($"{user.Name}: Seed ({seed}) calculation for {user.SeedCheckerName} complete ({sw.Elapsed}). Attempting to send the result...", "[UserQueue]");
LogUtil.Log($"{user.UserAuth.HostName}: Seed ({seed}) calculation for {user.UserAuth.SeedCheckerName} complete ({sw.Elapsed}). Attempting to send the result...", "[User Queue]");

if (!user.Stream.CanWrite)
{
LogUtil.Log($"{user.Name}: Unable to write to stream.", "[UserQueue]");
LogUtil.Log($"{user.UserAuth.HostName}: Unable to write to stream.", "[User Queue]");
DisposeStream(user);
continue;
}

var bytes = BitConverter.GetBytes(seed);
await user.Stream.WriteAsync(bytes, token).ConfigureAwait(false);

LogUtil.Log($"{user.Name}: Sent results to {user.Name}, removing from queue.", "[UserQueue]");
Settings.EtumrepsRun += 1;
LogUtil.Log($"{user.UserAuth.HostName}: Results were sent, removing from queue.", "[User Queue]");

DisposeStream(user);
Progress = 100;
Expand All @@ -176,14 +175,14 @@ private async Task RemoteUserQueue(CancellationToken token)
}
}

private static async Task<RemoteUser> AuthenticateConnection(TcpClient client)
private static async Task<RemoteUser?> AuthenticateConnection(TcpClient client)
{
var stream = client.GetStream();
var authStream = new NegotiateStream(stream, false);
var user = new RemoteUser(client, authStream);

try
{
var stream = client.GetStream();
var authStream = new NegotiateStream(stream, false);
var user = new RemoteUser(client, authStream);

await authStream.AuthenticateAsServerAsync().ConfigureAwait(false);
user.IsAuthenticated = true;
LogUtil.Log("Initial authentication complete.", "[Connection Authentication]");
Expand All @@ -192,7 +191,7 @@ private static async Task<RemoteUser> AuthenticateConnection(TcpClient client)
catch (Exception ex)
{
LogUtil.Log($"Failed to authenticate user.\n{ex.Message}", "[Connection Authentication]");
return user;
return null;
}
}

Expand All @@ -214,25 +213,22 @@ private static async Task<RemoteUser> AuthenticateConnection(TcpClient client)
LogUtil.Log("User did not send an authentication packet.", "[User Authentication]");
return null;
}
else if (!HostWhitelist.Contains(authObj.HostID))
else if (!Settings.HostWhitelist.Exists(x => x.ID == authObj.HostID))
{
LogUtil.Log($"{authObj.HostName} ({authObj.HostID}) is not a whitelisted bot host.", "[User Authentication]");
return null;
}
else if (UserBlacklist.Contains(authObj.SeedCheckerID))
else if (Settings.UserBlacklist.Exists(x => x.ID == authObj.SeedCheckerID))
{
LogUtil.Log($"{authObj.SeedCheckerName} ({authObj.SeedCheckerID}) is a blacklisted user.", "[User Authentication]");
return null;
}
else if (authObj.Token != Token)
else if (Settings.Token != authObj.Token)
{
LogUtil.Log($"The provided token ({authObj.Token}) does not match the token defined by us.", "[User Authentication]");
return null;
}

user.Name = authObj.HostName;
user.SeedCheckerName = authObj.SeedCheckerName;

// Send confirmation to client.
var bytes = BitConverter.GetBytes(true);
await user.Stream.WriteAsync(bytes, token).ConfigureAwait(false);
Expand All @@ -254,10 +250,22 @@ private async Task ReportUserProgress(CancellationToken token)
}
}

private static void DisposeStream(RemoteUser user)
private async Task UpdateLabels(CancellationToken token)
{
user.Client.Dispose();
user.Stream.Dispose();
while (!token.IsCancellationRequested)
{
await Task.Delay(0_100, token).ConfigureAwait(false);
Labels.Report((Settings.ConnectionsAccepted, Settings.UsersAuthenticated, Settings.EtumrepsRun));
}
}

private static void DisposeStream(RemoteUser? user)
{
if (user is not null)
{
user.Client.Dispose();
user.Stream.Dispose();
}
}
}
}
Loading

0 comments on commit 4ab1048

Please sign in to comment.