diff --git a/.gitignore b/.gitignore index 6d77637..2c909ec 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ obj/ /packages/ riderModule.iml /_ReSharper.Caches/ -nupkgs/ \ No newline at end of file +nupkgs/ +.idea/* +.vs/* \ No newline at end of file diff --git a/Benchmark/ETAMP.Compress.Benchmark/CompressBench.cs b/Benchmark/ETAMP.Compress.Benchmark/CompressBench.cs new file mode 100644 index 0000000..2f5a337 --- /dev/null +++ b/Benchmark/ETAMP.Compress.Benchmark/CompressBench.cs @@ -0,0 +1,50 @@ +using BenchmarkDotNet.Attributes; +using ETAMP.Compression.Interfaces; +using ETAMP.Core.Management; +using ETAMP.Core.Models; +using ETAMP.Extension.ServiceCollection; +using Microsoft.Extensions.DependencyInjection; + +namespace ETAMP.Compress.Benchmark; + +[MemoryDiagnoser] +[ThreadingDiagnoser] +[GcServer] +//[HardwareCounters(HardwareCounter.CacheMisses, HardwareCounter.BranchMispredictions)] +public class CompressBench +{ + private ETAMPModelBuilder _builder; + private ICompressionManager _compressionManager; + private ETAMPModel _model; + + [GlobalSetup] + public async Task Setup() + { + ServiceCollection services = new(); + services.AddCompositionServices(); + var provider = services.BuildServiceProvider(); + _compressionManager = provider.GetRequiredService(); + _model = new ETAMPModel + { + Version = 2, + CompressionType = CompressionNames.Deflate, + Token = new Token + { + Data = "string" + } + }; + _builder = await _compressionManager.CompressAsync(_model); + } + + [Benchmark] + public async Task Compress() + { + await _compressionManager.CompressAsync(_model); + } + + [Benchmark] + public async Task Decompress() + { + await _compressionManager.DecompressAsync(_builder); + } +} \ No newline at end of file diff --git a/Benchmark/ETAMP.Create.Benchmark/ETAMP.Create.Benchmark.csproj b/Benchmark/ETAMP.Compress.Benchmark/ETAMP.Compress.Benchmark.csproj similarity index 64% rename from Benchmark/ETAMP.Create.Benchmark/ETAMP.Create.Benchmark.csproj rename to Benchmark/ETAMP.Compress.Benchmark/ETAMP.Compress.Benchmark.csproj index 277c02f..ab60344 100644 --- a/Benchmark/ETAMP.Create.Benchmark/ETAMP.Create.Benchmark.csproj +++ b/Benchmark/ETAMP.Compress.Benchmark/ETAMP.Compress.Benchmark.csproj @@ -2,20 +2,19 @@ Exe - net8.0 enable enable + net9.0 + default - - - + - + diff --git a/Benchmark/ETAMP.Compress.Benchmark/Program.cs b/Benchmark/ETAMP.Compress.Benchmark/Program.cs new file mode 100644 index 0000000..eea8e3c --- /dev/null +++ b/Benchmark/ETAMP.Compress.Benchmark/Program.cs @@ -0,0 +1,11 @@ +using BenchmarkDotNet.Running; + +namespace ETAMP.Compress.Benchmark; + +internal class Program +{ + private static void Main(string[] args) + { + BenchmarkRunner.Run(); + } +} \ No newline at end of file diff --git a/Benchmark/ETAMP.Create.Benchmark/ETAMPBenchmark.cs b/Benchmark/ETAMP.Create.Benchmark/ETAMPBenchmark.cs deleted file mode 100644 index 31aae16..0000000 --- a/Benchmark/ETAMP.Create.Benchmark/ETAMPBenchmark.cs +++ /dev/null @@ -1,95 +0,0 @@ -#region - -using BenchmarkDotNet.Attributes; -using ETAMP.Compression.Interfaces.Factory; -using ETAMP.Core.Interfaces; -using ETAMP.Core.Management; -using ETAMP.Core.Models; -using ETAMP.Extension.Builder; -using ETAMP.Extension.ServiceCollection; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Serilog; - -#endregion - -namespace ETAMP.Create.Benchmark; - -[MemoryDiagnoser] -[ThreadingDiagnoser] -[GcServer] -//[HardwareCounters(HardwareCounter.CacheMisses, HardwareCounter.BranchMispredictions)] -public class ETAMPBenchmark -{ - private ICompressionServiceFactory? _compressionServiceFactory; - private IETAMPBase _etampBase; - private IServiceProvider _provider; - - [GlobalSetup] - public void Setup() - { - IServiceCollection serviceCollection = new ServiceCollection(); - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Warning() - .WriteTo.Async(a => a.Console( - outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level:u3}] {Message:lj}{NewLine}{Exception}")) - .CreateLogger(); - - serviceCollection.AddLogging(builder => - { - builder.ClearProviders(); - builder.AddSerilog(dispose: true); - }); - - serviceCollection.AddBaseServices(false); - serviceCollection.AddCompositionServices(false); - _provider = serviceCollection.BuildServiceProvider(); - _etampBase = _provider.GetService(); - _compressionServiceFactory = _provider.GetService(); - } - - [Benchmark] - public async Task CreateETAMP() - { - var tokenModel = new TokenModel - { - Message = "Hello World!", - Email = "", - Data = "Some data", - IsEncrypted = false, - LastName = "Last", - Name = "Name", - Phone = "+1234567890" - }; - - await _etampBase.CreateETAMPModel("Message", tokenModel, CompressionNames.GZip) - .BuildAsync(_compressionServiceFactory); - } - - [Benchmark] - public async Task CreateETAMP_LargeData() - { - var largeData = new string('A', 10_000); // Строка размером 10KB - var tokenModel = new TokenModel - { - Email = "", - Data = largeData, - IsEncrypted = false, - LastName = "Last", - Name = "Name", - Phone = "+1234567890" - }; - - await _etampBase.CreateETAMPModel("Message", tokenModel, CompressionNames.GZip) - .BuildAsync(_compressionServiceFactory); - } -} - -public class TokenModel : Token -{ - public string? Name { get; set; } - public string? LastName { get; set; } - public string? Email { get; set; } - public string? Phone { get; set; } - public string? Message { get; set; } -} \ No newline at end of file diff --git a/Benchmark/ETAMP.Create.Benchmark/Program.cs b/Benchmark/ETAMP.Create.Benchmark/Program.cs deleted file mode 100644 index 08dca95..0000000 --- a/Benchmark/ETAMP.Create.Benchmark/Program.cs +++ /dev/null @@ -1,15 +0,0 @@ -#region - -using BenchmarkDotNet.Running; - -#endregion - -namespace ETAMP.Create.Benchmark; - -internal class Program -{ - private static void Main(string[] args) - { - BenchmarkRunner.Run(); - } -} \ No newline at end of file diff --git a/Examples/ETAMP.CreateETAMP.Console/ETAMP.CreateETAMP.Console.csproj b/Benchmark/ETAMP.Encryption.Benchmark/ETAMP.Encryption.Benchmark.csproj similarity index 76% rename from Examples/ETAMP.CreateETAMP.Console/ETAMP.CreateETAMP.Console.csproj rename to Benchmark/ETAMP.Encryption.Benchmark/ETAMP.Encryption.Benchmark.csproj index d72b1e7..8428029 100644 --- a/Examples/ETAMP.CreateETAMP.Console/ETAMP.CreateETAMP.Console.csproj +++ b/Benchmark/ETAMP.Encryption.Benchmark/ETAMP.Encryption.Benchmark.csproj @@ -5,12 +5,11 @@ net9.0 enable enable - ETAMP.CreateETAMP.Console - ETAMP.CreateETAMP.Console - + + diff --git a/Benchmark/ETAMP.Encryption.Benchmark/EncryptionBenchmark.cs b/Benchmark/ETAMP.Encryption.Benchmark/EncryptionBenchmark.cs new file mode 100644 index 0000000..ae8175b --- /dev/null +++ b/Benchmark/ETAMP.Encryption.Benchmark/EncryptionBenchmark.cs @@ -0,0 +1,46 @@ +using System.Security.Cryptography; +using BenchmarkDotNet.Attributes; +using ETAMP.Encryption.Interfaces; +using ETAMP.Extension.ServiceCollection; +using Microsoft.Extensions.DependencyInjection; + +namespace ETAMP.Encryption.Benchmark; + +[MemoryDiagnoser] +[ThreadingDiagnoser] +[GcServer] +public class EncryptionBenchmark +{ + private readonly string _data = "String to encrypt"; + private string _encryptData; + private IECIESEncryptionManager _encryptionManager; + private ECDiffieHellman _person1; + private ECDiffieHellman _person2; + + [GlobalSetup] + public async Task Setup() + { + ServiceCollection services = new(); + services.AddEncryptionServices(); + var provider = services.BuildServiceProvider(); + + _person1 = ECDiffieHellman.Create(); + _person2 = ECDiffieHellman.Create(); + + _encryptionManager = provider.GetRequiredService(); + + _encryptData = await _encryptionManager.EncryptAsync(_data, _person1, _person2.PublicKey); + } + + [Benchmark] + public async Task Encrypt() + { + await _encryptionManager.EncryptAsync(_data, _person1, _person2.PublicKey); + } + + [Benchmark] + public async Task Decrypt() + { + await _encryptionManager.DecryptAsync(_encryptData, _person2, _person1.PublicKey); + } +} \ No newline at end of file diff --git a/Benchmark/ETAMP.Encryption.Benchmark/Program.cs b/Benchmark/ETAMP.Encryption.Benchmark/Program.cs new file mode 100644 index 0000000..b94ab9c --- /dev/null +++ b/Benchmark/ETAMP.Encryption.Benchmark/Program.cs @@ -0,0 +1,11 @@ +using BenchmarkDotNet.Running; + +namespace ETAMP.Encryption.Benchmark; + +internal class Program +{ + private static void Main(string[] args) + { + BenchmarkRunner.Run(); + } +} \ No newline at end of file diff --git a/Benchmark/ETAMP.Sign.Benchmark/ETAMPSignBenchmark.cs b/Benchmark/ETAMP.Sign.Benchmark/ETAMPSignBenchmark.cs deleted file mode 100644 index aac6740..0000000 --- a/Benchmark/ETAMP.Sign.Benchmark/ETAMPSignBenchmark.cs +++ /dev/null @@ -1,106 +0,0 @@ -#region - -using System.Security.Cryptography; -using BenchmarkDotNet.Attributes; -using ETAMP.Compression.Interfaces.Factory; -using ETAMP.Core.Interfaces; -using ETAMP.Core.Management; -using ETAMP.Core.Models; -using ETAMP.Extension.Builder; -using ETAMP.Extension.ServiceCollection; -using ETAMP.Wrapper.Interfaces; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Serilog; - -#endregion - -namespace ETAMP.Sign.Benchmark; - -[MemoryDiagnoser] -[ThreadingDiagnoser] -[GcServer] -//[HardwareCounters(HardwareCounter.CacheMisses, HardwareCounter.BranchMispredictions)] -public class ETAMPSignBenchmark -{ - private ICompressionServiceFactory? _compressionServiceFactory; - private IETAMPBase _etampBase; - private IServiceProvider _provider; - private ISignWrapper _signWrapper; - - [GlobalSetup] - public void Setup() - { - IServiceCollection serviceCollection = new ServiceCollection(); - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Warning() - .WriteTo.Async(a => a.Console( - outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level:u3}] {Message:lj}{NewLine}{Exception}")) - .CreateLogger(); - - serviceCollection.AddLogging(builder => - { - builder.ClearProviders(); - builder.AddSerilog(dispose: true); - }); - - serviceCollection.AddBaseServices(false); - serviceCollection.AddWrapperServices(false); - serviceCollection.AddCompositionServices(false); - _provider = serviceCollection.BuildServiceProvider(); - _etampBase = _provider.GetService(); - _compressionServiceFactory = _provider.GetService(); - _signWrapper = _provider.GetService(); - var ecDsa = ECDsa.Create(); - _signWrapper.Initialize(ecDsa, HashAlgorithmName.SHA3_256); - } - - [Benchmark] - public async Task CreateETAMP() - { - var tokenModel = new TokenModel - { - Message = "Hello World!", - Email = "", - Data = "Some data", - IsEncrypted = false, - LastName = "Last", - Name = "Name", - Phone = "+1234567890" - }; - - var signModel = await _etampBase.CreateETAMPModel("Message", tokenModel, CompressionNames.Deflate) - .Sign(_signWrapper); - - await signModel.BuildAsync(_compressionServiceFactory); - } - - - [Benchmark] - public async Task CreateETAMP_LargeData() - { - var largeData = new string('A', 10_000); // Строка размером 10KB - var tokenModel = new TokenModel - { - Email = "", - Data = largeData, - IsEncrypted = false, - LastName = "Last", - Name = "Name", - Phone = "+1234567890" - }; - - var singModel = await _etampBase.CreateETAMPModel("Message", tokenModel, CompressionNames.Deflate) - .Sign(_signWrapper); - await singModel.BuildAsync(_compressionServiceFactory); - } -} - -public class TokenModel : Token -{ - public string? Name { get; set; } - public string? LastName { get; set; } - public string? Email { get; set; } - public string? Phone { get; set; } - public string? Message { get; set; } -} \ No newline at end of file diff --git a/Benchmark/ETAMP.Sign.Benchmark/Program.cs b/Benchmark/ETAMP.Sign.Benchmark/Program.cs deleted file mode 100644 index 27cd3ed..0000000 --- a/Benchmark/ETAMP.Sign.Benchmark/Program.cs +++ /dev/null @@ -1,15 +0,0 @@ -#region - -using BenchmarkDotNet.Running; - -#endregion - -namespace ETAMP.Sign.Benchmark; - -class Program -{ - private static void Main(string[] args) - { - BenchmarkRunner.Run(); - } -} \ No newline at end of file diff --git a/ETAMP.sln b/ETAMP.sln index 1c7d2f2..cedb154 100644 --- a/ETAMP.sln +++ b/ETAMP.sln @@ -8,11 +8,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Core", "Source\ETAMP. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Encryption", "Source\ETAMP.Encryption\ETAMP.Encryption.csproj", "{6302A087-7783-4497-9BFE-2BE26557A907}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Extension", "Source\ETAMP.Extension\ETAMP.Extension.csproj", "{5FB2CD01-4815-484E-8F16-E652EDFF5758}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Validation", "Source\ETAMP.Validation\ETAMP.Validation.csproj", "{976A600A-CAB4-4747-86BA-42C4B980847C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Wrapper", "Source\ETAMP.Wrapper\ETAMP.Wrapper.csproj", "{14A761E3-C207-4002-9D72-523380553B4A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Provider", "Source\ETAMP.Provider\ETAMP.Provider.csproj", "{14A761E3-C207-4002-9D72-523380553B4A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Extension.ServiceCollection", "Source\ETAMP.Extension.ServiceCollection\ETAMP.Extension.ServiceCollection.csproj", "{CB7A1CC9-A7A3-433C-B224-8777917215ED}" EndProject @@ -22,27 +20,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6CC71BCA EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Console", "Console", "{209B4ADB-53F9-4D67-8134-D2EDC7BA9FEC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.CreateETAMP.Console", "Examples\ETAMP.CreateETAMP.Console\ETAMP.CreateETAMP.Console.csproj", "{33A58AE1-5303-42BA-BEAF-8F01C3DAADDC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.ValidateETAMP.Console", "Examples\ETAMP.ValidateETAMP.Console\ETAMP.ValidateETAMP.Console.csproj", "{EA88DE45-4AEA-47A8-A1B8-0B3D8CA14980}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.SignETAMP.Console", "Examples\ETAMP.SignETAMP.Console\ETAMP.SignETAMP.Console.csproj", "{5CFB2981-38DC-458E-A68A-0AF99994C641}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Compression.Tests", "Tests\ETAMP.Compression.Tests\ETAMP.Compression.Tests.csproj", "{9F42CC80-A15E-4511-B130-8FAC2671579D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Core.Tests", "Tests\ETAMP.Core.Tests\ETAMP.Core.Tests.csproj", "{650CBC31-BF2D-4D9E-821C-AE4D2A0FBEB9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Encryption.Tests", "Tests\ETAMP.Encryption.Tests\ETAMP.Encryption.Tests.csproj", "{33E22E4C-716D-4228-A01C-44579113ABFA}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmark", "Benchmark", "{DF7BE1D8-6A3D-4234-9D28-74F7038EB0D0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Extension.Tests", "Tests\ETAMP.Extension.Tests\ETAMP.Extension.Tests.csproj", "{DD41A704-7968-44D8-BD80-9BFF7AF14FBC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Compress.Benchmark", "Benchmark\ETAMP.Compress.Benchmark\ETAMP.Compress.Benchmark.csproj", "{C2E48BBF-CF96-4F35-BA70-7F386C0B2EEA}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmark", "Benchmark", "{DF7BE1D8-6A3D-4234-9D28-74F7038EB0D0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Compress.Console", "Examples\ETAMP.Compress.Console\ETAMP.Compress.Console.csproj", "{07A88B77-EB40-48ED-BE60-DBA01F8C72BA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Create.Benchmark", "Benchmark\ETAMP.Create.Benchmark\ETAMP.Create.Benchmark.csproj", "{0D43DA7B-3FA1-48B7-8184-1F2412382060}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Encryption.Console", "Examples\ETAMP.Encryption.Console\ETAMP.Encryption.Console.csproj", "{072E9540-B163-4FEE-BDDC-3C478DE39937}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Sign.Benchmark", "Benchmark\ETAMP.Sign.Benchmark\ETAMP.Sign.Benchmark.csproj", "{B9328DC5-0EB6-437F-99AE-9230786DBA9D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Encryption.Benchmark", "Benchmark\ETAMP.Encryption.Benchmark\ETAMP.Encryption.Benchmark.csproj", "{6FF39476-C9E7-4B47-9177-F85BC879294C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Validation.Tests", "Tests\ETAMP.Validation.Tests\ETAMP.Validation.Tests.csproj", "{8A54A1BE-1537-42D7-8900-FAE0DC2C8C9C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ETAMP.Compression.Tests", "Tests\ETAMP.Compression.Tests\ETAMP.Compression.Tests.csproj", "{F1F99F76-6AB8-43D7-A1DA-8B2C5A8BAFC9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -62,10 +50,6 @@ Global {6302A087-7783-4497-9BFE-2BE26557A907}.Debug|Any CPU.Build.0 = Debug|Any CPU {6302A087-7783-4497-9BFE-2BE26557A907}.Release|Any CPU.ActiveCfg = Release|Any CPU {6302A087-7783-4497-9BFE-2BE26557A907}.Release|Any CPU.Build.0 = Release|Any CPU - {5FB2CD01-4815-484E-8F16-E652EDFF5758}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5FB2CD01-4815-484E-8F16-E652EDFF5758}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5FB2CD01-4815-484E-8F16-E652EDFF5758}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5FB2CD01-4815-484E-8F16-E652EDFF5758}.Release|Any CPU.Build.0 = Release|Any CPU {976A600A-CAB4-4747-86BA-42C4B980847C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {976A600A-CAB4-4747-86BA-42C4B980847C}.Debug|Any CPU.Build.0 = Debug|Any CPU {976A600A-CAB4-4747-86BA-42C4B980847C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -78,65 +62,39 @@ Global {CB7A1CC9-A7A3-433C-B224-8777917215ED}.Debug|Any CPU.Build.0 = Debug|Any CPU {CB7A1CC9-A7A3-433C-B224-8777917215ED}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB7A1CC9-A7A3-433C-B224-8777917215ED}.Release|Any CPU.Build.0 = Release|Any CPU - {33A58AE1-5303-42BA-BEAF-8F01C3DAADDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {33A58AE1-5303-42BA-BEAF-8F01C3DAADDC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {33A58AE1-5303-42BA-BEAF-8F01C3DAADDC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {33A58AE1-5303-42BA-BEAF-8F01C3DAADDC}.Release|Any CPU.Build.0 = Release|Any CPU - {EA88DE45-4AEA-47A8-A1B8-0B3D8CA14980}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EA88DE45-4AEA-47A8-A1B8-0B3D8CA14980}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA88DE45-4AEA-47A8-A1B8-0B3D8CA14980}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EA88DE45-4AEA-47A8-A1B8-0B3D8CA14980}.Release|Any CPU.Build.0 = Release|Any CPU - {5CFB2981-38DC-458E-A68A-0AF99994C641}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5CFB2981-38DC-458E-A68A-0AF99994C641}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5CFB2981-38DC-458E-A68A-0AF99994C641}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5CFB2981-38DC-458E-A68A-0AF99994C641}.Release|Any CPU.Build.0 = Release|Any CPU - {9F42CC80-A15E-4511-B130-8FAC2671579D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9F42CC80-A15E-4511-B130-8FAC2671579D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9F42CC80-A15E-4511-B130-8FAC2671579D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9F42CC80-A15E-4511-B130-8FAC2671579D}.Release|Any CPU.Build.0 = Release|Any CPU - {650CBC31-BF2D-4D9E-821C-AE4D2A0FBEB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {650CBC31-BF2D-4D9E-821C-AE4D2A0FBEB9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {650CBC31-BF2D-4D9E-821C-AE4D2A0FBEB9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {650CBC31-BF2D-4D9E-821C-AE4D2A0FBEB9}.Release|Any CPU.Build.0 = Release|Any CPU - {33E22E4C-716D-4228-A01C-44579113ABFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {33E22E4C-716D-4228-A01C-44579113ABFA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {33E22E4C-716D-4228-A01C-44579113ABFA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {33E22E4C-716D-4228-A01C-44579113ABFA}.Release|Any CPU.Build.0 = Release|Any CPU - {DD41A704-7968-44D8-BD80-9BFF7AF14FBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DD41A704-7968-44D8-BD80-9BFF7AF14FBC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DD41A704-7968-44D8-BD80-9BFF7AF14FBC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DD41A704-7968-44D8-BD80-9BFF7AF14FBC}.Release|Any CPU.Build.0 = Release|Any CPU - {0D43DA7B-3FA1-48B7-8184-1F2412382060}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0D43DA7B-3FA1-48B7-8184-1F2412382060}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0D43DA7B-3FA1-48B7-8184-1F2412382060}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0D43DA7B-3FA1-48B7-8184-1F2412382060}.Release|Any CPU.Build.0 = Release|Any CPU - {B9328DC5-0EB6-437F-99AE-9230786DBA9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9328DC5-0EB6-437F-99AE-9230786DBA9D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9328DC5-0EB6-437F-99AE-9230786DBA9D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9328DC5-0EB6-437F-99AE-9230786DBA9D}.Release|Any CPU.Build.0 = Release|Any CPU - {8A54A1BE-1537-42D7-8900-FAE0DC2C8C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A54A1BE-1537-42D7-8900-FAE0DC2C8C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A54A1BE-1537-42D7-8900-FAE0DC2C8C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A54A1BE-1537-42D7-8900-FAE0DC2C8C9C}.Release|Any CPU.Build.0 = Release|Any CPU + {C2E48BBF-CF96-4F35-BA70-7F386C0B2EEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2E48BBF-CF96-4F35-BA70-7F386C0B2EEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2E48BBF-CF96-4F35-BA70-7F386C0B2EEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2E48BBF-CF96-4F35-BA70-7F386C0B2EEA}.Release|Any CPU.Build.0 = Release|Any CPU + {07A88B77-EB40-48ED-BE60-DBA01F8C72BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07A88B77-EB40-48ED-BE60-DBA01F8C72BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07A88B77-EB40-48ED-BE60-DBA01F8C72BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07A88B77-EB40-48ED-BE60-DBA01F8C72BA}.Release|Any CPU.Build.0 = Release|Any CPU + {072E9540-B163-4FEE-BDDC-3C478DE39937}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {072E9540-B163-4FEE-BDDC-3C478DE39937}.Debug|Any CPU.Build.0 = Debug|Any CPU + {072E9540-B163-4FEE-BDDC-3C478DE39937}.Release|Any CPU.ActiveCfg = Release|Any CPU + {072E9540-B163-4FEE-BDDC-3C478DE39937}.Release|Any CPU.Build.0 = Release|Any CPU + {6FF39476-C9E7-4B47-9177-F85BC879294C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FF39476-C9E7-4B47-9177-F85BC879294C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FF39476-C9E7-4B47-9177-F85BC879294C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FF39476-C9E7-4B47-9177-F85BC879294C}.Release|Any CPU.Build.0 = Release|Any CPU + {F1F99F76-6AB8-43D7-A1DA-8B2C5A8BAFC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1F99F76-6AB8-43D7-A1DA-8B2C5A8BAFC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1F99F76-6AB8-43D7-A1DA-8B2C5A8BAFC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1F99F76-6AB8-43D7-A1DA-8B2C5A8BAFC9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {D77F3A64-FF22-497A-BE28-23DE51E0DB14} = {B1D33957-166B-491B-B3B4-AE0BCB253E85} {4720D834-DDAF-4035-9133-A4C0C4F43C43} = {B1D33957-166B-491B-B3B4-AE0BCB253E85} {6302A087-7783-4497-9BFE-2BE26557A907} = {B1D33957-166B-491B-B3B4-AE0BCB253E85} - {5FB2CD01-4815-484E-8F16-E652EDFF5758} = {B1D33957-166B-491B-B3B4-AE0BCB253E85} {976A600A-CAB4-4747-86BA-42C4B980847C} = {B1D33957-166B-491B-B3B4-AE0BCB253E85} {14A761E3-C207-4002-9D72-523380553B4A} = {B1D33957-166B-491B-B3B4-AE0BCB253E85} {CB7A1CC9-A7A3-433C-B224-8777917215ED} = {B1D33957-166B-491B-B3B4-AE0BCB253E85} {209B4ADB-53F9-4D67-8134-D2EDC7BA9FEC} = {EAA98E34-C22D-41D5-BAF1-1C880243E938} - {33A58AE1-5303-42BA-BEAF-8F01C3DAADDC} = {209B4ADB-53F9-4D67-8134-D2EDC7BA9FEC} - {EA88DE45-4AEA-47A8-A1B8-0B3D8CA14980} = {209B4ADB-53F9-4D67-8134-D2EDC7BA9FEC} - {5CFB2981-38DC-458E-A68A-0AF99994C641} = {209B4ADB-53F9-4D67-8134-D2EDC7BA9FEC} - {9F42CC80-A15E-4511-B130-8FAC2671579D} = {6CC71BCA-D510-4292-A3D7-75A5B00752FC} - {650CBC31-BF2D-4D9E-821C-AE4D2A0FBEB9} = {6CC71BCA-D510-4292-A3D7-75A5B00752FC} - {33E22E4C-716D-4228-A01C-44579113ABFA} = {6CC71BCA-D510-4292-A3D7-75A5B00752FC} - {DD41A704-7968-44D8-BD80-9BFF7AF14FBC} = {6CC71BCA-D510-4292-A3D7-75A5B00752FC} - {0D43DA7B-3FA1-48B7-8184-1F2412382060} = {DF7BE1D8-6A3D-4234-9D28-74F7038EB0D0} - {B9328DC5-0EB6-437F-99AE-9230786DBA9D} = {DF7BE1D8-6A3D-4234-9D28-74F7038EB0D0} - {8A54A1BE-1537-42D7-8900-FAE0DC2C8C9C} = {6CC71BCA-D510-4292-A3D7-75A5B00752FC} + {C2E48BBF-CF96-4F35-BA70-7F386C0B2EEA} = {DF7BE1D8-6A3D-4234-9D28-74F7038EB0D0} + {07A88B77-EB40-48ED-BE60-DBA01F8C72BA} = {209B4ADB-53F9-4D67-8134-D2EDC7BA9FEC} + {072E9540-B163-4FEE-BDDC-3C478DE39937} = {209B4ADB-53F9-4D67-8134-D2EDC7BA9FEC} + {6FF39476-C9E7-4B47-9177-F85BC879294C} = {DF7BE1D8-6A3D-4234-9D28-74F7038EB0D0} + {F1F99F76-6AB8-43D7-A1DA-8B2C5A8BAFC9} = {6CC71BCA-D510-4292-A3D7-75A5B00752FC} EndGlobalSection EndGlobal diff --git a/ETAMP.sln.DotSettings.user b/ETAMP.sln.DotSettings.user index 9d99d61..7aa1281 100644 --- a/ETAMP.sln.DotSettings.user +++ b/ETAMP.sln.DotSettings.user @@ -1,55 +1,46 @@  - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded - ForceIncluded - ForceIncluded + ForceIncluded ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ECDSA - C:\Users\black\AppData\Local\JetBrains\Rider2024.3\resharper-host\temp\Rider\vAny\CoverageData\_ETAMP.-1647244694\Snapshot\snapshot.utdcvr + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + True - 650CBC31-BF2D-4D9E-821C-AE4D2A0FBEB9 - ETAMP.Extension.Tests - 33E22E4C-716D-4228-A01C-44579113ABFA - 8A54A1BE-1537-42D7-8900-FAE0DC2C8C9C - 9F42CC80-A15E-4511-B130-8FAC2671579D + 62BC192F-E57D-473E-A883-912B99DEA549 + F1F99F76-6AB8-43D7-A1DA-8B2C5A8BAFC9 33ff99d8-b1ad-4ed5-a4fd-376b97c2c841 - <SessionState ContinuousTestingMode="0" Name="All tests from Solution #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <Solution /> </SessionState> + <SessionState ContinuousTestingMode="0" Name="CompressAsync_CompressesCorrectly" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <TestAncestor> + <TestId>xUnit::F1F99F76-6AB8-43D7-A1DA-8B2C5A8BAFC9::net9.0::ETAMP.Compression.Tests.CompressionManagerTest.CompressAsync_CompressesCorrectly</TestId> + </TestAncestor> +</SessionState> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="CompressAsync_ShouldThrowInvalidOperationException_WhenInputNull" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <TestAncestor> + <TestId>xUnit::F1F99F76-6AB8-43D7-A1DA-8B2C5A8BAFC9::net9.0::ETAMP.Compression.Tests.Codec.StreamCompressionServiceTests</TestId> + </TestAncestor> +</SessionState> + - <SessionState ContinuousTestingMode="0" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Solution /> -</SessionState> - <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Solution /> -</SessionState> - <SessionState ContinuousTestingMode="0" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Solution /> -</SessionState> - True - \ No newline at end of file + True \ No newline at end of file diff --git a/Benchmark/ETAMP.Sign.Benchmark/ETAMP.Sign.Benchmark.csproj b/Examples/ETAMP.Compress.Console/ETAMP.Compress.Console.csproj similarity index 51% rename from Benchmark/ETAMP.Sign.Benchmark/ETAMP.Sign.Benchmark.csproj rename to Examples/ETAMP.Compress.Console/ETAMP.Compress.Console.csproj index eb88cd9..b225a39 100644 --- a/Benchmark/ETAMP.Sign.Benchmark/ETAMP.Sign.Benchmark.csproj +++ b/Examples/ETAMP.Compress.Console/ETAMP.Compress.Console.csproj @@ -2,20 +2,18 @@ Exe - net8.0 + net9.0 enable enable - - - + + - diff --git a/Examples/ETAMP.Compress.Console/Program.cs b/Examples/ETAMP.Compress.Console/Program.cs new file mode 100644 index 0000000..409544c --- /dev/null +++ b/Examples/ETAMP.Compress.Console/Program.cs @@ -0,0 +1,31 @@ +using ETAMP.Compression.Interfaces; +using ETAMP.Core.Interfaces; +using ETAMP.Core.Management; +using ETAMP.Core.Models; +using ETAMP.Extension.ServiceCollection; +using Microsoft.Extensions.DependencyInjection; + +internal class Program +{ + private static ICompressionManager _compressionManager; + private static ETAMPModel _model; + + private static async Task Main(string[] args) + { + ServiceCollection services = new(); + services.AddCompositionServices(); + services.AddBaseServices(); + var provider = services.BuildServiceProvider(); + _compressionManager = provider.GetRequiredService(); + + var protocol = provider.GetRequiredService(); + var token = new Token + { + Data = "string" + }; + _model = protocol.CreateETAMPModel("update", token, CompressionNames.Deflate); + var model = await _compressionManager.CompressAsync(_model); + var etamp = await _compressionManager.DecompressAsync(model); + Console.WriteLine(etamp); + } +} \ No newline at end of file diff --git a/Examples/ETAMP.CreateETAMP.Console/CreateETAMP.cs b/Examples/ETAMP.CreateETAMP.Console/CreateETAMP.cs deleted file mode 100644 index 20b3e5e..0000000 --- a/Examples/ETAMP.CreateETAMP.Console/CreateETAMP.cs +++ /dev/null @@ -1,51 +0,0 @@ -#region - -using ETAMP.Compression.Interfaces.Factory; -using ETAMP.Console.CreateETAMP.Models; -using ETAMP.Core.Interfaces; -using ETAMP.Core.Management; -using ETAMP.Core.Models; -using ETAMP.Extension.Builder; -using ETAMP.Extension.ServiceCollection; -using Microsoft.Extensions.DependencyInjection; - -#endregion - -public static class CreateETAMP -{ - private static ServiceProvider _provider; - - private static async Task Main(string[] args) - { - _provider = ConfigureServices(); - var compression = _provider.GetService(); - var etampModel = InitializeEtampModel(_provider); - Console.WriteLine(await etampModel.BuildAsync(compression)); - } - - public static ETAMPModel InitializeEtampModel(IServiceProvider provider) - { - var etampBase = provider.GetService(); - - var token = new Token(); - var tokenModel = new TokenModel - { - Message = "Hello World!", - Email = "", - Data = "Some data", - IsEncrypted = false, - LastName = "Last", - Name = "Name", - Phone = "+1234567890" - }; - - return etampBase.CreateETAMPModel("Message", tokenModel, CompressionNames.GZip); - } - - public static ServiceProvider ConfigureServices() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddETAMPServices(); - return serviceCollection.BuildServiceProvider(); - } -} \ No newline at end of file diff --git a/Examples/ETAMP.CreateETAMP.Console/Models/TokenModel.cs b/Examples/ETAMP.CreateETAMP.Console/Models/TokenModel.cs deleted file mode 100644 index 19675ba..0000000 --- a/Examples/ETAMP.CreateETAMP.Console/Models/TokenModel.cs +++ /dev/null @@ -1,16 +0,0 @@ -#region - -using ETAMP.Core.Models; - -#endregion - -namespace ETAMP.Console.CreateETAMP.Models; - -public class TokenModel : Token -{ - public string? Name { get; set; } - public string? LastName { get; set; } - public string? Email { get; set; } - public string? Phone { get; set; } - public string? Message { get; set; } -} \ No newline at end of file diff --git a/Examples/ETAMP.ValidateETAMP.Console/ETAMP.ValidateETAMP.Console.csproj b/Examples/ETAMP.Encryption.Console/ETAMP.Encryption.Console.csproj similarity index 57% rename from Examples/ETAMP.ValidateETAMP.Console/ETAMP.ValidateETAMP.Console.csproj rename to Examples/ETAMP.Encryption.Console/ETAMP.Encryption.Console.csproj index 12cf43f..c10d225 100644 --- a/Examples/ETAMP.ValidateETAMP.Console/ETAMP.ValidateETAMP.Console.csproj +++ b/Examples/ETAMP.Encryption.Console/ETAMP.Encryption.Console.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/Examples/ETAMP.Encryption.Console/Program.cs b/Examples/ETAMP.Encryption.Console/Program.cs new file mode 100644 index 0000000..40663c8 --- /dev/null +++ b/Examples/ETAMP.Encryption.Console/Program.cs @@ -0,0 +1,37 @@ +using System.Security.Cryptography; +using ETAMP.Encryption.Interfaces; +using ETAMP.Extension.ServiceCollection; +using Microsoft.Extensions.DependencyInjection; + +internal class Program +{ + private static IServiceProvider _provider; + private static IECIESEncryptionManager _encryptionManager; + + private static async Task Main(string[] args) + { + Setup(); + + var data = "String to encrypt"; + + var person1 = ECDiffieHellman.Create(); + var person2 = ECDiffieHellman.Create(); + + var encrResult = await _encryptionManager.EncryptAsync(data, person1, person2.PublicKey); + + Console.WriteLine(encrResult); + + + var decrResult = await _encryptionManager.DecryptAsync(encrResult, person2, person1.PublicKey); + Console.WriteLine(decrResult); + } + + private static void Setup() + { + ServiceCollection services = new(); + services.AddEncryptionServices(); + services.AddBaseServices(); + var provider = services.BuildServiceProvider(); + _encryptionManager = provider.GetRequiredService(); + } +} \ No newline at end of file diff --git a/Examples/ETAMP.SignETAMP.Console/CreateSignETAMP.cs b/Examples/ETAMP.SignETAMP.Console/CreateSignETAMP.cs deleted file mode 100644 index 6e38e49..0000000 --- a/Examples/ETAMP.SignETAMP.Console/CreateSignETAMP.cs +++ /dev/null @@ -1,42 +0,0 @@ -#region - -using System.Security.Cryptography; -using ETAMP.Console.CreateETAMP.Models; -using ETAMP.Core.Models; -using ETAMP.Extension.Builder; -using ETAMP.Wrapper.Interfaces; -using Microsoft.Extensions.DependencyInjection; - -#endregion - -public class CreateSignETAMP -{ - private static ECDsa? _ecdsaInstance; - public static ECDKeyModelProvider? KeyModelProvider { get; private set; } - public static ETAMPModel Etamp { get; private set; } - - public static async Task Main() - { - _ecdsaInstance = ECDsa.Create(); - await using var provider = CreateETAMP.ConfigureServices(); - Etamp = await SignETAMP(provider); - Console.WriteLine(await Etamp.ToJsonAsync()); - } - - private static async Task> SignETAMP(IServiceProvider provider) - { - var sign = provider.GetService(); - InitializeSigning(sign); - var etampModel = CreateETAMP.InitializeEtampModel(provider); - return await etampModel.Sign(sign); - } - - private static void InitializeSigning(ISignWrapper? sign) - { - KeyModelProvider = new ECDKeyModelProvider - { - PublicKey = ECDKeyModelProvider.ClearPemFormatting(_ecdsaInstance.ExportSubjectPublicKeyInfoPem()) - }; - sign?.Initialize(_ecdsaInstance, HashAlgorithmName.SHA512); - } -} \ No newline at end of file diff --git a/Examples/ETAMP.SignETAMP.Console/ETAMP.SignETAMP.Console.csproj b/Examples/ETAMP.SignETAMP.Console/ETAMP.SignETAMP.Console.csproj deleted file mode 100644 index be085b1..0000000 --- a/Examples/ETAMP.SignETAMP.Console/ETAMP.SignETAMP.Console.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - Exe - net9.0 - enable - enable - ETAMP.CreateSignETAMP.Console - - - - - - - diff --git a/Examples/ETAMP.ValidateETAMP.Console/ValidateETAMP.cs b/Examples/ETAMP.ValidateETAMP.Console/ValidateETAMP.cs deleted file mode 100644 index 6a0680f..0000000 --- a/Examples/ETAMP.ValidateETAMP.Console/ValidateETAMP.cs +++ /dev/null @@ -1,36 +0,0 @@ -#region - -using System.Security.Cryptography; -using ETAMP.Validation.Interfaces; -using Microsoft.Extensions.DependencyInjection; - -#endregion - -internal class ETAMPValidationRunner -{ - private static readonly HashAlgorithmName DefaultHashAlgorithm = HashAlgorithmName.SHA512; - - private static async Task Main(string[] args) - { - var provider = CreateETAMP.ConfigureServices(); - var etampValidator = provider.GetService(); - - CreateSignETAMP.Main(); - - var publicKeyBytes = Convert.FromBase64String(CreateSignETAMP.KeyModelProvider.PublicKey); - var initializedEcdsa = CreateInitializedEcdsa(publicKeyBytes); - - - etampValidator.Initialize(initializedEcdsa, DefaultHashAlgorithm); - var validationResult = await etampValidator.ValidateETAMPAsync(CreateSignETAMP.Etamp, false); - - Console.WriteLine(validationResult.IsValid); - } - - private static ECDsa? CreateInitializedEcdsa(byte[] publicKey) - { - var ecdsa = ECDsa.Create(); - ecdsa.ImportSubjectPublicKeyInfo(publicKey, out _); - return ecdsa; - } -} \ No newline at end of file diff --git a/README.md b/README.md index e38ddb4..bda8a9f 100644 --- a/README.md +++ b/README.md @@ -35,172 +35,11 @@ Install the required ETAMP libraries using the NuGet Package Manager: Install-Package ETAMP.Compression Install-Package ETAMP.Core Install-Package ETAMP.Encryption -Install-Package ETAMP.Extension Install-Package ETAMP.Extension.ServiceCollection +Install-Package ETAMP.Provider Install-Package ETAMP.Validation -Install-Package ETAMP.Wrapper ``` -## Usage - -### Prerequisites - -Ensure you have the necessary dependencies and services configured in your application. The examples below use -`Microsoft.Extensions.DependencyInjection` for dependency injection. - -### Example Files - -1. **Program.cs** - - Demonstrates the initialization of services and ETAMP model creation. - - ```csharp - using ETAMP.Compression.Interfaces.Factory; - using ETAMP.Core.Models; - using ETAMP.Extension.ServiceCollection; - using Microsoft.Extensions.DependencyInjection; - - public static class Program - { - public static void Main(string[] args) - { - var provider = ConfigureServices(); - var compression = provider.GetService(); - var etampModel = CreateETAMP.InitializeEtampModel(provider); - Console.WriteLine(etampModel.Build(compression)); - } - - private static ServiceProvider ConfigureServices() - { - var services = new ServiceCollection(); - services.AddETAMPServices(); - return services.BuildServiceProvider(); - } - } - ``` - -2. **CreateETAMP.cs** - - Handles the creation of a basic ETAMP message. - - ```csharp - using ETAMP.Core.Interfaces; - using ETAMP.Core.Models; - using ETAMP.Extension.Builder; - - public static class CreateETAMP - { - public static ETAMPModel InitializeEtampModel(IServiceProvider provider) - { - var etampBase = provider.GetService(); - var tokenModel = new TokenModel - { - Message = "Hello World!", - Email = "", - Data = "Some data", - IsEncrypted = false, - LastName = "Last", - Name = "Name", - Phone = "+1234567890" - }; - return etampBase.CreateETAMPModel("Message", tokenModel, CompressionNames.GZip); - } - } - ``` - -3. **CreateSignETAMP.cs** - - Handles signing of ETAMP messages. - - ```csharp - using System.Security.Cryptography; - using ETAMP.Console.CreateETAMP.Models; - using ETAMP.Core.Models; - using ETAMP.Encryption.Interfaces.ECDSAManager; - using ETAMP.Extension.Builder; - using ETAMP.Wrapper.Interfaces; - using Microsoft.Extensions.DependencyInjection; - - public class CreateSignETAMP - { - private static ServiceProvider _provider; - private static ECDsa _ecdsaInstance; - public static string PublicKey { get; private set; } - - private static void Main(string[] args) - { - _ecdsaInstance = ECDsa.Create(); - _provider = CreateETAMP.ConfigureServices(); - var etamp = SignETAMP(_provider).ToJson(); - Console.WriteLine(etamp); - } - - public static ETAMPModel SignETAMP(IServiceProvider provider) - { - var (sign, pemCleaner) = GetServices(provider); - - InitializeSigning(sign, pemCleaner); - - var etampModel = CreateETAMP.InitializeEtampModel(provider); - etampModel.Sign(sign); - return etampModel; - } - - private static (ISignWrapper?, IPemKeyCleaner?) GetServices(IServiceProvider provider) - { - return ( - provider.GetService(), - provider.GetService() - ); - } - - private static void InitializeSigning(ISignWrapper sign, IPemKeyCleaner pemCleaner) - { - var publicKeyPem = pemCleaner.ClearPemPublicKey(_ecdsaInstance.ExportSubjectPublicKeyInfoPem()); - PublicKey = publicKeyPem.KeyModelProvider.PublicKey; - - sign.Initialize(_ecdsaInstance, HashAlgorithmName.SHA512); - } - } - ``` - -4. **ValidateETAMP.cs** - - Handles validation of ETAMP messages. - - ```csharp - using System.Security.Cryptography; - using ETAMP.Validation.Interfaces; - using Microsoft.Extensions.DependencyInjection; - - internal class ETAMPValidationRunner - { - private static readonly HashAlgorithmName DefaultHashAlgorithm = HashAlgorithmName.SHA512; - private static async Task Main(string[] args) - { - var provider = CreateETAMP.ConfigureServices(); - var etampValidator = provider.GetService(); - - CreateSignETAMP.Main(); - - var publicKeyBytes = Convert.FromBase64String(CreateSignETAMP.PublicKey); - var initializedEcdsa = CreateInitializedEcdsa(publicKeyBytes); - - etampValidator.Initialize(initializedEcdsa, DefaultHashAlgorithm); - var validationResult = await etampValidator.ValidateETAMPAsync(CreateSignETAMP.ETAMP, false); - - Console.WriteLine(validationResult.IsValid); - } - - private static ECDsa CreateInitializedEcdsa(byte[] publicKey) - { - var ecdsa = ECDsa.Create(); - ecdsa.ImportSubjectPublicKeyInfo(publicKey, out _); - return ecdsa; - } - } - ``` - ## Conclusion This README provides an updated overview of the ETAMP protocol, including its usage with examples. Follow the structure diff --git a/Source/ETAMP.Compression/Codec/DeflateCompressionService.cs b/Source/ETAMP.Compression/Codec/DeflateCompressionService.cs index 571fc90..c70f119 100644 --- a/Source/ETAMP.Compression/Codec/DeflateCompressionService.cs +++ b/Source/ETAMP.Compression/Codec/DeflateCompressionService.cs @@ -1,87 +1,50 @@ -#region - -using System.IO.Compression; -using ETAMP.Compression.Interfaces; +using System.IO.Compression; using Microsoft.Extensions.Logging; -#endregion - namespace ETAMP.Compression.Codec; /// -/// Provides functionality for compressing and decompressing string data using Deflate compression. +/// Provides data compression and decompression services using the Deflate algorithm. /// -public sealed class DeflateCompressionService : ICompressionService +/// +/// This class inherits from and implements the methods +/// required to create compression and decompression streams based on the `DeflateStream` class. +/// +public sealed class DeflateCompressionService : StreamCompressionService { - private readonly ILogger _logger; - + /// + /// A compression service implementing Deflate-based stream compression and decompression. + /// + /// + /// This class utilizes the DeflateStream implementation provided by .NET for compression and decompression of streams. + /// It inherits from the , which provides the core capabilities for handling + /// input and output streams, and compressing or decompressing data asynchronously. + /// public DeflateCompressionService(ILogger logger) + : base(logger) { - _logger = logger; } - public async Task CompressStream(Stream data, CancellationToken cancellationToken = default) + /// + /// Creates a compression stream using the Deflate algorithm. + /// + /// The output stream where compressed data will be written. + /// The indicating whether to compress or decompress the data. + /// A instance configured for compression or decompression based on the specified mode. + protected override Stream CreateCompressionStream(Stream output, CompressionMode mode) { - if (data is not { CanRead: true }) - { - _logger.LogError("The input stream must not be null and must be readable."); - throw new ArgumentException("The input stream must not be null and must be readable.", nameof(data)); - } - - var outputStream = new MemoryStream(); - await using (var compressor = new DeflateStream(outputStream, CompressionMode.Compress, true)) - { - _logger.LogDebug("Compressing data stream..."); - await data.CopyToAsync(compressor, cancellationToken); - } - - _logger.LogDebug("Data stream compressed."); - outputStream.Position = 0; - - return outputStream; + return new DeflateStream(output, mode, true); } - - public async Task DecompressStream(Stream compressedStream, CancellationToken cancellationToken = default) + /// + /// Creates a decompression stream that uses the Deflate algorithm for decompressing data. + /// + /// The input stream to be decompressed. + /// The compression mode specifying that the stream is to be used for decompression. + /// A decompression stream using the Deflate algorithm for the given input stream. + protected override Stream CreateDecompressionStream(Stream input, CompressionMode mode) { - if (compressedStream is not { CanRead: true }) - { - _logger.LogError("The input stream must not be null and must be readable."); - throw new ArgumentException("The input stream must not be null and must be readable.", - nameof(compressedStream)); - } - - var outputStream = new MemoryStream(); - - try - { - await using var decompressor = new DeflateStream(compressedStream, CompressionMode.Decompress); - _logger.LogDebug("Decompressing data stream..."); - await decompressor.CopyToAsync(outputStream, cancellationToken); - } - catch (InvalidDataException ex) - { - _logger.LogError(ex, "Failed to decompress the stream."); - throw new InvalidDataException( - "Failed to decompress the stream. The input data may be invalid or corrupted.", ex); - } - catch (Exception ex) - { - _logger.LogError(ex, "An unexpected error occurred during decompression."); - throw new InvalidOperationException("An unexpected error occurred during decompression.", ex); - } - - if (outputStream.Length == 0) - { - _logger.LogError("The decompressed data is empty."); - throw new InvalidOperationException( - "The decompressed data is empty. This may indicate invalid or corrupted input data."); - } - - outputStream.Position = 0; - - _logger.LogDebug("Data stream decompressed successfully."); - return outputStream; + return new DeflateStream(input, mode, true); } } \ No newline at end of file diff --git a/Source/ETAMP.Compression/Codec/GZipCompressionService.cs b/Source/ETAMP.Compression/Codec/GZipCompressionService.cs index 0a2ce53..57c212d 100644 --- a/Source/ETAMP.Compression/Codec/GZipCompressionService.cs +++ b/Source/ETAMP.Compression/Codec/GZipCompressionService.cs @@ -1,90 +1,52 @@ -#region - -using System.IO.Compression; -using ETAMP.Compression.Interfaces; +using System.IO.Compression; using Microsoft.Extensions.Logging; -#endregion - namespace ETAMP.Compression.Codec; /// -/// Provides functionality for compressing and decompressing string data using GZip compression. -/// This class implements the ICompressionService interface and provides methods -/// to compress a string into a compressed Base64-encoded format and to decompress -/// a Base64-encoded compressed string back to its original format. +/// Provides GZip-based compression and decompression services by extending the functionality +/// of the class. /// -public sealed class GZipCompressionService : ICompressionService +/// +/// This service uses for compressing and decompressing data streams. +/// It is designed to work with the ETAMP compression framework and implements the interface. +/// +public sealed class GZipCompressionService : StreamCompressionService { - private readonly ILogger _logger; - + /// + /// Provides GZip-based compression and decompression services for data streams. + /// + /// + /// This service uses the GZipStream class to implement the compression and decompression logic + /// by creating streams with the specified compression mode. + /// public GZipCompressionService(ILogger logger) + : base(logger) { - _logger = logger; } - - public async Task CompressStream(Stream data, CancellationToken cancellationToken = default) + /// + /// Creates a compression stream for handling data compression or decompression. + /// + /// The stream to which the compressed data will be written. + /// The compression mode, either Compress or Decompress, indicating the operation. + /// A stream that performs compression or decompression based on the specified mode. + protected override Stream CreateCompressionStream(Stream output, CompressionMode mode) { - if (data is not { CanRead: true }) - { - _logger.LogError("The input stream must not be null and must be readable."); - throw new ArgumentException("The input stream must not be null and must be readable.", nameof(data)); - } - - var outputStream = new MemoryStream(); - await using (var compressor = new GZipStream(outputStream, CompressionMode.Compress, true)) - { - _logger.LogDebug("Compressing data stream..."); - await data.CopyToAsync(compressor, cancellationToken); - } - - _logger.LogDebug("Data stream compressed."); - outputStream.Position = 0; - - return outputStream; + return new GZipStream(output, mode, true); } - - public async Task DecompressStream(Stream compressedStream, CancellationToken cancellationToken = default) + /// + /// Creates a decompression stream for decompressing data using GZip compression. + /// + /// The input stream containing compressed data. + /// + /// The compression mode specifying whether the stream is used for compression or decompression. + /// Should typically be set to . + /// + /// A configured for decompressing data. + protected override Stream CreateDecompressionStream(Stream input, CompressionMode mode) { - if (compressedStream is not { CanRead: true }) - { - _logger.LogError("The input stream must not be null and must be readable."); - throw new ArgumentException("The input stream must not be null and must be readable.", - nameof(compressedStream)); - } - - var outputStream = new MemoryStream(); - - try - { - await using var decompressor = new GZipStream(compressedStream, CompressionMode.Decompress); - _logger.LogDebug("Decompressing data stream..."); - await decompressor.CopyToAsync(outputStream, cancellationToken); - } - catch (InvalidDataException ex) - { - _logger.LogError(ex, "Failed to decompress the stream."); - throw new InvalidDataException( - "Failed to decompress the stream. The input data may be invalid or corrupted.", ex); - } - catch (Exception ex) - { - _logger.LogError(ex, "An unexpected error occurred during decompression."); - throw new InvalidOperationException("An unexpected error occurred during decompression.", ex); - } - - if (outputStream.Length == 0) - { - _logger.LogError("The decompressed data is empty."); - throw new InvalidOperationException( - "The decompressed data is empty. This may indicate invalid or corrupted input data."); - } - - outputStream.Position = 0; - - _logger.LogDebug("Data stream decompressed successfully."); - return outputStream; + return new GZipStream(input, mode, true); } } \ No newline at end of file diff --git a/Source/ETAMP.Compression/Codec/StreamCompressionService.cs b/Source/ETAMP.Compression/Codec/StreamCompressionService.cs new file mode 100644 index 0000000..29b4173 --- /dev/null +++ b/Source/ETAMP.Compression/Codec/StreamCompressionService.cs @@ -0,0 +1,169 @@ +using System.IO.Compression; +using System.IO.Pipelines; +using ETAMP.Compression.Interfaces; +using Microsoft.Extensions.Logging; + +namespace ETAMP.Compression.Codec; + +/// +/// Abstract base class that provides foundational methods for stream-based data compression and decompression. +/// +/// +/// This class defines the structure for creating compression and decompression streams and managing +/// asynchronous compression and decompression workflows. Specific implementations for different compression +/// algorithms need to inherit from this class and provide concrete implementations of the required methods. +/// +public abstract class StreamCompressionService : ICompressionService +{ + /// + /// Represents a logger instance used to log messages and exceptions during compression + /// and decompression operations in the . + /// + /// + /// This logger is utilized to record debug information, warnings, and errors to aid in + /// diagnostics and monitoring of the compression service. + /// + private readonly ILogger _logger; + + /// + /// An abstract base class for stream compression and decompression services. + /// + /// + /// This class provides the core framework for implementing stream-based compression and decompression + /// functionality. It defines abstract methods for creating compression and decompression streams + /// that are implemented by derived classes, as well as asynchronous methods for performing compression + /// and decompression operations. + /// + protected StreamCompressionService(ILogger logger) + { + _logger = logger; + } + + /// Compresses the data from the specified input pipe reader and writes the compressed result to the specified output pipe writer. + /// This method utilizes a compression stream and handles potential exceptions during the compression process. + /// The providing input data to be compressed. + /// The for writing the compressed data. + /// An optional to observe for cancellation requests. + /// A representing the asynchronous compression operation. + /// Thrown when either the input or output stream is null. + /// Thrown when the compression operation is canceled. + /// Thrown when an unexpected error occurs during compression. + public async Task CompressAsync(PipeReader inputData, PipeWriter outputData, + CancellationToken cancellationToken = default) + { + try + { + await using var compressor = CreateCompressionStream(outputData.AsStream(), CompressionMode.Compress); + _logger.LogDebug("Compressing data stream..."); + await inputData.CopyToAsync(compressor, cancellationToken); + await compressor.FlushAsync(cancellationToken); + } + catch (ArgumentNullException ex) + { + _logger.LogError(ex, "One of the streams (input or output) is null."); + throw new ArgumentNullException("One of the streams (input or output) is null.", ex); + } + catch (OperationCanceledException ex) + { + _logger.LogWarning(ex, "Compression operation was canceled."); + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Compression failed."); + throw new InvalidOperationException("Compression failed.", ex); + } + finally + { + await CompleteFlushAsync(inputData, outputData, cancellationToken); + } + } + + /// + /// Decompresses a stream of input data and writes the decompressed data to the specified output stream. + /// + /// + /// The representing the input stream containing compressed data. + /// + /// + /// The representing the output stream where the decompressed data will be written. + /// + /// + /// Optional. A to monitor for cancellation requests during the decompression operation. + /// + /// + /// A that represents the asynchronous decompression operation. + /// + /// + /// Thrown when either the input or output stream is null. + /// + /// + /// Thrown if the decompression operation is canceled via the provided . + /// + /// + /// Thrown if an error occurs during decompression. + /// + public async Task DecompressAsync(PipeReader inputData, PipeWriter outputData, + CancellationToken cancellationToken = default) + { + try + { + await using var decompressor = CreateDecompressionStream(inputData.AsStream(), CompressionMode.Decompress); + _logger.LogDebug("Decompressing data stream..."); + await decompressor.CopyToAsync(outputData.AsStream(), cancellationToken); + } + catch (ArgumentNullException ex) + { + _logger.LogError(ex, "One of the streams (input or output) is null."); + throw new ArgumentNullException("One of the streams (input or output) is null.", ex); + } + catch (OperationCanceledException ex) + { + _logger.LogWarning(ex, "Decompression operation was canceled."); + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Decompression failed."); + throw new InvalidOperationException("Decompression failed.", ex); + } + finally + { + await CompleteFlushAsync(inputData, outputData, cancellationToken); + } + } + + /// + /// Creates a compression stream to compress or decompress data. + /// + /// The output stream where data will be written. + /// The specifying whether to compress or decompress the data. + /// A configured for compression or decompression based on the specified mode. + protected abstract Stream CreateCompressionStream(Stream output, CompressionMode mode); + + /// + /// Creates a decompression stream for decompressing data from the specified input stream. + /// + /// The input stream containing the compressed data to be decompressed. + /// + /// The compression mode specifying the operation to perform. + /// For decompression, this should typically be set to . + /// + /// A stream configured for decompressing data. + protected abstract Stream CreateDecompressionStream(Stream input, CompressionMode mode); + + /// + /// Completes the flushing of the provided PipeReader and PipeWriter streams and ensures that resources are finalized appropriately. + /// + /// The representing the input data stream. + /// The representing the output data stream. + /// A to observe while waiting for the operation to complete. + /// A that represents the asynchronous operation. + private async Task CompleteFlushAsync(PipeReader inputData, PipeWriter outputData, + CancellationToken cancellationToken) + { + await outputData.FlushAsync(cancellationToken); + await outputData.CompleteAsync(); + await inputData.CompleteAsync(); + } +} \ No newline at end of file diff --git a/Source/ETAMP.Compression/CompressionManager.cs b/Source/ETAMP.Compression/CompressionManager.cs new file mode 100644 index 0000000..e36f5a1 --- /dev/null +++ b/Source/ETAMP.Compression/CompressionManager.cs @@ -0,0 +1,204 @@ +using System.IO.Pipelines; +using System.Text.Json; +using ETAMP.Compression.Interfaces; +using ETAMP.Compression.Interfaces.Factory; +using ETAMP.Core.Models; +using ETAMP.Core.Utils; +using Microsoft.Extensions.Logging; + +namespace ETAMP.Compression; + +/// +/// The CompressionManager class provides functionality for compressing and decompressing models using specified +/// compression algorithms. It acts as a manager for handling compression operations and coordinates between +/// compression services and the model data. +/// +public record CompressionManager : ICompressionManager +{ + private readonly ICompressionServiceFactory _compressionServiceFactory; + + /// + /// Logger used to log messages and information related to compression and decompression processes + /// within the . This instance of the logger provides structured + /// logging capabilities for debugging, monitoring, and tracking the behavior of the compression + /// manager during operations. + /// + private readonly ILogger _logger; + + /// + /// The CompressionManager class provides functionality for compressing and decompressing models + /// using specified compression algorithms. It serves as the primary manager for handling the + /// coordination between compression operations, models, and associated services. + /// + public CompressionManager(ICompressionServiceFactory compressionServiceFactory, ILogger logger) + { + _compressionServiceFactory = compressionServiceFactory; + _logger = logger; + } + + /// Compresses the data contained in the specified ETAMP model using the defined compression type + /// and returns a builder object containing the compressed data and other model properties. + /// + /// An instance of ETAMPModel containing the data and associated properties to compress. + /// + /// + /// A CancellationToken to observe while waiting for the task to complete. + /// Defaults to CancellationToken.None if not provided. + /// + /// + /// The type of the Token contained within the provided ETAMPModel. Must inherit from the Token class. + /// + /// + /// An ETAMPModelBuilder containing the compressed data, model properties, and metadata. + /// + public async Task CompressAsync(ETAMPModel model, + CancellationToken cancellationToken = default) where T : Token + { + _logger.LogInformation("Starting compression for model Id: {ModelId} with compression type: {CompressionType}", + model.Id, model.CompressionType); + + Pipe dataPipe = new(); + Pipe outputData = new(); + + CheckCompressionType(model.CompressionType); + + var compression = _compressionServiceFactory.Get(model.CompressionType); + _logger.LogDebug("Compression service created for type: {CompressionType}", model.CompressionType); + + var jsonBytes = JsonSerializer.SerializeToUtf8Bytes(model.Token); + _logger.LogDebug("Serialized model.Token to JSON bytes, size: {Size} bytes", jsonBytes.Length); + + await dataPipe.Writer.WriteAsync(jsonBytes, cancellationToken); + _logger.LogDebug("Written JSON bytes to data pipe writer."); + + await dataPipe.Writer.FlushAsync(cancellationToken); + await dataPipe.Writer.CompleteAsync(); + _logger.LogDebug("Data pipe writer completed."); + + await compression.CompressAsync(dataPipe.Reader, outputData.Writer, cancellationToken); + _logger.LogDebug("Compression service finished compressing data."); + + using var ms = await ReadAllBytesFromPipeAsync(outputData.Reader, cancellationToken); + var compressedBytes = ms.ToArray(); + + var token = Base64UrlEncoder.Encode(compressedBytes); + _logger.LogInformation("Compression successful for model Id: {ModelId}, resulting token length: {TokenLength}", + model.Id, token.Length); + + return new ETAMPModelBuilder + { + Id = model.Id, + Version = model.Version, + UpdateType = model.UpdateType, + Token = token, + CompressionType = model.CompressionType, + SignatureMessage = model.SignatureMessage + }; + } + + + /// + /// Decompresses the compressed data from the given and deserializes it into the + /// specified token type. + /// + /// The type of the token to deserialize into. Must inherit from . + /// The builder model containing the data to be decompressed and deserialized. + /// A token to cancel the asynchronous operation. + /// + /// An instance of containing the decompressed and deserialized data as the specified + /// token type. + /// + public async Task> DecompressAsync(ETAMPModelBuilder model, + CancellationToken cancellationToken = default) where T : Token + { + _logger.LogInformation( + "Starting decompression for model Id: {ModelId} with compression type: {CompressionType}", + model.Id, model.CompressionType); + + Pipe dataPipe = new(); + Pipe outputData = new(); + + CheckCompressionType(model.CompressionType); + + var compression = _compressionServiceFactory.Get(model.CompressionType); + _logger.LogDebug("Decompression service created for type: {CompressionType}", model.CompressionType); + + if (string.IsNullOrWhiteSpace(model.Token)) + { + _logger.LogError("Decompression failed: token is missing for model Id: {ModelId}", model.Id); + throw new ArgumentException($"Decompression failed: token is missing for model Id: {model.Id}"); + } + + var tokenBytes = Base64UrlEncoder.DecodeBytes(model.Token); + await dataPipe.Writer.WriteAsync(tokenBytes, cancellationToken); + await dataPipe.Writer.CompleteAsync(); + + await compression.DecompressAsync(dataPipe.Reader, outputData.Writer, cancellationToken); + _logger.LogDebug("Decompression service finished decompressing data."); + + await using var ms = await ReadAllBytesFromPipeAsync(outputData.Reader, cancellationToken); + _logger.LogDebug("Starting JSON deserialization from decompressed stream."); + + var token = await JsonSerializer.DeserializeAsync(ms, cancellationToken: cancellationToken); + _logger.LogInformation("Decompression and deserialization successful for model Id: {ModelId}", model.Id); + + return new ETAMPModel + { + Id = model.Id, + Version = model.Version, + UpdateType = model.UpdateType, + Token = token, + CompressionType = model.CompressionType, + SignatureMessage = model.SignatureMessage + }; + } + + /// + /// Validates the compression type provided as input. + /// + /// + /// The compression type to validate. Must not be null, empty, or whitespace. + /// Throws an if the input is invalid. + /// + private void CheckCompressionType(string compressionType) + { + if (string.IsNullOrWhiteSpace(compressionType)) + throw new ArgumentException("Compression type is required."); + } + + /// Reads all bytes from a specified pipe and returns them in a memory stream. + /// + /// The instance from which data will be read. + /// + /// + /// A to observe while waiting for the operation to complete. + /// + /// + /// A containing the bytes read from the pipe. + /// + private async Task ReadAllBytesFromPipeAsync(PipeReader reader, CancellationToken cancellationToken) + { + var ms = new MemoryStream(); + + while (true) + { + var result = await reader.ReadAsync(cancellationToken); + var buffer = result.Buffer; + + foreach (var segment in buffer) ms.Write(segment.Span); + + reader.AdvanceTo(buffer.End); + + if (!result.IsCompleted) + continue; + + _logger.LogDebug("Completed reading from output data pipe."); + break; + } + + ms.Position = 0; + await reader.CompleteAsync(); + _logger.LogDebug("Compression complete, total compressed size: {Size} bytes", ms.Length); + return ms; + } +} \ No newline at end of file diff --git a/Source/ETAMP.Compression/ETAMP.Compression.csproj b/Source/ETAMP.Compression/ETAMP.Compression.csproj index 8a48ac5..fb9f1f8 100644 --- a/Source/ETAMP.Compression/ETAMP.Compression.csproj +++ b/Source/ETAMP.Compression/ETAMP.Compression.csproj @@ -7,7 +7,7 @@ true ETAMP.Compression README.md - net9.0;net8.0 + net9.0 @@ -32,8 +32,8 @@ - - + + diff --git a/Source/ETAMP.Compression/Factory/CompressionServiceFactory.cs b/Source/ETAMP.Compression/Factory/CompressionServiceFactory.cs index 1fa7010..7b5eb33 100644 --- a/Source/ETAMP.Compression/Factory/CompressionServiceFactory.cs +++ b/Source/ETAMP.Compression/Factory/CompressionServiceFactory.cs @@ -1,90 +1,52 @@ -#region - -using System.Collections.Concurrent; -using ETAMP.Compression.Codec; -using ETAMP.Compression.Interfaces; +using ETAMP.Compression.Codec; using ETAMP.Compression.Interfaces.Factory; -using ETAMP.Core.Management; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -#endregion namespace ETAMP.Compression.Factory; /// -/// Factory for creating compression service instances based on the specified compression type. -/// Utilizes the service provider to resolve the requested compression service dynamically at runtime. +/// Provides a factory implementation for creating and retrieving instances of +/// based on a specified compression type. /// -public sealed class CompressionServiceFactory : ICompressionServiceFactory +/// +/// This class is designed to work with the dependency injection container and leverages keyed service +/// resolution to retrieve the appropriate compression service. +/// +public sealed record CompressionServiceFactory : ICompressionServiceFactory { - private readonly ILogger _logger; - - - /// - /// Provides a factory for managing and creating instances of compression services. This factory supports - /// registering and retrieving compression services by a specified type. - /// - public CompressionServiceFactory(IServiceProvider serviceProvider, ILogger logger) - { - _logger = logger; - Factory = new ConcurrentDictionary(); - Factory.TryAdd(CompressionNames.Deflate, serviceProvider.GetRequiredService()); - Factory.TryAdd(CompressionNames.GZip, serviceProvider.GetRequiredService()); - } - /// - /// Gets the dictionary mapping compression types to their corresponding compression service instances. + /// Represents the service provider used to resolve dependencies within the compression service factory. /// - public ConcurrentDictionary Factory { get; } + /// + /// This variable holds an instance of , which is used to dynamically retrieve + /// keyed or non-keyed compression services based on the requested compression type. + /// + private readonly IServiceProvider _serviceProvider; /// - /// Creates and returns an instance of a compression service based on the specified compression type. + /// A factory class to create and retrieve instances of StreamCompressionService based on the compression type. /// - /// - /// The type of compression service to create. This should match one of the predefined - /// constants in . - /// - /// An instance of the requested compression service. - /// Thrown if the specified compression type is not recognized or supported. - public ICompressionService Create(string compressionType) + public CompressionServiceFactory(IServiceProvider serviceProvider) { - if (!Factory.TryGetValue(compressionType, out var serviceFactory)) - { - _logger.LogError("ETAMPBuilder service '{0}' not recognized.", compressionType); - throw new KeyNotFoundException($"ETAMPBuilder service '{compressionType}' not recognized."); - } - - return serviceFactory; + _serviceProvider = serviceProvider; } - /// - /// Registers a new compression service under a specified compression type. + /// Retrieves an instance of the StreamCompressionService based on the specified compression type. + /// Returns null if the compression type is null, empty, or whitespace. /// - /// The type of compression to register. - /// The compression service instance to be registered. - /// - /// Thrown if either or - /// is null. - /// - public void RegisterCompressionService(string compressionType, ICompressionService serviceFactory) + /// + /// The type of compression for which the StreamCompressionService instance is required. + /// + /// + /// An instance of corresponding to the specified compression type, + /// or null if the compression type is invalid. + /// + public StreamCompressionService? Get(string compressionType) { - ArgumentException.ThrowIfNullOrWhiteSpace(compressionType, nameof(compressionType)); - ArgumentNullException.ThrowIfNull(serviceFactory, nameof(serviceFactory)); + if (string.IsNullOrWhiteSpace(compressionType)) + return null; - Factory.TryAdd(compressionType, serviceFactory); - } - - /// - /// Unregisters an existing compression service for a specified compression type. - /// - /// The type of compression to unregister. - /// True if the service was successfully unregistered; otherwise, false. - /// Thrown if is null. - public bool UnregisterCompressionService(string compressionType) - { - ArgumentException.ThrowIfNullOrWhiteSpace(compressionType, nameof(compressionType)); - return Factory.TryRemove(compressionType, out _); + return _serviceProvider.GetKeyedService(compressionType); } } \ No newline at end of file diff --git a/Source/ETAMP.Compression/Interfaces/Factory/ICompressionServiceFactory.cs b/Source/ETAMP.Compression/Interfaces/Factory/ICompressionServiceFactory.cs index 3bc8116..1187aaf 100644 --- a/Source/ETAMP.Compression/Interfaces/Factory/ICompressionServiceFactory.cs +++ b/Source/ETAMP.Compression/Interfaces/Factory/ICompressionServiceFactory.cs @@ -1,48 +1,20 @@ -#region - -using System.Collections.Concurrent; - -#endregion +using ETAMP.Compression.Codec; namespace ETAMP.Compression.Interfaces.Factory; /// -/// Defines a factory interface for creating instances of compression services based on a specified type. +/// Defines a factory interface for creating and retrieving instances of +/// compression services based on the specified compression type. /// public interface ICompressionServiceFactory { /// - /// Represents a factory for creating instances of compression services based on a specified type. - /// - ConcurrentDictionary Factory { get; } - - /// - /// Creates an instance of a compression service based on the specified compression type. - /// - /// - /// The type of compression to create. This type should correspond to one of the supported - /// compression algorithms. - /// - /// An instance of that corresponds to the specified compression type. - ICompressionService Create(string compressionType); - - - /// - /// Registers a new compression service under a specified compression type. - /// - /// The type of compression to register. - /// The compression service instance to be registered. - /// - /// Thrown if either or - /// is null. - /// - void RegisterCompressionService(string compressionType, ICompressionService serviceFactory); - - /// - /// Unregisters an existing compression service for a specified compression type. + /// Retrieves an instance of based on the specified compression type. /// - /// The type of compression to unregister. - /// True if the service was successfully unregistered; otherwise, false. - /// Thrown if is null. - bool UnregisterCompressionService(string compressionType); + /// The type of compression for which the corresponding service is required. + /// + /// An instance of that matches the specified compression type, or null if no + /// matching service is found. + /// + StreamCompressionService? Get(string compressionType); } \ No newline at end of file diff --git a/Source/ETAMP.Compression/Interfaces/ICompressionManager.cs b/Source/ETAMP.Compression/Interfaces/ICompressionManager.cs new file mode 100644 index 0000000..0b71428 --- /dev/null +++ b/Source/ETAMP.Compression/Interfaces/ICompressionManager.cs @@ -0,0 +1,35 @@ +using ETAMP.Core.Models; + +namespace ETAMP.Compression.Interfaces; + +/// +/// Interface for managing compression and decompression operations. +/// Provides methods to compress and decompress models. +/// +public interface ICompressionManager +{ + /// + /// Compresses the specified ETAMP model asynchronously and returns a corresponding ETAMPModelBuilder instance. + /// + /// The type of token contained in the ETAMP model, which must inherit from . + /// The instance of to be compressed. + /// + /// A token to monitor for cancellation requests. Defaults to if not provided. + /// + /// + /// A task that represents the asynchronous operation. The task result contains an + /// representing the compressed model. + /// + Task CompressAsync(ETAMPModel model, CancellationToken cancellationToken = default) + where T : Token; + + /// + /// Converts a compressed ETAMPModelBuilder back into an ETAMPModel of a specified token type. + /// + /// The type of token associated with the ETAMP model, constrained to inherit from Token. + /// The builder representation of the compressed model to be decompressed. + /// A token to monitor for cancellation requests. + /// Returns the decompressed ETAMPModel of the specified token type. + Task> DecompressAsync(ETAMPModelBuilder model, CancellationToken cancellationToken = default) + where T : Token; +} \ No newline at end of file diff --git a/Source/ETAMP.Compression/Interfaces/ICompressionService.cs b/Source/ETAMP.Compression/Interfaces/ICompressionService.cs index 0489dae..d369afb 100644 --- a/Source/ETAMP.Compression/Interfaces/ICompressionService.cs +++ b/Source/ETAMP.Compression/Interfaces/ICompressionService.cs @@ -1,10 +1,30 @@ -namespace ETAMP.Compression.Interfaces; +using System.IO.Pipelines; + +namespace ETAMP.Compression.Interfaces; /// -/// Defines a contract for services that can compress and decompress strings. +/// Interface for services that provide methods to compress and decompress data streams. /// public interface ICompressionService { - Task CompressStream(Stream data, CancellationToken cancellationToken = default); - Task DecompressStream(Stream base64CompressedData, CancellationToken cancellationToken = default); + /// + /// Compresses the input data stream and writes the compressed data to the output stream asynchronously. + /// + /// The instance for reading the data to be compressed. + /// The instance for writing the compressed data. + /// + /// An optional to observe while waiting for the task to + /// complete. + /// + /// A that represents the asynchronous operation. + Task CompressAsync(PipeReader inputData, PipeWriter outputData, CancellationToken cancellationToken = default); + + /// + /// Decompresses data from an input PipeReader and writes the decompressed data to an output PipeWriter. + /// + /// The PipeReader containing the compressed input data to be decompressed. + /// The PipeWriter where the decompressed output data will be written. + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous decompression operation. + Task DecompressAsync(PipeReader inputData, PipeWriter outputData, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Source/ETAMP.Core/ETAMP.Core.csproj b/Source/ETAMP.Core/ETAMP.Core.csproj index f2acbd2..2ea51ae 100644 --- a/Source/ETAMP.Core/ETAMP.Core.csproj +++ b/Source/ETAMP.Core/ETAMP.Core.csproj @@ -7,7 +7,7 @@ true ETAMP.Core README.md - net9.0;net8.0 + net9.0 @@ -33,7 +33,8 @@ - + + 1.1.0 diff --git a/Source/ETAMP.Core/ETAMPProtocol.cs b/Source/ETAMP.Core/ETAMPProtocol.cs deleted file mode 100644 index 98f0e47..0000000 --- a/Source/ETAMP.Core/ETAMPProtocol.cs +++ /dev/null @@ -1,44 +0,0 @@ -#region - -using ETAMP.Core.Interfaces; -using ETAMP.Core.Models; -using ETAMP.Core.Utils; - -#endregion - -namespace ETAMP.Core; - -/// -/// This class represents the base implementation of the ETAMP (Encrypted Token And Message Protocol) functionality. -/// -public sealed class ETAMPProtocol : IETAMPBase -{ - private readonly VersionInfo _versionInfo; - - public ETAMPProtocol(VersionInfo versionInfo) - { - _versionInfo = versionInfo; - } - - /// - /// Creates an ETAMP model for the given payload. - /// - /// The type of the payload. - /// The update type. - /// The payload object. - /// The compression type. - /// The ETAMP model. - public ETAMPModel CreateETAMPModel(string updateType, T payload, string compressionType) where T : Token - { - var messageId = Guid.NewGuid(); - payload.MessageId = messageId; - return new ETAMPModel - { - Id = messageId, - Version = _versionInfo.ProtocolVersion, - Token = payload, - UpdateType = updateType, - CompressionType = compressionType - }; - } -} \ No newline at end of file diff --git a/Source/ETAMP.Core/Extensions/ECDKeyModelProviderExtensions.cs b/Source/ETAMP.Core/Extensions/ECDKeyModelProviderExtensions.cs new file mode 100644 index 0000000..8c1ee6d --- /dev/null +++ b/Source/ETAMP.Core/Extensions/ECDKeyModelProviderExtensions.cs @@ -0,0 +1,25 @@ +using ETAMP.Core.Models; + +namespace ETAMP.Core.Extensions; + +/// +/// Provides extension methods for the class. +/// +public static class ECDKeyModelProviderExtensions +{ + /// + /// Removes PEM formatting from both public and private keys by stripping prefix, suffix, and line breaks. + /// + /// The key model provider containing the PEM-formatted public and private keys. + public static void ClearPemFormatting(this ECDKeyModelProvider model) + { + model.PrivateKey.Replace("-----BEGIN PRIVATE KEY-----", "") + .Replace("-----END PRIVATE KEY-----", "") + .Replace("\n", "") + .Replace("\r", ""); + model.PublicKey.Replace("-----BEGIN PUBLIC KEY-----", "") + .Replace("-----END PUBLIC KEY-----", "") + .Replace("\n", "") + .Replace("\r", ""); + } +} \ No newline at end of file diff --git a/Source/ETAMP.Core/Extensions/ETAMPModelBuilderExtensions.cs b/Source/ETAMP.Core/Extensions/ETAMPModelBuilderExtensions.cs new file mode 100644 index 0000000..9226fe1 --- /dev/null +++ b/Source/ETAMP.Core/Extensions/ETAMPModelBuilderExtensions.cs @@ -0,0 +1,26 @@ +using System.Text; +using System.Text.Json; +using ETAMP.Core.Models; + +namespace ETAMP.Core.Extensions; + +public static class ETAMPModelBuilderExtensions +{ + /// + /// Converts the current instance to a JSON string representation asynchronously. + /// + /// The instance to be converted to JSON format. + /// + /// A task representing the asynchronous operation. The task result contains the JSON string representation of the + /// builder. + /// + public static async Task ToJsonAsync(this ETAMPModelBuilder builder) + { + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, builder); + ms.Position = 0; + + using var sr = new StreamReader(ms, Encoding.UTF8); + return await sr.ReadToEndAsync(); + } +} \ No newline at end of file diff --git a/Source/ETAMP.Core/Extensions/ETAMPModelExtensions.cs b/Source/ETAMP.Core/Extensions/ETAMPModelExtensions.cs new file mode 100644 index 0000000..4c57447 --- /dev/null +++ b/Source/ETAMP.Core/Extensions/ETAMPModelExtensions.cs @@ -0,0 +1,27 @@ +using System.Text; +using System.Text.Json; +using ETAMP.Core.Models; + +namespace ETAMP.Core.Extensions; + +public static class ETAMPModelExtensions +{ + /// + /// Converts the specified ETAMP model to its JSON string representation asynchronously. + /// + /// The type of token associated with the ETAMP model, constrained to inherit from Token. + /// The ETAMP model instance to be serialized to JSON. + /// + /// A task that represents the asynchronous operation. + /// The task result contains the JSON string representation of the ETAMP model. + /// + public static async Task ToJsonAsync(this ETAMPModel model) where T : Token + { + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, model); + ms.Position = 0; + + using var sr = new StreamReader(ms, Encoding.UTF8); + return await sr.ReadToEndAsync(); + } +} \ No newline at end of file diff --git a/Source/ETAMP.Core/Extensions/TokenExtensions.cs b/Source/ETAMP.Core/Extensions/TokenExtensions.cs new file mode 100644 index 0000000..da1b3cd --- /dev/null +++ b/Source/ETAMP.Core/Extensions/TokenExtensions.cs @@ -0,0 +1,89 @@ +using System.Text; +using System.Text.Json; +using ETAMP.Core.Models; + +namespace ETAMP.Core.Extensions; + +public static class TokenExtensions +{ + /// + /// Asynchronously serializes the given object to a JSON-encoded string. + /// + /// The type of the object to serialize. + /// The object to be serialized. + /// A token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous operation. The task result contains the JSON string representation of + /// the serialized object. + /// + private static async Task SerializeToJsonAsync(T data, CancellationToken cancellationToken = default) + { + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, data, cancellationToken: cancellationToken); + ms.Position = 0; + + using var sr = new StreamReader(ms, Encoding.UTF8); + return await sr.ReadToEndAsync(cancellationToken); + } + + /// + /// Deserializes a JSON string into an object of type . + /// + /// The type of the object to deserialize. Must be a reference type. + /// The JSON string to deserialize. + /// A cancellation token that can be used to cancel the operation. + /// The deserialized object of type , or null if deserialization fails. + private static async Task DeserializeFromJsonAsync(string json, + CancellationToken cancellationToken = default) + where T : class + { + var jsonBytes = Encoding.UTF8.GetBytes(json); + using var stream = new MemoryStream(jsonBytes); + + return await JsonSerializer.DeserializeAsync(stream, cancellationToken: cancellationToken); + } + + /// + /// Sets the data for the token by serializing the provided object to a JSON string. + /// + /// The type of the data object to be serialized and set in the token. + /// The token where the serialized data will be stored. + /// The object to be serialized and set in the token. + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous operation. + public static async Task SetData(this Token token, T dataObject, CancellationToken cancellationToken = default) + where T : class + { + token.Data = await SerializeToJsonAsync(dataObject, cancellationToken); + } + + /// + /// Asynchronously retrieves and deserializes the data stored in the property into an object + /// of the specified type. + /// + /// The type to deserialize the stored data into. + /// The token containing the serialized data. + /// A cancellation token that can be used to cancel the asynchronous operation. + /// The deserialized object of type or null if deserialization fails. + /// Thrown if is null. + public static async Task GetData(this Token token, CancellationToken cancellationToken = default) + where T : class + { + ArgumentNullException.ThrowIfNull(token.Data); + return await DeserializeFromJsonAsync(token.Data, cancellationToken); + } + + /// + /// Converts the current instance to its JSON representation asynchronously. + /// + /// The instance to serialize to JSON format. + /// A CancellationToken used to observe cancellation requests. + /// + /// A task that represents the asynchronous operation. The task result contains the JSON string representation of + /// the . + /// + public static async Task ToJsonAsync(this Token token, CancellationToken cancellationToken = default) + { + return await SerializeToJsonAsync(token, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/ETAMP.Core/Factories/ETAMPModelFactory.cs b/Source/ETAMP.Core/Factories/ETAMPModelFactory.cs new file mode 100644 index 0000000..4a4eee0 --- /dev/null +++ b/Source/ETAMP.Core/Factories/ETAMPModelFactory.cs @@ -0,0 +1,65 @@ +using ETAMP.Core.Interfaces; +using ETAMP.Core.Models; +using ETAMP.Core.Utils; + +namespace ETAMP.Core.Factories; + +/// +/// Factory class for creating instances of the . +/// +/// +/// This class generates ETAMP models, which encapsulate payload data, update type, +/// compression type, and other metadata specific to the ETAMP protocol. +/// +public class ETAMPModelFactory : IETAMPBase +{ + /// + /// Represents the protocol version used within the ETAMPModelFactory. + /// + /// + /// This variable is an instance of the class, encapsulating details + /// about the protocol's current version. It plays a critical role in ensuring that all models + /// created by the factory adhere to the same protocol version, maintaining consistency + /// across the communication framework. + /// + private readonly VersionInfo _version; + + /// + /// A factory class for creating instances of the . + /// + /// + /// The ETAMPModelFactory provides a mechanism to create ETAMPModel instances with specific + /// properties like update type, token payload, and compression type. It also associates + /// these instances with a unique message ID and protocol version. + /// + public ETAMPModelFactory(VersionInfo version) + { + _version = version; + } + + /// + /// Creates an instance of the class with specified update type, payload, and compression + /// type. + /// + /// The type of the token payload, constrained to inherit from . + /// The type of update being performed. + /// The payload containing the token to be encapsulated within the model. + /// The compression type to be applied to the model. + /// + /// An instance of populated with the provided parameters and additional default + /// values. + /// + public ETAMPModel CreateETAMPModel(string updateType, T payload, string compressionType) where T : Token + { + var messageId = Guid.CreateVersion7(); + payload.MessageId = messageId; + return new ETAMPModel + { + Id = messageId, + Version = _version.ProtocolVersion, + Token = payload, + UpdateType = updateType, + CompressionType = compressionType + }; + } +} \ No newline at end of file diff --git a/Source/ETAMP.Core/Interfaces/IETAMPBase.cs b/Source/ETAMP.Core/Interfaces/IETAMPBase.cs index 6742cab..d01194a 100644 --- a/Source/ETAMP.Core/Interfaces/IETAMPBase.cs +++ b/Source/ETAMP.Core/Interfaces/IETAMPBase.cs @@ -1,8 +1,4 @@ -#region - -using ETAMP.Core.Models; - -#endregion +using ETAMP.Core.Models; namespace ETAMP.Core.Interfaces; diff --git a/Source/ETAMP.Core/Interfaces/IInitialize.cs b/Source/ETAMP.Core/Interfaces/IInitialize.cs index 9951483..74ee56f 100644 --- a/Source/ETAMP.Core/Interfaces/IInitialize.cs +++ b/Source/ETAMP.Core/Interfaces/IInitialize.cs @@ -1,12 +1,22 @@ -#region - -using System.Security.Cryptography; - -#endregion +using System.Security.Cryptography; namespace ETAMP.Core.Interfaces; +/// +/// Provides an interface for components that require initialization with cryptographic providers and algorithms. +/// +/// +/// Classes implementing this interface can be initialized with an optional ECDsa cryptographic provider and a +/// specified +/// hash algorithm name. The interface also extends to ensure proper disposal of resources +/// used during initialization. +/// public interface IInitialize : IDisposable { + /// + /// Initializes the cryptographic provider and hash algorithm. + /// + /// The ECDsa provider to be used for cryptographic operations. + /// The hash algorithm name to use with the provider. void Initialize(ECDsa? provider, HashAlgorithmName algorithmName); } \ No newline at end of file diff --git a/Source/ETAMP.Core/Models/ECDKeyModelProvider.cs b/Source/ETAMP.Core/Models/ECDKeyModelProvider.cs index ae8f6bb..319fd0e 100644 --- a/Source/ETAMP.Core/Models/ECDKeyModelProvider.cs +++ b/Source/ETAMP.Core/Models/ECDKeyModelProvider.cs @@ -1,13 +1,9 @@ -#region - -#endregion - -namespace ETAMP.Core.Models; +namespace ETAMP.Core.Models; /// /// Provides storage for public and private keys in a model, typically used in cryptographic operations. /// -public class ECDKeyModelProvider +public record ECDKeyModelProvider { /// /// Gets or sets the public key in a format suitable for the cryptographic operation. @@ -18,17 +14,4 @@ public class ECDKeyModelProvider /// Gets or sets the private key in a format suitable for the cryptographic operation. /// public string? PrivateKey { get; set; } - - public static string ClearPemFormatting(string key) - { - if (!string.IsNullOrWhiteSpace(key)) - return key.Replace("-----BEGIN PRIVATE KEY-----", "") - .Replace("-----END PRIVATE KEY-----", "") - .Replace("-----BEGIN PUBLIC KEY-----", "") - .Replace("-----END PUBLIC KEY-----", "") - .Replace("\n", "") - .Replace("\r", ""); - - throw new ArgumentException("Key cannot be null or empty.", nameof(key)); - } } \ No newline at end of file diff --git a/Source/ETAMP.Core/Models/ETAMPModel.cs b/Source/ETAMP.Core/Models/ETAMPModel.cs index 4d6fdf8..b4d8395 100644 --- a/Source/ETAMP.Core/Models/ETAMPModel.cs +++ b/Source/ETAMP.Core/Models/ETAMPModel.cs @@ -1,18 +1,12 @@ -#region - -using System.Text; -using System.Text.Json; - -#endregion +using System.Text; namespace ETAMP.Core.Models; /// -/// Represents the model for the ETAMP (Encrypted Token And Message Protocol) structure. -/// This model is designed to facilitate secure and efficient exchanges of messages and transactions within a network, -/// by leveraging the ETAMP framework which encapsulates data in a secure and structured manner. +/// Represents a model used in the ETAMP protocol with generic support for token types. /// -public struct ETAMPModel where T : Token +/// The type of token associated with the model, constrained to inherit from Token. +public record ETAMPModel where T : Token { /// /// Gets or sets the unique identifier for the ETAMP model instance. @@ -51,79 +45,6 @@ public struct ETAMPModel where T : Token public string? SignatureMessage { get; set; } - /// - /// Determines whether the specified object is equal to the current object. - /// - /// The object to compare with the current object. - /// - /// true if the specified object is equal to the current object; otherwise, false. - /// - public override bool Equals(object? obj) - { - if (obj is ETAMPModel other) - return Id == other.Id - && Version.Equals(other.Version) - && UpdateType == other.UpdateType - && Token == other.Token - && CompressionType == other.CompressionType - && SignatureMessage == other.SignatureMessage; - - return false; - } - - - public async Task ToJsonStreamAsync() - { - var stream = new MemoryStream(); - await using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Indented = false - }); - - await WriteJson(writer); - stream.Position = 0; - await writer.FlushAsync(); - return stream; - } - - public async Task ToJsonAsync() - { - var stream = await ToJsonStreamAsync(); - using StreamReader reader = new(stream); - return await reader.ReadToEndAsync(); - } - - private async Task WriteJson(Utf8JsonWriter writer) - { - writer.WriteStartObject(); - writer.WriteString(nameof(Id), Id.ToString()); - writer.WriteNumber(nameof(Version), Version); - - - if (Token != null) - { - writer.WritePropertyName(nameof(Token)); - writer.WriteRawValue(await Token.ToJsonAsync(), true); - } - - if (!string.IsNullOrEmpty(UpdateType)) - { - writer.WriteString(nameof(UpdateType), UpdateType); - } - - if (!string.IsNullOrEmpty(CompressionType)) - { - writer.WriteString(nameof(CompressionType), CompressionType); - } - - if (!string.IsNullOrEmpty(SignatureMessage)) - { - writer.WriteString(nameof(SignatureMessage), SignatureMessage); - } - - writer.WriteEndObject(); - } - /// /// Serves as the default hash function. /// @@ -139,8 +60,8 @@ public override string ToString() sb.Append("Id: ").Append(Id) .Append(", Version: ").Append(Version) .Append(", UpdateType: ").Append(UpdateType) - .Append(", CompressionType: ") - .Append(CompressionType) + .Append(", Token: ").Append(Token) + .Append("CompressionType: ").Append(CompressionType) .Append(", SignatureMessage: ").Append(SignatureMessage); return sb.ToString(); } diff --git a/Source/ETAMP.Core/Models/ETAMPModelBuilder.cs b/Source/ETAMP.Core/Models/ETAMPModelBuilder.cs index e01524a..6e31cf2 100644 --- a/Source/ETAMP.Core/Models/ETAMPModelBuilder.cs +++ b/Source/ETAMP.Core/Models/ETAMPModelBuilder.cs @@ -1,13 +1,10 @@ -using System.Text; -using System.Text.Json; - -namespace ETAMP.Core.Models; +namespace ETAMP.Core.Models; /// /// Represents a data model builder for the ETAMP framework. /// This class is used to encapsulate the properties required for building or reconstructing ETAMP-related data models. /// -public class ETAMPModelBuilder +public record struct ETAMPModelBuilder { /// /// Represents a unique identifier that is used to distinguish individual instances of the type. @@ -52,48 +49,4 @@ public class ETAMPModelBuilder /// or other forms of validation/verification messages. /// public string? SignatureMessage { get; set; } - - public async Task ToJsonAsync() - { - await using var stream = new MemoryStream(); - await using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Indented = false - }); - - WriteJson(writer); - await writer.FlushAsync(); - - return Encoding.UTF8.GetString(stream.ToArray()); - } - - private void WriteJson(Utf8JsonWriter writer) - { - writer.WriteStartObject(); - writer.WriteString(nameof(Id), Id.ToString()); - writer.WriteNumber(nameof(Version), Version); - - - if (!string.IsNullOrEmpty(Token)) - { - writer.WriteString(nameof(Token), Token); - } - - if (!string.IsNullOrEmpty(UpdateType)) - { - writer.WriteString(nameof(UpdateType), UpdateType); - } - - if (!string.IsNullOrEmpty(CompressionType)) - { - writer.WriteString(nameof(CompressionType), CompressionType); - } - - if (!string.IsNullOrEmpty(SignatureMessage)) - { - writer.WriteString(nameof(SignatureMessage), SignatureMessage); - } - - writer.WriteEndObject(); - } } \ No newline at end of file diff --git a/Source/ETAMP.Core/Models/Token.cs b/Source/ETAMP.Core/Models/Token.cs index 6ed2468..3a8b007 100644 --- a/Source/ETAMP.Core/Models/Token.cs +++ b/Source/ETAMP.Core/Models/Token.cs @@ -1,26 +1,13 @@ -#region - +using System.Reflection; using System.Text; -using System.Text.Json; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -#endregion namespace ETAMP.Core.Models; /// /// Represents a token object. /// -public class Token +public record Token { - private readonly ILogger _logger; - - public Token(ILogger? logger = null) - { - _logger = logger ?? NullLogger.Instance; - } - /// /// Represents the unique identifier of a token. /// @@ -29,7 +16,7 @@ public Token(ILogger? logger = null) /// /// Represents the unique identifier of a message. /// - public Guid MessageId { get; protected internal set; } + public Guid MessageId { get; set; } /// /// Gets or sets a value indicating whether the data for this token is encrypted. @@ -47,63 +34,14 @@ public Token(ILogger? logger = null) /// public DateTimeOffset TimeStamp { get; } = DateTimeOffset.Now.ToUniversalTime(); - /// - /// Set data after converting an object to a JSON string. - /// - public async Task SetData(T dataObject) where T : class + public override string ToString() { - _logger.LogDebug("Serializing token data"); - await using var memoryStream = new MemoryStream(); - await JsonSerializer.SerializeAsync(memoryStream, dataObject); - _logger.LogDebug("Serializing token data done"); - Data = Encoding.UTF8.GetString(memoryStream.ToArray()); - } + var type = typeof(Token); + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + StringBuilder sb = new(); + foreach (var property in properties) + sb.Append(property.Name).Append(": ").Append(property.GetValue(this)).Append(", "); - /// - /// Get data by converting the JSON string back to an object of type T. - /// - public async Task GetData() where T : class - { - if (string.IsNullOrEmpty(Data)) - return null; - try - { - _logger.LogDebug("Deserializing token data"); - var byteArray = Encoding.UTF8.GetBytes(Data); - await using var memoryStream = new MemoryStream(byteArray); - _logger.LogDebug("Deserializing token data done"); - return await JsonSerializer.DeserializeAsync(memoryStream); - } - catch (JsonException ex) - { - _logger.LogError(ex, "Error deserializing token data"); - return null; - } - } - - public string ToJson() - { - return JsonSerializer.Serialize(this); - } - - public async Task ToJsonStreamAsync(CancellationToken cancellationToken = default) - { - _logger.LogDebug("Serializing token"); - var memoryStream = new MemoryStream(); - await JsonSerializer.SerializeAsync(memoryStream, this, cancellationToken: cancellationToken); - memoryStream.Position = 0; - _logger.LogDebug("Serializing token done"); - return memoryStream; - } - - public async Task ToJsonAsync(CancellationToken cancellationToken = default) - { - _logger.LogDebug("Converting token to JSON string"); - using var memoryStream = new MemoryStream(); - await JsonSerializer.SerializeAsync(memoryStream, this, cancellationToken: cancellationToken); - memoryStream.Position = 0; - using var reader = new StreamReader(memoryStream, Encoding.UTF8); - _logger.LogDebug("Token successfully serialized to JSON string"); - return await reader.ReadToEndAsync(cancellationToken); + return sb.ToString(); } } \ No newline at end of file diff --git a/Source/ETAMP.Core/Models/ValidationResult.cs b/Source/ETAMP.Core/Models/ValidationResult.cs index 3eb9581..f7cd44a 100644 --- a/Source/ETAMP.Core/Models/ValidationResult.cs +++ b/Source/ETAMP.Core/Models/ValidationResult.cs @@ -37,7 +37,7 @@ public ValidationResult(bool isValid, string errorMessage = "", Exception? excep /// /// The exception related to the validation failure, or null if no exception occurred. /// - public Exception Exception { get; set; } + public Exception? Exception { get; set; } /// diff --git a/Source/ETAMP.Core/Utils/Base64UrlEncoder.cs b/Source/ETAMP.Core/Utils/Base64UrlEncoder.cs index d05752f..f1626c5 100644 --- a/Source/ETAMP.Core/Utils/Base64UrlEncoder.cs +++ b/Source/ETAMP.Core/Utils/Base64UrlEncoder.cs @@ -1,84 +1,124 @@ -#region - -using System.Globalization; +using System.Buffers.Text; using System.Text; -#endregion - namespace ETAMP.Core.Utils; /// -/// Provides methods to encode and decode strings using the Base64 URL encoding scheme. +/// Provides utility methods for Base64 URL encoding and decoding. /// +/// +/// This class allows encoding and decoding of data using the Base64 URL format, which is a URL-safe version of Base64 +/// encoding. +/// It is useful for encoding binary data or strings into a URL-safe format and decoding it back without introducing +/// invalid URL characters. +/// public static class Base64UrlEncoder { - private static readonly char base64PadCharacter = '='; - - private static string doubleBase64PadCharacter = - string.Format(CultureInfo.InvariantCulture, "{0}{0}", base64PadCharacter); - - private static readonly char base64Character62 = '+'; - private static readonly char base64Character63 = '/'; - private static readonly char base64UrlCharacter62 = '-'; - private static readonly char base64UrlCharacter63 = '_'; - + /// Encodes the specified string into a Base64Url encoded string. + /// The input string to be encoded. It must not be null. + /// A Base64Url encoded representation of the input string. + /// Thrown if the input string is null. public static string Encode(string arg) { ArgumentNullException.ThrowIfNull(arg); - - return Encode(Encoding.UTF8.GetBytes(arg)); + var data = Encode(Encoding.UTF8.GetBytes(arg).AsSpan()); + return Encoding.UTF8.GetString(data); } + /// Encodes a byte array to a Base64Url-encoded string representation. + /// + /// The byte array to be encoded. Cannot be null. + /// + /// + /// A string containing the Base64Url-encoded representation of the input byte array. + /// + /// + /// Thrown when the input byte array is null. + /// public static string Encode(byte[] arg) { ArgumentNullException.ThrowIfNull(arg); + var data = Encode(arg.AsSpan()); + return Encoding.UTF8.GetString(data); + } - var base64 = Convert.ToBase64String(arg); - var padIndex = base64.IndexOf(base64PadCharacter); - - Span result = stackalloc char[padIndex < 0 ? base64.Length : padIndex]; - base64.AsSpan(0, result.Length).CopyTo(result); - - for (var i = 0; i < result.Length; i++) - if (result[i] == base64Character62) - result[i] = base64UrlCharacter62; - else if (result[i] == base64Character63) - result[i] = base64UrlCharacter63; - - return new string(result); + /// Encodes a given input using Base64 URL encoding. + /// The string input to encode. It must not be null. + /// A Base64 URL-encoded string representation of the input. + public static Span Encode(ReadOnlySpan arg) + { + var destination = new Span(new byte[Base64Url.GetEncodedLength(arg.Length)]); + Base64Url.EncodeToUtf8(arg, destination); + return destination; } + /// Encodes the specified byte array into a Base64Url encoded byte array. + /// The input byte array to be encoded. It must not be null. + /// A byte array containing the Base64Url encoded representation of the input byte array. + /// Thrown if the input byte array is null. + public static byte[] EncodeBytes(byte[] arg) + { + ArgumentNullException.ThrowIfNull(arg); + return Encode(arg.AsSpan()).ToArray(); + } + /// Decodes a Base64 URL-encoded string into a byte array. + /// + /// The Base64 URL-encoded input string to decode. + /// + /// + /// A byte array containing the decoded data from the input string. + /// + /// + /// Thrown when the input string is null, empty, or contains only whitespace. + /// public static byte[] DecodeBytes(string str) { - ArgumentNullException.ThrowIfNull(str); - - // Оптимизированная замена с `string.Create` - var fixedStr = string.Create(str.Length, str, (span, s) => - { - s.AsSpan().CopyTo(span); - for (var i = 0; i < span.Length; i++) - { - if (span[i] == base64UrlCharacter62) - span[i] = base64Character62; - else if (span[i] == base64UrlCharacter63) - span[i] = base64Character63; - } - }); - - var mod = fixedStr.Length % 4; - if (mod > 0) - { - fixedStr = fixedStr.PadRight(fixedStr.Length + (4 - mod), base64PadCharacter); - } + ArgumentException.ThrowIfNullOrWhiteSpace(str); + var dataSpan = DecodeAsSpan(Encoding.UTF8.GetBytes(str).AsSpan()); + return dataSpan.ToArray(); + } - return Convert.FromBase64String(fixedStr); + /// Decodes the specified byte array, interpreting it as Base64Url encoded data, and returns the decoded byte array. + /// The input byte array to be decoded. It must not be null. + /// The decoded byte array corresponding to the Base64Url encoded input. + /// Thrown if the input byte array is null. + public static byte[] DecodeBytes(byte[] bytes) + { + ArgumentNullException.ThrowIfNull(bytes); + var dataSpan = DecodeAsSpan(bytes.AsSpan()); + return dataSpan.ToArray(); } + /// Decodes a Base64Url-encoded byte array into a UTF-8 encoded string. + /// The byte array that contains the Base64Url-encoded data. Cannot be null. + /// A UTF-8 encoded string representation of the decoded data. + public static string Decode(byte[] encodedBytes) + { + ArgumentNullException.ThrowIfNull(encodedBytes); + var dataSpan = DecodeAsSpan(encodedBytes.AsSpan()); + return Encoding.UTF8.GetString(dataSpan); + } + /// + /// Decodes a Base64 URL-encoded string into its original representation. + /// + /// The Base64 URL-encoded string to decode. + /// The original string decoded from the Base64 URL-encoded representation. public static string Decode(string arg) { - ArgumentNullException.ThrowIfNull(arg); - return Encoding.UTF8.GetString(DecodeBytes(arg)); + ArgumentException.ThrowIfNullOrWhiteSpace(arg); + var dataSpan = DecodeAsSpan(Encoding.UTF8.GetBytes(arg).AsSpan()); + return Encoding.UTF8.GetString(dataSpan); + } + + /// Decodes a Base64Url-encoded byte span into its original form and returns the result as a byte span. + /// The encoded input data as a read-only byte span. + /// The decoded data as a span of bytes. + public static Span DecodeAsSpan(ReadOnlySpan arg) + { + var destination = new Span(new byte[Base64Url.GetMaxDecodedLength(arg.Length)]); + Base64Url.DecodeFromUtf8(arg, destination); + return destination; } } \ No newline at end of file diff --git a/Source/ETAMP.Core/Utils/VersionInfo.cs b/Source/ETAMP.Core/Utils/VersionInfo.cs index b70ccb9..7cd2699 100644 --- a/Source/ETAMP.Core/Utils/VersionInfo.cs +++ b/Source/ETAMP.Core/Utils/VersionInfo.cs @@ -1,10 +1,6 @@ -#region - -using System.Reflection; +using System.Reflection; using ETAMP.Core.Attributes; -#endregion - namespace ETAMP.Core.Utils; /// diff --git a/Source/ETAMP.Encryption/AESEncryptionService.cs b/Source/ETAMP.Encryption/AESEncryptionService.cs index 39ea900..3896527 100644 --- a/Source/ETAMP.Encryption/AESEncryptionService.cs +++ b/Source/ETAMP.Encryption/AESEncryptionService.cs @@ -1,15 +1,13 @@ -#region - +using System.Buffers; +using System.IO.Pipelines; using System.Security.Cryptography; using ETAMP.Encryption.Interfaces; using Microsoft.Extensions.Logging; -#endregion - namespace ETAMP.Encryption; /// -/// Provides AES encryption and decryption services. +/// Provides AES encryption and decryption services using Pipe for streaming. /// public class AESEncryptionService : IEncryptionService { @@ -25,107 +23,123 @@ public AESEncryptionService(ILogger logger) } /// - /// Encrypts the provided data asynchronously using the AES encryption algorithm and streams. + /// Encrypts the provided data using AES encryption and Pipe. /// - /// The input stream containing the data to be encrypted. - /// The encryption key. Must be 128, 192, or 256 bits long (16, 24, or 32 bytes). - /// Optional cancellation token to cancel the operation. - /// A stream containing the encrypted data, including the initialization vector (IV) at the beginning. - public async Task EncryptAsync(Stream inputStream, byte[] key, + public async Task EncryptAsync(PipeReader inputReader, PipeWriter outputWriter, byte[] key, CancellationToken cancellationToken = default) { - ValidateParameters(inputStream, key); - - var outputStream = new MemoryStream(); + ValidateKey(key); using var aes = Aes.Create(); aes.Key = key; aes.GenerateIV(); + await outputWriter.WriteAsync(aes.IV, cancellationToken); - await WriteIVAsync(aes, outputStream, cancellationToken); + try + { + await using (var cryptoStream = new CryptoStream(outputWriter.AsStream(), aes.CreateEncryptor(), + CryptoStreamMode.Write, true)) + { + var readResult = await inputReader.ReadAsync(cancellationToken); + var buffer = readResult.Buffer; + while (true) + { + foreach (var segment in buffer) await cryptoStream.WriteAsync(segment, cancellationToken); - await using var cryptoStream = - new CryptoStream(outputStream, aes.CreateEncryptor(), CryptoStreamMode.Write, true); - await inputStream.CopyToAsync(cryptoStream, cancellationToken); - FlushFinalBlock(cryptoStream); + inputReader.AdvanceTo(buffer.End); - outputStream.Position = 0; - return outputStream; + if (readResult.IsCompleted) + break; + } + + await cryptoStream.FlushAsync(cancellationToken); + } + + await outputWriter.CompleteAsync(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Encryption failed."); + await outputWriter.CompleteAsync(ex); + throw; + } + finally + { + await inputReader.CompleteAsync(); + } } /// - /// Decrypts the provided encrypted data asynchronously using the AES encryption algorithm and streams. + /// Decrypts the provided encrypted data using AES and Pipe. /// - /// The input stream with encrypted data. - /// The decryption key. - /// Optional cancellation token to cancel the operation. - /// A stream containing the decrypted data. - public async Task DecryptAsync(Stream inputStream, byte[] key, + public async Task DecryptAsync(PipeReader inputReader, PipeWriter outputWriter, byte[] key, CancellationToken cancellationToken = default) { - ValidateParameters(inputStream, key); + ValidateKey(key); - var outputStream = new MemoryStream(); using var aes = Aes.Create(); - - var iv = await ReadIVAsync(inputStream, aes.BlockSize / 8, cancellationToken); + var iv = await ReadIVAsync(inputReader, aes.BlockSize / 8, cancellationToken); aes.Key = key; aes.IV = iv; - await using var cryptoStream = - new CryptoStream(inputStream, aes.CreateDecryptor(), CryptoStreamMode.Read, true); - await cryptoStream.CopyToAsync(outputStream, cancellationToken); - FlushFinalBlock(cryptoStream); + try + { + await using var cryptoStream = new CryptoStream(outputWriter.AsStream(), aes.CreateDecryptor(), + CryptoStreamMode.Write, true); - outputStream.Position = 0; + var readResult = await inputReader.ReadAsync(cancellationToken); + var buffer = readResult.Buffer; + while (true) + { + foreach (var segment in buffer) + await cryptoStream.WriteAsync(segment, cancellationToken); - return outputStream; - } + inputReader.AdvanceTo(buffer.End); - private void ValidateParameters(Stream inputStream, byte[] key) - { - ArgumentNullException.ThrowIfNull(inputStream, nameof(inputStream)); - ArgumentNullException.ThrowIfNull(key, nameof(key)); + if (readResult.IsCompleted) + break; + } - if (key.Length is not (KeySize128 or KeySize192 or KeySize256)) + await cryptoStream.FlushAsync(cancellationToken); + } + catch (Exception ex) { - _logger.LogError("Invalid key length: {KeyLength} bytes.", key.Length); - throw new ArgumentException("Key must be 128, 192, or 256 bits long.", nameof(key)); + _logger.LogError(ex, "Decryption failed."); + await outputWriter.CompleteAsync(ex); + throw; } - - if (!inputStream.CanSeek) + finally { - _logger.LogError("Input stream must support seeking."); - throw new NotSupportedException("Input stream must support seeking."); + await inputReader.CompleteAsync(); } - - inputStream.Position = 0; } - private async Task WriteIVAsync(Aes aes, Stream outputStream, CancellationToken cancellationToken) + /// + /// Reads the IV (initialization vector) from the input Pipe. + /// + private async Task ReadIVAsync(PipeReader inputReader, int ivLength, CancellationToken cancellationToken) { - await outputStream.WriteAsync(aes.IV.AsMemory(0, aes.IV.Length), cancellationToken); - _logger.LogInformation("IV written to output stream."); - } + var result = await inputReader.ReadAsync(cancellationToken); + var buffer = result.Buffer; - private async Task ReadIVAsync(Stream inputStream, int ivLength, CancellationToken cancellationToken) - { - var iv = new byte[ivLength]; - var bytesRead = await inputStream.ReadAsync(iv.AsMemory(0, iv.Length), cancellationToken); + if (buffer.Length < ivLength) + throw new CryptographicException("Failed to read the IV (initialization vector)."); - if (bytesRead != iv.Length) - { - _logger.LogError("Failed to read the initialization vector (IV) from the input stream."); - throw new CryptographicException("Failed to read the initialization vector (IV) from the input stream."); - } + var iv = buffer.Slice(0, ivLength); + inputReader.AdvanceTo(buffer.GetPosition(ivLength)); - return iv; + return iv.ToArray(); } - private void FlushFinalBlock(CryptoStream cryptoStream) + /// + /// Validates the encryption key size. + /// + private void ValidateKey(byte[] key) { - if (cryptoStream.HasFlushedFinalBlock) + if (key.Length is KeySize128 or KeySize192 or KeySize256) return; - cryptoStream.FlushFinalBlock(); + + _logger.LogError("Invalid key length: {KeyLength} bytes.", key.Length); + throw new ArgumentException("Key must be 128, 192, or 256 bits long.", nameof(key)); } } \ No newline at end of file diff --git a/Source/ETAMP.Encryption/ECDsaManager/ECDSAStore.cs b/Source/ETAMP.Encryption/ECDsaManager/ECDsaStore.cs similarity index 93% rename from Source/ETAMP.Encryption/ECDsaManager/ECDSAStore.cs rename to Source/ETAMP.Encryption/ECDsaManager/ECDsaStore.cs index 3be6662..a714ad6 100644 --- a/Source/ETAMP.Encryption/ECDsaManager/ECDSAStore.cs +++ b/Source/ETAMP.Encryption/ECDsaManager/ECDsaStore.cs @@ -1,19 +1,15 @@ -#region - -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Security.Cryptography; -using ETAMP.Encryption.Interfaces.ECDSAManager; +using ETAMP.Encryption.Interfaces.ECDsaManager; using Microsoft.Extensions.Logging; -#endregion - namespace ETAMP.Encryption.ECDsaManager; /// /// Manages storage of ECDSA providers, allowing registration, retrieval, and removal /// by unique identifiers or names. /// -public class ECDSAStore : IECDSAStore +public class ECDsaStore : IECDsaStore { /// /// A concurrent dictionary for storing ECDSAProviderBase instances indexed by their unique Guid identifiers. @@ -21,20 +17,20 @@ public class ECDSAStore : IECDSAStore /// private readonly ConcurrentDictionary _guidStore; + private readonly ILogger _logger; + /// /// A private dictionary that maps string-based names to instances of . /// This collection is used to store and manage ECDSA cryptographic providers by their associated names. /// private readonly ConcurrentDictionary _nameStore; - private readonly ILogger _logger; - /// - /// An implementation of the interface. + /// An implementation of the interface. /// Provides functionality to manage and store instances of ECDSA cryptographic providers /// using unique identifiers or string-based names. /// - public ECDSAStore(ILogger logger) + public ECDsaStore(ILogger logger) { _logger = logger; _guidStore = new ConcurrentDictionary(); diff --git a/Source/ETAMP.Encryption/ECIESEncryptionManager.cs b/Source/ETAMP.Encryption/ECIESEncryptionManager.cs new file mode 100644 index 0000000..771a152 --- /dev/null +++ b/Source/ETAMP.Encryption/ECIESEncryptionManager.cs @@ -0,0 +1,148 @@ +using System.Buffers; +using System.IO.Pipelines; +using System.Security.Cryptography; +using System.Text; +using ETAMP.Core.Utils; +using ETAMP.Encryption.Interfaces; +using Microsoft.Extensions.Logging; + +namespace ETAMP.Encryption; + +/// +/// Manages encryption and decryption using the Elliptic Curve Integrated Encryption Scheme (ECIES). +/// Provides asynchronous methods for encrypting and decrypting data in both string and byte array formats. +/// +public class ECIESEncryptionManager : IECIESEncryptionManager +{ + /// + /// Represents a private dependency of type used to perform + /// Elliptic Curve Integrated Encryption Scheme (ECIES) operations, such as encryption and decryption of data streams. + /// + private readonly IECIESEncryptionService _ecies; + + private readonly ILogger _logger; + + /// + /// Provides encryption and decryption functionalities using the Elliptic Curve Integrated Encryption Scheme (ECIES). + /// This class serves as a manager to handle operations for encrypting and decrypting data using ECIES with + /// support for both string and byte array formats asynchronously. + /// Implements the interface. + /// + public ECIESEncryptionManager(IECIESEncryptionService ecies, ILogger logger) + { + _ecies = ecies; + _logger = logger; + } + + + /// + /// Encrypts data asynchronously using the Elliptic Curve Integrated Encryption Scheme (ECIES). + /// Supports encryption of string or byte array data using the specified private and public keys. + /// + /// The input string data to encrypt. + /// The ECDiffieHellman private key to use for encryption. + /// The ECDiffieHellmanPublicKey public key to use for encryption. + /// A task representing the asynchronous operation. The task result contains the encrypted string data. + public async Task EncryptAsync(string data, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey) + { + return Encoding.UTF8.GetString(await EncryptData(Encoding.UTF8.GetBytes(data), privateKey, publicKey)); + } + + /// + /// Encrypts data asynchronously using ECIES (Elliptic Curve Integrated Encryption Scheme). + /// + /// The byte array representing the plaintext data to be encrypted. + /// The ECDiffieHellman private key to be used for encryption. + /// The ECDiffieHellmanPublicKey public key to be used for encryption. + /// A task that represents the asynchronous operation. The task result contains the encrypted byte array. + public async Task EncryptAsync(byte[] data, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey) + { + return await EncryptData(data, privateKey, publicKey); + } + + /// + /// Decrypts the specified encrypted data string using the provided private and public keys asynchronously. + /// + /// The encrypted data string to be decrypted. + /// The private ECDiffieHellman key for decryption. + /// The public ECDiffieHellman key associated with the sender. + /// A task that represents the asynchronous operation. The task result contains the decrypted string. + public async Task DecryptAsync(string data, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey) + { + return Encoding.UTF8.GetString(await DecryptData(Encoding.UTF8.GetBytes(data), privateKey, publicKey)); + } + + /// + /// Decrypts the specified byte array using the Elliptic Curve Integrated Encryption Scheme (ECIES). + /// The operation is performed asynchronously and requires the private key and public key for decryption. + /// + /// The encrypted data to be decrypted in byte array format. + /// The ECDiffieHellman private key used for decryption. + /// The ECDiffieHellman public key used for decryption. + /// A task representing the asynchronous operation, containing the decrypted data as a byte array. + public async Task DecryptAsync(byte[] data, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey) + { + return await DecryptData(data, privateKey, publicKey); + } + + /// + /// Encrypts data using the specified private and public ECDH keys and returns the encrypted data as a byte array. + /// + /// The byte array containing the data to be encrypted. + /// The private ECDiffieHellman key used for encryption. + /// The public ECDiffieHellman key used for encryption. + /// + /// A task representing the asynchronous operation, + /// with a byte array containing the encrypted data as the result. + /// + private async Task EncryptData(byte[] data, ECDiffieHellman privateKey, + ECDiffieHellmanPublicKey publicKey) + { + _logger.LogInformation("Encrypting data"); + var dataPipe = new Pipe(); + var outputPipe = new Pipe(); + + await dataPipe.Writer.WriteAsync(data); + await dataPipe.Writer.CompleteAsync(); + _logger.LogInformation("Data written to pipe"); + + await _ecies.EncryptAsync(dataPipe.Reader, outputPipe.Writer, privateKey, publicKey); + _logger.LogInformation("Data encrypted"); + + var result = await outputPipe.Reader.ReadAsync(); + _logger.LogInformation("Data read from pipe"); + var encodeBytes = Base64UrlEncoder.EncodeBytes(result.Buffer.ToArray()); + outputPipe.Reader.AdvanceTo(result.Buffer.End); + await outputPipe.Reader.CompleteAsync(); + _logger.LogInformation("Data complete"); + return encodeBytes; + } + + /// Decrypts the provided encrypted data using the specified private key and public key. + /// The encrypted data to be decrypted as a byte array. + /// The ECDiffieHellman private key used for decryption. + /// The ECDiffieHellman public key of the counterpart used for decryption. + /// A task representing the asynchronous operation that returns the decrypted data as a byte array. + private async Task DecryptData(byte[] data, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey) + { + _logger.LogInformation("Decrypting data"); + var decryptionPipe = new Pipe(); + var decryptionOutputPipe = new Pipe(); + + var base64 = Base64UrlEncoder.DecodeBytes(data); + await decryptionPipe.Writer.WriteAsync(base64); + await decryptionPipe.Writer.CompleteAsync(); + _logger.LogInformation("Data written to pipe"); + + await _ecies.DecryptAsync(decryptionPipe.Reader, decryptionOutputPipe.Writer, privateKey, publicKey); + _logger.LogInformation("Data decrypted"); + + var result = await decryptionOutputPipe.Reader.ReadAsync(); + _logger.LogInformation("Data read from pipe"); + var decryptedData = result.Buffer.ToArray(); + decryptionOutputPipe.Reader.AdvanceTo(result.Buffer.End); + await decryptionOutputPipe.Reader.CompleteAsync(); + _logger.LogInformation("Data complete"); + return decryptedData; + } +} \ No newline at end of file diff --git a/Source/ETAMP.Encryption/ECIESEncryptionService.cs b/Source/ETAMP.Encryption/ECIESEncryptionService.cs index c84b311..fbac472 100644 --- a/Source/ETAMP.Encryption/ECIESEncryptionService.cs +++ b/Source/ETAMP.Encryption/ECIESEncryptionService.cs @@ -1,26 +1,37 @@ -#region - +using System.IO.Pipelines; using System.Security.Cryptography; using ETAMP.Encryption.Interfaces; using Microsoft.Extensions.Logging; -#endregion - namespace ETAMP.Encryption; /// -/// Represents a service implementing the Elliptic Curve Integrated Encryption Scheme (ECIES) -/// for secure data encryption and decryption. This class integrates elliptic curve cryptography -/// with symmetric encryption to provide a reliable encryption mechanism. +/// A service for performing encryption and decryption using Elliptic Curve Integrated Encryption Scheme (ECIES). +/// Implements asynchronous methods for encrypting and decrypting data streams. /// public sealed class ECIESEncryptionService : IECIESEncryptionService { - private readonly IEncryptionService? _encryptionService; + /// + /// An instance of the encryption service implementing the interface. + /// Used to perform encryption and decryption operations with cryptographic keys derived during the ECIES process. + /// + private readonly IEncryptionService _encryptionService; + + /// + /// The logger instance used for logging messages, errors, and informational data + /// during the operation of the class. + /// + /// + /// This logger is specifically configured for the . + /// It is used to track application execution, assist in debugging, and report runtime + /// information related to encryption and decryption processes. + /// private readonly ILogger _logger; /// - /// Provides functionality for encrypting and decrypting data using the Elliptic Curve Integrated Encryption Scheme (ECIES). - /// Combines elliptic curve cryptography with symmetric encryption to ensure secure data transmission. + /// Provides an implementation of the IECIESEncryptionService interface, enabling ECIES + /// (Elliptic Curve Integrated Encryption Scheme) encryption and decryption functionality. + /// This service uses elliptic curve cryptography to securely encrypt and decrypt data streams. /// public ECIESEncryptionService(IEncryptionService encryptionService, ILogger logger) { @@ -29,102 +40,184 @@ public ECIESEncryptionService(IEncryptionService encryptionService, ILoggerThe stream representing the message to be encrypted. - /// The private key of the entity encrypting the message. - /// The public key of the recipient. - /// An optional token to monitor for cancellation requests. - /// An encrypted stream of the message. - /// Thrown if the message stream is null. - public async Task EncryptAsync(Stream message, ECDiffieHellman privateKey, + /// + /// Asynchronously encrypts data from the input reader and writes the encrypted data to the output writer + /// using the ECIES (Elliptic Curve Integrated Encryption Scheme). + /// + /// The from which the plaintext data is read. + /// The to which the encrypted data is written. + /// The private key used for encryption purposes. + /// The of the recipient used for key agreement. + /// A that can be used to cancel the operation. + /// A representing the asynchronous operation. + public async Task EncryptAsync(PipeReader inputReader, PipeWriter outputWriter, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(message, nameof(message)); - var sharedSecret = DeriveSharedSecret(privateKey, publicKey); - return await _encryptionService!.EncryptAsync(message, sharedSecret, cancellationToken); + EnsureArgumentsAreNotNull(inputReader, outputWriter); + await DiffieHellmanEncryptAsync(inputReader, outputWriter, privateKey, publicKey, cancellationToken); } - /// - /// Encrypts a given input stream using ECDH-based shared secret and an encryption service. + /// Encrypts data from the specified and writes the encrypted data to the specified + /// + /// using the provided private key and public key. /// - /// The input stream containing the data to encrypt. - /// The private ECDiffieHellman key to derive the shared secret. - /// The public key of the counterpart used to derive the shared secret. - /// A cancellation token to observe while waiting for the operation to complete. - /// - /// A stream containing the encrypted data. - /// - public async Task EncryptAsync(Stream message, ECDiffieHellman privateKey, byte[] publicKey, + /// The from which the data to be encrypted is read. + /// The to which the encrypted data is written. + /// The private key of type used for encryption. + /// The public key as a byte array used for encryption. + /// A used to observe cancellation requests. + /// A that represents the asynchronous encryption operation. + public async Task EncryptAsync(PipeReader inputReader, PipeWriter outputWriter, ECDiffieHellman privateKey, + byte[] publicKey, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(message, nameof(message)); - var sharedSecret = DeriveSharedSecret(privateKey, publicKey); - return await _encryptionService!.EncryptAsync(message, sharedSecret, cancellationToken); + EnsureArgumentsAreNotNull(inputReader, outputWriter); + await DiffieHellmanEncryptAsync(inputReader, outputWriter, privateKey, publicKey, cancellationToken); } + /// + /// Asynchronously decrypts data from the input stream and writes the decrypted data to the output stream + /// using the given private key and peer's public key for the ECIES scheme. + /// + /// The reader for the encrypted input stream. + /// The writer for the decrypted output stream. + /// The ECDiffieHellman private key for decryption. + /// The ECDiffieHellmanPublicKey from the peer for shared key computation. + /// The token used to cancel the decryption operation, if needed. + /// A Task representing the asynchronous decryption operation. + public async Task DecryptAsync(PipeReader inputReader, PipeWriter outputWriter, ECDiffieHellman privateKey, + ECDiffieHellmanPublicKey publicKey, CancellationToken cancellationToken = default) + { + EnsureArgumentsAreNotNull(inputReader, outputWriter); + await DiffieHellmanDecryptAsync(inputReader, outputWriter, privateKey, publicKey, cancellationToken); + } + + /// + /// Decrypts the data read from the input reader using the specified private key and the public key, + /// and writes the decrypted data to the output writer. + /// + /// The to read the encrypted data from. + /// The to write the decrypted data to. + /// The private key used for decryption. + /// The public key as a byte array used for key agreement during decryption. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + public async Task DecryptAsync(PipeReader inputReader, PipeWriter outputWriter, ECDiffieHellman privateKey, + byte[] publicKey, CancellationToken cancellationToken = default) + { + EnsureArgumentsAreNotNull(inputReader, outputWriter); + await DiffieHellmanDecryptAsync(inputReader, outputWriter, privateKey, publicKey, cancellationToken); + } /// - /// Decrypts an encrypted message using the ECIES (Elliptic Curve Integrated Encryption Scheme) approach. + /// Derives a shared key using Elliptic Curve Diffie-Hellman (ECDH) algorithm and decrypts data using the shared key. /// - /// The encrypted message in base64 format as a stream. - /// The ECDiffieHellman private key of the recipient used to derive the shared secret. - /// The ECDiffieHellman public key of the sender used to derive the shared secret. + /// The to read encrypted input data. + /// The to write decrypted output data. + /// The private key used for deriving the shared key. + /// The public key used for deriving the shared key. /// A token to monitor for cancellation requests. - /// A task representing the asynchronous operation, containing the decrypted message as a stream. - public async Task DecryptAsync(Stream encryptedMessageBase64, ECDiffieHellman privateKey, - ECDiffieHellmanPublicKey publicKey, CancellationToken cancellationToken = default) + /// A that represents the asynchronous operation. + private async Task DiffieHellmanDecryptAsync(PipeReader inputReader, PipeWriter outputWriter, + ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(encryptedMessageBase64, nameof(encryptedMessageBase64)); var sharedSecret = DeriveSharedSecret(privateKey, publicKey); - return await _encryptionService!.DecryptAsync(encryptedMessageBase64, sharedSecret, cancellationToken); + await _encryptionService.DecryptAsync(inputReader, outputWriter, sharedSecret, cancellationToken); } - /// - /// Decrypts an encrypted message using ECIES (Elliptic Curve Integrated Encryption Scheme) with the provided - /// private key and public key. + /// Derives a shared secret key using an ECDiffieHellman private key and a provided public key, + /// then uses the derived shared key to decrypt data from the input reader to the output writer. /// - /// The encrypted message as a Base64-encoded stream. - /// The ECDiffieHellman private key used for decryption. - /// The byte array representation of the public key used in the decryption process. - /// A token to observe while waiting for the task to complete. - /// A stream containing the decrypted message. - public async Task DecryptAsync(Stream encryptedMessageBase64, ECDiffieHellman privateKey, - byte[] publicKey, CancellationToken cancellationToken = default) + /// The instance to read encrypted data from. + /// The instance to write the decrypted data to. + /// The private key used to derive the shared secret. + /// The public key used to derive the shared secret in conjunction with the private key. + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous operation. + private async Task DiffieHellmanDecryptAsync(PipeReader inputReader, PipeWriter outputWriter, + ECDiffieHellman privateKey, byte[] publicKey, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(encryptedMessageBase64, nameof(encryptedMessageBase64)); var sharedSecret = DeriveSharedSecret(privateKey, publicKey); - return await _encryptionService!.DecryptAsync(encryptedMessageBase64, sharedSecret, cancellationToken); + await _encryptionService.DecryptAsync(inputReader, outputWriter, sharedSecret, cancellationToken); } /// - /// Derives a shared secret for encryption or decryption using Elliptic Curve Diffie-Hellman (ECDH). + /// Performs encryption using the Diffie-Hellman key exchange to derive a shared secret for securing the data. + /// This method combines the private key of the sender and the public key of the recipient to produce + /// a shared secret used for encryption of the data being transmitted. /// - /// The ECDiffieHellman private key used for the shared secret derivation. - /// The ECDiffieHellmanPublicKey used for the shared secret derivation. - /// A byte array representing the derived shared secret. - private byte[] DeriveSharedSecret(ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey) + /// The for reading the input data to be encrypted. + /// The for writing the encrypted output data. + /// The private key used to compute the shared secret. + /// The public key used to compute the shared secret. + /// + /// A to observe while waiting for the operation to + /// complete. + /// + /// A representing the asynchronous encryption operation. + private async Task DiffieHellmanEncryptAsync(PipeReader inputReader, PipeWriter outputWriter, + ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(publicKey, nameof(publicKey)); - return privateKey.DeriveKeyMaterial(publicKey); + var sharedSecret = DeriveSharedSecret(privateKey, publicKey); + await _encryptionService.EncryptAsync(inputReader, outputWriter, sharedSecret, cancellationToken); + } + + /// + /// Performs encryption of data using ECDiffieHellman keys to derive a shared secret. + /// This encapsulates the encryption process to securely create a shared key between + /// two parties for subsequent data encryption. + /// + /// The providing the input data to be encrypted. + /// The where the encrypted output data will be written. + /// The private key used by the sender to derive the shared secret. + /// The public key of the recipient used to derive the shared secret. + /// An optional token to observe for cancellation of the operation. + /// A representing the asynchronous operation of encrypting the data. + private async Task DiffieHellmanEncryptAsync(PipeReader inputReader, PipeWriter outputWriter, + ECDiffieHellman privateKey, byte[] publicKey, CancellationToken cancellationToken = default) + { + var sharedSecret = DeriveSharedSecret(privateKey, publicKey); + await _encryptionService.EncryptAsync(inputReader, outputWriter, sharedSecret, cancellationToken); } /// - /// Derives a shared secret using the private key and the provided public key. - /// This method computes the symmetric key material based on elliptic curve Diffie-Hellman (ECDH) key exchange. + /// Ensures that the provided arguments are not null. /// + /// The to be checked for null. + /// The to be checked for null. + /// Thrown if any of the arguments are null. + private static void EnsureArgumentsAreNotNull(PipeReader inputReader, PipeWriter outputWriter) + { + ArgumentNullException.ThrowIfNull(inputReader, nameof(inputReader)); + ArgumentNullException.ThrowIfNull(outputWriter, nameof(outputWriter)); + } + + /// Derives a shared secret using Elliptic Curve Diffie-Hellman (ECDH) techniques. /// - /// The elliptic curve Diffie-Hellman (ECDH) private key used in generating the shared secret. + /// The ECDiffieHellman private key for the key exchange. /// /// - /// The public key in byte array format to derive the shared secret. + /// The ECDiffieHellmanPublicKey from the intended recipient. /// /// - /// A byte array representing the derived shared secret. + /// A byte array representing the derived shared secret for secure communication. /// + private byte[] DeriveSharedSecret(ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey) + { + ArgumentNullException.ThrowIfNull(publicKey, nameof(publicKey)); + return privateKey.DeriveKeyMaterial(publicKey); + } + + /// + /// Derives a shared secret using the private key and the provided public key. + /// + /// The ECDiffieHellman private key used for deriving the shared secret. + /// The public key represented as a byte array used in the shared secret derivation. + /// A byte array containing the derived shared secret. private byte[] DeriveSharedSecret(ECDiffieHellman privateKey, byte[] publicKey) { ArgumentNullException.ThrowIfNull(publicKey, nameof(publicKey)); diff --git a/Source/ETAMP.Encryption/ETAMP.Encryption.csproj b/Source/ETAMP.Encryption/ETAMP.Encryption.csproj index fcff475..0c0bedc 100644 --- a/Source/ETAMP.Encryption/ETAMP.Encryption.csproj +++ b/Source/ETAMP.Encryption/ETAMP.Encryption.csproj @@ -7,11 +7,11 @@ true ETAMP.Encryption README.md - net9.0;net8.0 + net9.0 - + diff --git a/Source/ETAMP.Encryption/Interfaces/ECDSAManager/IECDSAStore.cs b/Source/ETAMP.Encryption/Interfaces/ECDsaManager/IECDsaStore.cs similarity index 95% rename from Source/ETAMP.Encryption/Interfaces/ECDSAManager/IECDSAStore.cs rename to Source/ETAMP.Encryption/Interfaces/ECDsaManager/IECDsaStore.cs index 4cced25..156d304 100644 --- a/Source/ETAMP.Encryption/Interfaces/ECDSAManager/IECDSAStore.cs +++ b/Source/ETAMP.Encryption/Interfaces/ECDsaManager/IECDsaStore.cs @@ -1,17 +1,13 @@ -#region +using System.Security.Cryptography; -using System.Security.Cryptography; - -#endregion - -namespace ETAMP.Encryption.Interfaces.ECDSAManager; +namespace ETAMP.Encryption.Interfaces.ECDsaManager; /// /// Represents a storage mechanism for managing ECDsa cryptographic providers. /// This interface defines the contract for adding, retrieving, and removing /// ECDsa providers using unique identifiers or names. /// -public interface IECDSAStore +public interface IECDsaStore { /// /// Adds a new ECDSA provider to the store. diff --git a/Source/ETAMP.Encryption/Interfaces/IECIESEncryptionManager.cs b/Source/ETAMP.Encryption/Interfaces/IECIESEncryptionManager.cs new file mode 100644 index 0000000..50be4f4 --- /dev/null +++ b/Source/ETAMP.Encryption/Interfaces/IECIESEncryptionManager.cs @@ -0,0 +1,54 @@ +using System.Security.Cryptography; + +namespace ETAMP.Encryption.Interfaces; + +/// +/// Defines methods for encrypting and decrypting data using the Elliptic Curve Integrated Encryption Scheme (ECIES). +/// Provides support for both string and byte array data formats with asynchronous operations. +/// +public interface IECIESEncryptionManager +{ + /// + /// Asynchronously encrypts the provided data using Elliptic Curve Integrated Encryption Scheme (ECIES). + /// + /// The input data to be encrypted as a string. + /// The private key for encryption, represented as an instance of ECDiffieHellman. + /// The recipient's public key, represented as an instance of ECDiffieHellmanPublicKey. + /// + /// A task representing the asynchronous operation. The task result contains the encrypted data as a UTF-8 encoded + /// string. + /// + Task EncryptAsync(string data, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey); + + /// + /// Asynchronously encrypts the provided data using the specified private and public keys. + /// + /// The data to be encrypted, represented as a string. + /// The private key used for the encryption process. + /// The public key used for the encryption process. + /// + /// A task representing the asynchronous encryption operation. The result contains the encrypted data as a + /// base64-encoded string. + /// + Task EncryptAsync(byte[] data, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey); + + /// + /// Asynchronously decrypts a string using ECIES (Elliptic Curve Integrated Encryption Scheme) with the specified + /// private and public keys. + /// + /// The encrypted data as a string to be decrypted. + /// The private key used for decryption. + /// The public key used for decryption. + /// Returns the decrypted string. + Task DecryptAsync(string data, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey); + + /// Asynchronously decrypts the specified byte array using ECIES (Elliptic Curve Integrated Encryption Scheme) with the provided private and public keys. + /// The encrypted data as a byte array to be decrypted. + /// The private key used in decrypting the data. + /// The public key associated with the encrypted data. + /// + /// A task that represents the asynchronous decryption operation. The task result contains the decrypted data as a + /// byte array. + /// + Task DecryptAsync(byte[] data, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey); +} \ No newline at end of file diff --git a/Source/ETAMP.Encryption/Interfaces/IECIESEncryptionService.cs b/Source/ETAMP.Encryption/Interfaces/IECIESEncryptionService.cs index fd6bfc3..cc83516 100644 --- a/Source/ETAMP.Encryption/Interfaces/IECIESEncryptionService.cs +++ b/Source/ETAMP.Encryption/Interfaces/IECIESEncryptionService.cs @@ -1,70 +1,75 @@ -#region - +using System.IO.Pipelines; using System.Security.Cryptography; -#endregion - namespace ETAMP.Encryption.Interfaces; /// -/// Defines a service for encrypting and decrypting messages using Elliptic Curve Integrated ETAMPEncryption Scheme -/// (ECIES). +/// Represents a contract for services implementing encryption and decryption using the Elliptic Curve Integrated +/// Encryption Scheme (ECIES). Provides asynchronous methods for handling secure data streams. /// public interface IECIESEncryptionService { /// - /// Encrypts a given message stream using the specified private and public keys. + /// Asynchronously encrypts data from the input reader and writes the encrypted data to the output writer + /// using the Elliptic Curve Integrated Encryption Scheme (ECIES). /// - /// The stream containing the message to be encrypted. - /// The private ECDiffieHellman key used for encryption. - /// The public key used for encryption, represented either as an ECDiffieHellmanPublicKey or a byte array. - /// A token to monitor for cancellation requests. - /// A task that represents the asynchronous encryption operation, with a stream containing the encrypted message as its result. - Task EncryptAsync(Stream message, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey, + /// The from which the plaintext data is read. + /// The to which the encrypted data is written. + /// The private key used for encryption operations. + /// + /// The of the recipient used for key agreement during + /// encryption. + /// + /// + /// An optional to cancel the asynchronous encryption + /// operation. + /// + /// A that represents the asynchronous operation of encrypting the data. + Task EncryptAsync(PipeReader inputReader, PipeWriter outputWriter, ECDiffieHellman privateKey, + ECDiffieHellmanPublicKey publicKey, CancellationToken cancellationToken = default); /// - /// Encrypts a message using the ECIES (Elliptic Curve Integrated Encryption Scheme). + /// Encrypts data asynchronously using ECIES (Elliptic Curve Integrated Encryption Scheme) with the provided private + /// key + /// and public key. Reads the input data from the specified and writes the encrypted data + /// to the specified . /// - /// The input stream containing the message to be encrypted. - /// The sender's private ECDiffieHellman key used in the encryption process. - /// The receiver's public key used in the encryption process. - /// A token to observe while waiting for the task to complete. - /// A stream containing the encrypted message data. - Task EncryptAsync(Stream message, ECDiffieHellman privateKey, byte[] publicKey, + /// The from which the plaintext data is read. + /// The to which the encrypted data is written. + /// + /// The private key of type used in encryption to generate a shared + /// secret. + /// + /// The public key as a byte array used in encryption to establish a shared secret. + /// A used to observe cancellation requests. + /// A that represents the asynchronous operation of encrypting the data. + Task EncryptAsync(PipeReader inputReader, PipeWriter outputWriter, ECDiffieHellman privateKey, byte[] publicKey, CancellationToken cancellationToken = default); /// - /// Decrypts an encrypted message using the Elliptic Curve Integrated Encryption Scheme (ECIES). + /// Asynchronously decrypts data from the input stream and writes the decrypted data to the output stream + /// using the given private key and peer's public key for the ECIES scheme. /// - /// - /// A stream containing the base64-encoded encrypted message to be decrypted. - /// - /// - /// The ECDiffieHellman private key used for decryption. - /// - /// - /// The ECDiffieHellmanPublicKey corresponding to the sender's public key. - /// - /// - /// The cancellation token to cancel the operation, if necessary. Optional. - /// - /// - /// A task that represents the asynchronous decryption operation. The task result contains a stream with the decrypted message. - /// - Task DecryptAsync(Stream encryptedMessageBase64, ECDiffieHellman privateKey, + /// The reader for the encrypted input stream. + /// The writer for the decrypted output stream. + /// The ECDiffieHellman private key for decryption. + /// The ECDiffieHellmanPublicKey from the peer for shared key computation. + /// The token used to cancel the decryption operation, if needed. + /// A Task representing the asynchronous decryption operation. + Task DecryptAsync(PipeReader inputReader, PipeWriter outputWriter, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey, CancellationToken cancellationToken = default); /// - /// Decrypts an encrypted message represented as a base64 stream using the provided private key and a public key. + /// Decrypts the data read from the input reader using the specified private key and the public key, + /// and writes the decrypted data to the output writer. /// - /// The encrypted message as a base64-encoded stream to decrypt. - /// The ECDiffieHellman private key used for the decryption process. - /// The public key in byte array format associated with the encryption process. - /// A token to monitor for cancellation requests. - /// - /// A task that represents the asynchronous decryption operation. The task result is a stream containing the decrypted message. - /// - Task DecryptAsync(Stream encryptedMessageBase64, ECDiffieHellman privateKey, byte[] publicKey, + /// The to read the encrypted data from. + /// The to write the decrypted data to. + /// The private key used for decryption. + /// The public key as a byte array used for key agreement during decryption. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + Task DecryptAsync(PipeReader inputReader, PipeWriter outputWriter, ECDiffieHellman privateKey, byte[] publicKey, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Source/ETAMP.Encryption/Interfaces/IEncryptionService.cs b/Source/ETAMP.Encryption/Interfaces/IEncryptionService.cs index 1c91681..c8039a0 100644 --- a/Source/ETAMP.Encryption/Interfaces/IEncryptionService.cs +++ b/Source/ETAMP.Encryption/Interfaces/IEncryptionService.cs @@ -1,26 +1,31 @@ -namespace ETAMP.Encryption.Interfaces; +using System.IO.Pipelines; + +namespace ETAMP.Encryption.Interfaces; /// -/// Defines an interface for encryption and decryption services. -/// Provides methods for transforming data streams securely using a given cryptographic key. +/// Represents a service for performing encryption and decryption operations. +/// Defines methods for processing data streams using a specified cryptographic algorithm and key. /// public interface IEncryptionService { /// - /// Encrypts the provided data stream asynchronously using a specific encryption mechanism. + /// Encrypts the provided input data stream using AES encryption and writes the encrypted data to the output stream. /// - /// The input stream containing the data to be encrypted. - /// The encryption key used for encrypting the data. - /// A token for propagating cancellation signals during the asynchronous operation. - /// A stream containing the encrypted data. - Task EncryptAsync(Stream inputStream, byte[] key, CancellationToken cancellationToken); + /// The PipeReader to read the unencrypted input data from. + /// The PipeWriter to write the encrypted output data to. + /// The encryption key as a byte array. The key must be 128, 192, or 256 bits in length. + /// The cancellation token to observe while waiting for the task to complete. + /// A Task that represents the asynchronous operation. + Task EncryptAsync(PipeReader inputReader, PipeWriter outputWriter, byte[] key, CancellationToken cancellationToken); /// - /// Decrypts the provided encrypted data stream asynchronously using a specific decryption mechanism. + /// Asynchronously decrypts data from the input stream and writes the decrypted data to the output stream using the + /// specified cryptographic key. /// - /// The input stream containing the encrypted data to be decrypted. - /// The decryption key used for decrypting the data. - /// A token for propagating cancellation signals during the asynchronous operation. - /// A stream containing the decrypted data. - Task DecryptAsync(Stream inputStream, byte[] key, CancellationToken cancellationToken); + /// The PipeReader instance from which the encrypted data will be read. + /// The PipeWriter instance to which the decrypted data will be written. + /// The cryptographic key used for decryption. + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous decryption operation. + Task DecryptAsync(PipeReader inputReader, PipeWriter outputWriter, byte[] key, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Source/ETAMP.Extension.ServiceCollection/ETAMP.Extension.ServiceCollection.csproj b/Source/ETAMP.Extension.ServiceCollection/ETAMP.Extension.ServiceCollection.csproj index 940a906..80cf3b7 100644 --- a/Source/ETAMP.Extension.ServiceCollection/ETAMP.Extension.ServiceCollection.csproj +++ b/Source/ETAMP.Extension.ServiceCollection/ETAMP.Extension.ServiceCollection.csproj @@ -7,13 +7,14 @@ true ETAMP.Extension.ServiceCollection README.md - net9.0;net8.0 + net9.0 + @@ -29,7 +30,7 @@ - + diff --git a/Source/ETAMP.Extension.ServiceCollection/ETAMPServiceCollectionExtensions.cs b/Source/ETAMP.Extension.ServiceCollection/ETAMPServiceCollectionExtensions.cs index 4c6b42c..332fd81 100644 --- a/Source/ETAMP.Extension.ServiceCollection/ETAMPServiceCollectionExtensions.cs +++ b/Source/ETAMP.Extension.ServiceCollection/ETAMPServiceCollectionExtensions.cs @@ -1,25 +1,25 @@ -#region - +using ETAMP.Compression; using ETAMP.Compression.Codec; using ETAMP.Compression.Factory; +using ETAMP.Compression.Interfaces; using ETAMP.Compression.Interfaces.Factory; -using ETAMP.Core; +using ETAMP.Core.Factories; using ETAMP.Core.Interfaces; +using ETAMP.Core.Management; using ETAMP.Core.Utils; using ETAMP.Encryption; using ETAMP.Encryption.ECDsaManager; using ETAMP.Encryption.Interfaces; -using ETAMP.Encryption.Interfaces.ECDSAManager; +using ETAMP.Encryption.Interfaces.ECDsaManager; +using ETAMP.Provider; +using ETAMP.Provider.Interfaces; using ETAMP.Validation; using ETAMP.Validation.Interfaces; -using ETAMP.Wrapper; using ETAMP.Wrapper.Interfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; -#endregion - namespace ETAMP.Extension.ServiceCollection; /// @@ -52,7 +52,7 @@ public static void AddETAMPServices(this IServiceCollection services, bool addlo AddCompositionServices(services, false); AddEncryptionServices(services, false); AddValidationServices(services, false); - AddWrapperServices(services, false); + AddProviders(services, false); } /// @@ -98,9 +98,10 @@ public static void AddCompositionServices(this IServiceCollection services, bool Action? configureLogging = null) { AddLogging(services, addlogger, configureLogging); - services.AddScoped(); - services.AddScoped(); + services.AddKeyedScoped(CompressionNames.Deflate); + services.AddKeyedScoped(CompressionNames.GZip); services.AddScoped(); + services.AddScoped(); } /// @@ -117,12 +118,12 @@ public static void AddCompositionServices(this IServiceCollection services, bool /// An optional action to configure logging using . /// If not provided, the default logging configuration is applied. /// - public static void AddWrapperServices(this IServiceCollection services, bool addlogger = true, + public static void AddProviders(this IServiceCollection services, bool addlogger = true, Action? configureLogging = null) { AddLogging(services, addlogger, configureLogging); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } /// @@ -146,7 +147,8 @@ public static void AddEncryptionServices(this IServiceCollection services, bool AddLogging(services, addlogger, configureLogging); services.AddScoped(); services.AddScoped(); - services.AddSingleton(); + services.AddScoped(); + services.AddSingleton(); } /// @@ -168,7 +170,7 @@ public static void AddBaseServices(this IServiceCollection services, bool addlog Action? configureLogging = null) { AddLogging(services, addlogger, configureLogging); - services.AddScoped(); + services.AddScoped(); services.AddSingleton(_ => { var versionInfo = new VersionInfo(); @@ -196,12 +198,14 @@ public static void AddBaseServices(this IServiceCollection services, bool addlog private static void AddLogging(IServiceCollection services, bool addlogger = true, Action? configureLogging = null) { - if (!addlogger) return; + if (!addlogger) + return; Log.Logger = new LoggerConfiguration() .MinimumLevel.Warning() .WriteTo.Async(a => a.Console( outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level:u3}] {Message:lj}{NewLine}{Exception}")) .CreateLogger(); + services.AddLogging(builder => { if (configureLogging != null) diff --git a/Source/ETAMP.Extension/Builder/ETAMPBuilder.cs b/Source/ETAMP.Extension/Builder/ETAMPBuilder.cs deleted file mode 100644 index 9feb5b2..0000000 --- a/Source/ETAMP.Extension/Builder/ETAMPBuilder.cs +++ /dev/null @@ -1,160 +0,0 @@ -#region - -using System.Text; -using System.Text.Json; -using ETAMP.Compression.Interfaces.Factory; -using ETAMP.Core.Models; -using ETAMP.Core.Utils; - -#endregion - -namespace ETAMP.Extension.Builder; - -/// -/// Provides utility methods to build and parse ETAMP (Enhanced Tokenized Application Message Protocol) models -/// with support for compression. -/// -/// -/// Provides utility methods to build and parse ETAMP (Enhanced Tokenized Application Message Protocol) models -/// with support for compression. -/// -public static class ETAMPBuilder -{ - /// - /// Asynchronously builds a serialized JSON representation of the ETAMP model - /// with the token compressed using the specified compression service factory. - /// - public static async Task BuildAsync(this ETAMPModel model, - ICompressionServiceFactory? compressionServiceFactory, - CancellationToken cancellationToken = default - ) where T : Token - { - ValidateBuildInputs(model, compressionServiceFactory); - - var compressionService = compressionServiceFactory.Create(model.CompressionType); - - await using var compressedStream = - await compressionService.CompressStream(await model.Token.ToJsonStreamAsync(cancellationToken), - cancellationToken); - - // Prepare the ETAMP model for serialization - var tempModel = await CreateModelBuilder(model, compressedStream, cancellationToken); - return await tempModel.ToJsonAsync(); - } - - /// - /// Deconstructs an ETAMP JSON string into an object. - /// - public static async Task> DeconstructETAMPAsync(this string? jsonEtamp, - ICompressionServiceFactory compressionServiceFactory, CancellationToken cancellationToken = default) - where T : Token - { - ValidateDeconstructInputs(jsonEtamp, compressionServiceFactory); - - var tempModel = await DeserializeJsonAsync(jsonEtamp, cancellationToken); - - var compressionService = compressionServiceFactory.Create(tempModel.CompressionType); - - // Decompress the token - await using var tokenStream = new MemoryStream(Encoding.UTF8.GetBytes(tempModel.Token)); - await using var decompressedStream = await compressionService.DecompressStream(tokenStream, cancellationToken); - - var token = await DeserializeJsonAsync(decompressedStream, cancellationToken); - - return ReconstructETAMPModel(tempModel, token); - } - - /// - /// Converts a stream to a Base64 string asynchronously. - /// - private static async Task EncodeStreamToBase64Async(Stream stream, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(stream); - - if (stream.Length == 0) throw new InvalidOperationException("The stream is empty, cannot encode to Base64."); - - stream.Position = 0; - - using var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream, cancellationToken); - - var span = memoryStream.GetBuffer().AsSpan(0, (int)memoryStream.Length); - - return Base64UrlEncoder.Encode(span.ToArray()); - } - - /// - /// Creates an ETAMP model builder based on the provided inputs. - /// - private static async Task CreateModelBuilder(ETAMPModel model, Stream compressedStream, - CancellationToken cancellationToken) where T : Token - { - return new ETAMPModelBuilder - { - Id = model.Id, - Version = model.Version, - Token = await EncodeStreamToBase64Async(compressedStream, cancellationToken), - UpdateType = model.UpdateType, - CompressionType = model.CompressionType, - SignatureMessage = model.SignatureMessage - }; - } - - /// - /// Reconstructs an ETAMP model from its builder and token. - /// - private static ETAMPModel ReconstructETAMPModel(ETAMPModelBuilder builder, T token) where T : Token - { - return new ETAMPModel - { - Id = builder.Id, - Version = builder.Version, - Token = token, - UpdateType = builder.UpdateType, - CompressionType = builder.CompressionType, - SignatureMessage = builder.SignatureMessage - }; - } - - /// - /// Validates the inputs for the BuildAsync method. - /// - private static void ValidateBuildInputs(ETAMPModel model, - ICompressionServiceFactory? compressionServiceFactory) where T : Token - { - ArgumentNullException.ThrowIfNull(model.Token, nameof(model.Token)); - ArgumentNullException.ThrowIfNull(compressionServiceFactory, nameof(compressionServiceFactory)); - ArgumentException.ThrowIfNullOrWhiteSpace(model.CompressionType, nameof(model.CompressionType)); - } - - /// - /// Validates the inputs for the DeconstructETAMPAsync method. - /// - private static void ValidateDeconstructInputs(string? jsonEtamp, - ICompressionServiceFactory compressionServiceFactory) - { - ArgumentNullException.ThrowIfNull(compressionServiceFactory, nameof(compressionServiceFactory)); - ArgumentException.ThrowIfNullOrWhiteSpace(jsonEtamp, nameof(jsonEtamp)); - - if (!jsonEtamp!.Contains('{') || !jsonEtamp.Contains('}')) - { - throw new ArgumentException("The provided string is not valid JSON.", nameof(jsonEtamp)); - } - } - - /// - /// Deserializes a JSON string or stream into an object. - /// - private static async Task DeserializeJsonAsync(Stream stream, CancellationToken cancellationToken) - { - stream.Position = 0; - return await JsonSerializer.DeserializeAsync(stream, cancellationToken: cancellationToken) - ?? throw new InvalidOperationException($"Failed to deserialize JSON into {typeof(T).Name}."); - } - - private static async Task DeserializeJsonAsync(string json, CancellationToken cancellationToken) - { - await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - return await DeserializeJsonAsync(stream, cancellationToken); - } -} \ No newline at end of file diff --git a/Source/ETAMP.Extension/Builder/ETAMPEncryption.cs b/Source/ETAMP.Extension/Builder/ETAMPEncryption.cs deleted file mode 100644 index fe86b80..0000000 --- a/Source/ETAMP.Extension/Builder/ETAMPEncryption.cs +++ /dev/null @@ -1,68 +0,0 @@ -#region - -using System.Security.Cryptography; -using ETAMP.Core.Models; -using ETAMP.Encryption.Interfaces; - -#endregion - -namespace ETAMP.Extension.Builder; - -public static class ETAMPEncryption -{ - /// - /// Encrypts the data in the given ETAMPModel using the provided IECIESEncryptionService. - /// - /// The type of token contained in the ETAMPModel. - /// The ETAMPModel to be encrypted. - /// The IECIESEncryptionService used to encrypt the data. - /// The encrypted ETAMPModel with the data encrypted in the token. - public static async Task> EncryptData(this ETAMPModel model, - IECIESEncryptionService eciesEncryptionService, ECDiffieHellman privateKey, ECDiffieHellmanPublicKey publicKey) - where T : Token - { - ArgumentException.ThrowIfNullOrWhiteSpace(model.Token.Data); - ArgumentNullException.ThrowIfNull(eciesEncryptionService); - var stream = await eciesEncryptionService.EncryptAsync(await GenerateStreamFromString(model.Token.Data), - privateKey, - publicKey); - using StreamReader reader = new(stream); - model.Token.Data = await reader.ReadToEndAsync(); - model.Token.IsEncrypted = true; - return model; - } - - /// - /// Encrypts the data in the specified ETAMPModel using the provided IECIESEncryptionService. - /// - /// The type of token contained in the ETAMPModel. - /// The ETAMPModel to be encrypted. - /// The IECIESEncryptionService used to perform the encryption. - /// The public key used in the encryption process. - /// The encrypted ETAMPModel with the token's data encrypted. - public static async Task> EncryptData(this ETAMPModel model, - IECIESEncryptionService eciesEncryptionService, ECDiffieHellman privateKey, byte[] publicKey) - where T : Token - { - ArgumentException.ThrowIfNullOrWhiteSpace(model.Token.Data); - ArgumentNullException.ThrowIfNull(eciesEncryptionService); - var stream = await eciesEncryptionService.EncryptAsync(await GenerateStreamFromString(model.Token.Data), - privateKey, - publicKey); - using StreamReader reader = new(stream); - model.Token.Data = await reader.ReadToEndAsync(); - model.Token.IsEncrypted = true; - return model; - } - - private static async Task GenerateStreamFromString(string s) - { - var stream = new MemoryStream(); - await using var writer = new StreamWriter(stream); - await writer.WriteAsync(s); - await writer.FlushAsync(); - stream.Position = 0; - - return stream; - } -} \ No newline at end of file diff --git a/Source/ETAMP.Extension/Builder/ETAMPSign.cs b/Source/ETAMP.Extension/Builder/ETAMPSign.cs deleted file mode 100644 index 497de75..0000000 --- a/Source/ETAMP.Extension/Builder/ETAMPSign.cs +++ /dev/null @@ -1,27 +0,0 @@ -#region - -using ETAMP.Core.Models; -using ETAMP.Wrapper.Interfaces; - -#endregion - -namespace ETAMP.Extension.Builder; - -/// -/// Provides functionality to digitally sign an ETAMPModel using a specified signature wrapper. -/// -public static class ETAMPSign -{ - /// - /// Provides functionality to digitally sign an ETAMPModel using a specified signature wrapper. - /// - /// The type of Token. - /// The ETAMPModel to be signed. - /// The instance of ISignWrapper used for signing. - /// The signed ETAMPModel. - public static async Task> Sign(this ETAMPModel model, ISignWrapper? sign) where T : Token - { - ArgumentNullException.ThrowIfNull(sign); - return await sign.SignEtampModel(model); - } -} \ No newline at end of file diff --git a/Source/ETAMP.Extension/ETAMP.Extension.csproj b/Source/ETAMP.Extension/ETAMP.Extension.csproj deleted file mode 100644 index 4bd0887..0000000 --- a/Source/ETAMP.Extension/ETAMP.Extension.csproj +++ /dev/null @@ -1,50 +0,0 @@ - - - - latest - enable - enable - true - ETAMP.Extension - README.md - net9.0;net8.0 - - - - - - - - - - - True - \ - README.md - - - True - \ - Etamp.png - - - - - 2.0.0 - Maksym Nikitiuk - - ETAMP (Encrypted Token And Message Protocol) is designed for secure and efficient message transmission in a semi-decentralized network. The protocol ensures message integrity and supports encryption and signing using ECC ( Elliptic Curve Cryptography). - - true - $(SolutionDir)\nupkgs - https://www.nuget.org/packages/ETAMP/ - https://github.com/max2020204/ETAMP - git - ETAMP, cybersecurity, encryption, message, transactions - Etamp.png - MIT - True - Copyright (c) 2025 Maksym Nikitiuk - en-US - - diff --git a/Source/ETAMP.Wrapper/SignWrapper.cs b/Source/ETAMP.Provider/ECDsaSignatureProvider.cs similarity index 70% rename from Source/ETAMP.Wrapper/SignWrapper.cs rename to Source/ETAMP.Provider/ECDsaSignatureProvider.cs index 0223c14..ffb3b77 100644 --- a/Source/ETAMP.Wrapper/SignWrapper.cs +++ b/Source/ETAMP.Provider/ECDsaSignatureProvider.cs @@ -1,26 +1,23 @@ -#region - +using System.Runtime.InteropServices; using System.Security.Cryptography; -using System.Text; +using ETAMP.Core.Extensions; using ETAMP.Core.Models; using ETAMP.Core.Utils; -using ETAMP.Wrapper.Interfaces; - -#endregion +using ETAMP.Provider.Interfaces; -namespace ETAMP.Wrapper; +namespace ETAMP.Provider; /// /// Signs data using Elliptic Curve Digital Signature Algorithm (ECDsa). /// -public sealed class SignWrapper : ISignWrapper +public sealed class ECDsaSignatureProvider : IECDsaSignatureProvider { private HashAlgorithmName _algorithmName; private ECDsa? _ecdsa; /// - /// Signs the specified ETAMP model of type T, generating a signature message. + /// Signs the specified ETAMP model of type T, generating a signature message. /// /// The ETAMP model to be signed, containing the token and metadata. /// The token to monitor for cancellation requests. @@ -30,21 +27,21 @@ public async Task> SignEtampModel(ETAMPModel etamp, CancellationToken cancellationToken = default) where T : Token { ArgumentNullException.ThrowIfNull(etamp.Token, nameof(etamp.Token)); - await using var stream = new MemoryStream(); - await using (var writer = new StreamWriter(stream, Encoding.UTF8, bufferSize: 1024, leaveOpen: true)) - { - await writer.WriteAsync(etamp.Id.ToString()); - await writer.WriteAsync(etamp.Version.ToString()); - await writer.WriteAsync(await etamp.Token.ToJsonAsync(cancellationToken)); - await writer.WriteAsync(etamp.UpdateType); - await writer.WriteAsync(etamp.CompressionType); - await writer.FlushAsync(cancellationToken); - } + var etampModel = new ETAMPModelBuilder + { + Id = etamp.Id, + Version = etamp.Version, + Token = await etamp.Token.ToJsonAsync(cancellationToken), + UpdateType = etamp.UpdateType, + CompressionType = etamp.CompressionType + }; + //Create ReadOnlySpan from model + var modelSpan = MemoryMarshal.CreateSpan(ref etampModel, 1); + var byteModel = MemoryMarshal.AsBytes(modelSpan); - stream.Position = 0; + var signature = Sign(byteModel); - var signature = Sign(stream); etamp.SignatureMessage = Base64UrlEncoder.Encode(signature); return etamp; } @@ -61,7 +58,7 @@ public void Initialize(ECDsa? provider, HashAlgorithmName algorithmName) } /// - /// Releases all resources used by the current instance of the SignWrapper class. + /// Releases all resources used by the current instance of the ECDsaSignatureProvider class. /// public void Dispose() { @@ -74,7 +71,7 @@ public void Dispose() /// The byte array representing the data to be signed. /// A byte array containing the generated digital signature. /// Thrown if the ECDsa provider is not initialized. - private byte[] Sign(Stream data) + private byte[] Sign(ReadOnlySpan data) { return _ecdsa!.SignData(data, _algorithmName); } diff --git a/Source/ETAMP.Provider/ECDsaVerificationProvider.cs b/Source/ETAMP.Provider/ECDsaVerificationProvider.cs new file mode 100644 index 0000000..8dc797d --- /dev/null +++ b/Source/ETAMP.Provider/ECDsaVerificationProvider.cs @@ -0,0 +1,76 @@ +using System.Security.Cryptography; +using ETAMP.Core.Utils; +using ETAMP.Wrapper.Interfaces; +using Microsoft.Extensions.Logging; + +namespace ETAMP.Provider; + +/// +/// Provides cryptographic verification using ECDsa, supporting both string and byte array data formats. +/// +/// > +public sealed class ECDsaVerificationProvider : IECDsaVerificationProvider +{ + private readonly ILogger _logger; + private HashAlgorithmName _algorithmName; + private ECDsa? _ecdsa; + + public ECDsaVerificationProvider(ILogger logger) + { + _logger = logger; + } + + /// + /// Verifies the specified data against a signature using the current ECDsa and hash algorithm. + /// + /// + /// The data to verify, provided as a read-only span of bytes. + /// + /// + /// The signature to verify against, provided as a read-only span of bytes. + /// + /// + /// Returns true if the signature is valid for the provided data; otherwise, false. + /// + public bool VerifyData(ReadOnlySpan data, ReadOnlySpan signature) + { + ValidateState(); + + _logger.LogDebug("Verifying data with signature."); + var isValid = _ecdsa!.VerifyData(data, signature, _algorithmName); + + if (!isValid) + _logger.LogWarning("Signature verification failed."); + + return isValid; + } + + /// + /// Disposes the underlying ECDsa instance, releasing all associated resources. + /// + public void Dispose() + { + _logger.LogDebug("Disposing ECDsa instance."); + _ecdsa?.Dispose(); + } + + /// + /// Initializes the ECDsa provider and hash algorithm for cryptographic operations. + /// + /// The ECDsa provider to use for cryptographic operations. + /// The hash algorithm to associate with the ECDsa provider. + public void Initialize(ECDsa? provider, HashAlgorithmName algorithmName) + { + _logger.LogInformation("Initializing ECDsa with algorithm {Algorithm}", algorithmName); + _ecdsa = provider; + _algorithmName = algorithmName; + } + + private void ValidateState() + { + if (_ecdsa != null) + return; + _logger.LogError("ECDsa is not initialized. Call Initialize before verifying data."); + throw new InvalidOperationException("ECDsa is not initialized."); + } +} \ No newline at end of file diff --git a/Source/ETAMP.Wrapper/ETAMP.Wrapper.csproj b/Source/ETAMP.Provider/ETAMP.Provider.csproj similarity index 92% rename from Source/ETAMP.Wrapper/ETAMP.Wrapper.csproj rename to Source/ETAMP.Provider/ETAMP.Provider.csproj index dcaa520..40ec015 100644 --- a/Source/ETAMP.Wrapper/ETAMP.Wrapper.csproj +++ b/Source/ETAMP.Provider/ETAMP.Provider.csproj @@ -7,7 +7,9 @@ true ETAMP.Wrapper README.md - net9.0;net8.0 + net9.0 + ETAMP.Provider + ETAMP.Provider diff --git a/Source/ETAMP.Provider/Interfaces/IECDsaSignatureProvider.cs b/Source/ETAMP.Provider/Interfaces/IECDsaSignatureProvider.cs new file mode 100644 index 0000000..f47dc42 --- /dev/null +++ b/Source/ETAMP.Provider/Interfaces/IECDsaSignatureProvider.cs @@ -0,0 +1,30 @@ +using ETAMP.Core.Interfaces; +using ETAMP.Core.Models; + +namespace ETAMP.Provider.Interfaces; + +/// +/// Provides functionality for signing ETAMP messages. +/// +public interface IECDsaSignatureProvider : IInitialize +{ + /// + /// Signs an ETAMP model using the provided signing implementation. + /// + /// + /// The type of the token contained within the ETAMP model. Must inherit from . + /// + /// + /// The ETAMP model to be signed. This model includes the token and associated message data. + /// + /// + /// A token to signal the cancellation of the asynchronous operation. Defaults to + /// if none is provided. + /// + /// + /// A task that represents the asynchronous operation. The task result contains the signed + /// object. + /// + Task> SignEtampModel(ETAMPModel etamp, CancellationToken cancellationToken = default) + where T : Token; +} \ No newline at end of file diff --git a/Source/ETAMP.Provider/Interfaces/IECDsaVerificationProvider.cs b/Source/ETAMP.Provider/Interfaces/IECDsaVerificationProvider.cs new file mode 100644 index 0000000..76a1748 --- /dev/null +++ b/Source/ETAMP.Provider/Interfaces/IECDsaVerificationProvider.cs @@ -0,0 +1,17 @@ +using ETAMP.Core.Interfaces; + +namespace ETAMP.Wrapper.Interfaces; + +/// +/// Verifies signatures using ECDsa. +/// +public interface IECDsaVerificationProvider : IInitialize +{ + /// + /// Verifies the signature of the provided data using ECDsa. + /// + /// The data to be verified, represented as a read-only byte span. + /// The signature to validate the data against, represented as a read-only byte span. + /// A boolean value indicating whether the signature validation is successful (true) or not (false). + bool VerifyData(ReadOnlySpan data, ReadOnlySpan signature); +} \ No newline at end of file diff --git a/Source/ETAMP.Validation/ETAMP.Validation.csproj b/Source/ETAMP.Validation/ETAMP.Validation.csproj index c2a7c7e..f2412a3 100644 --- a/Source/ETAMP.Validation/ETAMP.Validation.csproj +++ b/Source/ETAMP.Validation/ETAMP.Validation.csproj @@ -7,12 +7,8 @@ true ETAMP.Validation README.md - net9.0;net8.0 + net9.0 - - - - True @@ -25,6 +21,13 @@ Etamp.png + + + + + + + 2.0.0 diff --git a/Source/ETAMP.Validation/ETAMPValidator.cs b/Source/ETAMP.Validation/ETAMPValidator.cs index ead624b..615d689 100644 --- a/Source/ETAMP.Validation/ETAMPValidator.cs +++ b/Source/ETAMP.Validation/ETAMPValidator.cs @@ -1,22 +1,20 @@ -#region - -using System.Security.Cryptography; +using System.Security.Cryptography; using ETAMP.Core.Models; using ETAMP.Validation.Interfaces; using Microsoft.Extensions.Logging; +using ValidationResult = ETAMP.Core.Models.ValidationResult; -#endregion namespace ETAMP.Validation; /// -/// Provides validation services for the ETAMP model including structure validation, -/// token validation, and signature validation. +/// Provides validation services for the ETAMP model including structure validation, +/// token validation, and signature validation. /// /// -/// This class implements the IETAMPValidator interface and encapsulates -/// the process of validating an ETAMP model in a sequential manner: structure validation, -/// token validation, and signature validation. +/// This class implements the IETAMPValidator interface and encapsulates +/// the process of validating an ETAMP model in a sequential manner: structure validation, +/// token validation, and signature validation. /// public sealed class ETAMPValidator : IETAMPValidator { @@ -58,8 +56,9 @@ public sealed class ETAMPValidator : IETAMPValidator private readonly ITokenValidator _tokenValidator; /// - /// ETAMPValidator provides the functionality to validate ETAMP models, tokens, and their associated signatures - /// using the injected validators and logger. Implements the IETAMPValidator interface along with IDisposable and IInitialize. + /// ETAMPValidator provides the functionality to validate ETAMP models, tokens, and their associated signatures + /// using the injected validators and logger. Implements the IETAMPValidator interface along with IDisposable and + /// IInitialize. /// public ETAMPValidator(ITokenValidator tokenValidator, IStructureValidator structureValidator, ISignatureValidator signature, ILogger logger) @@ -72,24 +71,24 @@ public ETAMPValidator(ITokenValidator tokenValidator, IStructureValidator struct /// - /// Asynchronously validates an ETAMP model, including its structure, token, and associated signature. - /// Combines multiple validation steps to ensure all parts of the ETAMP model are valid before returning - /// the overall validation result. + /// Asynchronously validates an ETAMP model, including its structure, token, and associated signature. + /// Combines multiple validation steps to ensure all parts of the ETAMP model are valid before returning + /// the overall validation result. /// /// - /// The type of the token associated with the ETAMP model. Must inherit from the class. + /// The type of the token associated with the ETAMP model. Must inherit from the class. /// /// The ETAMP model to be validated, which contains the necessary data and token. /// - /// A boolean value indicating whether a lightweight validation should be performed. When set to true, - /// fewer validation checks may be conducted. + /// A boolean value indicating whether a lightweight validation should be performed. When set to true, + /// fewer validation checks may be conducted. /// /// - /// A token to monitor for cancellation requests. Allows the asynchronous operation to be canceled if requested. + /// A token to monitor for cancellation requests. Allows the asynchronous operation to be canceled if requested. /// /// - /// A instance representing the outcome of the validation. Contains details about - /// whether the ETAMP model is valid and any associated error messages. + /// A instance representing the outcome of the validation. Contains details about + /// whether the ETAMP model is valid and any associated error messages. /// public async Task ValidateETAMPAsync(ETAMPModel etamp, bool validateLite, CancellationToken cancellationToken = default) where T : Token @@ -109,7 +108,7 @@ public async Task ValidateETAMPAsync(ETAMPModel etamp, b } - var signatureValidationResult = await _signature.ValidateETAMPMessageAsync(etamp, cancellationToken); + var signatureValidationResult = await _signature.ValidateETAMPSignatureAsync(etamp, cancellationToken); _logger.LogInformation(signatureValidationResult.IsValid ? "Signature is valid" : "Signature is invalid"); return !signatureValidationResult.IsValid diff --git a/Source/ETAMP.Validation/Interfaces/IETAMPValidator.cs b/Source/ETAMP.Validation/Interfaces/IETAMPValidator.cs index 752f606..814be37 100644 --- a/Source/ETAMP.Validation/Interfaces/IETAMPValidator.cs +++ b/Source/ETAMP.Validation/Interfaces/IETAMPValidator.cs @@ -1,10 +1,6 @@ -#region - -using ETAMP.Core.Interfaces; +using ETAMP.Core.Interfaces; using ETAMP.Core.Models; -#endregion - namespace ETAMP.Validation.Interfaces; /// diff --git a/Source/ETAMP.Validation/Interfaces/ISignatureValidator.cs b/Source/ETAMP.Validation/Interfaces/ISignatureValidator.cs index 80f6e73..e1d9b96 100644 --- a/Source/ETAMP.Validation/Interfaces/ISignatureValidator.cs +++ b/Source/ETAMP.Validation/Interfaces/ISignatureValidator.cs @@ -1,10 +1,6 @@ -#region - -using ETAMP.Core.Interfaces; +using ETAMP.Core.Interfaces; using ETAMP.Core.Models; -#endregion - namespace ETAMP.Validation.Interfaces; /// @@ -13,12 +9,21 @@ namespace ETAMP.Validation.Interfaces; public interface ISignatureValidator : IInitialize { /// - /// Asynchronously validates the signature of an ETAMP message. + /// Asynchronously validates the signature of an ETAMP model. /// - /// The type of token associated with the ETAMP message. - /// The ETAMP model containing the message and signature to validate. - /// The cancellation token used to cancel the operation if needed. Defaults to . - /// A that indicates whether the validation was successful or not. - Task ValidateETAMPMessageAsync(ETAMPModel etamp, + /// + /// The type of token associated with the ETAMP model. Must inherit from . + /// + /// + /// The ETAMP model containing the signature to validate. + /// + /// + /// A cancellation token that can be used to signal the asynchronous operation should be canceled. Defaults to + /// . + /// + /// + /// A indicating whether the signature validation succeeded or failed. + /// + Task ValidateETAMPSignatureAsync(ETAMPModel etamp, CancellationToken cancellationToken = default) where T : Token; } \ No newline at end of file diff --git a/Source/ETAMP.Validation/Interfaces/IStructureValidator.cs b/Source/ETAMP.Validation/Interfaces/IStructureValidator.cs index 6ded42c..2b1ff73 100644 --- a/Source/ETAMP.Validation/Interfaces/IStructureValidator.cs +++ b/Source/ETAMP.Validation/Interfaces/IStructureValidator.cs @@ -1,8 +1,4 @@ -#region - -using ETAMP.Core.Models; - -#endregion +using ETAMP.Core.Models; namespace ETAMP.Validation.Interfaces; @@ -19,14 +15,4 @@ public interface IStructureValidator /// Indicates whether to perform a lite validation (optional, default is false). /// The validation result indicating whether the ETAMP model is valid or not, and any error message if applicable. ValidationResult ValidateETAMP(ETAMPModel model, bool validateLite = false) where T : Token; - - /// - /// Validates the structure and consistency of an ETAMP model. - /// - /// The type of token used in the ETAMPModel. - /// The ETAMP JSON string to be validated. - /// Indicates whether to perform a lite validation (optional, default is false). - /// The validation result indicating whether the ETAMP model is valid or not, and any error message if applicable. - Task ValidateETAMPAsync(string etampJson, bool validateLite = false, - CancellationToken cancellationToken = default) where T : Token; } \ No newline at end of file diff --git a/Source/ETAMP.Validation/Interfaces/ITokenValidator.cs b/Source/ETAMP.Validation/Interfaces/ITokenValidator.cs index 6bf0bdc..1301442 100644 --- a/Source/ETAMP.Validation/Interfaces/ITokenValidator.cs +++ b/Source/ETAMP.Validation/Interfaces/ITokenValidator.cs @@ -1,8 +1,4 @@ -#region - -using ETAMP.Core.Models; - -#endregion +using ETAMP.Core.Models; namespace ETAMP.Validation.Interfaces; diff --git a/Source/ETAMP.Validation/SignatureValidator.cs b/Source/ETAMP.Validation/SignatureValidator.cs index bb49065..7ed02df 100644 --- a/Source/ETAMP.Validation/SignatureValidator.cs +++ b/Source/ETAMP.Validation/SignatureValidator.cs @@ -1,34 +1,33 @@ -#region - +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; +using ETAMP.Core.Extensions; using ETAMP.Core.Models; using ETAMP.Validation.Interfaces; using ETAMP.Wrapper.Interfaces; using Microsoft.Extensions.Logging; -#endregion - namespace ETAMP.Validation; /// -/// Provides functionality to validate signatures in ETAMP messages. +/// Provides functionality to validate signatures in ETAMP messages. /// public sealed class SignatureValidator : ISignatureValidator { + private readonly IECDsaVerificationProvider _iecDsaVerificationProvider; private readonly ILogger _logger; private readonly IStructureValidator _structureValidator; - private readonly IVerifyWrapper _verifyWrapper; private ECDsa? _ecdsa; /// - /// Provides functionality to validate signatures in ETAMP messages. + /// Provides functionality to validate signatures in ETAMP messages. /// - public SignatureValidator(IVerifyWrapper verifyWrapper, IStructureValidator structureValidator, + public SignatureValidator(IECDsaVerificationProvider iecDsaVerificationProvider, + IStructureValidator structureValidator, ILogger logger) { - _verifyWrapper = verifyWrapper; + _iecDsaVerificationProvider = iecDsaVerificationProvider; _structureValidator = structureValidator ?? throw new ArgumentNullException(nameof(structureValidator)); _logger = logger; @@ -36,14 +35,24 @@ public SignatureValidator(IVerifyWrapper verifyWrapper, IStructureValidator stru /// - /// Asynchronously validates the ETAMP message by checking the structure of the ETAMP model, - /// verifying the presence of required fields, and validating the signature of the message. + /// Asynchronously validates the ETAMP message by checking the structure of the ETAMP model, + /// verifying the presence of required fields, and validating the signature of the message. /// - /// The type of the token associated with the ETAMP model, which must derive from the class. + /// + /// The type of the token associated with the ETAMP model, which must derive from the + /// class. + /// /// The ETAMP model containing the token, signature message, and related data to be validated. - /// An optional token to observe while waiting for the task to complete. Defaults to . - /// A task that represents the asynchronous validation operation. The task result is a indicating whether the validation succeeded or failed, along with error details if applicable. - public async Task ValidateETAMPMessageAsync(ETAMPModel etamp, + /// + /// An optional token to observe while waiting for the task to complete. Defaults to + /// . + /// + /// + /// A task that represents the asynchronous validation operation. The task result is a + /// indicating whether the validation succeeded or failed, along with error details if + /// applicable. + /// + public async Task ValidateETAMPSignatureAsync(ETAMPModel etamp, CancellationToken cancellationToken = default) where T : Token { ArgumentNullException.ThrowIfNull(etamp.Token); @@ -60,22 +69,23 @@ public async Task ValidateETAMPMessageAsync(ETAMPModel e return new ValidationResult(false, "SignatureMessage is missing in the ETAMP model."); } - await using (var stream = new MemoryStream()) + + var etampModel = new ETAMPModelBuilder { - await using (var writer = new StreamWriter(stream, Encoding.UTF8, bufferSize: 1024, leaveOpen: true)) - { - await writer.WriteAsync(etamp.Id.ToString()); - await writer.WriteAsync(etamp.Version.ToString()); - await writer.WriteAsync(await etamp.Token.ToJsonAsync()); - await writer.WriteAsync(etamp.UpdateType); - await writer.WriteAsync(etamp.CompressionType); - } + Id = etamp.Id, + Version = etamp.Version, + Token = await etamp.Token.ToJsonAsync(cancellationToken), + UpdateType = etamp.UpdateType, + CompressionType = etamp.CompressionType + }; + var modelSpan = MemoryMarshal.CreateSpan(ref etampModel, 1); + var byteModel = MemoryMarshal.AsBytes(modelSpan); + var isVerified = + _iecDsaVerificationProvider.VerifyData(byteModel, Encoding.UTF8.GetBytes(etamp.SignatureMessage)); - var isVerified = _verifyWrapper.VerifyData(stream, etamp.SignatureMessage); + if (isVerified) + return new ValidationResult(true); - if (isVerified) - return new ValidationResult(true); - } _logger.LogError("Failed to verify data."); return new ValidationResult(false, "Failed to verify data."); @@ -93,7 +103,7 @@ public async Task ValidateETAMPMessageAsync(ETAMPModel e public void Initialize(ECDsa? provider, HashAlgorithmName algorithmName) { _ecdsa = provider; - _verifyWrapper.Initialize(provider, algorithmName); + _iecDsaVerificationProvider.Initialize(provider, algorithmName); } /// @@ -102,7 +112,7 @@ public void Initialize(ECDsa? provider, HashAlgorithmName algorithmName) /// public void Dispose() { - _verifyWrapper.Dispose(); - _ecdsa.Dispose(); + _iecDsaVerificationProvider.Dispose(); + _ecdsa?.Dispose(); } } \ No newline at end of file diff --git a/Source/ETAMP.Validation/StructureValidator.cs b/Source/ETAMP.Validation/StructureValidator.cs index ca1fe8e..6279b62 100644 --- a/Source/ETAMP.Validation/StructureValidator.cs +++ b/Source/ETAMP.Validation/StructureValidator.cs @@ -1,13 +1,7 @@ -#region - -using ETAMP.Compression.Interfaces.Factory; -using ETAMP.Core.Models; -using ETAMP.Extension.Builder; +using ETAMP.Core.Models; using ETAMP.Validation.Interfaces; using Microsoft.Extensions.Logging; -#endregion - namespace ETAMP.Validation; /// @@ -15,12 +9,10 @@ namespace ETAMP.Validation; /// public sealed class StructureValidator : IStructureValidator { - private readonly ICompressionServiceFactory _compressionServiceFactory; private readonly ILogger _logger; - public StructureValidator(ICompressionServiceFactory compressionServiceFactory, ILogger logger) + public StructureValidator(ILogger logger) { - _compressionServiceFactory = compressionServiceFactory; _logger = logger; } @@ -39,31 +31,18 @@ public ValidationResult ValidateETAMP(ETAMPModel model, bool validateLite return new ValidationResult(false, "ETAMP model is uninitialized (default)."); } - if (model.Id != Guid.Empty && - !string.IsNullOrWhiteSpace(model.UpdateType) && - !string.IsNullOrWhiteSpace(model.CompressionType) && - model.Token != null && - (validateLite || !string.IsNullOrWhiteSpace(model.SignatureMessage))) + var hasValidId = model.Id != Guid.Empty; + var hasValidUpdateType = !string.IsNullOrWhiteSpace(model.UpdateType); + var hasValidCompressionType = !string.IsNullOrWhiteSpace(model.CompressionType); + var hasToken = model.Token != null; + var hasValidSignature = !string.IsNullOrWhiteSpace(model.SignatureMessage); + + if (hasValidId && hasValidUpdateType && hasValidCompressionType && hasToken && + (validateLite || hasValidSignature)) return new ValidationResult(true); + _logger.LogError("ETAMP model has empty/missing fields or contains invalid values."); return new ValidationResult(false, "ETAMP model has empty/missing fields or contains invalid values."); } - - - /// - /// Asynchronously validates an ETAMP model from its JSON representation. - /// - /// The type of token. - /// The JSON representation of the ETAMP model to validate. - /// Optional. Specifies whether to perform lite validation. Default is false. - /// Optional. A cancellation token to observe while waiting for the task to complete. - /// A Task that represents the asynchronous operation, containing a ValidationResult object indicating whether the model is valid or not. - public async Task ValidateETAMPAsync(string etampJson, bool validateLite = false, - CancellationToken cancellationToken = default) - where T : Token - { - var model = await etampJson.DeconstructETAMPAsync(_compressionServiceFactory, cancellationToken); - return ValidateETAMP(model, validateLite); - } } \ No newline at end of file diff --git a/Source/ETAMP.Validation/TokenValidator.cs b/Source/ETAMP.Validation/TokenValidator.cs index e5ebb1a..9bf9ca0 100644 --- a/Source/ETAMP.Validation/TokenValidator.cs +++ b/Source/ETAMP.Validation/TokenValidator.cs @@ -1,11 +1,7 @@ -#region - -using ETAMP.Core.Models; +using ETAMP.Core.Models; using ETAMP.Validation.Interfaces; using Microsoft.Extensions.Logging; -#endregion - namespace ETAMP.Validation; /// @@ -46,7 +42,8 @@ public ValidationResult ValidateToken(ETAMPModel model) where T : Token return new ValidationResult(false, "MessageId does not match the model Id."); } - if (model.Token.Id != Guid.Empty) return new ValidationResult(true); + if (model.Token.Id != Guid.Empty) + return new ValidationResult(true); _logger.LogError("Token Id cannot be empty."); return new ValidationResult(false, "Token Id cannot be empty."); diff --git a/Source/ETAMP.Wrapper/Interfaces/ISignWrapper.cs b/Source/ETAMP.Wrapper/Interfaces/ISignWrapper.cs deleted file mode 100644 index d7d5c50..0000000 --- a/Source/ETAMP.Wrapper/Interfaces/ISignWrapper.cs +++ /dev/null @@ -1,32 +0,0 @@ -#region - -using ETAMP.Core.Interfaces; -using ETAMP.Core.Models; - -#endregion - -namespace ETAMP.Wrapper.Interfaces; - -/// -/// Provides functionality for signing ETAMP messages. -/// -public interface ISignWrapper : IInitialize -{ - /// - /// Signs an ETAMP model using the provided signing implementation. - /// - /// - /// The type of the token contained within the ETAMP model. Must inherit from . - /// - /// - /// The ETAMP model to be signed. This model includes the token and associated message data. - /// - /// - /// A token to signal the cancellation of the asynchronous operation. Defaults to if none is provided. - /// - /// - /// A task that represents the asynchronous operation. The task result contains the signed object. - /// - Task> SignEtampModel(ETAMPModel etamp, CancellationToken cancellationToken = default) - where T : Token; -} \ No newline at end of file diff --git a/Source/ETAMP.Wrapper/Interfaces/IVerifyWrapper.cs b/Source/ETAMP.Wrapper/Interfaces/IVerifyWrapper.cs deleted file mode 100644 index 9eb4455..0000000 --- a/Source/ETAMP.Wrapper/Interfaces/IVerifyWrapper.cs +++ /dev/null @@ -1,29 +0,0 @@ -#region - -using ETAMP.Core.Interfaces; - -#endregion - -namespace ETAMP.Wrapper.Interfaces; - -/// -/// Verifies signatures using ECDsa. -/// -public interface IVerifyWrapper : IInitialize -{ - /// - /// Verifies the signature of string data. - /// - /// The data to verify. - /// The signature to check against. - /// True if valid; otherwise, false. - bool VerifyData(Stream data, string signature); - - /// - /// Verifies the signature of byte array data. - /// - /// The data to verify. - /// The signature to check against. - /// True if valid; otherwise, false. - bool VerifyData(Stream data, byte[] signature); -} \ No newline at end of file diff --git a/Source/ETAMP.Wrapper/VerifyWrapper.cs b/Source/ETAMP.Wrapper/VerifyWrapper.cs deleted file mode 100644 index ae298ea..0000000 --- a/Source/ETAMP.Wrapper/VerifyWrapper.cs +++ /dev/null @@ -1,114 +0,0 @@ -#region - -using System.Security.Cryptography; -using ETAMP.Core.Utils; -using ETAMP.Wrapper.Interfaces; -using Microsoft.Extensions.Logging; - -#endregion - -namespace ETAMP.Wrapper; - -/// -/// Provides cryptographic verification using ECDsa, supporting both string and byte array data formats. -/// -/// > -public sealed class VerifyWrapper : IVerifyWrapper -{ - private readonly ILogger _logger; - private HashAlgorithmName _algorithmName; - private ECDsa? _ecdsa; - - public VerifyWrapper(ILogger logger) - { - _logger = logger; - } - - /// - /// Verifies the signature of string data. - /// - /// Data to verify, in string format. - /// Base64-encoded signature to verify against. - /// True if the signature is valid; otherwise, false. - public bool VerifyData(Stream data, string signature) - { - ValidateState(); - EnsureStreamIsReadable(data); - - _logger.LogDebug("Verifying data stream of length {Length}", data.Length); - - var isValid = _ecdsa!.VerifyData(data, Base64UrlEncoder.DecodeBytes(signature), _algorithmName); - - if (!isValid) - { - _logger.LogWarning("Signature verification failed for data stream."); - } - - return isValid; - } - - /// - /// Verifies the signature of byte array data. - /// - /// Data to verify, as a byte array. - /// Signature to verify against, as a byte array. - /// True if the signature is valid; otherwise, false. - public bool VerifyData(Stream data, byte[] signature) - { - ValidateState(); - EnsureStreamIsReadable(data); - - _logger.LogDebug("Verifying data stream of length {Length}", data.Length); - - var isValid = _ecdsa!.VerifyData(data, signature, _algorithmName); - - if (!isValid) - { - _logger.LogWarning("Signature verification failed for data stream."); - } - - return isValid; - } - - /// - /// Disposes the underlying ECDsa instance, releasing all associated resources. - /// - public void Dispose() - { - _logger.LogDebug("Disposing ECDsa instance."); - _ecdsa?.Dispose(); - } - - /// - /// Initializes the ECDsa provider and hash algorithm for cryptographic operations. - /// - /// The ECDsa provider to use for cryptographic operations. - /// The hash algorithm to associate with the ECDsa provider. - public void Initialize(ECDsa? provider, HashAlgorithmName algorithmName) - { - _logger.LogInformation("Initializing ECDsa with algorithm {Algorithm}", algorithmName); - _ecdsa = provider; - _algorithmName = algorithmName; - } - - private void ValidateState() - { - if (_ecdsa != null) - return; - _logger.LogError("ECDsa is not initialized. Call Initialize before verifying data."); - throw new InvalidOperationException("ECDsa is not initialized."); - } - - private static void EnsureStreamIsReadable(Stream stream) - { - if (!stream.CanRead) - { - throw new ArgumentException("Stream is not readable.", nameof(stream)); - } - - if (stream.CanSeek) - { - stream.Position = 0; - } - } -} \ No newline at end of file diff --git a/Tests/ETAMP.Compression.Tests/Codec/DeflateCompressionServiceTest.cs b/Tests/ETAMP.Compression.Tests/Codec/DeflateCompressionServiceTest.cs deleted file mode 100644 index f2adeb7..0000000 --- a/Tests/ETAMP.Compression.Tests/Codec/DeflateCompressionServiceTest.cs +++ /dev/null @@ -1,187 +0,0 @@ -#region - -using System.IO.Compression; -using AutoFixture; -using ETAMP.Compression.Codec; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using Moq; - -#endregion - -namespace ETAMP.Compression.Tests.Codec; - -[TestSubject(typeof(DeflateCompressionServiceTests))] -public class DeflateCompressionServiceTests -{ - private readonly Fixture _fixture; - private readonly Mock> _loggerMock; - private readonly DeflateCompressionService _sut; - - public DeflateCompressionServiceTests() - { - _fixture = new Fixture(); - _loggerMock = new Mock>(); - _sut = new DeflateCompressionService(_loggerMock.Object); - } - - [Fact] - public async Task CompressStream_ValidInput_ReturnsCompressedStream() - { - // Arrange - var inputStream = new MemoryStream(_fixture.Create()); - - // Act - var result = await _sut.CompressStream(inputStream); - - // Assert - Assert.NotNull(result); - Assert.True(result.Length > 0); - _loggerMock.Verify( - x => x.Log( - LogLevel.Debug, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Compressing data stream...")), - It.IsAny(), - It.IsAny>()), - Times.Once); - _loggerMock.Verify( - x => x.Log( - LogLevel.Debug, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Data stream compressed.")), - It.IsAny(), - It.IsAny>()), - Times.Once); - } - - [Fact] - public async Task CompressStream_NullOrUnreadableStream_ThrowsArgumentException() - { - // Arrange - Stream nullStream = null; - var unreadableStream = new Mock(); - unreadableStream.Setup(s => s.CanRead).Returns(false); - - // Act & Assert - await Assert.ThrowsAsync(() => _sut.CompressStream(nullStream)); - await Assert.ThrowsAsync(() => _sut.CompressStream(unreadableStream.Object)); - - _loggerMock.Verify( - x => x.Log( - LogLevel.Error, - It.IsAny(), - It.Is((v, t) => - v.ToString().Contains("The input stream must not be null and must be readable.")), - It.IsAny(), - It.IsAny>()), - Times.Exactly(2)); - } - - [Fact] - public async Task DecompressStream_ValidInput_ReturnsDecompressedStream() - { - // Arrange - var inputData = _fixture.Create(); - var compressedStream = new MemoryStream(); - await using (var compressor = new DeflateStream(compressedStream, CompressionMode.Compress, true)) - { - await compressor.WriteAsync(inputData, 0, inputData.Length); - } - - compressedStream.Position = 0; - - // Act - var result = await _sut.DecompressStream(compressedStream); - - // Assert - Assert.NotNull(result); - Assert.Equal(inputData.Length, result.Length); - _loggerMock.Verify( - x => x.Log( - LogLevel.Debug, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Decompressing data stream...")), - It.IsAny(), - It.IsAny>()), - Times.Once); - _loggerMock.Verify( - x => x.Log( - LogLevel.Debug, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Data stream decompressed successfully.")), - It.IsAny(), - It.IsAny>()), - Times.Once); - } - - [Fact] - public async Task DecompressStream_NullOrUnreadableStream_ThrowsArgumentException() - { - // Arrange - Stream nullStream = null; - var unreadableStream = new Mock(); - unreadableStream.Setup(s => s.CanRead).Returns(false); - - // Act & Assert - await Assert.ThrowsAsync(() => _sut.DecompressStream(nullStream)); - await Assert.ThrowsAsync(() => _sut.DecompressStream(unreadableStream.Object)); - - _loggerMock.Verify( - x => x.Log( - LogLevel.Error, - It.IsAny(), - It.Is((v, t) => - v.ToString().Contains("The input stream must not be null and must be readable.")), - It.IsAny(), - It.IsAny>()), - Times.Exactly(2)); - } - - - [Fact] - public async Task DecompressStream_EmptyDecompressedData_ThrowsInvalidOperationException() - { - // Arrange - var emptyCompressedStream = new MemoryStream(); - using (var compressor = new DeflateStream(emptyCompressedStream, CompressionMode.Compress, true)) - { - // Write nothing to the stream - } - - emptyCompressedStream.Position = 0; - - // Act & Assert - await Assert.ThrowsAsync(() => _sut.DecompressStream(emptyCompressedStream)); - - _loggerMock.Verify( - x => x.Log( - LogLevel.Error, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains("The decompressed data is empty.")), - It.IsAny(), - It.IsAny>()), - Times.Once); - } - - [Fact] - public async Task DecompressStream_UnexpectedError_ThrowsInvalidOperationException() - { - // Arrange - var faultyStream = new FaultyStream(); - faultyStream.SetException(new Exception("Unexpected error")); - - // Act & Assert - await Assert.ThrowsAsync(() => _sut.DecompressStream(faultyStream)); - - _loggerMock.Verify( - x => x.Log( - LogLevel.Error, - It.IsAny(), - It.Is((v, t) => - v.ToString().Contains("An unexpected error occurred during decompression.")), - It.IsAny(), - It.IsAny>()), - Times.Once); - } -} \ No newline at end of file diff --git a/Tests/ETAMP.Compression.Tests/Codec/FaultyPipeReader.cs b/Tests/ETAMP.Compression.Tests/Codec/FaultyPipeReader.cs new file mode 100644 index 0000000..391ba20 --- /dev/null +++ b/Tests/ETAMP.Compression.Tests/Codec/FaultyPipeReader.cs @@ -0,0 +1,34 @@ +using System.IO.Pipelines; + +namespace ETAMP.Compression.Tests.Codec; + +internal class FaultyPipeReader : PipeReader +{ + public override void AdvanceTo(SequencePosition consumed) + { + // throw new NotImplementedException(); + } + + public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) + { + // throw new NotImplementedException(); + } + + public override void CancelPendingRead() + { + } + + public override void Complete(Exception? exception = null) + { + } + + public override ValueTask ReadAsync(CancellationToken cancellationToken = default) + { + throw new IOException("Simulated read failure"); + } + + public override bool TryRead(out ReadResult result) + { + throw new IOException("Simulated TryRead failure"); + } +} \ No newline at end of file diff --git a/Tests/ETAMP.Compression.Tests/Codec/FaultyStream.cs b/Tests/ETAMP.Compression.Tests/Codec/FaultyStream.cs deleted file mode 100644 index 846a302..0000000 --- a/Tests/ETAMP.Compression.Tests/Codec/FaultyStream.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace ETAMP.Compression.Tests.Codec; - -public class FaultyStream : Stream -{ - private Exception _exception; - - // Остальные методы Stream должны быть переопределены, но они не используются в тесте - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => false; - public override long Length => throw new NotSupportedException(); - public override long Position { get; set; } - - public void SetException(Exception exception) - { - _exception = exception; - } - - public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - if (_exception != null) throw _exception; - - await base.CopyToAsync(destination, bufferSize, cancellationToken); - } - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } -} \ No newline at end of file diff --git a/Tests/ETAMP.Compression.Tests/Codec/GZipCompressionServiceTest.cs b/Tests/ETAMP.Compression.Tests/Codec/GZipCompressionServiceTest.cs deleted file mode 100644 index 5ae6c60..0000000 --- a/Tests/ETAMP.Compression.Tests/Codec/GZipCompressionServiceTest.cs +++ /dev/null @@ -1,187 +0,0 @@ -#region - -using System.IO.Compression; -using AutoFixture; -using ETAMP.Compression.Codec; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using Moq; - -#endregion - -namespace ETAMP.Compression.Tests.Codec; - -[TestSubject(typeof(GZipCompressionService))] -public class GZipCompressionServiceTest -{ - private readonly Fixture _fixture; - private readonly Mock> _loggerMock; - private readonly GZipCompressionService _sut; - - public GZipCompressionServiceTest() - { - _fixture = new Fixture(); - _loggerMock = new Mock>(); - _sut = new GZipCompressionService(_loggerMock.Object); - } - - [Fact] - public async Task CompressStream_ValidInput_ReturnsCompressedStream() - { - // Arrange - var inputStream = new MemoryStream(_fixture.Create()); - - // Act - var result = await _sut.CompressStream(inputStream); - - // Assert - Assert.NotNull(result); - Assert.True(result.Length > 0); - _loggerMock.Verify( - x => x.Log( - LogLevel.Debug, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Compressing data stream...")), - It.IsAny(), - It.IsAny>()), - Times.Once); - _loggerMock.Verify( - x => x.Log( - LogLevel.Debug, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Data stream compressed.")), - It.IsAny(), - It.IsAny>()), - Times.Once); - } - - [Fact] - public async Task CompressStream_NullOrUnreadableStream_ThrowsArgumentException() - { - // Arrange - Stream nullStream = null; - var unreadableStream = new Mock(); - unreadableStream.Setup(s => s.CanRead).Returns(false); - - // Act & Assert - await Assert.ThrowsAsync(() => _sut.CompressStream(nullStream)); - await Assert.ThrowsAsync(() => _sut.CompressStream(unreadableStream.Object)); - - _loggerMock.Verify( - x => x.Log( - LogLevel.Error, - It.IsAny(), - It.Is((v, t) => - v.ToString().Contains("The input stream must not be null and must be readable.")), - It.IsAny(), - It.IsAny>()), - Times.Exactly(2)); - } - - [Fact] - public async Task DecompressStream_ValidInput_ReturnsDecompressedStream() - { - // Arrange - var inputData = _fixture.Create(); - var compressedStream = new MemoryStream(); - await using (var compressor = new GZipStream(compressedStream, CompressionMode.Compress, true)) - { - await compressor.WriteAsync(inputData); - } - - compressedStream.Position = 0; - - // Act - var result = await _sut.DecompressStream(compressedStream); - - // Assert - Assert.NotNull(result); - Assert.Equal(inputData.Length, result.Length); - _loggerMock.Verify( - x => x.Log( - LogLevel.Debug, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Decompressing data stream...")), - It.IsAny(), - It.IsAny>()), - Times.Once); - _loggerMock.Verify( - x => x.Log( - LogLevel.Debug, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Data stream decompressed successfully.")), - It.IsAny(), - It.IsAny>()), - Times.Once); - } - - [Fact] - public async Task DecompressStream_NullOrUnreadableStream_ThrowsArgumentException() - { - // Arrange - Stream nullStream = null; - var unreadableStream = new Mock(); - unreadableStream.Setup(s => s.CanRead).Returns(false); - - // Act & Assert - await Assert.ThrowsAsync(() => _sut.DecompressStream(nullStream)); - await Assert.ThrowsAsync(() => _sut.DecompressStream(unreadableStream.Object)); - - _loggerMock.Verify( - x => x.Log( - LogLevel.Error, - It.IsAny(), - It.Is((v, t) => - v.ToString().Contains("The input stream must not be null and must be readable.")), - It.IsAny(), - It.IsAny>()), - Times.Exactly(2)); - } - - - [Fact] - public async Task DecompressStream_EmptyDecompressedData_ThrowsInvalidOperationException() - { - // Arrange - var emptyCompressedStream = new MemoryStream(); - using (var compressor = new DeflateStream(emptyCompressedStream, CompressionMode.Compress, true)) - { - // Write nothing to the stream - } - - emptyCompressedStream.Position = 0; - - // Act & Assert - await Assert.ThrowsAsync(() => _sut.DecompressStream(emptyCompressedStream)); - - _loggerMock.Verify( - x => x.Log( - LogLevel.Error, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains("The decompressed data is empty.")), - It.IsAny(), - It.IsAny>()), - Times.Once); - } - - [Fact] - public async Task DecompressStream_UnexpectedError_ThrowsInvalidOperationException() - { - // Arrange - var faultyStream = new FaultyStream(); - faultyStream.SetException(new Exception("Unexpected error")); - - // Act & Assert - await Assert.ThrowsAsync(() => _sut.DecompressStream(faultyStream)); - - _loggerMock.Verify( - x => x.Log( - LogLevel.Error, - It.IsAny(), - It.Is((v, t) => - v.ToString().Contains("An unexpected error occurred during decompression.")), - It.IsAny(), - It.IsAny>()), - Times.Once); - } -} \ No newline at end of file diff --git a/Tests/ETAMP.Compression.Tests/Codec/StreamCompressionServiceTests.cs b/Tests/ETAMP.Compression.Tests/Codec/StreamCompressionServiceTests.cs new file mode 100644 index 0000000..42fc4f6 --- /dev/null +++ b/Tests/ETAMP.Compression.Tests/Codec/StreamCompressionServiceTests.cs @@ -0,0 +1,98 @@ +using System.IO.Pipelines; +using System.Text; +using ETAMP.Compression.Codec; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; +using Moq; + +namespace ETAMP.Compression.Tests.Codec; + +[TestSubject(typeof(StreamCompressionService))] +public class StreamCompressionServiceTests +{ + private readonly DeflateCompressionService _compressionService; + private readonly Mock> _loggerMock = new(); + + public StreamCompressionServiceTests() + { + _compressionService = new DeflateCompressionService(_loggerMock.Object); + } + + [Fact] + public async Task CompressAsync_CompressesAndDecompresses_Correctly() + { + // Arrange + var originalText = "This is some sample text to be compressed!"; + var inputBytes = Encoding.UTF8.GetBytes(originalText); + + var inputPipe = new Pipe(); + var outputPipe = new Pipe(); + + await inputPipe.Writer.WriteAsync(inputBytes); + await inputPipe.Writer.CompleteAsync(); + + // Act: compress + await _compressionService.CompressAsync(inputPipe.Reader, outputPipe.Writer); + var compressed = await ReadAllBytesFromPipeAsync(outputPipe.Reader); + + // Now decompress to verify data + var compressedInput = new Pipe(); + var decompressedOutput = new Pipe(); + + await compressedInput.Writer.WriteAsync(compressed); + compressedInput.Writer.Complete(); + + await _compressionService.DecompressAsync(compressedInput.Reader, decompressedOutput.Writer); + var decompressed = await ReadAllBytesFromPipeAsync(decompressedOutput.Reader); + + var decompressedText = Encoding.UTF8.GetString(decompressed); + + // Assert + Assert.Equal(originalText, decompressedText); + } + + [Fact] + public async Task CompressAsync_ThrowsInvalidOperationException_OnFault() + { + var faultyReader = new FaultyPipeReader(); + var output = new Pipe(); + + var ex = await Assert.ThrowsAsync(() => + _compressionService.CompressAsync(faultyReader, output.Writer)); + + Assert.Equal("Compression failed.", ex.Message); + } + + [Fact] + public async Task DecompressAsync_ThrowsInvalidOperationException_OnFault() + { + var faultyReader = new FaultyPipeReader(); + var output = new Pipe(); + + var ex = await Assert.ThrowsAsync(() => + _compressionService.DecompressAsync(faultyReader, output.Writer)); + + Assert.Equal("Decompression failed.", ex.Message); + } + + private static async Task ReadAllBytesFromPipeAsync(PipeReader reader) + { + using var ms = new MemoryStream(); + while (true) + { + var result = await reader.ReadAsync(); + var buffer = result.Buffer; + + foreach (var segment in buffer) + await ms.WriteAsync(segment); + + reader.AdvanceTo(buffer.End); + + if (result.IsCompleted) + break; + } + + await reader.CompleteAsync(); + return ms.ToArray(); + } +} \ No newline at end of file diff --git a/Tests/ETAMP.Compression.Tests/CompressionManagerTest.cs b/Tests/ETAMP.Compression.Tests/CompressionManagerTest.cs new file mode 100644 index 0000000..3d5eceb --- /dev/null +++ b/Tests/ETAMP.Compression.Tests/CompressionManagerTest.cs @@ -0,0 +1,63 @@ +using AutoFixture.Xunit2; +using ETAMP.Compression.Codec; +using ETAMP.Compression.Interfaces; +using ETAMP.Compression.Interfaces.Factory; +using ETAMP.Core.Factories; +using ETAMP.Core.Interfaces; +using ETAMP.Core.Management; +using ETAMP.Core.Models; +using ETAMP.Core.Utils; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; +using Moq; + +namespace ETAMP.Compression.Tests; + +[TestSubject(typeof(CompressionManager))] +public class CompressionManagerTest +{ + private CompressionManager _compressionManager; + private Mock> _logger; + private Mock> _loggerDeflate; + private Mock _compressionServiceFactory; + + public CompressionManagerTest() + { + _logger = new Mock>(); + _loggerDeflate = new Mock>(); + _compressionServiceFactory = new Mock(); + _compressionManager = new CompressionManager(_compressionServiceFactory.Object, _logger.Object); + } + + [Theory, AutoData] + public async Task CompressAsync_CompressesCorrectly(string input) + { + _compressionServiceFactory.Setup(x => x.Get(CompressionNames.Deflate)) + .Returns(new DeflateCompressionService(_loggerDeflate.Object)); + + + var token = new Token + { + Data = input + }; + IETAMPBase etampBase = new ETAMPModelFactory(new VersionInfo()); + var model = etampBase.CreateETAMPModel("update", token, CompressionNames.Deflate); + + var builder = await _compressionManager.CompressAsync(model); + + + Assert.NotEqual(Guid.Empty, builder.Id); + Assert.NotEqual(0, builder.Version); + + Assert.NotNull(builder.UpdateType); + Assert.False(string.IsNullOrWhiteSpace(builder.UpdateType)); + Assert.Equal("update", builder.UpdateType); + + Assert.NotNull(builder.Token); + Assert.False(string.IsNullOrWhiteSpace(builder.Token)); + + Assert.NotNull(builder.CompressionType); + Assert.False(string.IsNullOrWhiteSpace(builder.CompressionType)); + Assert.Equal(CompressionNames.Deflate, builder.CompressionType); + } +} \ No newline at end of file diff --git a/Tests/ETAMP.Compression.Tests/ETAMP.Compression.Tests.csproj b/Tests/ETAMP.Compression.Tests/ETAMP.Compression.Tests.csproj index 079371f..fe7b8e1 100644 --- a/Tests/ETAMP.Compression.Tests/ETAMP.Compression.Tests.csproj +++ b/Tests/ETAMP.Compression.Tests/ETAMP.Compression.Tests.csproj @@ -1,26 +1,26 @@  + net9.0 enable enable false - net9.0;net8.0 + - all - runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/ETAMP.Compression.Tests/Factory/CompressionServiceFactoryTests.cs b/Tests/ETAMP.Compression.Tests/Factory/CompressionServiceFactoryTests.cs deleted file mode 100644 index a12be92..0000000 --- a/Tests/ETAMP.Compression.Tests/Factory/CompressionServiceFactoryTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -#region - -using ETAMP.Compression.Codec; -using ETAMP.Compression.Factory; -using ETAMP.Compression.Interfaces; -using ETAMP.Core.Management; -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; - -#endregion - -namespace ETAMP.Compression.Tests.Factory; - -[TestSubject(typeof(CompressionServiceFactory))] -public class CompressionServiceFactoryTests -{ - private readonly CompressionServiceFactory _factory; - private readonly IServiceProvider _serviceProvider; - - public CompressionServiceFactoryTests() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddLogging(); - // Mock the services to be added to the service provider - var deflateMock = new Mock(); - var gzipMock = new Mock(); - var loggerMock = new Mock>(); - - // Add these services to the service collection - serviceCollection.AddTransient(_ => deflateMock.Object) - .AddSingleton(); - serviceCollection.AddTransient(_ => gzipMock.Object) - .AddSingleton(); - - _serviceProvider = serviceCollection.BuildServiceProvider(); - - _factory = new CompressionServiceFactory(_serviceProvider, loggerMock.Object); - } - - [Fact] - public void Create_ValidCompressionType_ReturnsService() - { - var service = _factory.Create(CompressionNames.Deflate); - Assert.NotNull(service); - } - - [Fact] - public void Create_InvalidCompressionType_ThrowsKeyNotFoundException() - { - Assert.Throws(() => _factory.Create("InvalidType")); - } - - [Fact] - public void RegisterCompressionService_ValidService_AddsService() - { - var newServiceMock = new Mock(); - _factory.RegisterCompressionService("NewType", newServiceMock.Object); - var service = _factory.Create("NewType"); - - Assert.Equal(newServiceMock.Object, service); - } - - [Fact] - public void UnregisterCompressionService_ExistingType_ReturnsTrue() - { - var result = _factory.UnregisterCompressionService(CompressionNames.Deflate); - Assert.True(result); - Assert.Throws(() => _factory.Create(CompressionNames.Deflate)); - } - - [Fact] - public void UnregisterCompressionService_NonExistingType_ReturnsFalse() - { - var result = _factory.UnregisterCompressionService("NonExistingType"); - Assert.False(result); - } -} \ No newline at end of file diff --git a/Tests/ETAMP.Core.Tests/ETAMP.Core.Tests.csproj b/Tests/ETAMP.Core.Tests/ETAMP.Core.Tests.csproj deleted file mode 100644 index ad4937a..0000000 --- a/Tests/ETAMP.Core.Tests/ETAMP.Core.Tests.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - enable - enable - false - net9.0;net8.0 - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - diff --git a/Tests/ETAMP.Core.Tests/Utils/Base64UrlEncoderTest.cs b/Tests/ETAMP.Core.Tests/Utils/Base64UrlEncoderTest.cs deleted file mode 100644 index af59f78..0000000 --- a/Tests/ETAMP.Core.Tests/Utils/Base64UrlEncoderTest.cs +++ /dev/null @@ -1,176 +0,0 @@ -#region - -using System.Text; -using ETAMP.Core.Utils; -using JetBrains.Annotations; - -#endregion - -namespace ETAMP.Core.Tests.Utils; - -[TestSubject(typeof(Base64UrlEncoder))] -public class Base64UrlEncoderTest - -{ - [Fact] - public void Encode_ValidStringInput_ShouldReturnEncodedString() - { - // Arrange - var input = "Hello World!"; - var expectedOutput = "SGVsbG8gV29ybGQh"; - - // Act - var result = Base64UrlEncoder.Encode(input); - - // Assert - Assert.NotNull(result); - Assert.Equal(expectedOutput, result); - } - - [Fact] - public void Encode_EmptyString_ShouldThrowArgumentException() - { - // Arrange - var input = ""; - - // Act & Assert - var exception = Assert.Throws(() => Base64UrlEncoder.Encode(input)); - Assert.Equal("Value cannot be null or whitespace. (Parameter 'arg')", exception.Message); - } - - [Fact] - public void Encode_NullString_ShouldThrowArgumentException() - { - // Act & Assert - var exception = Assert.Throws(() => Base64UrlEncoder.Encode((string)null!)); - Assert.Equal("Value cannot be null or whitespace. (Parameter 'arg')", exception.Message); - } - - [Fact] - public void Encode_ValidByteArray_ShouldReturnEncodedString() - { - // Arrange - var input = Encoding.UTF8.GetBytes("Hello World!"); - var expectedOutput = "SGVsbG8gV29ybGQh"; - - // Act - var result = Base64UrlEncoder.Encode(input); - - // Assert - Assert.NotNull(result); - Assert.Equal(expectedOutput, result); - } - - [Fact] - public void Encode_NullByteArray_ShouldThrowArgumentNullException() - { - // Act & Assert - var exception = Assert.Throws(() => Base64UrlEncoder.Encode((byte[])null!)); - Assert.Equal("Value cannot be null. (Parameter 'inArray')", exception.Message); - } - - [Fact] - public void Encode_EmptyByteArray_ShouldThrowArgumentNullException() - { - // Arrange - var input = Array.Empty(); - - // Act & Assert - var exception = Assert.Throws(() => Base64UrlEncoder.Encode(input)); - Assert.Equal("Value cannot be null. (Parameter 'inArray')", exception.Message); - } - - [Fact] - public void Decode_ValidEncodedString_ShouldReturnOriginalString() - { - // Arrange - var input = "SGVsbG8gV29ybGQh"; - var expectedOutput = "Hello World!"; - - // Act - var result = Base64UrlEncoder.Decode(input); - - // Assert - Assert.NotNull(result); - Assert.Equal(expectedOutput, result); - } - - [Fact] - public void Decode_InvalidEncodedString_ShouldThrowFormatException() - { - // Arrange - var input = "Invalid_Base64!"; - - // Act & Assert - Assert.Throws(() => Base64UrlEncoder.Decode(input)); - } - - [Fact] - public void Decode_NullString_ShouldThrowArgumentNullException() - { - // Act & Assert - var exception = Assert.Throws(() => Base64UrlEncoder.Decode(null!)); - Assert.Equal("Value cannot be null. (Parameter 'str')", exception.Message); - } - - [Fact] - public void Decode_EmptyString_ShouldThrowArgumentNullException() - { - // Arrange - var input = ""; - - // Act & Assert - var exception = Assert.Throws(() => Base64UrlEncoder.Decode(input)); - Assert.Equal("Value cannot be null. (Parameter 'str')", exception.Message); - } - - [Fact] - public void DecodeBytes_ValidEncodedString_ShouldReturnOriginalBytes() - { - // Arrange - var input = "SGVsbG8gV29ybGQh"; - var expectedOutput = Encoding.UTF8.GetBytes("Hello World!"); - - // Act - var result = Base64UrlEncoder.DecodeBytes(input); - - // Assert - Assert.NotNull(result); - Assert.Equal(expectedOutput, result); - } - - [Fact] - public void DecodeBytes_NullString_ShouldThrowArgumentNullException() - { - // Act & Assert - var exception = Assert.Throws(() => Base64UrlEncoder.DecodeBytes(null!)); - Assert.Equal("Value cannot be null. (Parameter 'str')", exception.Message); - } - - [Fact] - public void DecodeBytes_EmptyString_ShouldThrowArgumentNullException() - { - // Arrange - var input = ""; - - // Act & Assert - var exception = Assert.Throws(() => Base64UrlEncoder.DecodeBytes(input)); - Assert.Equal("Value cannot be null. (Parameter 'str')", exception.Message); - } - - [Fact] - public void EncodeDecode_CorrectFunctionality_ShouldRoundTripSuccessfully() - { - // Arrange - var originalString = "This is a test string for round-trip!"; - - // Act - var encoded = Base64UrlEncoder.Encode(originalString); - var decoded = Base64UrlEncoder.Decode(encoded); - - // Assert - Assert.NotNull(encoded); - Assert.NotNull(decoded); - Assert.Equal(originalString, decoded); - } -} \ No newline at end of file diff --git a/Tests/ETAMP.Encryption.Tests/AESEncryptionServiceTests.cs b/Tests/ETAMP.Encryption.Tests/AESEncryptionServiceTests.cs deleted file mode 100644 index 346efce..0000000 --- a/Tests/ETAMP.Encryption.Tests/AESEncryptionServiceTests.cs +++ /dev/null @@ -1,174 +0,0 @@ -#region - -using System; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Threading.Tasks; -using AutoFixture; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using Moq; -using Xunit; - -#endregion - -namespace ETAMP.Encryption.Tests; - -[TestSubject(typeof(AESEncryptionService))] -public class AESEncryptionServiceTests -{ - private readonly AESEncryptionService _encryptionService; - private readonly Fixture _fixture; - - public AESEncryptionServiceTests() - { - var logger = new Mock>(); - _fixture = new Fixture(); - _encryptionService = new AESEncryptionService(logger.Object); - } - - [Fact] - public async Task EncryptAsync_WhenInputStreamIsNull_ThrowsArgumentNullException() - { - // Arrange - var key = _fixture.Create().Take(16).ToArray(); - - // Act & Assert - await Assert.ThrowsAsync(() => - _encryptionService.EncryptAsync(null, key)); - } - - [Fact] - public async Task EncryptAsync_WhenKeyIsNull_ThrowsArgumentNullException() - { - // Arrange - var inputStream = new MemoryStream(_fixture.CreateMany(100).ToArray()); - - // Act & Assert - await Assert.ThrowsAsync(() => - _encryptionService.EncryptAsync(inputStream, null)); - } - - [Theory] - [InlineData(15)] - [InlineData(17)] - [InlineData(31)] - public async Task EncryptAsync_WhenKeyLengthIsInvalid_ThrowsArgumentException(int keyLength) - { - // Arrange - var key = _fixture.Create().Take(keyLength).ToArray(); - var inputStream = new MemoryStream(_fixture.CreateMany(100).ToArray()); - - // Act & Assert - await Assert.ThrowsAsync(() => - _encryptionService.EncryptAsync(inputStream, key)); - } - - [Fact] - public async Task EncryptAsync_EncryptsStreamCorrectly() - { - // Arrange - var key = _fixture.CreateMany(16).ToArray(); - var inputData = _fixture.CreateMany(100).ToArray(); - var inputStream = new MemoryStream(inputData); - - // Act - var encryptedStream = await _encryptionService.EncryptAsync(inputStream, key); - - // Assert - Assert.NotNull(encryptedStream); - Assert.NotEqual(0, encryptedStream.Length); // Encrypted stream should not be empty - - inputStream.Close(); - encryptedStream.Close(); - } - - [Fact] - public async Task DecryptAsync_WhenInputStreamIsNull_ThrowsArgumentNullException() - { - // Arrange - var key = _fixture.Create().Take(16).ToArray(); - - // Act & Assert - await Assert.ThrowsAsync(() => - _encryptionService.DecryptAsync(null, key)); - } - - [Fact] - public async Task DecryptAsync_WhenKeyIsNull_ThrowsArgumentNullException() - { - // Arrange - var inputStream = new MemoryStream(_fixture.CreateMany(100).ToArray()); - - // Act & Assert - await Assert.ThrowsAsync(() => - _encryptionService.DecryptAsync(inputStream, null)); - } - - [Theory] - [InlineData(15)] - [InlineData(17)] - [InlineData(31)] - public async Task DecryptAsync_WhenKeyLengthIsInvalid_ThrowsArgumentException(int keyLength) - { - // Arrange - var key = _fixture.Create().Take(keyLength).ToArray(); - var inputStream = new MemoryStream(_fixture.CreateMany(100).ToArray()); - - // Act & Assert - await Assert.ThrowsAsync(() => - _encryptionService.DecryptAsync(inputStream, key)); - } - - [Fact] - public async Task DecryptAsync_WithInvalidIV_ThrowsCryptographicException() - { - // Arrange - var key = _fixture.CreateMany(16).ToArray(); - // Create an invalid encrypted stream without IV - var inputStream = new MemoryStream(_fixture.CreateMany(100).ToArray()); - - // Act & Assert - await Assert.ThrowsAsync(() => - _encryptionService.DecryptAsync(inputStream, key)); - } - - [Fact] - public async Task EncryptAndDecryptAsync_ReturnsOriginalData() - { - // Arrange - var key = _fixture.CreateMany(32).ToArray(); // Valid 256-bit key - var originalData = _fixture.CreateMany(100).ToArray(); - var inputStream = new MemoryStream(originalData); - - // Act - var encryptedStream = await _encryptionService.EncryptAsync(inputStream, key); - var decryptedStream = await _encryptionService.DecryptAsync(encryptedStream, key); - var decryptedData = new byte[decryptedStream.Length]; - await decryptedStream.ReadAsync(decryptedData); - - // Assert - Assert.Equal(originalData, decryptedData); - - inputStream.Close(); - encryptedStream.Close(); - decryptedStream.Close(); - } - - [Fact] - public async Task EncryptAsync_WhenInputStreamIsSeekable_ResetsPosition() - { - // Arrange - var key = _fixture.CreateMany(16).ToArray(); - var inputData = _fixture.CreateMany(100).ToArray(); - using var inputStream = new MemoryStream(inputData); - inputStream.Position = 10; - - // Act - var encryptedStream = await _encryptionService.EncryptAsync(inputStream, key); - - // Assert - Assert.Equal(0, encryptedStream.Position); - } -} \ No newline at end of file diff --git a/Tests/ETAMP.Encryption.Tests/ECIESEncryptionServiceTests.cs b/Tests/ETAMP.Encryption.Tests/ECIESEncryptionServiceTests.cs deleted file mode 100644 index c4ea5d0..0000000 --- a/Tests/ETAMP.Encryption.Tests/ECIESEncryptionServiceTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -#region - -using System; -using System.IO; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; -using AutoFixture; -using AutoFixture.AutoMoq; -using ETAMP.Encryption.Interfaces; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using Moq; -using Xunit; - -#endregion - -namespace ETAMP.Encryption.Tests; - -[TestSubject(typeof(ECIESEncryptionService))] -public class ECIESEncryptionServiceTests -{ - private readonly ECIESEncryptionService _eciesEncryptionService; - private readonly IFixture _fixture; - private readonly Mock _mockEncryptionService; - - public ECIESEncryptionServiceTests() - { - _fixture = new Fixture().Customize(new AutoMoqCustomization()); - _mockEncryptionService = _fixture.Freeze>(); - var logger = new Mock>(); - _eciesEncryptionService = new ECIESEncryptionService(_mockEncryptionService.Object, logger.Object); - } - - [Fact] - public async Task EncryptAsync_UsingPublicKey_StreamIsEncrypted() - { - // Arrange - var message = new MemoryStream(_fixture.Create()); - var privateKey = ECDiffieHellman.Create(); - var publicKey = privateKey.PublicKey; - var sharedSecret = _fixture.Create(); - var encryptedStream = new MemoryStream(); - - _mockEncryptionService - .Setup(e => e.EncryptAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(encryptedStream); - - // Act - var result = await _eciesEncryptionService.EncryptAsync(message, privateKey, publicKey); - - // Assert - Assert.Equal(encryptedStream, result); - _mockEncryptionService.Verify( - e => e.EncryptAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task DecryptAsync_UsingPublicKey_StreamIsDecrypted() - { - // Arrange - var encryptedStream = new MemoryStream(_fixture.Create()); - var privateKey = ECDiffieHellman.Create(); - var publicKey = privateKey.PublicKey; - var sharedSecret = _fixture.Create(); - var decryptedStream = new MemoryStream(); - - _mockEncryptionService - .Setup(e => e.DecryptAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(decryptedStream); - - // Act - var result = await _eciesEncryptionService.DecryptAsync(encryptedStream, privateKey, publicKey); - - // Assert - Assert.Equal(decryptedStream, result); - _mockEncryptionService.Verify( - e => e.DecryptAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - - [Fact] - public async Task EncryptAsync_ThrowsArgumentNullExceptionForNullMessage_Stream() - { - // Arrange - Stream message = null!; - var privateKey = ECDiffieHellman.Create(); - var publicKey = privateKey.PublicKey; - - // Act & Assert - await Assert.ThrowsAsync(() => - _eciesEncryptionService.EncryptAsync(message, privateKey, publicKey)); - } - - [Fact] - public async Task DecryptAsync_ThrowsArgumentNullExceptionForNullMessage_Stream() - { - // Arrange - Stream encryptedMessage = null!; - var privateKey = ECDiffieHellman.Create(); - var publicKey = privateKey.PublicKey; - - // Act & Assert - await Assert.ThrowsAsync(() => - _eciesEncryptionService.DecryptAsync(encryptedMessage, privateKey, publicKey)); - } -} \ No newline at end of file diff --git a/Tests/ETAMP.Encryption.Tests/ETAMP.Encryption.Tests.csproj b/Tests/ETAMP.Encryption.Tests/ETAMP.Encryption.Tests.csproj deleted file mode 100644 index 4eb99f9..0000000 --- a/Tests/ETAMP.Encryption.Tests/ETAMP.Encryption.Tests.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net9.0;net8.0 - - false - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - \ No newline at end of file diff --git a/Tests/ETAMP.Extension.Tests/Builder/ETAMPBuilderTests.cs b/Tests/ETAMP.Extension.Tests/Builder/ETAMPBuilderTests.cs deleted file mode 100644 index 7bb2d03..0000000 --- a/Tests/ETAMP.Extension.Tests/Builder/ETAMPBuilderTests.cs +++ /dev/null @@ -1,13 +0,0 @@ -#region - -using ETAMP.Extension.Builder; -using JetBrains.Annotations; - -#endregion - -namespace ETAMP.Extension.Tests.Builder; - -[TestSubject(typeof(ETAMPBuilder))] -public class ETAMPBuilderTests -{ -} \ No newline at end of file diff --git a/Tests/ETAMP.Extension.Tests/ETAMP.Extension.Tests.csproj b/Tests/ETAMP.Extension.Tests/ETAMP.Extension.Tests.csproj deleted file mode 100644 index c610bc6..0000000 --- a/Tests/ETAMP.Extension.Tests/ETAMP.Extension.Tests.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net9.0;net8.0 - - false - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - \ No newline at end of file diff --git a/Tests/ETAMP.Validation.Tests/ETAMP.Validation.Tests.csproj b/Tests/ETAMP.Validation.Tests/ETAMP.Validation.Tests.csproj deleted file mode 100644 index ca897f2..0000000 --- a/Tests/ETAMP.Validation.Tests/ETAMP.Validation.Tests.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - enable - enable - - false - true - net8.0;net9.0 - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - diff --git a/Tests/ETAMP.Validation.Tests/ETAMPValidatorTests.cs b/Tests/ETAMP.Validation.Tests/ETAMPValidatorTests.cs deleted file mode 100644 index eff114d..0000000 --- a/Tests/ETAMP.Validation.Tests/ETAMPValidatorTests.cs +++ /dev/null @@ -1,169 +0,0 @@ -#region - -using System.Security.Cryptography; -using AutoFixture; -using ETAMP.Core.Models; -using ETAMP.Validation.Interfaces; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using Moq; - -#endregion - -namespace ETAMP.Validation.Tests; - -[TestSubject(typeof(ETAMPValidator))] -public class ETAMPValidatorTests -{ - private readonly Fixture _fixture; - private readonly Mock> _loggerMock; - private readonly Mock _signatureValidatorMock; - private readonly Mock _structureValidatorMock; - private readonly ETAMPValidator _sut; - private readonly Mock _tokenValidatorMock; - - public ETAMPValidatorTests() - { - _fixture = new Fixture(); - _tokenValidatorMock = new Mock(); - _structureValidatorMock = new Mock(); - _signatureValidatorMock = new Mock(); - _loggerMock = new Mock>(); - _fixture.Customize(new SupportMutableValueTypesCustomization()); - _sut = new ETAMPValidator( - _tokenValidatorMock.Object, - _structureValidatorMock.Object, - _signatureValidatorMock.Object, - _loggerMock.Object - ); - } - - [Fact] - public async Task ValidateETAMPAsync_StructureInvalid_ReturnsStructureResult() - { - // Arrange - var expectedResult = new ValidationResult(false, "Structure error"); - var etampModel = _fixture.Create>(); - - _structureValidatorMock - .Setup(v => v.ValidateETAMP(etampModel, It.IsAny())) - .Returns(expectedResult); - - // Act - var result = await _sut.ValidateETAMPAsync(etampModel, true); - - // Assert - Assert.Equal(expectedResult, result); - _loggerMock.VerifyLog(LogLevel.Error, "Structure error", Times.Once()); - _tokenValidatorMock.VerifyNoOtherCalls(); - _signatureValidatorMock.VerifyNoOtherCalls(); - } - - [Fact] - public async Task ValidateETAMPAsync_TokenInvalid_ReturnsTokenResult() - { - // Arrange - var structureResult = new ValidationResult(true); - var tokenResult = new ValidationResult(false, "Token error"); - var etampModel = _fixture.Create>(); - - _structureValidatorMock - .Setup(v => v.ValidateETAMP(etampModel, It.IsAny())) - .Returns(structureResult); - - _tokenValidatorMock - .Setup(v => v.ValidateToken(etampModel)) - .Returns(tokenResult); - - // Act - var result = await _sut.ValidateETAMPAsync(etampModel, true); - - // Assert - Assert.Equal(tokenResult, result); - _loggerMock.VerifyLog(LogLevel.Error, "Token error", Times.Once()); - _signatureValidatorMock.VerifyNoOtherCalls(); - } - - [Fact] - public async Task ValidateETAMPAsync_SignatureInvalid_ReturnsSignatureResult() - { - // Arrange - var structureResult = new ValidationResult(true); - var tokenResult = new ValidationResult(true); - var signatureResult = new ValidationResult(false, "Signature error"); - var etampModel = _fixture.Create>(); - - _structureValidatorMock - .Setup(v => v.ValidateETAMP(etampModel, It.IsAny())) - .Returns(structureResult); - - _tokenValidatorMock - .Setup(v => v.ValidateToken(etampModel)) - .Returns(tokenResult); - - _signatureValidatorMock - .Setup(v => v.ValidateETAMPMessageAsync(etampModel, It.IsAny())) - .ReturnsAsync(signatureResult); - - // Act - var result = await _sut.ValidateETAMPAsync(etampModel, true); - - // Assert - Assert.Equal(signatureResult, result); - _loggerMock.VerifyLog(LogLevel.Information, "Signature is invalid", Times.Once()); - } - - [Fact] - public async Task ValidateETAMPAsync_AllValid_ReturnsSuccess() - { - // Arrange - var structureResult = new ValidationResult(true); - var tokenResult = new ValidationResult(true); - var signatureResult = new ValidationResult(true); - var etampModel = _fixture.Create>(); - - _structureValidatorMock - .Setup(v => v.ValidateETAMP(etampModel, It.IsAny())) - .Returns(structureResult); - - _tokenValidatorMock - .Setup(v => v.ValidateToken(etampModel)) - .Returns(tokenResult); - - _signatureValidatorMock - .Setup(v => v.ValidateETAMPMessageAsync(etampModel, It.IsAny())) - .ReturnsAsync(signatureResult); - - // Act - var result = await _sut.ValidateETAMPAsync(etampModel, true); - - // Assert - Assert.True(result.IsValid); - _loggerMock.VerifyLog(LogLevel.Information, "Signature is valid", Times.Once()); - } - - [Fact] - public void Dispose_CallsSignatureDispose() - { - // Act - _sut.Dispose(); - - // Assert - _signatureValidatorMock.Verify(v => v.Dispose(), Times.Once); - } - - [Fact] - public void Initialize_CallsSignatureInitialize() - { - // Arrange - using var ecdsa = ECDsa.Create(); - var algorithmName = HashAlgorithmName.SHA256; - - // Act - _sut.Initialize(ecdsa, algorithmName); - - // Assert - _signatureValidatorMock.Verify(v => - v.Initialize(ecdsa, algorithmName), Times.Once); - } -} \ No newline at end of file diff --git a/Tests/ETAMP.Validation.Tests/LoggerExtensions.cs b/Tests/ETAMP.Validation.Tests/LoggerExtensions.cs deleted file mode 100644 index 7215ed1..0000000 --- a/Tests/ETAMP.Validation.Tests/LoggerExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -#region - -using Microsoft.Extensions.Logging; -using Moq; - -#endregion - -namespace ETAMP.Validation.Tests; - -public static class LoggerExtensions -{ - public static void VerifyLog(this Mock> loggerMock, LogLevel level, string message, Times times) - { - loggerMock.Verify(x => x.Log( - level, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains(message)), - It.IsAny(), - It.IsAny>()), - times); - } -} \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 0000000..f4fd385 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "9.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file