diff --git a/Luski.net/API.cs b/Luski.net/API.cs index 951adbe..7cc78c5 100644 --- a/Luski.net/API.cs +++ b/Luski.net/API.cs @@ -15,24 +15,19 @@ public class API public async Task 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 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)) { diff --git a/Luski.net/JsonTypes/LocalServerInfo.cs b/Luski.net/JsonTypes/LocalServerInfo.cs new file mode 100644 index 0000000..8489c80 --- /dev/null +++ b/Luski.net/JsonTypes/LocalServerInfo.cs @@ -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(); +} + +[JsonSerializable(typeof(LocalServerInfo))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class LocalServerInfoContext : JsonSerializerContext +{ + +} \ No newline at end of file diff --git a/Luski.net/JsonTypes/ServerData.cs b/Luski.net/JsonTypes/ServerData.cs new file mode 100644 index 0000000..3e25f0b --- /dev/null +++ b/Luski.net/JsonTypes/ServerData.cs @@ -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 +{ + +} \ No newline at end of file diff --git a/Luski.net/JsonTypes/ServerInfo.cs b/Luski.net/JsonTypes/ServerInfo.cs index 04685a8..f341418 100644 --- a/Luski.net/JsonTypes/ServerInfo.cs +++ b/Luski.net/JsonTypes/ServerInfo.cs @@ -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))] diff --git a/Luski.net/Luski.net.csproj b/Luski.net/Luski.net.csproj index a003ead..62ec8b5 100755 --- a/Luski.net/Luski.net.csproj +++ b/Luski.net/Luski.net.csproj @@ -13,7 +13,7 @@ https://github.com/JacobTech-com/Luski.net True 1.0.0 - 2.0.0-alpha12 + 2.0.0-alpha23 diff --git a/Luski.net/PublicServer.cs b/Luski.net/PublicServer.cs index fffe07a..1539d63 100644 --- a/Luski.net/PublicServer.cs +++ b/Luski.net/PublicServer.cs @@ -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 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 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 GetCategory(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; } } \ No newline at end of file diff --git a/Luski.net/Server.Encryption.cs b/Luski.net/Server.Encryption.cs index ef0820d..0cfa80c 100644 --- a/Luski.net/Server.Encryption.cs +++ b/Luski.net/Server.Encryption.cs @@ -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) diff --git a/Luski.net/Server.Globals.cs b/Luski.net/Server.Globals.cs index a39d109..27893e5 100644 --- a/Luski.net/Server.Globals.cs +++ b/Luski.net/Server.Globals.cs @@ -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; diff --git a/Luski.net/Server.Storage.cs b/Luski.net/Server.Storage.cs index 80e079c..af191e4 100644 --- a/Luski.net/Server.Storage.cs +++ b/Luski.net/Server.Storage.cs @@ -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(StorageDirectory Directory, string Resource, bool CreateOnMissing, JsonTypeInfo 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); diff --git a/Luski.net/Server.cs b/Luski.net/Server.cs index 9f514ec..00ca01d 100644 --- a/Luski.net/Server.cs +++ b/Luski.net/Server.cs @@ -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; diff --git a/Luski.net/Structures/Main/MainSocketUserBase.cs b/Luski.net/Structures/Main/MainSocketUserBase.cs index 2892b77..bf3eb69 100644 --- a/Luski.net/Structures/Main/MainSocketUserBase.cs +++ b/Luski.net/Structures/Main/MainSocketUserBase.cs @@ -33,7 +33,6 @@ public class MainSocketUserBase : IncomingHTTP, IUser public Task 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) }}); } }