From 02c162ee8f468515082917817135a864552621bd Mon Sep 17 00:00:00 2001 From: zerlei <1445089819@qq.com> Date: Wed, 31 Jul 2024 17:14:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20local=20server=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/Common/AES.cs | 128 ++++++++++++ Server/Common/Message.cs | 7 + .../Controllers/LocalServerController.cs | 14 ++ Server/LocalServer/LocalServer.csproj | 4 + Server/LocalServer/LocalSyncServer.cs | 95 ++++++++- Server/LocalServer/LocalSyncServerFactory.cs | 16 +- Server/LocalServer/Models/Config.cs | 32 +++ Server/LocalServer/StateHelper.cs | 189 ++++++++++++++++++ 8 files changed, 479 insertions(+), 6 deletions(-) create mode 100644 Server/Common/AES.cs create mode 100644 Server/Common/Message.cs create mode 100644 Server/LocalServer/Models/Config.cs create mode 100644 Server/LocalServer/StateHelper.cs diff --git a/Server/Common/AES.cs b/Server/Common/AES.cs new file mode 100644 index 0000000..2ba39cb --- /dev/null +++ b/Server/Common/AES.cs @@ -0,0 +1,128 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Common; + + +/// +/// 与目标服务器通信将会加密 +/// +public class AESHelper +{ + static readonly byte[] Key = + [ + 0x11, + 0xF2, + 0xAF, + 0xCF, + 0xFF, + 0x8B, + 0x4C, + 0x7D, + 0x23, + 0x96, + 0x1B, + 0x32, + 0x43, + 0xA4, + 0x55, + 0xF6, + 0x29, + 0x1C, + 0x1B, + 0x92, + 0x23, + 0x44, + 0xB5, + 0xF6, + ]; + static readonly byte[] IV = + [ + 0xD1, + 0xF7, + 0xAB, + 0xCA, + 0xBC, + 0x7B, + 0x2C, + 0x3D, + 0xFA, + 0xAA, + 0xFC, + 0xA8, + 0x28, + 0x19, + 0x9C, + 0xB6, + ]; + + public static byte[] EncryptStringToBytes_Aes(string plainText) + { + // Check arguments. + if (plainText == null || plainText.Length <= 0) + throw new ArgumentNullException(nameof(plainText), "can't be null"); + byte[] encrypted; + + // Create an Aes object + // with the specified key and IV. + using (Aes aesAlg = Aes.Create()) + { + aesAlg.Key = Key; + aesAlg.IV = IV; + + // Create an encryptor to perform the stream transform. + ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); + + // Create the streams used for encryption. + using MemoryStream msEncrypt = new(); + using CryptoStream csEncrypt = new( + msEncrypt, + encryptor, + CryptoStreamMode.Write + ); + using (StreamWriter swEncrypt = new(csEncrypt)) + { + //Write all data to the stream. + swEncrypt.Write(plainText); + } + encrypted = msEncrypt.ToArray(); + } + + // Return the encrypted bytes from the memory stream. + return encrypted; + } + + public static string DecryptStringFromBytes_Aes(byte[] cipherText) + { + // Check arguments. + if (cipherText == null || cipherText.Length <= 0) + throw new ArgumentNullException(nameof(cipherText), "can't be null"); + + // Declare the string used to hold + // the decrypted text. + string plaintext = string.Empty; + + // Create an Aes object + // with the specified key and IV. + using (Aes aesAlg = Aes.Create()) + { + aesAlg.Key = Key; + aesAlg.IV = IV; + + // Create a decryptor to perform the stream transform. + ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); + + // Create the streams used for decryption. + using MemoryStream msDecrypt = new(cipherText); + using CryptoStream csDecrypt = new(msDecrypt, decryptor, CryptoStreamMode.Read); + using StreamReader srDecrypt = new(csDecrypt); + // Read the decrypted bytes from the decrypting stream + // and place them in a string. + plaintext = srDecrypt.ReadToEnd(); + } + + return plaintext; + } +} diff --git a/Server/Common/Message.cs b/Server/Common/Message.cs new file mode 100644 index 0000000..ca076ef --- /dev/null +++ b/Server/Common/Message.cs @@ -0,0 +1,7 @@ +namespace Common; + +public class SyncMsg(bool isSuccess, string body) +{ + public bool IsSuccess { get; set; } = isSuccess; + public string Body { get; set; } = body; +} diff --git a/Server/LocalServer/Controllers/LocalServerController.cs b/Server/LocalServer/Controllers/LocalServerController.cs index 2f2f7a9..fe5a360 100644 --- a/Server/LocalServer/Controllers/LocalServerController.cs +++ b/Server/LocalServer/Controllers/LocalServerController.cs @@ -1,3 +1,4 @@ +using System.Net.NetworkInformation; using System.Text; using Microsoft.AspNetCore.Mvc; @@ -28,6 +29,19 @@ namespace LocalServer.Controllers HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest; } } + + [Route("/macaddr")] + public string GetMacAddress() + { + NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); + string macaddrs = ""; + foreach (NetworkInterface nic in nics) + { + PhysicalAddress physicalAddress = nic.GetPhysicalAddress(); + macaddrs += physicalAddress.ToString() + ";"; + } + return macaddrs; + } //TODO 是否在本地记载同步日志? } } diff --git a/Server/LocalServer/LocalServer.csproj b/Server/LocalServer/LocalServer.csproj index 9daa180..2f1bad2 100644 --- a/Server/LocalServer/LocalServer.csproj +++ b/Server/LocalServer/LocalServer.csproj @@ -10,4 +10,8 @@ + + + + diff --git a/Server/LocalServer/LocalSyncServer.cs b/Server/LocalServer/LocalSyncServer.cs index a55b662..c03b213 100644 --- a/Server/LocalServer/LocalSyncServer.cs +++ b/Server/LocalServer/LocalSyncServer.cs @@ -1,19 +1,108 @@ using System.Net.WebSockets; +using System.Text; +using System.Text.Json; +using Common; +using LocalServer.Models; namespace LocalServer; public class LocalSyncServer { - public readonly WebSocket Socket; + public StateHelpBase StateHelper; + + public Config? SyncConfig; + + /// + /// 发布源连接 + /// + public readonly WebSocket LocalSocket; + + /// + /// 发布源-缓冲区,存储数据 最大1MB + /// + public byte[] Buffer = new byte[1024 * 1024]; + + /// + /// 发布目标-连接 + /// + public readonly ClientWebSocket RemoteSocket = new(); + + /// + /// 发布开始时间 + /// + private readonly DateTime StartTime = DateTime.Now; + + /// + /// 发布名称 + /// public readonly string Name; + /// + /// 父工程,用于释放资源 + /// public readonly LocalSyncServerFactory Factory; public LocalSyncServer(WebSocket socket, string name, LocalSyncServerFactory factory) { - Socket = socket; + LocalSocket = socket; Name = name; Factory = factory; - + StateHelper = new LocalAuthorityState(this); } + + public async Task Start() + { + //最大1MB + var buffer = new byte[1024 * 1024]; + var receiveResult = await LocalSocket.ReceiveAsync( + new ArraySegment(buffer), + CancellationToken.None + ); + + while (!receiveResult.CloseStatus.HasValue) + { + await LocalSocket.SendAsync( + new ArraySegment(buffer, 0, receiveResult.Count), + receiveResult.MessageType, + receiveResult.EndOfMessage, + CancellationToken.None + ); + + receiveResult = await LocalSocket.ReceiveAsync( + new ArraySegment(buffer), + CancellationToken.None + ); + } + Factory.RemoveLocalSyncServer(this); + await LocalSocket.CloseAsync( + receiveResult.CloseStatus.Value, + receiveResult.CloseStatusDescription, + CancellationToken.None + ); + } + + public async void LocalSocketSendMsg(object msgOb) + { + string msg = JsonSerializer.Serialize(msgOb); + await LocalSocket.SendAsync( + new ArraySegment(Encoding.UTF8.GetBytes(msg)), + WebSocketMessageType.Text, + true, + CancellationToken.None + ); + } + + public async void RemoteSocketSendMsg(object msgOb) + { + string msg = JsonSerializer.Serialize(msgOb); + var buffer = AESHelper.EncryptStringToBytes_Aes(msg); + await RemoteSocket.SendAsync( + buffer, + WebSocketMessageType.Text, + true, + CancellationToken.None + ); + } + + public void Close() { } } diff --git a/Server/LocalServer/LocalSyncServerFactory.cs b/Server/LocalServer/LocalSyncServerFactory.cs index b06c64f..a273c83 100644 --- a/Server/LocalServer/LocalSyncServerFactory.cs +++ b/Server/LocalServer/LocalSyncServerFactory.cs @@ -4,19 +4,29 @@ namespace LocalServer; public class LocalSyncServerFactory { - public void CreateLocalSyncServer(WebSocket socket, string name) + private readonly object Lock = new(); + + public async void CreateLocalSyncServer(WebSocket socket, string name) { if (Servers.Select(x => x.Name == name).Any()) { throw new Exception("there already is a server with that name is Runing!"); } - Servers.Add(new LocalSyncServer(socket, name, this)); + var server = new LocalSyncServer(socket, name, this); + lock (Lock) + { + Servers.Add(server); + } + await server.Start(); } private readonly List Servers = []; public void RemoveLocalSyncServer(LocalSyncServer server) { - Servers.Remove(server); + lock (Lock) + { + Servers.Remove(server); + } } } diff --git a/Server/LocalServer/Models/Config.cs b/Server/LocalServer/Models/Config.cs new file mode 100644 index 0000000..222fa79 --- /dev/null +++ b/Server/LocalServer/Models/Config.cs @@ -0,0 +1,32 @@ +namespace LocalServer.Models; + +public class DirFileConfig +{ + /// + /// 本地-源根目录 + /// + public required string LocalRootPath { get; set; } + + /// + /// 远程-目标根目录 + /// + public required string RemoteRootPath { get; set; } + + /// + /// 排除的文件,它是根目录的相对路径 + /// + public List? ExcludeFiles { get; set; } + + /// + /// 除此外全部忽略,最高优先级,若有值,ExcludeFiles 将被忽略,它是根目录的相对路径 + /// + public List? CherryPickFiles { get; set; } +} + +public class Config +{ + public required string Name { get; set; } + public required string RemoteUrl { get; set; } + + public List? DirFileConfigs { get; set; } +} diff --git a/Server/LocalServer/StateHelper.cs b/Server/LocalServer/StateHelper.cs new file mode 100644 index 0000000..707f2f9 --- /dev/null +++ b/Server/LocalServer/StateHelper.cs @@ -0,0 +1,189 @@ +using System.Net.NetworkInformation; +using System.Net.WebSockets; +using System.Runtime.Intrinsics.Arm; +using System.Security.AccessControl; +using System.Text; +using System.Text.Json; +using Common; +using LocalServer.Models; + +namespace LocalServer; + +// enum StateWhenMsg +// { +// Authority = 0, +// ConfigInfo = 1, +// LocalPackAndUpload = 2, +// RemoteUnPackAndRelease = 3, +// } + +public abstract class StateHelpBase(LocalSyncServer context) +{ + protected readonly LocalSyncServer Context = context; + + public abstract void HandleRemoteMsg(SyncMsg? msg); + + public abstract void HandleLocalMsg(SyncMsg? msg); +} + +/// +/// 0. 发布源验证密码 +/// +/// +public class LocalAuthorityState(LocalSyncServer context) : StateHelpBase(context) +{ + public override void HandleRemoteMsg(SyncMsg? msg) + { + throw new NotImplementedException("error usage!"); + } + + public override void HandleLocalMsg(SyncMsg? msg) + { + if (msg == null) + { + return; + } + else + { + string Pwd = msg.Body; + if (Pwd == "Xfs1%$@_fdYU.>>") + { + Context.LocalSocketSendMsg(new SyncMsg(true, "源服务密码校验成功!")); + Context.StateHelper = new WaitingConfigInfoState(Context); + } + else + { + throw new UnauthorizedAccessException("pwd error!"); + } + } + } +} + +/// +/// 1. 获取配置信息,它包含目标的服务器的配置信息 +/// +/// +public class WaitingConfigInfoState(LocalSyncServer context) : StateHelpBase(context) +{ + public override void HandleRemoteMsg(SyncMsg? msg) { } + + public override void HandleLocalMsg(SyncMsg? msg) + { + if (msg == null) + { + return; + } + else + { + string ConfigInfo = msg.Body; + Context.SyncConfig = + JsonSerializer.Deserialize(ConfigInfo) + ?? throw new NullReferenceException("ConfigInfo is null"); + var task = Context.RemoteSocket.ConnectAsync( + new Uri(Context.SyncConfig.RemoteUrl), + CancellationToken.None + ); + if (task.Wait(10000)) + { + if (Context.RemoteSocket.State == WebSocketState.Open) + { + var state = new RemoteAuthorityState(Context); + state.SendPwdToRemoteServer(); + Context.StateHelper = state; + } + else + { + throw new Exception("connect remote server failed!"); + } + } + else + { + throw new TimeoutException("connect remote server timeout"); + } + } + } +} + +/// +/// 2. 目标服务器权限校验 +/// +/// +public class RemoteAuthorityState(LocalSyncServer context) : StateHelpBase(context) +{ + public override void HandleRemoteMsg(SyncMsg? msg) + { + if (msg == null) + { + return; + } + else { } + } + + public override void HandleLocalMsg(SyncMsg? msg) { } + + public void SendPwdToRemoteServer() + { + var authorityInfo = new + { + Pwd = "xfs@#123hd??1>>|12#4", + MacAdr = new LocalServer.Controllers.LocalServerController( + Context.Factory + ).GetMacAddress() + }; + Context.RemoteSocketSendMsg(authorityInfo); + } +} + +/// +/// 3. 文件比较 +/// +/// +public class DirFilesDiffState(LocalSyncServer context) : StateHelpBase(context) +{ + public override void HandleRemoteMsg(SyncMsg? msg) + { + if (msg == null) + { + return; + } + else { } + } + + public override void HandleLocalMsg(SyncMsg? msg) { } +} + +/// +/// 4. 本地打包并上传 +/// +/// +public class LocalPackAndUploadState(LocalSyncServer context) : StateHelpBase(context) +{ + public override void HandleRemoteMsg(SyncMsg? msg) + { + if (msg == null) + { + return; + } + else { } + } + + public override void HandleLocalMsg(SyncMsg? msg) { } +} + +/// +/// 5. 目标服务器解包并发布 +/// +/// +public class RemoteUnPackAndReleaseState(LocalSyncServer context) : StateHelpBase(context) +{ + public override void HandleRemoteMsg(SyncMsg? msg) + { + if (msg == null) + { + return; + } + else { } + } + + public override void HandleLocalMsg(SyncMsg? msg) { } +}