diff --git a/.gitignore b/.gitignore index 4f61c89..a1f4567 100644 --- a/.gitignore +++ b/.gitignore @@ -362,5 +362,4 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd -/TeraIO.Test/ -/TeraIO.Test/* +/TeraIO.Test/Program.cs \ No newline at end of file diff --git a/Extension/ExtensionMethods.cs b/Extension/ExtensionMethods.cs index 44daf5c..2affebc 100644 --- a/Extension/ExtensionMethods.cs +++ b/Extension/ExtensionMethods.cs @@ -30,5 +30,13 @@ public static void Merge(this ICollection left, IEnumerable right) left.Add(item); } } + + public static void ForEach(this IEnumerable values, Action action) + { + foreach (T item in values) + { + action(item); + } + } } } diff --git a/Network/Http/HttpServerAppBase.cs b/Network/Http/HttpServerAppBase.cs index d7193a0..b877178 100644 --- a/Network/Http/HttpServerAppBase.cs +++ b/Network/Http/HttpServerAppBase.cs @@ -6,7 +6,6 @@ using System.Text; using System.Threading.Tasks; using TeraIO.Extension; -using TeraIO.Runnable; namespace TeraIO.Network.Http { @@ -14,7 +13,7 @@ namespace TeraIO.Network.Http /// 返回的 HttpServer 实例。调用 来启动一个简单的 Http 服务器 /// 正常情况下,你不应该创建这个类的实例,而是由 创建或者通过它的子类来获得它的实例! /// - public class HttpServerAppBase : RunnableBase + public class HttpServerAppBase { public List UriPrefixes { get; set; } @@ -22,7 +21,7 @@ public class HttpServerAppBase : RunnableBase protected HttpListener listener; - public HttpServerAppBase(Dictionary? methods = null, ILoggerBuilder? loggerBuilder = null) : base(loggerBuilder) + public HttpServerAppBase(Dictionary? methods = null) : base() { if (methods == null) { @@ -57,7 +56,7 @@ protected void LoadNew() this.methods = methods; } - protected override int Run(string[] args) + protected int Run(string[] args) { int result = 0; this.LoadNew(); diff --git a/Network/Http/SimpleWebServer.cs b/Network/Http/SimpleWebServer.cs index ab63eff..07be982 100644 --- a/Network/Http/SimpleWebServer.cs +++ b/Network/Http/SimpleWebServer.cs @@ -7,15 +7,14 @@ using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Text; +using TeraIO.Extension; using System.Threading.Tasks; -using TeraIO.Runnable; using System.Text.RegularExpressions; -using TeraIO.Extension; using System.Diagnostics; namespace CSharpOpenBMCLAPI.Modules.WebServer { - public class SimpleWebServer : RunnableBase + public class SimpleWebServer { private int Port = 0; // TCP 随机端口 private readonly X509Certificate2? _certificate; // SSL证书 @@ -28,7 +27,7 @@ public SimpleWebServer(int port, X509Certificate2? certificate) _certificate = certificate; } - protected override int Run(string[] args) + protected int Run(string[] args) { while (true) { @@ -61,7 +60,7 @@ protected async Task AsyncRun() tcpClient = await listener.AcceptTcpClientAsync(); _ = Task.Run(() => HandleRequest(tcpClient)); } - catch (Exception ex) + catch { if (tcpClient != null && tcpClient.Connected) { @@ -144,7 +143,7 @@ private static void PrintBytes(byte[] bytes) private static void PrintArrayBytes(byte[][] bytes) { - bytes.ForEach(e => PrintBytes(e)); + bytes.ForEach(PrintBytes); } private static string ByteToString(byte hex) diff --git a/Network/RsaStream.cs b/Network/RsaStream.cs new file mode 100644 index 0000000..14efd05 --- /dev/null +++ b/Network/RsaStream.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +using System; +using System.IO; + +public class RsaStream : Stream +{ + protected readonly Stream _stream; + protected RSA _rsaPrivate; + protected RSA _rsaPublic; + protected RSAParameters _publicKey; + protected RSA? _remotePublicKey; + protected ushort _protocolVersion = 1; + + public RsaStream(Stream stream) + { + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + if (!_stream.CanRead || !_stream.CanWrite) + { + throw new InvalidOperationException("Stream must be readable and writable."); + } + _rsaPrivate = RSA.Create(); + _rsaPublic = RSA.Create(); + _publicKey = _rsaPrivate.ExportParameters(false); + _rsaPublic.ImportParameters(_publicKey); + } + + public void Handshake() + { + // Send public key + byte[] publicKeyBytes = _rsaPublic.ExportRSAPublicKey(); + _stream.Write(publicKeyBytes, 0, publicKeyBytes.Length); + _stream.Flush(); + + // Receive remote public key + byte[] remotePublicKeyBytes = new byte[4096]; // Adjust size as needed + int bytesRead = _stream.Read(remotePublicKeyBytes, 0, remotePublicKeyBytes.Length); + if (bytesRead == 0) throw new IOException("Failed to read remote public key."); + + _remotePublicKey = RSA.Create(); + _remotePublicKey.ImportRSAPublicKey(remotePublicKeyBytes.AsSpan(0, bytesRead), out _); + + // Test encryption/decryption + byte[] helloBytes = Encoding.UTF8.GetBytes("RSA HELLO"); + byte[] encryptedHello = _remotePublicKey.Encrypt(helloBytes, RSAEncryptionPadding.OaepSHA256); + _stream.Write(encryptedHello, 0, encryptedHello.Length); + _stream.Flush(); + + byte[] responseBytes = new byte[4096]; // Adjust size as needed + int responseLength = _stream.Read(responseBytes, 0, responseBytes.Length); + if (responseLength == 0) throw new IOException("Failed to read response."); + byte[] decryptedResponse = _rsaPrivate.Decrypt(responseBytes.AsSpan(0, responseLength), RSAEncryptionPadding.OaepSHA256); + string decryptedResponseStr = Encoding.UTF8.GetString(decryptedResponse); + if (decryptedResponseStr != "RSA HELLO") throw new InvalidOperationException("Handshake failed."); + + // Send protocol version + byte[] versionBytes = BitConverter.GetBytes(_protocolVersion); + _stream.Write(versionBytes, 0, versionBytes.Length); + _stream.Flush(); + + // Receive protocol version + byte[] remoteVersionBytes = new byte[2]; + int versionLength = _stream.Read(remoteVersionBytes, 0, remoteVersionBytes.Length); + if (versionLength == 0) throw new IOException("Failed to read remote protocol version."); + ushort remoteVersion = BitConverter.ToUInt16(remoteVersionBytes); + if (remoteVersion != _protocolVersion) throw new InvalidOperationException("Protocol version mismatch."); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); + + // The max block size depends on the key size and padding. With OAEP and SHA-256, it's approximately: KeySize/8 - 2*HashSize - 2 + int maxBlockSize = _remotePublicKey!.KeySize / 8 - 2 * 32 - 2; + + using (var cryptoStream = new MemoryStream()) + { + for (int i = offset; i < offset + count; i += maxBlockSize) + { + int blockSize = Math.Min(maxBlockSize, count - (i - offset)); + byte[] block = new byte[blockSize]; + Array.Copy(buffer, i, block, 0, blockSize); + + byte[] encryptedBlock = _remotePublicKey.Encrypt(block, RSAEncryptionPadding.OaepSHA256); + cryptoStream.Write(encryptedBlock, 0, encryptedBlock.Length); + } + + _stream.Write(cryptoStream.ToArray(), 0, (int)cryptoStream.Length); + _stream.Flush(); + } + } + + + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException("Offset and count must be non-negative."); + if (buffer.Length - offset < count) throw new ArgumentException("Invalid offset and count relative to buffer length."); + + using (var cryptoStream = new MemoryStream()) + { + int totalBytesRead = 0; + int encryptedBlockSize = _rsaPrivate.KeySize / 8; // Each encrypted block's size should be equal to the RSA key size in bytes + + byte[] encryptedBuffer = new byte[encryptedBlockSize]; + int bytesRead; + + while ((bytesRead = _stream.Read(encryptedBuffer, 0, encryptedBlockSize)) > 0) + { + if (bytesRead != encryptedBlockSize) + throw new CryptographicException("The length of the data to decrypt is not valid for the size of this key."); + + byte[] decryptedBlock = _rsaPrivate.Decrypt(encryptedBuffer, RSAEncryptionPadding.OaepSHA256); + + if (decryptedBlock.Length > 0) + { + int copySize = Math.Min(decryptedBlock.Length, count - totalBytesRead); + Array.Copy(decryptedBlock, 0, buffer, offset + totalBytesRead, copySize); + totalBytesRead += copySize; + } + + if (totalBytesRead >= count) + break; + } + + return totalBytesRead; + } + } + + #region Stream Overrides + + public override bool CanRead => _stream.CanRead; + public override bool CanSeek => _stream.CanSeek; + public override bool CanWrite => _stream.CanWrite; + public override long Length => _stream.Length; + public override long Position + { + get => _stream.Position; + set => _stream.Position = value; + } + + public override void Flush() => _stream.Flush(); + public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin); + public override void SetLength(long value) => _stream.SetLength(value); + + #endregion +} \ No newline at end of file diff --git a/Network/RsaStreamStatus.cs b/Network/RsaStreamStatus.cs new file mode 100644 index 0000000..96b1109 --- /dev/null +++ b/Network/RsaStreamStatus.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TeraIO.Network +{ + public enum RsaStreamStatus + { + NotStarted, + Handshaking, + Established, + Closed, + Failed = 100, + AuthenticationFailed = 101 + } +} diff --git a/Network/WebDav/WebDavClient.cs b/Network/WebDav/WebDavClient.cs index bc1feb5..3567a3f 100644 --- a/Network/WebDav/WebDavClient.cs +++ b/Network/WebDav/WebDavClient.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using System.Web; using System.Xml.Linq; -using TeraIO.Runnable; +using TeraIO.Extension; namespace TeraIO.Network.WebDav { diff --git a/Runnable/EventHandler.cs b/Runnable/EventHandler.cs deleted file mode 100644 index 01fb3b3..0000000 --- a/Runnable/EventHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TeraIO.Runnable -{ - public delegate void EventHandler(TSender sender, TEventArgs e) where TEventArgs : EventArgs; -} diff --git a/Runnable/ExceptionEventArgs.cs b/Runnable/ExceptionEventArgs.cs deleted file mode 100644 index 3970fc6..0000000 --- a/Runnable/ExceptionEventArgs.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TeraIO.Runnable -{ - public class ExceptionEventArgs : EventArgs - { - public ExceptionEventArgs(Exception exception) - { - Exception = exception; - } - - public Exception Exception { get; } - } -} diff --git a/Runnable/Extensions.cs b/Runnable/Extensions.cs deleted file mode 100644 index 146973f..0000000 --- a/Runnable/Extensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TeraIO.Runnable -{ - public static class Extensions - { - public static void ForEach(this IEnumerable it, Action action) - { - if (it == null) throw new ArgumentNullException(nameof(it)); - if (action == null) throw new ArgumentNullException(nameof(action)); - if (it.Count() == 0) return; - - IEnumerator enumerator = it.GetEnumerator(); - - for (int i = 0; i < it.Count(); i++) - { - enumerator.MoveNext(); - T item = enumerator.Current; - action(item, i); - } - } - - public static void ForEach(this IEnumerable it, Action action) - { - if (it == null) throw new ArgumentNullException(nameof(it)); - if (action == null) throw new ArgumentNullException(nameof(action)); - if (it.Count() == 0) return; - - foreach (var item in it) - { - action(item); - } - } - - } -} diff --git a/Runnable/ILogger.cs b/Runnable/ILogger.cs deleted file mode 100644 index 215caa5..0000000 --- a/Runnable/ILogger.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TeraIO.Runnable -{ - public interface ILogger - { - public void Debug(string message); - - public void Debug(string message, Exception exception); - - public void Info(string message); - - public void Info(string message, Exception exception); - - public void Warn(string message); - - public void Warn(string message, Exception exception); - - public void Error(string message); - - public void Error(string message, Exception exception); - - public void Fatal(string message); - - public void Fatal(string message, Exception exception); - } -} diff --git a/Runnable/ILoggerBuilder.cs b/Runnable/ILoggerBuilder.cs deleted file mode 100644 index ae8ddd1..0000000 --- a/Runnable/ILoggerBuilder.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TeraIO.Runnable -{ - public interface ILoggerBuilder - { - public ILogger GetLogger(); - - public ILogger GetLogger(Type type); - - public ILogger GetLogger(string name); - } -} diff --git a/Runnable/IRunnable.cs b/Runnable/IRunnable.cs deleted file mode 100644 index d62d0a9..0000000 --- a/Runnable/IRunnable.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TeraIO.Runnable -{ - public interface IRunnable - { - public Thread? Thread { get; } - - public bool IsRunning { get; } - - public event EventHandler Started; - - public event EventHandler Stopped; - - public event EventHandler ThrowException; - - public bool Start(); - - public void Stop(); - - public void WaitForStop(); - - public Task WaitForStopAsync(); - } -} diff --git a/Runnable/RunnableBase.cs b/Runnable/RunnableBase.cs deleted file mode 100644 index 34b717a..0000000 --- a/Runnable/RunnableBase.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace TeraIO.Runnable -{ - public abstract class RunnableBase : IRunnable - { - protected RunnableBase(string[] args, ILoggerBuilder? logbuilder = null) - { - Type type = GetType(); - if (logbuilder is not null) - _logger = logbuilder.GetLogger(type); - - this.args = args; - - IsRunning = false; - ThreadName = type.FullName ?? type.Name; - _stopSemaphore = new(0); - _stopTask = GetStopTask(); - - Started += OnStarted; - Stopped += OnStopped; - ThrowException += OnThrowException; - } - - protected RunnableBase(ILoggerBuilder? logbuilder = null) : this(new string[0], logbuilder) - { - - } - - private readonly object _lock = new(); - - private readonly ILogger? _logger; - - private readonly SemaphoreSlim _stopSemaphore; - - private Task _stopTask; - - public virtual Thread? Thread { get; protected set; } - - public virtual string ThreadName { get; protected set; } - - private string[] args; - - public virtual bool IsRunning { get; protected set; } - - public event EventHandler Started; - - public event EventHandler Stopped; - - public event EventHandler ThrowException; - - protected virtual void OnStarted(IRunnable sender, EventArgs e) - { - _logger?.Info($"线程({Thread?.Name ?? "null"})已启动"); - } - - protected virtual void OnStopped(IRunnable sender, EventArgs e) - { - _logger?.Info($"线程({Thread?.Name ?? "null"})已停止"); - } - - protected virtual void OnThrowException(IRunnable sender, ExceptionEventArgs e) - { - _logger?.Error($"线程({Thread?.Name ?? "null"})抛出了异常", e.Exception); - } - - protected abstract int Run(string[] args); - - public virtual bool Start(string threadName) - { - ArgumentException.ThrowIfNullOrEmpty(threadName, nameof(threadName)); - - ThreadName = threadName; - return Start(); - } - - public virtual bool Start() - { - lock (_lock) - { - if (IsRunning) - return false; - - IsRunning = true; - Thread = new(ThreadStart); - Thread.Name = ThreadName; - Thread.Start(); - return true; - } - } - - public virtual void Stop() - { - lock (_lock) - { - if (IsRunning) - { - IsRunning = false; - int i = 0; - try - { - while (Thread is not null) - { - Thread.Join(1000); - if (!Thread.IsAlive) - break; - i++; - _logger?.Warn($"正在等待线程({Thread?.Name})停止,已等待{i}秒"); - if (i >= 5) - { - _logger?.Warn($"即将强行停止线程({Thread?.Name})"); - _stopSemaphore.Release(); - _stopTask = GetStopTask(); - Thread.Abort(); - break; - } - } - } - catch (Exception ex) - { - if (Thread is not null && Thread.IsAlive) - _logger?.Error($"无法停止线程({Thread?.Name})", ex); - } - } - } - } - - public void WaitForStop() - { - _stopTask.Wait(); - } - - public async Task WaitForStopAsync() - { - await _stopTask; - } - - protected async Task GetStopTask() - { - await _stopSemaphore.WaitAsync(); - } - - protected void ThreadStart() - { - try - { - Started.Invoke(this, EventArgs.Empty); - Run(this.args); - } - catch (Exception ex) - { - ThrowException.Invoke(this, new(ex)); - } - finally - { - IsRunning = false; - _stopSemaphore.Release(); - _stopTask = GetStopTask(); - Stopped.Invoke(this, EventArgs.Empty); - } - } - } -} diff --git a/TeraIO.Test/TeraIO.Test.csproj b/TeraIO.Test/TeraIO.Test.csproj new file mode 100644 index 0000000..f4cb001 --- /dev/null +++ b/TeraIO.Test/TeraIO.Test.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + diff --git a/TeraIO.csproj b/TeraIO.csproj index 5e42449..c67002b 100644 --- a/TeraIO.csproj +++ b/TeraIO.csproj @@ -11,7 +11,7 @@ TeraIO.png README.md true - 1.0.4 + 1.1.0 default LICENSE True diff --git a/TeraIO.sln b/TeraIO.sln index 3146b6f..d3efec8 100644 --- a/TeraIO.sln +++ b/TeraIO.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 17.8.34316.72 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeraIO", "TeraIO.csproj", "{34DE7DD8-F748-4DA8-BCAE-97EFE196FFAF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeraIO.Test", "TeraIO.Test\TeraIO.Test.csproj", "{87CE782C-CE36-4EC1-B54D-F808B374770D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeraIO.Test", "TeraIO.Test\TeraIO.Test.csproj", "{B02199A1-E3F5-4990-A15C-070399CD8698}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,10 +17,10 @@ Global {34DE7DD8-F748-4DA8-BCAE-97EFE196FFAF}.Debug|Any CPU.Build.0 = Debug|Any CPU {34DE7DD8-F748-4DA8-BCAE-97EFE196FFAF}.Release|Any CPU.ActiveCfg = Release|Any CPU {34DE7DD8-F748-4DA8-BCAE-97EFE196FFAF}.Release|Any CPU.Build.0 = Release|Any CPU - {87CE782C-CE36-4EC1-B54D-F808B374770D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {87CE782C-CE36-4EC1-B54D-F808B374770D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {87CE782C-CE36-4EC1-B54D-F808B374770D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87CE782C-CE36-4EC1-B54D-F808B374770D}.Release|Any CPU.Build.0 = Release|Any CPU + {B02199A1-E3F5-4990-A15C-070399CD8698}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B02199A1-E3F5-4990-A15C-070399CD8698}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B02199A1-E3F5-4990-A15C-070399CD8698}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B02199A1-E3F5-4990-A15C-070399CD8698}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE