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)
{
DateTime dt = DateTime.UtcNow;
Console.WriteLine("Connecting to public server '{0}' using API {1}.", Domain, Version);
PublicServer s;
try
{
IEnumerable<PublicServer> isl = InternalServers.Where(a => (a.Domain == Domain && a.ApiVersion == Version));
if (isl.Any()) return isl.First();
s = new(Domain, Version, Secure)
{
ServerType = ServerType.Public
};
s = await PublicServer.GetServer(Domain, Version, Secure);
}
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;
}
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";
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 description { get; set; }
public long owner { get; set; }
[JsonInclude]
[JsonPropertyName("alternate_servers")]
public ServerData[] AlternateServers { get; set; } = default!;
}
[JsonSerializable(typeof(ServerInfo))]

View File

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

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
@ -29,14 +30,63 @@ public partial class PublicServer : Server
public List<Role> roles { get; } = new();
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)
{ }
internal static async Task<PublicServer> GetServer(string Domain, string API_Version, bool Secure = true)
{
ServerInfo si = GetFromServer("socketserver", ServerInfoContext.Default.ServerInfo, CancellationToken.None).Result;
Name = si.name;
Description = si.description;
wssurl = si.wssv4;
DateTime dt = DateTime.UtcNow;
Console.WriteLine("Connecting to public server '{0}' using API {1}.", Domain, API_Version);
PublicServer s = new(Domain, API_Version, Secure);
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()
@ -295,6 +345,6 @@ public partial class PublicServer : Server
return Task.CompletedTask;
}
public string Name { get; }
public string Description { get; }
public string Name { get; private set; }
public string Description { get; private set; }
}

View File

@ -12,12 +12,11 @@ public class ServerEncryption
{
internal bool Generating, Generated;
internal string ServerPublicKey = "", MyPublicKey = "", myPrivateKey = "", OfflinePrivateKey = "", OfflinePublicKey = "";
internal byte[] Hash = default!;
internal ServerEncryption(string Domain, string API_Version, ServerStorage Storage, bool Secure)
internal ServerEncryption(ServerStorage 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)

View File

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

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using JacobTechEncryption;
using JacobTechEncryption.Enums;
using Luski.net.Enums;
@ -73,6 +74,20 @@ public class ServerStorage
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)
{
try
@ -218,6 +233,11 @@ public class ServerStorage
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)
{
return Encoding.UTF8.GetString(Encryption.AES.Decrypt(GetResourceBytes(Directory, Resource), Key));
@ -283,6 +303,23 @@ public class ServerStorage
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)
{
File.WriteAllBytes(Location + Directories[(byte)Directory] + "/" + Resource, data);

View File

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

View File

@ -33,7 +33,6 @@ public class MainSocketUserBase : IncomingHTTP, IUser
public Task<PublicKeyInfo[]> GetUserKeys(CancellationToken CancellationToken)
{
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) }});
}
}