Alternate servers.

On load, the client can find alternate servers to connect to.
This commit is contained in:
JacobTech 2023-08-25 12:07:36 -04:00
parent 9f641e7429
commit 0349745944
11 changed files with 151 additions and 23 deletions

View File

@ -15,24 +15,19 @@ public class API
public async Task<PublicServer> GetPublicServer(string Domain, string Version = "v1", bool Secure = true) public async Task<PublicServer> GetPublicServer(string Domain, string Version = "v1", bool Secure = true)
{ {
DateTime dt = DateTime.UtcNow;
Console.WriteLine("Connecting to public server '{0}' using API {1}.", Domain, Version);
PublicServer s; PublicServer s;
try try
{ {
IEnumerable<PublicServer> isl = InternalServers.Where(a => (a.Domain == Domain && a.ApiVersion == Version)); IEnumerable<PublicServer> isl = InternalServers.Where(a => (a.Domain == Domain && a.ApiVersion == Version));
if (isl.Any()) return isl.First(); if (isl.Any()) return isl.First();
s = new(Domain, Version, Secure) s = await PublicServer.GetServer(Domain, Version, Secure);
{
ServerType = ServerType.Public
};
} }
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine("Failed to connect to public server '{0}' using API {1}.", Domain, Version); Console.WriteLine("Failed to connect to public server '{0}' using API {1}. No alternate server was found.", Domain, Version);
throw; throw;
} }
Console.WriteLine("Connected to public server '{0}' using API {1} in {4}.\nServer Name: {2}\nServer Description: {3}", Domain, Version, s.Name, s.Description, DateTime.UtcNow.Subtract(dt).ToString("g"));
string? f = s.Storage.GetStorageDirectory(StorageDirectory.StorageInfo) + "token"; string? f = s.Storage.GetStorageDirectory(StorageDirectory.StorageInfo) + "token";
if (File.Exists(f)) if (File.Exists(f))
{ {

View File

@ -0,0 +1,21 @@
using System;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes;
public class LocalServerInfo
{
[JsonInclude]
[JsonPropertyName("alternate_servers")]
public ServerData[] AlternateServers { get; set; } = Array.Empty<ServerData>();
}
[JsonSerializable(typeof(LocalServerInfo))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class LocalServerInfoContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,23 @@
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes;
public class ServerData
{
[JsonInclude]
[JsonPropertyName("address")]
public string DomainAndPort = default!;
[JsonInclude]
[JsonPropertyName("secure")]
public bool Secure;
}
[JsonSerializable(typeof(ServerData))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class ServerDataContext : JsonSerializerContext
{
}

View File

@ -9,6 +9,10 @@ public class ServerInfo : IncomingHTTP
public string wssv4 { get; set; } public string wssv4 { get; set; }
public string description { get; set; } public string description { get; set; }
public long owner { get; set; } public long owner { get; set; }
[JsonInclude]
[JsonPropertyName("alternate_servers")]
public ServerData[] AlternateServers { get; set; } = default!;
} }
[JsonSerializable(typeof(ServerInfo))] [JsonSerializable(typeof(ServerInfo))]

View File

@ -13,7 +13,7 @@
<RepositoryUrl>https://github.com/JacobTech-com/Luski.net</RepositoryUrl> <RepositoryUrl>https://github.com/JacobTech-com/Luski.net</RepositoryUrl>
<IncludeSymbols>True</IncludeSymbols> <IncludeSymbols>True</IncludeSymbols>
<FileVersion>1.0.0</FileVersion> <FileVersion>1.0.0</FileVersion>
<Version>2.0.0-alpha12</Version> <Version>2.0.0-alpha23</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Text; using System.Text;
using System.Text.Json.Serialization.Metadata; using System.Text.Json.Serialization.Metadata;
using System.Threading; using System.Threading;
@ -29,14 +30,63 @@ public partial class PublicServer : Server
public List<Role> roles { get; } = new(); public List<Role> roles { get; } = new();
public SocketAppUser User { get; private set; } = null!; public SocketAppUser User { get; private set; } = null!;
internal PublicServer(string Domain, string API_Version, bool Secure = true): private PublicServer(string Domain, string API_Version, bool Secure = true) :
base(Domain, API_Version, Secure) base(Domain, API_Version, Secure)
{ }
internal static async Task<PublicServer> GetServer(string Domain, string API_Version, bool Secure = true)
{ {
ServerInfo si = GetFromServer("socketserver", ServerInfoContext.Default.ServerInfo, CancellationToken.None).Result; DateTime dt = DateTime.UtcNow;
Name = si.name; Console.WriteLine("Connecting to public server '{0}' using API {1}.", Domain, API_Version);
Description = si.description; PublicServer s = new(Domain, API_Version, Secure);
wssurl = si.wssv4; ServerInfo? si = null;
try
{
si = await s.GetFromServer("socketserver", ServerInfoContext.Default.ServerInfo, CancellationToken.None);
s.EncryptionHandler.ServerPublicKey = await (await new HttpClient()
.GetAsync($"{(s.Secure ? "https" : "http")}://{s.Domain}/{s.ApiVersion}/Keys/PublicKey"))
.Content
.ReadAsStringAsync();
}
catch (Exception e)
{
LocalServerInfo ServerListing = s.Storage.GetJson(StorageDirectory.ServerInfo, "Servers.json", true,
LocalServerInfoContext.Default.LocalServerInfo);
if (ServerListing.AlternateServers.Length > 0)
{
Console.WriteLine("Failed to connect to public server '{0}' using API {1}. Attempting to connect to alternate servers.", Domain, API_Version);
foreach (ServerData Server in ServerListing.AlternateServers)
{
s.Secure = Server.Secure;
s.Domain = Server.DomainAndPort;
try
{
si = await s.GetFromServer("socketserver", ServerInfoContext.Default.ServerInfo, CancellationToken.None);
s.EncryptionHandler.ServerPublicKey = await (await new HttpClient()
.GetAsync($"{(s.Secure ? "https" : "http")}://{s.Domain}/{s.ApiVersion}/Keys/PublicKey"))
.Content
.ReadAsStringAsync();
Console.WriteLine("Public server '{0}' connection restored by alternate server '{1}' using API {2}.", Domain, s.Domain, API_Version);
break;
}
catch
{
// ignored
}
}
}
if (si is null) throw;
}
s.Name = si.name;
s.Description = si.description;
s.wssurl = si.wssv4;
s.ServerType = ServerType.Public;
Console.WriteLine("Connected to public server '{0}' using API {1} in {4}.\nServer Name: {2}\nServer Description: {3}", Domain, API_Version, s.Name, s.Description, DateTime.UtcNow.Subtract(dt).ToString("g"));
return s;
} }
public async Task<TCategory> GetCategory<TCategory>(long id, CancellationToken CancellationToken) where TCategory : SocketCategory, new() public async Task<TCategory> GetCategory<TCategory>(long id, CancellationToken CancellationToken) where TCategory : SocketCategory, new()
@ -295,6 +345,6 @@ public partial class PublicServer : Server
return Task.CompletedTask; return Task.CompletedTask;
} }
public string Name { get; } public string Name { get; private set; }
public string Description { get; } public string Description { get; private set; }
} }

View File

@ -12,12 +12,11 @@ public class ServerEncryption
{ {
internal bool Generating, Generated; internal bool Generating, Generated;
internal string ServerPublicKey = "", MyPublicKey = "", myPrivateKey = "", OfflinePrivateKey = "", OfflinePublicKey = ""; internal string ServerPublicKey = "", MyPublicKey = "", myPrivateKey = "", OfflinePrivateKey = "", OfflinePublicKey = "";
internal byte[] Hash = default!; internal byte[] Hash = default!;
internal ServerEncryption(string Domain, string API_Version, ServerStorage Storage, bool Secure) internal ServerEncryption(ServerStorage Storage)
{ {
this.Storage = Storage; this.Storage = Storage;
ServerPublicKey = new HttpClient().GetAsync($"{(Secure ? "https" : "http" )}://{Domain}/{API_Version}/Keys/PublicKey").Result.Content
.ReadAsStringAsync().Result;
} }
public string GetChannelKey(long Channel) public string GetChannelKey(long Channel)

View File

@ -14,7 +14,7 @@ namespace Luski.net;
public partial class Server public partial class Server
{ {
public ServerType ServerType { get; internal set; } = ServerType.Public; public ServerType ServerType { get; internal set; } = ServerType.Public;
public string Domain { get; } = default!; public string Domain { get; set; } = default!;
public string ApiVersion { get; } = "v1"; public string ApiVersion { get; } = "v1";
internal WebSocket? ServerOut; internal WebSocket? ServerOut;
internal string? Token = null, Error = null, gen = null; internal string? Token = null, Error = null, gen = null;

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using JacobTechEncryption; using JacobTechEncryption;
using JacobTechEncryption.Enums; using JacobTechEncryption.Enums;
using Luski.net.Enums; using Luski.net.Enums;
@ -73,6 +74,20 @@ public class ServerStorage
internal ServerStorageInfo RawInfo { get; } internal ServerStorageInfo RawInfo { get; }
public TResult GetJson<TResult>(StorageDirectory Directory, string Resource, bool CreateOnMissing, JsonTypeInfo<TResult> JsonInfo) where TResult : new()
{
string FilePath = GetResourceDirectory(Directory, Resource);
if (!File.Exists(FilePath))
{
TResult res = new();
File.WriteAllText(FilePath, JsonSerializer.Serialize(res, JsonInfo));
return res;
}
Stream s = GetResourceStream(FilePath);
return JsonSerializer.Deserialize(s, JsonInfo)!;
}
public byte[] UpdateStorage(byte[] OldPassword) public byte[] UpdateStorage(byte[] OldPassword)
{ {
try try
@ -218,6 +233,11 @@ public class ServerStorage
return Location + Directories[(byte)Directory] + "/"; return Location + Directories[(byte)Directory] + "/";
} }
public string GetResourceDirectory(StorageDirectory Directory, string Resourse)
{
return Location + Directories[(byte)Directory] + "/" + Resourse;
}
public string GetResourceKey(StorageDirectory Directory, string Resource, string Key) public string GetResourceKey(StorageDirectory Directory, string Resource, string Key)
{ {
return Encoding.UTF8.GetString(Encryption.AES.Decrypt(GetResourceBytes(Directory, Resource), Key)); return Encoding.UTF8.GetString(Encryption.AES.Decrypt(GetResourceBytes(Directory, Resource), Key));
@ -283,6 +303,23 @@ public class ServerStorage
return ms; return ms;
} }
public Stream GetResourceStream(string dir)
{
byte[] buffer = new byte[16 * 1024];
MemoryStream ms = new();
using (FileStream r = File.OpenRead(dir))
{
int readBytes;
while ((readBytes = r.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, readBytes);
}
}
ms.Position = 0;
return ms;
}
public void SetResourceBytes(StorageDirectory Directory, string Resource, byte[] data) public void SetResourceBytes(StorageDirectory Directory, string Resource, byte[] data)
{ {
File.WriteAllBytes(Location + Directories[(byte)Directory] + "/" + Resource, data); File.WriteAllBytes(Location + Directories[(byte)Directory] + "/" + Resource, data);

View File

@ -25,7 +25,7 @@ public partial class Server
this.ApiVersion = API_Version; this.ApiVersion = API_Version;
this.Secure = Secure; this.Secure = Secure;
Storage = new(Domain); Storage = new(Domain);
EncryptionHandler = new(Domain, API_Version, Storage, this.Secure); EncryptionHandler = new(Storage);
} }
internal bool Secure = true; internal bool Secure = true;

View File

@ -33,7 +33,6 @@ public class MainSocketUserBase : IncomingHTTP, IUser
public Task<PublicKeyInfo[]> GetUserKeys(CancellationToken CancellationToken) public Task<PublicKeyInfo[]> GetUserKeys(CancellationToken CancellationToken)
{ {
string data = Server.GetFromServer($"Keys/GetUserKey/{Id}", CancellationToken).Content.ReadAsStringAsync().Result; string data = Server.GetFromServer($"Keys/GetUserKey/{Id}", CancellationToken).Content.ReadAsStringAsync().Result;
//return Task.FromResult(new long[] {long.Parse(data)});
return Task.FromResult(new[] { new PublicKeyInfo() { Id = long.Parse(data) }}); return Task.FromResult(new[] { new PublicKeyInfo() { Id = long.Parse(data) }});
} }
} }