From 62fbda88baaf4eae344988f33b76f2989f79f368 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Sat, 2 Nov 2024 21:29:34 +0100 Subject: [PATCH 1/3] (refactor): dependency injection, console and file logging --- src/MaksIT.LTO.Backup/Application.cs | 173 ++++++++++++------ .../MaksIT.LTO.Backup.csproj | 8 + src/MaksIT.LTO.Backup/Program.cs | 80 +++----- src/MaksIT.LTO.Backup/configuration.json | 86 +++++---- src/MaksIT.LTO.Core/DriverManager.cs | 8 +- src/MaksIT.LTO.Core/LTOBlockSizes.cs | 60 ------ src/MaksIT.LTO.Core/Logging/FileLogger.cs | 37 ++++ .../Logging/FileLoggerProvider.cs | 19 ++ .../Logging/LoggingBuilderExtensions.cs | 12 ++ src/MaksIT.LTO.Core/MaksIT.LTO.Core.csproj | 5 + .../MassStorage/LTOBlockSizes.cs | 65 +++++++ .../{ => MassStorage}/TapeDeviceHandler.cs | 11 +- .../TapeDeviceHandlerNtdstor.cs | 0 .../TapeDeviceHandlerNtdtape.cs | 0 .../{ => Networking}/NetworkConnection.cs | 26 ++- 15 files changed, 356 insertions(+), 234 deletions(-) delete mode 100644 src/MaksIT.LTO.Core/LTOBlockSizes.cs create mode 100644 src/MaksIT.LTO.Core/Logging/FileLogger.cs create mode 100644 src/MaksIT.LTO.Core/Logging/FileLoggerProvider.cs create mode 100644 src/MaksIT.LTO.Core/Logging/LoggingBuilderExtensions.cs create mode 100644 src/MaksIT.LTO.Core/MassStorage/LTOBlockSizes.cs rename src/MaksIT.LTO.Core/{ => MassStorage}/TapeDeviceHandler.cs (95%) rename src/MaksIT.LTO.Core/{ => MassStorage}/TapeDeviceHandlerNtdstor.cs (100%) rename src/MaksIT.LTO.Core/{ => MassStorage}/TapeDeviceHandlerNtdtape.cs (100%) rename src/MaksIT.LTO.Core/{ => Networking}/NetworkConnection.cs (69%) diff --git a/src/MaksIT.LTO.Backup/Application.cs b/src/MaksIT.LTO.Backup/Application.cs index d7ab5f5..dc5e917 100644 --- a/src/MaksIT.LTO.Backup/Application.cs +++ b/src/MaksIT.LTO.Backup/Application.cs @@ -1,42 +1,102 @@ using System.Text; using System.Text.Json; -using System.Diagnostics.CodeAnalysis; +using System.Net; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using MaksIT.LTO.Core; using MaksIT.LTO.Backup.Entities; -using System.Net; +using MaksIT.LTO.Core.MassStorage; +using MaksIT.LTO.Core.Networking; + namespace MaksIT.LTO.Backup; + public class Application { private const string _descriptoFileName = "descriptor.json"; - private const string _configurationFileName = "configuration.json"; private readonly string appPath = AppDomain.CurrentDomain.BaseDirectory; private readonly string _tapePath; private readonly string _descriptorFilePath; - private Configuration _configuration; + private readonly ILogger _logger; + private readonly ILogger _tapeDeviceLogger; + private readonly ILogger _networkConnectionLogger; + + private readonly Configuration _configuration; + + public Application( + ILogger logger, + ILoggerFactory loggerFactory, + IOptions configuration + ) { + _logger = logger; + _tapeDeviceLogger = loggerFactory.CreateLogger(); + _networkConnectionLogger = loggerFactory.CreateLogger(); - public Application() { _descriptorFilePath = Path.Combine(appPath, _descriptoFileName); - LoadConfiguration(); + _configuration = configuration.Value; _tapePath = _configuration.TapePath; } - [MemberNotNull(nameof(_configuration))] - public void LoadConfiguration() { - var configFilePath = Path.Combine(appPath, _configurationFileName); - var configuration = JsonSerializer.Deserialize(File.ReadAllText(configFilePath)); - if (configuration == null) - throw new InvalidOperationException("Failed to deserialize configuration."); + public void Run() { + Console.OutputEncoding = Encoding.UTF8; - _configuration = configuration; + while (true) { + Console.WriteLine("MaksIT.LTO.Backup v0.0.1"); + Console.WriteLine("© Maksym Sadovnychyy (MAKS-IT) 2024"); + + Console.WriteLine("\nSelect an action:"); + Console.WriteLine("1. Load tape"); + Console.WriteLine("2. Backup"); + Console.WriteLine("3. Restore"); + Console.WriteLine("4. Eject tape"); + Console.WriteLine("5. Get device status"); + Console.WriteLine("6. Tape Erase (Short)"); + Console.WriteLine("7. Exit"); + Console.Write("Enter your choice: "); + + var choice = Console.ReadLine(); + + try { + switch (choice) { + case "1": + LoadTape(); + break; + case "2": + Backup(); + break; + case "3": + Restore(); + break; + case "4": + EjectTape(); + break; + case "5": + GetDeviceStatus(); + break; + case "6": + TapeErase(); + break; + case "7": + Console.WriteLine("Exiting..."); + return; + default: + Console.WriteLine("Invalid choice. Please try again."); + break; + } + } + catch (Exception ex) { + _logger.LogError(ex, $"An error occurred: {ex.Message}"); + } + } } public void LoadTape() { - using var handler = new TapeDeviceHandler(_tapePath); + using var handler = new TapeDeviceHandler(_tapeDeviceLogger, _tapePath); LoadTape(handler); } @@ -44,11 +104,11 @@ public class Application { handler.Prepare(TapeDeviceHandler.TAPE_LOAD); Thread.Sleep(2000); - Console.WriteLine("Tape loaded."); + _logger.LogInformation("Tape loaded."); } public void EjectTape() { - using var handler = new TapeDeviceHandler(_tapePath); + using var handler = new TapeDeviceHandler(_tapeDeviceLogger, _tapePath); EjectTape(handler); } @@ -56,11 +116,11 @@ public class Application { handler.Prepare(TapeDeviceHandler.TAPE_UNLOAD); Thread.Sleep(2000); - Console.WriteLine("Tape ejected."); + _logger.LogInformation("Tape ejected."); } public void TapeErase() { - using var handler = new TapeDeviceHandler(_tapePath); + using var handler = new TapeDeviceHandler(_tapeDeviceLogger, _tapePath); LoadTape(handler); handler.SetMediaParams(LTOBlockSizes.LTO5); @@ -80,16 +140,14 @@ public class Application { handler.SetPosition(TapeDeviceHandler.TAPE_REWIND); Thread.Sleep(2000); - Console.WriteLine("Tape erased."); + _logger.LogInformation("Tape erased."); } - public void GetDeviceStatus() { - using var handler = new TapeDeviceHandler(_tapePath); + using var handler = new TapeDeviceHandler(_tapeDeviceLogger, _tapePath); handler.GetStatus(); } - public void PathAccessWrapper(WorkingFolder workingFolder, Action myAction) { if (workingFolder.LocalPath != null) { @@ -117,7 +175,7 @@ public class Application { throw new InvalidOperationException("Network credentials are required for remote paths."); } - using (new NetworkConnection(smbPath, networkCredential)) { + using (new NetworkConnection(_networkConnectionLogger, smbPath, networkCredential)) { myAction(smbPath); } } @@ -175,8 +233,8 @@ public class Application { } private void ZeroFillBlocks(TapeDeviceHandler handler, int blocks, uint blockSize) { - Console.WriteLine($"Writing {blocks} zero-filled blocks to tape."); - Console.WriteLine($"Block Size: {blockSize}."); + _logger.LogInformation($"Writing {blocks} zero-filled blocks to tape."); + _logger.LogInformation($"Block Size: {blockSize}."); var writeError = 0; @@ -191,10 +249,10 @@ public class Application { public void WriteFilesToTape(WorkingFolder workingFolder, string descriptorFilePath, uint blockSize) { PathAccessWrapper(workingFolder, (directoryPath) => { - Console.WriteLine($"Writing files to tape from: {directoryPath}."); - Console.WriteLine($"Block Size: {blockSize}."); + _logger.LogInformation($"Writing files to tape from: {directoryPath}."); + _logger.LogInformation($"Block Size: {blockSize}."); - using var handler = new TapeDeviceHandler(_tapePath); + using var handler = new TapeDeviceHandler(_tapeDeviceLogger, _tapePath); LoadTape(handler); @@ -240,7 +298,7 @@ public class Application { writeError = handler.WriteData(buffer); if (writeError != 0) { - Console.WriteLine($"Failed to write file: {filePath}"); + _logger.LogInformation($"Failed to write file: {filePath}"); return; } @@ -284,10 +342,10 @@ public class Application { } public BackupDescriptor? FindDescriptor(uint blockSize) { - Console.WriteLine("Searching for descriptor on tape..."); - Console.WriteLine($"Block Size: {blockSize}."); + _logger.LogInformation("Searching for descriptor on tape..."); + _logger.LogInformation($"Block Size: {blockSize}."); - using var handler = new TapeDeviceHandler(_tapePath); + using var handler = new TapeDeviceHandler(_tapeDeviceLogger,_tapePath); LoadTape(handler); @@ -331,12 +389,12 @@ public class Application { try { var descriptor = JsonSerializer.Deserialize(json); if (descriptor != null) { - Console.WriteLine("Descriptor read successfully."); + _logger.LogInformation("Descriptor read successfully."); return descriptor; } } catch (JsonException ex) { - Console.WriteLine($"Failed to parse descriptor JSON: {ex.Message}"); + _logger.LogInformation($"Failed to parse descriptor JSON: {ex.Message}"); } @@ -352,10 +410,10 @@ public class Application { public void RestoreDirectory(BackupDescriptor descriptor, WorkingFolder workingFolder) { PathAccessWrapper(workingFolder, (restoreDirectoryPath) => { - Console.WriteLine("Restoring files to directory: " + restoreDirectoryPath); - Console.WriteLine("Block Size: " + descriptor.BlockSize); + _logger.LogInformation("Restoring files to directory: " + restoreDirectoryPath); + _logger.LogInformation("Block Size: " + descriptor.BlockSize); - using var handler = new TapeDeviceHandler(_tapePath); + using var handler = new TapeDeviceHandler(_tapeDeviceLogger,_tapePath); LoadTape(handler); @@ -397,10 +455,10 @@ public class Application { var fileHashString = BitConverter.ToString(fileHash).Replace("-", "").ToLower(); if (fileHashString != file.FileHash) { - Console.WriteLine($"Checksum mismatch for file: {filePath}"); + _logger.LogInformation($"Checksum mismatch for file: {filePath}"); } else { - Console.WriteLine($"Restored file: {filePath}"); + _logger.LogInformation($"Restored file: {filePath}"); } } } @@ -414,7 +472,7 @@ public class Application { public int CheckMediaSize(string ltoGen) { var descriptor = JsonSerializer.Deserialize(File.ReadAllText(_descriptorFilePath)); if (descriptor == null) { - Console.WriteLine("Failed to read descriptor."); + _logger.LogInformation("Failed to read descriptor."); return 1; } @@ -430,11 +488,11 @@ public class Application { var maxBlocks = LTOBlockSizes.GetMaxBlocks(ltoGen); if (totalBlocks > maxBlocks) { - Console.WriteLine("Backup will not fit on tape. Please use a larger tape."); + _logger.LogInformation("Backup will not fit on tape. Please use a larger tape."); return 1; } else { - Console.WriteLine("Backup will fit on tape."); + _logger.LogInformation("Backup will fit on tape."); } return 0; @@ -442,21 +500,21 @@ public class Application { public void Backup() { while (true) { - Console.WriteLine("\nSelect a backup to perform:"); + _logger.LogInformation("\nSelect a backup to perform:"); for (int i = 0; i < _configuration.Backups.Count; i++) { var backupInt = _configuration.Backups[i]; - Console.WriteLine($"{i + 1}. Backup Name: {backupInt.Name}, Bar code {(string.IsNullOrEmpty(backupInt.Barcode) ? "None" : backupInt.Barcode)}, Source: {backupInt.Source}, Destination: {backupInt.Destination}"); + _logger.LogInformation($"{i + 1}. Backup Name: {backupInt.Name}, Bar code {(string.IsNullOrEmpty(backupInt.Barcode) ? "None" : backupInt.Barcode)}, Source: {backupInt.Source}, Destination: {backupInt.Destination}"); } Console.Write("Enter your choice (or '0' to go back): "); var choice = Console.ReadLine(); if (choice == "0") { - return; // Go back to the main menu + return; } if (!int.TryParse(choice, out int index) || index < 1 || index > _configuration.Backups.Count) { - Console.WriteLine("Invalid choice. Please try again."); + _logger.LogInformation("Invalid choice. Please try again."); continue; } @@ -476,28 +534,28 @@ public class Application { WriteFilesToTape(backup.Source, _descriptorFilePath, blockSize); File.Delete(_descriptorFilePath); - Console.WriteLine("Backup completed."); - return; // Go back to the main menu after completing the backup + _logger.LogInformation("Backup completed."); + return; } } public void Restore() { while (true) { - Console.WriteLine("\nSelect a backup to restore:"); + _logger.LogInformation("\nSelect a backup to restore:"); for (int i = 0; i < _configuration.Backups.Count; i++) { var backupInt = _configuration.Backups[i]; - Console.WriteLine($"{i + 1}. Backup Name: {backupInt.Name}, Bar code {(string.IsNullOrEmpty(backupInt.Barcode) ? "None" : backupInt.Barcode)}, Source: {backupInt.Source}, Destination: {backupInt.Destination}"); + _logger.LogInformation($"{i + 1}. Backup Name: {backupInt.Name}, Bar code {(string.IsNullOrEmpty(backupInt.Barcode) ? "None" : backupInt.Barcode)}, Source: {backupInt.Source}, Destination: {backupInt.Destination}"); } Console.Write("Enter your choice (or '0' to go back): "); var choice = Console.ReadLine(); if (choice == "0") { - return; // Go back to the main menu + return; } if (!int.TryParse(choice, out int index) || index < 1 || index > _configuration.Backups.Count) { - Console.WriteLine("Invalid choice. Please try again."); + _logger.LogInformation("Invalid choice. Please try again."); continue; } @@ -505,21 +563,22 @@ public class Application { uint blockSize = LTOBlockSizes.GetBlockSize(backup.LTOGen); + // Step 1: Find Descriptor on Tape var descriptor = FindDescriptor(blockSize); if (descriptor != null) { var json = JsonSerializer.Serialize(descriptor, new JsonSerializerOptions { WriteIndented = true }); - Console.WriteLine(json); + _logger.LogInformation(json); } if (descriptor == null) { - Console.WriteLine("Descriptor not found on tape."); + _logger.LogInformation("Descriptor not found on tape."); return; } - // Step 3: Test restore from tape + // Step 2: Restore Files to Directory RestoreDirectory(descriptor, backup.Destination); - Console.WriteLine("Restore completed."); - return; // Go back to the main menu after completing the restore + _logger.LogInformation("Restore completed."); + return; } } } diff --git a/src/MaksIT.LTO.Backup/MaksIT.LTO.Backup.csproj b/src/MaksIT.LTO.Backup/MaksIT.LTO.Backup.csproj index 4de6d32..a76cc2f 100644 --- a/src/MaksIT.LTO.Backup/MaksIT.LTO.Backup.csproj +++ b/src/MaksIT.LTO.Backup/MaksIT.LTO.Backup.csproj @@ -7,6 +7,14 @@ enable + + + + + + + + diff --git a/src/MaksIT.LTO.Backup/Program.cs b/src/MaksIT.LTO.Backup/Program.cs index 64c7852..e248738 100644 --- a/src/MaksIT.LTO.Backup/Program.cs +++ b/src/MaksIT.LTO.Backup/Program.cs @@ -1,65 +1,35 @@ -namespace MaksIT.LTO.Backup; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using MaksIT.LTO.Core.Logging; + +namespace MaksIT.LTO.Backup; class Program { public static void Main() { - var app = new Application(); + // Set up configuration with reload support + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("configuration.json", optional: false, reloadOnChange: true) // Enable reload on change + .Build(); - Console.OutputEncoding = System.Text.Encoding.UTF8; + var serviceProvider = new ServiceCollection() + .Configure(configuration.GetSection("Configuration")) // Bind AppConfig directly + .AddSingleton(configuration) // Make IConfiguration available if needed + .AddLogging(builder => + { + builder.AddConfiguration(configuration.GetSection("Logging")); + builder.AddConsole(); + builder.AddFile(Path.Combine(Directory.GetCurrentDirectory(), "log.txt")); + }) + .AddTransient() + .BuildServiceProvider(); - while (true) { - // Console.Clear(); - Console.WriteLine("MaksIT.LTO.Backup v0.0.1"); - Console.WriteLine("© Maksym Sadovnychyy (MAKS-IT) 2024"); - Console.WriteLine("\nSelect an action:"); - Console.WriteLine("1. Load tape"); - Console.WriteLine("2. Backup"); - Console.WriteLine("3. Restore"); - Console.WriteLine("4. Eject tape"); - Console.WriteLine("5. Get device status"); - Console.WriteLine("6. Tape Erase (Short)"); - Console.WriteLine("7. Reload configurations"); - Console.WriteLine("8. Exit"); - Console.Write("Enter your choice: "); - - var choice = Console.ReadLine(); - - try { - switch (choice) { - case "1": - app.LoadTape(); - break; - case "2": - app.Backup(); - break; - case "3": - app.Restore(); - break; - case "4": - app.EjectTape(); - break; - case "5": - app.GetDeviceStatus(); - break; - case "6": - app.TapeErase(); - break; - case "7": - app.LoadConfiguration(); - break; - case "8": - Console.WriteLine("Exiting..."); - return; - default: - Console.WriteLine("Invalid choice. Please try again."); - break; - } - } - catch (Exception ex) { - Console.WriteLine($"An error occurred: {ex.Message}"); - } - } + // Get the App service and run it + var app = serviceProvider.GetRequiredService(); + app.Run(); } } diff --git a/src/MaksIT.LTO.Backup/configuration.json b/src/MaksIT.LTO.Backup/configuration.json index f41b3b3..d7e4f7e 100644 --- a/src/MaksIT.LTO.Backup/configuration.json +++ b/src/MaksIT.LTO.Backup/configuration.json @@ -1,41 +1,51 @@ { - "TapePath": "\\\\.\\Tape0", - "WriteDelay": 100, - "Backups": [ - { - "Name": "Normal test", - "Barcode": "", - "LTOGen": "LTO5", - "Source": { - "LocalPath": { - "Path": "F:\\LTO\\Backup" - } - }, - "Destination": { - "LocalPath": { - "Path": "F:\\LTO\\Restore" - } - } - }, - { - "Name": "Network test", - "Barcode": "", - "LTOGen": "LTO5", - "Source": { - "RemotePath": { - "Path": "\\\\nassrv0002.corp.maks-it.com\\data-1\\Users", - "PasswordCredentials": { - "Username": "", - "Password": "" - }, - "Protocol": "SMB" - } - }, - "Destination": { - "LocalPath": { - "Path": "F:\\LTO\\Restore" - } - } + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" } - ] + }, + + "Configuration": { + "TapePath": "\\\\.\\Tape0", + "WriteDelay": 100, + "Backups": [ + { + "Name": "Normal test", + "Barcode": "", + "LTOGen": "LTO5", + "Source": { + "LocalPath": { + "Path": "F:\\LTO\\Backup" + } + }, + "Destination": { + "LocalPath": { + "Path": "F:\\LTO\\Restore" + } + } + }, + { + "Name": "Network test", + "Barcode": "", + "LTOGen": "LTO5", + "Source": { + "RemotePath": { + "Path": "\\\\nassrv0002.corp.maks-it.com\\data-1\\Users", + "PasswordCredentials": { + "Username": "", + "Password": "" + }, + "Protocol": "SMB" + } + }, + "Destination": { + "LocalPath": { + "Path": "F:\\LTO\\Restore" + } + } + } + ] + } } \ No newline at end of file diff --git a/src/MaksIT.LTO.Core/DriverManager.cs b/src/MaksIT.LTO.Core/DriverManager.cs index 77395a5..0eabe5e 100644 --- a/src/MaksIT.LTO.Core/DriverManager.cs +++ b/src/MaksIT.LTO.Core/DriverManager.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Diagnostics; + namespace MaksIT.LTO.Core; public class DriverManager { diff --git a/src/MaksIT.LTO.Core/LTOBlockSizes.cs b/src/MaksIT.LTO.Core/LTOBlockSizes.cs deleted file mode 100644 index 7261c2b..0000000 --- a/src/MaksIT.LTO.Core/LTOBlockSizes.cs +++ /dev/null @@ -1,60 +0,0 @@ - -namespace MaksIT.LTO.Core; - -public static class LTOBlockSizes { - public const uint LTO1 = 65536; // 64 KB - public const uint LTO2 = 65536; // 64 KB - public const uint LTO3 = 131072; // 128 KB - public const uint LTO4 = 131072; // 128 KB - public const uint LTO5 = 262144; // 256 KB - public const uint LTO6 = 262144; // 256 KB - public const uint LTO7 = 524288; // 512 KB - public const uint LTO8 = 524288; // 512 KB - public const uint LTO9 = 1048576; // 1 MB - - // Dictionary to store the total capacity for each LTO generation (in bytes) - private static readonly Dictionary TapeCapacities = new Dictionary - { - { "LTO1", 100UL * 1024 * 1024 * 1024 }, // 100 GB - { "LTO2", 200UL * 1024 * 1024 * 1024 }, // 200 GB - { "LTO3", 400UL * 1024 * 1024 * 1024 }, // 400 GB - { "LTO4", 800UL * 1024 * 1024 * 1024 }, // 800 GB - { "LTO5", 1500UL * 1024 * 1024 * 1024 }, // 1.5 TB - { "LTO6", 2500UL * 1024 * 1024 * 1024 }, // 2.5 TB - { "LTO7", 6000UL * 1024 * 1024 * 1024 }, // 6 TB - { "LTO8", 12000UL * 1024 * 1024 * 1024 },// 12 TB - { "LTO9", 18000UL * 1024 * 1024 * 1024 } // 18 TB - }; - - // Method to get the block size for a given LTO generation - // Method to get the block size for a given LTO generation - public static uint GetBlockSize(string ltoGen) { - return ltoGen switch { - "LTO1" => LTO1, - "LTO2" => LTO2, - "LTO3" => LTO3, - "LTO4" => LTO4, - "LTO5" => LTO5, - "LTO6" => LTO6, - "LTO7" => LTO7, - "LTO8" => LTO8, - "LTO9" => LTO9, - _ => throw new ArgumentException("Invalid LTO generation") - }; - } - - // Method to get the total capacity for a given LTO generation - public static ulong GetTapeCapacity(string ltoGen) { - if (TapeCapacities.TryGetValue(ltoGen, out var capacity)) { - return capacity; - } - throw new ArgumentException("Invalid LTO generation"); - } - - // Method to calculate the maximum number of blocks that can be written on the tape - public static ulong GetMaxBlocks(string ltoGen) { - var blockSize = GetBlockSize(ltoGen); - var tapeCapacity = GetTapeCapacity(ltoGen); - return tapeCapacity / blockSize; - } -} diff --git a/src/MaksIT.LTO.Core/Logging/FileLogger.cs b/src/MaksIT.LTO.Core/Logging/FileLogger.cs new file mode 100644 index 0000000..ff8009a --- /dev/null +++ b/src/MaksIT.LTO.Core/Logging/FileLogger.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Logging; + + +namespace MaksIT.LTO.Core.Logging; + +public class FileLogger : ILogger { + private readonly string _filePath; + private readonly object _lock = new object(); + + public FileLogger(string filePath) { + _filePath = filePath; + } + + public IDisposable? BeginScope(TState state) where TState : notnull => null; + + public bool IsEnabled(LogLevel logLevel) { + return logLevel != LogLevel.None; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { + if (!IsEnabled(logLevel)) + return; + + var message = formatter(state, exception); + if (string.IsNullOrEmpty(message)) + return; + + var logRecord = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{logLevel}] {message}"; + if (exception != null) { + logRecord += Environment.NewLine + exception; + } + + lock (_lock) { + File.AppendAllText(_filePath, logRecord + Environment.NewLine); + } + } +} diff --git a/src/MaksIT.LTO.Core/Logging/FileLoggerProvider.cs b/src/MaksIT.LTO.Core/Logging/FileLoggerProvider.cs new file mode 100644 index 0000000..69b79dc --- /dev/null +++ b/src/MaksIT.LTO.Core/Logging/FileLoggerProvider.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Logging; + + +namespace MaksIT.LTO.Core.Logging; + +[ProviderAlias("FileLogger")] +public class FileLoggerProvider : ILoggerProvider { + private readonly string _filePath; + + public FileLoggerProvider(string filePath) { + _filePath = filePath ?? throw new ArgumentNullException(nameof(filePath)); + } + + public ILogger CreateLogger(string categoryName) { + return new FileLogger(_filePath); + } + + public void Dispose() { } +} diff --git a/src/MaksIT.LTO.Core/Logging/LoggingBuilderExtensions.cs b/src/MaksIT.LTO.Core/Logging/LoggingBuilderExtensions.cs new file mode 100644 index 0000000..a02b69b --- /dev/null +++ b/src/MaksIT.LTO.Core/Logging/LoggingBuilderExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + + +namespace MaksIT.LTO.Core.Logging; + +public static class LoggingBuilderExtensions { + public static ILoggingBuilder AddFile(this ILoggingBuilder builder, string filePath) { + builder.Services.AddSingleton(new FileLoggerProvider(filePath)); + return builder; + } +} diff --git a/src/MaksIT.LTO.Core/MaksIT.LTO.Core.csproj b/src/MaksIT.LTO.Core/MaksIT.LTO.Core.csproj index c8734fc..e60b572 100644 --- a/src/MaksIT.LTO.Core/MaksIT.LTO.Core.csproj +++ b/src/MaksIT.LTO.Core/MaksIT.LTO.Core.csproj @@ -7,4 +7,9 @@ NTDDI_VERSION_05010000;NTDDI_WINXP_05010000 + + + + + diff --git a/src/MaksIT.LTO.Core/MassStorage/LTOBlockSizes.cs b/src/MaksIT.LTO.Core/MassStorage/LTOBlockSizes.cs new file mode 100644 index 0000000..6fcc272 --- /dev/null +++ b/src/MaksIT.LTO.Core/MassStorage/LTOBlockSizes.cs @@ -0,0 +1,65 @@ +namespace MaksIT.LTO.Core.MassStorage; + +public static class LTOBlockSizes +{ + public const uint LTO1 = 65536; // 64 KB + public const uint LTO2 = 65536; // 64 KB + public const uint LTO3 = 131072; // 128 KB + public const uint LTO4 = 131072; // 128 KB + public const uint LTO5 = 262144; // 256 KB + public const uint LTO6 = 262144; // 256 KB + public const uint LTO7 = 524288; // 512 KB + public const uint LTO8 = 524288; // 512 KB + public const uint LTO9 = 1048576; // 1 MB + + // Dictionary to store the total capacity for each LTO generation (in bytes) + private static readonly Dictionary TapeCapacities = new Dictionary + { + { "LTO1", 100UL * 1024 * 1024 * 1024 }, // 100 GB + { "LTO2", 200UL * 1024 * 1024 * 1024 }, // 200 GB + { "LTO3", 400UL * 1024 * 1024 * 1024 }, // 400 GB + { "LTO4", 800UL * 1024 * 1024 * 1024 }, // 800 GB + { "LTO5", 1500UL * 1024 * 1024 * 1024 }, // 1.5 TB + { "LTO6", 2500UL * 1024 * 1024 * 1024 }, // 2.5 TB + { "LTO7", 6000UL * 1024 * 1024 * 1024 }, // 6 TB + { "LTO8", 12000UL * 1024 * 1024 * 1024 },// 12 TB + { "LTO9", 18000UL * 1024 * 1024 * 1024 } // 18 TB + }; + + // Method to get the block size for a given LTO generation + // Method to get the block size for a given LTO generation + public static uint GetBlockSize(string ltoGen) + { + return ltoGen switch + { + "LTO1" => LTO1, + "LTO2" => LTO2, + "LTO3" => LTO3, + "LTO4" => LTO4, + "LTO5" => LTO5, + "LTO6" => LTO6, + "LTO7" => LTO7, + "LTO8" => LTO8, + "LTO9" => LTO9, + _ => throw new ArgumentException("Invalid LTO generation") + }; + } + + // Method to get the total capacity for a given LTO generation + public static ulong GetTapeCapacity(string ltoGen) + { + if (TapeCapacities.TryGetValue(ltoGen, out var capacity)) + { + return capacity; + } + throw new ArgumentException("Invalid LTO generation"); + } + + // Method to calculate the maximum number of blocks that can be written on the tape + public static ulong GetMaxBlocks(string ltoGen) + { + var blockSize = GetBlockSize(ltoGen); + var tapeCapacity = GetTapeCapacity(ltoGen); + return tapeCapacity / blockSize; + } +} diff --git a/src/MaksIT.LTO.Core/TapeDeviceHandler.cs b/src/MaksIT.LTO.Core/MassStorage/TapeDeviceHandler.cs similarity index 95% rename from src/MaksIT.LTO.Core/TapeDeviceHandler.cs rename to src/MaksIT.LTO.Core/MassStorage/TapeDeviceHandler.cs index dc4645b..9d646a8 100644 --- a/src/MaksIT.LTO.Core/TapeDeviceHandler.cs +++ b/src/MaksIT.LTO.Core/MassStorage/TapeDeviceHandler.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; using Microsoft.Win32.SafeHandles; // https://github.com/tpn/winsdk-10 @@ -8,11 +9,11 @@ using Microsoft.Win32.SafeHandles; namespace MaksIT.LTO.Core; public partial class TapeDeviceHandler : IDisposable { + + private readonly ILogger _logger; private string _tapeDevicePath; private SafeFileHandle _tapeHandle; - - private const uint GENERIC_READ = 0x80000000; private const uint GENERIC_WRITE = 0x40000000; private const uint OPEN_EXISTING = 3; @@ -67,7 +68,11 @@ public partial class TapeDeviceHandler : IDisposable { out uint lpNumberOfBytesRead, IntPtr lpOverlapped); - public TapeDeviceHandler(string tapeDevicePath) { + public TapeDeviceHandler( + ILogger logger, + string tapeDevicePath + ) { + _logger = logger; _tapeDevicePath = tapeDevicePath; OpenTapeDevice(GENERIC_READ | GENERIC_WRITE); } diff --git a/src/MaksIT.LTO.Core/TapeDeviceHandlerNtdstor.cs b/src/MaksIT.LTO.Core/MassStorage/TapeDeviceHandlerNtdstor.cs similarity index 100% rename from src/MaksIT.LTO.Core/TapeDeviceHandlerNtdstor.cs rename to src/MaksIT.LTO.Core/MassStorage/TapeDeviceHandlerNtdstor.cs diff --git a/src/MaksIT.LTO.Core/TapeDeviceHandlerNtdtape.cs b/src/MaksIT.LTO.Core/MassStorage/TapeDeviceHandlerNtdtape.cs similarity index 100% rename from src/MaksIT.LTO.Core/TapeDeviceHandlerNtdtape.cs rename to src/MaksIT.LTO.Core/MassStorage/TapeDeviceHandlerNtdtape.cs diff --git a/src/MaksIT.LTO.Core/NetworkConnection.cs b/src/MaksIT.LTO.Core/Networking/NetworkConnection.cs similarity index 69% rename from src/MaksIT.LTO.Core/NetworkConnection.cs rename to src/MaksIT.LTO.Core/Networking/NetworkConnection.cs index e76a8c5..bec6dea 100644 --- a/src/MaksIT.LTO.Core/NetworkConnection.cs +++ b/src/MaksIT.LTO.Core/Networking/NetworkConnection.cs @@ -1,27 +1,23 @@ using System.Net; using System.Runtime.InteropServices; -namespace MaksIT.LTO.Core; +using Microsoft.Extensions.Logging; -//public void RestoreFilesFromSmbShare(string smbPath, string username, string password, string domain, string restoreDirectory) { -// var credentials = new NetworkCredential(username, password, domain); -// using (new NetworkConnection(smbPath, credentials)) { -// var files = Directory.GetFiles(smbPath, "*.*", SearchOption.AllDirectories); -// foreach (var file in files) { -// var relativePath = Path.GetRelativePath(smbPath, file); -// var destinationPath = Path.Combine(restoreDirectory, relativePath); -// Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); -// File.Copy(file, destinationPath, overwrite: true); -// Console.WriteLine($"Restored file: {file} to {destinationPath}"); -// } -// } -//} +namespace MaksIT.LTO.Core.Networking; public class NetworkConnection : IDisposable { + + private readonly ILogger _logger; private readonly string _networkName; - public NetworkConnection(string networkName, NetworkCredential credentials) { + public NetworkConnection( + ILogger logger, + string networkName, + NetworkCredential credentials) { + + _logger = logger; + _networkName = networkName; var netResource = new NetResource { From 225debf8f213ffba40640173741a3ae33f59a6bc Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Sun, 3 Nov 2024 01:14:42 +0100 Subject: [PATCH 2/3] (feature): changed files checksum algorythm, padded files descriptor with encryption and integrity check, handling secret in env var or text file fallback --- src/MaksIT.LTO.Backup/Application.cs | 226 ++++++++++-------- .../MaksIT.LTO.Backup.csproj | 4 + src/MaksIT.LTO.Core/MaksIT.LTO.Core.csproj | 1 + .../Utilities/AESGCMUtility.cs | 57 +++++ .../Utilities/ChecksumUtility.cs | 130 ++++++++++ 5 files changed, 323 insertions(+), 95 deletions(-) create mode 100644 src/MaksIT.LTO.Core/Utilities/AESGCMUtility.cs create mode 100644 src/MaksIT.LTO.Core/Utilities/ChecksumUtility.cs diff --git a/src/MaksIT.LTO.Backup/Application.cs b/src/MaksIT.LTO.Backup/Application.cs index dc5e917..059c235 100644 --- a/src/MaksIT.LTO.Backup/Application.cs +++ b/src/MaksIT.LTO.Backup/Application.cs @@ -9,6 +9,8 @@ using MaksIT.LTO.Core; using MaksIT.LTO.Backup.Entities; using MaksIT.LTO.Core.MassStorage; using MaksIT.LTO.Core.Networking; +using MaksIT.LTO.Core.Utilities; +using MaksIT.LTO.Core.Helpers; namespace MaksIT.LTO.Backup; @@ -16,16 +18,19 @@ namespace MaksIT.LTO.Backup; public class Application { private const string _descriptoFileName = "descriptor.json"; + private const string _secretFileName = "secret.txt"; private readonly string appPath = AppDomain.CurrentDomain.BaseDirectory; private readonly string _tapePath; private readonly string _descriptorFilePath; + private readonly string _secretFilePath; private readonly ILogger _logger; private readonly ILogger _tapeDeviceLogger; private readonly ILogger _networkConnectionLogger; private readonly Configuration _configuration; + private readonly string _secret; public Application( ILogger logger, @@ -37,9 +42,26 @@ public class Application { _networkConnectionLogger = loggerFactory.CreateLogger(); _descriptorFilePath = Path.Combine(appPath, _descriptoFileName); + _secretFilePath = Path.Combine(appPath, _secretFileName); _configuration = configuration.Value; _tapePath = _configuration.TapePath; + + var secret = Environment.GetEnvironmentVariable("LTO_BACKUP_SECRET") + ?? Environment.GetEnvironmentVariable("LTO_BACKUP_SECRET", EnvironmentVariableTarget.Machine); + + if (!string.IsNullOrWhiteSpace(secret)) + _secret = secret; + else if (!File.Exists(_secretFilePath)) { + _secret = AESGCMUtility.GenerateKeyBase64(); + File.WriteAllText(_secretFilePath, _secret); + } + else + _secret = File.ReadAllText(_secretFilePath); + + if (string.IsNullOrWhiteSpace(_secret)) { + throw new InvalidOperationException("Secret is required for encryption."); + } } public void Run() { @@ -95,31 +117,31 @@ public class Application { } } - public void LoadTape() { + private void LoadTape() { using var handler = new TapeDeviceHandler(_tapeDeviceLogger, _tapePath); LoadTape(handler); } - public void LoadTape(TapeDeviceHandler handler) { + private void LoadTape(TapeDeviceHandler handler) { handler.Prepare(TapeDeviceHandler.TAPE_LOAD); Thread.Sleep(2000); _logger.LogInformation("Tape loaded."); } - public void EjectTape() { + private void EjectTape() { using var handler = new TapeDeviceHandler(_tapeDeviceLogger, _tapePath); EjectTape(handler); } - public void EjectTape(TapeDeviceHandler handler) { + private void EjectTape(TapeDeviceHandler handler) { handler.Prepare(TapeDeviceHandler.TAPE_UNLOAD); Thread.Sleep(2000); _logger.LogInformation("Tape ejected."); } - public void TapeErase() { + private void TapeErase() { using var handler = new TapeDeviceHandler(_tapeDeviceLogger, _tapePath); LoadTape(handler); @@ -143,12 +165,12 @@ public class Application { _logger.LogInformation("Tape erased."); } - public void GetDeviceStatus() { + private void GetDeviceStatus() { using var handler = new TapeDeviceHandler(_tapeDeviceLogger, _tapePath); handler.GetStatus(); } - public void PathAccessWrapper(WorkingFolder workingFolder, Action myAction) { + private void PathAccessWrapper(WorkingFolder workingFolder, Action myAction) { if (workingFolder.LocalPath != null) { var localPath = workingFolder.LocalPath.Path; @@ -182,7 +204,7 @@ public class Application { } } - public void CreateDescriptor(WorkingFolder workingFolder, string descriptorFilePath, uint blockSize) { + private void CreateDescriptor(WorkingFolder workingFolder, string descriptorFilePath, uint blockSize) { PathAccessWrapper(workingFolder, (directoryPath) => { var files = Directory.GetFiles(directoryPath, "*.*", SearchOption.AllDirectories); @@ -196,18 +218,8 @@ public class Application { var relativePath = Path.GetRelativePath(directoryPath, filePath); var numberOfBlocks = (uint)((fileInfo.Length + blockSize - 1) / blockSize); - // Optional: Calculate a simple hash for file integrity (e.g., MD5) - using var md5 = System.Security.Cryptography.MD5.Create(); - using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); - using var bufferedStream = new BufferedStream(fileStream, (int)blockSize); - - byte[] buffer = new byte[blockSize]; - int bytesRead; - while ((bytesRead = bufferedStream.Read(buffer, 0, buffer.Length)) > 0) { - md5.TransformBlock(buffer, 0, bytesRead, null, 0); - } - md5.TransformFinalBlock(Array.Empty(), 0, 0); - string fileHash = BitConverter.ToString(md5.Hash).Replace("-", "").ToLower(); + // Calculate CRC32 checksum for file integrity + string fileHash = ChecksumUtility.CalculateCRC32ChecksumFromFileInChunks(filePath, (int)blockSize); descriptor.Add(new FileDescriptor { StartBlock = currentTapeBlock, // Position of the file on the tape @@ -232,22 +244,29 @@ public class Application { }); } - private void ZeroFillBlocks(TapeDeviceHandler handler, int blocks, uint blockSize) { - _logger.LogInformation($"Writing {blocks} zero-filled blocks to tape."); - _logger.LogInformation($"Block Size: {blockSize}."); - - var writeError = 0; - - for (int i = 0; i < blocks; i++) { - writeError = handler.WriteData(new byte[blockSize]); - if (writeError != 0) - return; - - Thread.Sleep(_configuration.WriteDelay); + private static byte[] AddPadding(byte[] data, int blockSize) { + // Calculate the padding size + int paddingSize = blockSize - (data.Length % blockSize); + if (paddingSize == blockSize) { + paddingSize = 0; } + + // Create a new array with the original data plus padding + byte[] paddedData = new byte[data.Length + paddingSize + 1]; + Array.Copy(data, paddedData, data.Length); + + // Fill the padding with a specific value (e.g., 0x00) + for (int i = data.Length; i < paddedData.Length - 1; i++) { + paddedData[i] = 0x00; + } + + // Append the padding size at the end + paddedData[paddedData.Length - 1] = (byte)paddingSize; + + return paddedData; } - public void WriteFilesToTape(WorkingFolder workingFolder, string descriptorFilePath, uint blockSize) { + private void WriteFilesToTape(WorkingFolder workingFolder, string descriptorFilePath, uint blockSize) { PathAccessWrapper(workingFolder, (directoryPath) => { _logger.LogInformation($"Writing files to tape from: {directoryPath}."); _logger.LogInformation($"Block Size: {blockSize}."); @@ -279,9 +298,6 @@ public class Application { var currentTapeBlock = (descriptorJson.Length + blockSize - 1) / blockSize; - - int writeError = 0; - foreach (var file in descriptor.Files) { var filePath = Path.Combine(directoryPath, file.FilePath); using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); @@ -296,7 +312,7 @@ public class Application { Array.Clear(buffer, bytesRead, buffer.Length - bytesRead); } - writeError = handler.WriteData(buffer); + var writeError = handler.WriteData(buffer); if (writeError != 0) { _logger.LogInformation($"Failed to write file: {filePath}"); return; @@ -307,21 +323,28 @@ public class Application { } } - // write mark to indicate end of files handler.WriteMarks(TapeDeviceHandler.TAPE_FILEMARKS, 1); Thread.Sleep(_configuration.WriteDelay); // write descriptor to tape var descriptorData = Encoding.UTF8.GetBytes(descriptorJson); - var descriptorBlocks = (descriptorData.Length + blockSize - 1) / blockSize; - for (int i = 0; i < descriptorBlocks; i++) { + + // encrypt the serialized descriptor + var encryptedDescriptorData = AESGCMUtility.EncryptData(descriptorData, _secret); + + // add padding to the encrypted descriptor data + var paddedDescriptorData = AddPadding(encryptedDescriptorData, (int)blockSize); + + // calculate the number of blocks needed + var descriptorBlocks = (paddedDescriptorData.Length + blockSize - 1) / blockSize; + for (var i = 0; i < descriptorBlocks; i++) { var startIndex = i * blockSize; - var length = Math.Min(blockSize, descriptorData.Length - startIndex); - byte[] block = new byte[blockSize]; // Initialized with zeros by default - Array.Copy(descriptorData, startIndex, block, 0, length); - - writeError = handler.WriteData(block); + var length = Math.Min(blockSize, paddedDescriptorData.Length - startIndex); + var block = new byte[blockSize]; // Initialized with zeros by default + Array.Copy(paddedDescriptorData, startIndex, block, 0, length); + + var writeError = handler.WriteData(block); if (writeError != 0) return; @@ -329,8 +352,9 @@ public class Application { Thread.Sleep(_configuration.WriteDelay); // Small delay between blocks } - // write 3 0 filled blocks to indicate end of backup - ZeroFillBlocks(handler, 3, blockSize); + // write mark to indicate end of files + handler.WriteMarks(TapeDeviceHandler.TAPE_FILEMARKS, 2); + Thread.Sleep(_configuration.WriteDelay); handler.Prepare(TapeDeviceHandler.TAPE_UNLOCK); Thread.Sleep(2000); @@ -341,7 +365,7 @@ public class Application { }); } - public BackupDescriptor? FindDescriptor(uint blockSize) { + private BackupDescriptor? FindDescriptor(uint blockSize) { _logger.LogInformation("Searching for descriptor on tape..."); _logger.LogInformation($"Block Size: {blockSize}."); @@ -357,34 +381,55 @@ public class Application { handler.SetPosition(TapeDeviceHandler.TAPE_SPACE_FILEMARKS, 0, 1); Thread.Sleep(2000); - handler.WaitForTapeReady(); + var position = handler.GetPosition(TapeDeviceHandler.TAPE_ABSOLUTE_BLOCK); + if (position.Error != null) + return null; - // Read data from tape until 3 zero-filled blocks are found + var desctiptorBlocks = position.OffsetLow; + + handler.SetPosition(TapeDeviceHandler.TAPE_SPACE_FILEMARKS, 0, 2); + Thread.Sleep(2000); + + position = handler.GetPosition(TapeDeviceHandler.TAPE_ABSOLUTE_BLOCK); + if (position.Error != null) + return null; + + desctiptorBlocks = position.OffsetLow - desctiptorBlocks; + + + var padding = handler.ReadData(blockSize); + + handler.SetPosition(TapeDeviceHandler.TAPE_SPACE_FILEMARKS, 0, 1); + Thread.Sleep(2000); + + // read data from descriptorBlocks var buffer = new List(); - byte[] data; - var zeroBlocks = 0; - do { - data = handler.ReadData(blockSize); + for (var i = 0; i < desctiptorBlocks; i++) { + var data = handler.ReadData(blockSize); buffer.AddRange(data); - if (data.All(b => b == 0)) { - zeroBlocks++; - } - else { - zeroBlocks = 0; - } - } while (zeroBlocks < 3); - - // Remove the last 3 zero-filled blocks from the buffer - var totalZeroBlocksSize = (int)(3 * blockSize); - if (buffer.Count >= totalZeroBlocksSize) { - buffer.RemoveRange(buffer.Count - totalZeroBlocksSize, totalZeroBlocksSize); } - // Convert buffer to byte array - var byteArray = buffer.ToArray(); + // Convert buffer to array + var paddedData = buffer.ToArray(); + + // Retrieve the padding size from the last byte + int paddingSize = paddedData[^1]; + + // Calculate the length of the original data + int originalDataLength = paddedData.Length - paddingSize - 1; + + // Ensure the padding size is valid + if (paddingSize < 0 || paddingSize >= paddedData.Length || originalDataLength < 0) + return null; + + // Create a new array for the original data + var descriptorData = new byte[originalDataLength]; + Array.Copy(paddedData, descriptorData, originalDataLength); + + descriptorData = AESGCMUtility.DecryptData(descriptorData, _secret); // Convert byte array to string and trim ending zeros - var json = Encoding.UTF8.GetString(byteArray).TrimEnd('\0'); + var json = Encoding.UTF8.GetString(descriptorData); try { var descriptor = JsonSerializer.Deserialize(json); @@ -395,9 +440,9 @@ public class Application { } catch (JsonException ex) { _logger.LogInformation($"Failed to parse descriptor JSON: {ex.Message}"); + return null; } - handler.Prepare(TapeDeviceHandler.TAPE_UNLOCK); Thread.Sleep(2000); @@ -407,7 +452,7 @@ public class Application { return null; } - public void RestoreDirectory(BackupDescriptor descriptor, WorkingFolder workingFolder) { + private void RestoreDirectory(BackupDescriptor descriptor, WorkingFolder workingFolder) { PathAccessWrapper(workingFolder, (restoreDirectoryPath) => { _logger.LogInformation("Restoring files to directory: " + restoreDirectoryPath); @@ -448,20 +493,11 @@ public class Application { } } - // check md5 checksum of restored file with the one in descriptor - using (var md5 = System.Security.Cryptography.MD5.Create()) { - using (var fileStreamRead = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { - var fileHash = md5.ComputeHash(fileStreamRead); - var fileHashString = BitConverter.ToString(fileHash).Replace("-", "").ToLower(); - - if (fileHashString != file.FileHash) { - _logger.LogInformation($"Checksum mismatch for file: {filePath}"); - } - else { - _logger.LogInformation($"Restored file: {filePath}"); - } - } - } + // check checksum of restored file with the one in descriptor + if (ChecksumUtility.VerifyCRC32ChecksumFromFileInChunks(filePath, file.FileHash, (int)descriptor.BlockSize)) + _logger.LogInformation($"Restored file: {filePath}"); + else + _logger.LogInformation($"Checksum mismatch for file: {filePath}"); } handler.SetPosition(TapeDeviceHandler.TAPE_REWIND); @@ -469,22 +505,23 @@ public class Application { }); } - public int CheckMediaSize(string ltoGen) { + private int CheckMediaSize(string ltoGen) { var descriptor = JsonSerializer.Deserialize(File.ReadAllText(_descriptorFilePath)); if (descriptor == null) { _logger.LogInformation("Failed to read descriptor."); return 1; } - var totalBlocks = (ulong)descriptor.Files.Sum(f => f.NumberOfBlocks); + var encryptedDescriptorData = AESGCMUtility.EncryptData(File.ReadAllBytes(_descriptorFilePath), _secret); - const ulong fileMarkBlocks = 1; - const ulong terminalBlocks = 3; + var paddedDescriptorData = AddPadding(encryptedDescriptorData, (int)descriptor.BlockSize); - var descriptorSize = new FileInfo(_descriptoFileName).Length; - ulong descriptorSizeBlocks = (ulong)Math.Ceiling((double)descriptorSize / descriptor.BlockSize); + const ulong fileMarkBlocks = 2; + + var descriptorSize = paddedDescriptorData.Length; + var descriptorSizeBlocks = Math.Ceiling((double)descriptorSize / descriptor.BlockSize); - totalBlocks += fileMarkBlocks + descriptorSizeBlocks + terminalBlocks; + var totalBlocks = fileMarkBlocks + descriptorSizeBlocks; var maxBlocks = LTOBlockSizes.GetMaxBlocks(ltoGen); if (totalBlocks > maxBlocks) { @@ -498,7 +535,7 @@ public class Application { return 0; } - public void Backup() { + private void Backup() { while (true) { _logger.LogInformation("\nSelect a backup to perform:"); for (int i = 0; i < _configuration.Backups.Count; i++) { @@ -539,7 +576,7 @@ public class Application { } } - public void Restore() { + private void Restore() { while (true) { _logger.LogInformation("\nSelect a backup to restore:"); for (int i = 0; i < _configuration.Backups.Count; i++) { @@ -582,4 +619,3 @@ public class Application { } } } - diff --git a/src/MaksIT.LTO.Backup/MaksIT.LTO.Backup.csproj b/src/MaksIT.LTO.Backup/MaksIT.LTO.Backup.csproj index a76cc2f..a722edc 100644 --- a/src/MaksIT.LTO.Backup/MaksIT.LTO.Backup.csproj +++ b/src/MaksIT.LTO.Backup/MaksIT.LTO.Backup.csproj @@ -5,6 +5,7 @@ net8.0 enable enable + 0.0.2 @@ -23,6 +24,9 @@ PreserveNewest + + PreserveNewest + diff --git a/src/MaksIT.LTO.Core/MaksIT.LTO.Core.csproj b/src/MaksIT.LTO.Core/MaksIT.LTO.Core.csproj index e60b572..a01e4fb 100644 --- a/src/MaksIT.LTO.Core/MaksIT.LTO.Core.csproj +++ b/src/MaksIT.LTO.Core/MaksIT.LTO.Core.csproj @@ -5,6 +5,7 @@ enable enable NTDDI_VERSION_05010000;NTDDI_WINXP_05010000 + 0.0.2 diff --git a/src/MaksIT.LTO.Core/Utilities/AESGCMUtility.cs b/src/MaksIT.LTO.Core/Utilities/AESGCMUtility.cs new file mode 100644 index 0000000..c85c9cd --- /dev/null +++ b/src/MaksIT.LTO.Core/Utilities/AESGCMUtility.cs @@ -0,0 +1,57 @@ +using System.Security.Cryptography; + + +namespace MaksIT.LTO.Core.Utilities; + +public static class AESGCMUtility { + private const int IvLength = 12; // 12 bytes for AES-GCM IV + private const int TagLength = 16; // 16 bytes for AES-GCM Tag + + public static byte[] EncryptData(byte[] data, string base64Key) { + var key = Convert.FromBase64String(base64Key); + using (AesGcm aesGcm = new AesGcm(key, AesGcm.TagByteSizes.MaxSize)) { + var iv = new byte[IvLength]; + RandomNumberGenerator.Fill(iv); + + var cipherText = new byte[data.Length]; + var tag = new byte[TagLength]; + + aesGcm.Encrypt(iv, data, cipherText, tag); + + // Concatenate cipherText, tag, and iv + var result = new byte[cipherText.Length + tag.Length + iv.Length]; + Buffer.BlockCopy(cipherText, 0, result, 0, cipherText.Length); + Buffer.BlockCopy(tag, 0, result, cipherText.Length, tag.Length); + Buffer.BlockCopy(iv, 0, result, cipherText.Length + tag.Length, iv.Length); + + return result; + } + } + + public static byte[] DecryptData(byte[] data, string base64Key) { + var key = Convert.FromBase64String(base64Key); + + // Extract cipherText, tag, and iv + var cipherTextLength = data.Length - IvLength - TagLength; + + var cipherText = new byte[cipherTextLength]; + var tag = new byte[TagLength]; + var iv = new byte[IvLength]; + + Buffer.BlockCopy(data, 0, cipherText, 0, cipherTextLength); + Buffer.BlockCopy(data, cipherTextLength, tag, 0, TagLength); + Buffer.BlockCopy(data, cipherTextLength + TagLength, iv, 0, IvLength); + + using (AesGcm aesGcm = new AesGcm(key, AesGcm.TagByteSizes.MaxSize)) { + var decryptedData = new byte[cipherText.Length]; + aesGcm.Decrypt(iv, cipherText, tag, decryptedData); + return decryptedData; + } + } + + public static string GenerateKeyBase64() { + var key = new byte[32]; // 256-bit key for AES-256 + RandomNumberGenerator.Fill(key); + return Convert.ToBase64String(key); + } +} diff --git a/src/MaksIT.LTO.Core/Utilities/ChecksumUtility.cs b/src/MaksIT.LTO.Core/Utilities/ChecksumUtility.cs new file mode 100644 index 0000000..861f30c --- /dev/null +++ b/src/MaksIT.LTO.Core/Utilities/ChecksumUtility.cs @@ -0,0 +1,130 @@ +using System.IO; +using System.Security.Cryptography; + +namespace MaksIT.LTO.Core.Helpers; + +public static class ChecksumUtility { + public static string CalculateCRC32Checksum(byte[] data) { + using var crc32 = new Crc32(); + byte[] hashBytes = crc32.ComputeHash(data); + return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); + } + + public static string CalculateCRC32ChecksumFromFile(string filePath) { + using var crc32 = new Crc32(); + using var stream = File.OpenRead(filePath); + byte[] hashBytes = crc32.ComputeHash(stream); + return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); + } + + public static string CalculateCRC32ChecksumFromFileInChunks(string filePath, int chunkSize = 8192) { + using var crc32 = new Crc32(); + using var stream = File.OpenRead(filePath); + var buffer = new byte[chunkSize]; + int bytesRead; + while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) { + crc32.TransformBlock(buffer, 0, bytesRead, null, 0); + } + crc32.TransformFinalBlock(buffer, 0, 0); + byte[] hashBytes = crc32.Hash; + return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); + } + + public static bool VerifyCRC32Checksum(byte[] data, string expectedChecksum) { + string calculatedChecksum = CalculateCRC32Checksum(data); + return string.Equals(calculatedChecksum, expectedChecksum, StringComparison.OrdinalIgnoreCase); + } + + public static bool VerifyCRC32ChecksumFromFile(string filePath, string expectedChecksum) { + string calculatedChecksum = CalculateCRC32ChecksumFromFile(filePath); + return string.Equals(calculatedChecksum, expectedChecksum, StringComparison.OrdinalIgnoreCase); + } + + public static bool VerifyCRC32ChecksumFromFileInChunks(string filePath, string expectedChecksum, int chunkSize = 8192) { + string calculatedChecksum = CalculateCRC32ChecksumFromFileInChunks(filePath, chunkSize); + return string.Equals(calculatedChecksum, expectedChecksum, StringComparison.OrdinalIgnoreCase); + } +} + +public class Crc32 : HashAlgorithm { + public const uint DefaultPolynomial = 0xedb88320; + public const uint DefaultSeed = 0xffffffff; + + private static uint[]? defaultTable; + + private readonly uint seed; + private readonly uint[] table; + private uint hash; + + public Crc32() + : this(DefaultPolynomial, DefaultSeed) { + } + + public Crc32(uint polynomial, uint seed) { + table = InitializeTable(polynomial); + this.seed = hash = seed; + } + + public override void Initialize() { + hash = seed; + } + + protected override void HashCore(byte[] buffer, int start, int length) { + hash = CalculateHash(table, hash, buffer, start, length); + } + + protected override byte[] HashFinal() { + var hashBuffer = UInt32ToBigEndianBytes(~hash); + HashValue = hashBuffer; + return hashBuffer; + } + + public override int HashSize => 32; + + public static uint Compute(byte[] buffer) { + return Compute(DefaultPolynomial, DefaultSeed, buffer); + } + + public static uint Compute(uint seed, byte[] buffer) { + return Compute(DefaultPolynomial, seed, buffer); + } + + public static uint Compute(uint polynomial, uint seed, byte[] buffer) { + return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length); + } + + private static uint[] InitializeTable(uint polynomial) { + if (polynomial == DefaultPolynomial && defaultTable != null) + return defaultTable; + + var createTable = new uint[256]; + for (var i = 0; i < 256; i++) { + var entry = (uint)i; + for (var j = 0; j < 8; j++) + if ((entry & 1) == 1) + entry = (entry >> 1) ^ polynomial; + else + entry >>= 1; + createTable[i] = entry; + } + + if (polynomial == DefaultPolynomial) + defaultTable = createTable; + + return createTable; + } + + private static uint CalculateHash(uint[] table, uint seed, byte[] buffer, int start, int size) { + var crc = seed; + for (var i = start; i < size - start; i++) + crc = (crc >> 8) ^ table[buffer[i] ^ crc & 0xff]; + return crc; + } + + private static byte[] UInt32ToBigEndianBytes(uint x) => [ + (byte)((x >> 24) & 0xff), + (byte)((x >> 16) & 0xff), + (byte)((x >> 8) & 0xff), + (byte)(x & 0xff) + ]; +} From fcbf7e17b54449614531b3b3a66226dd34ac0927 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Sun, 3 Nov 2024 06:08:18 -0800 Subject: [PATCH 3/3] (bugfix): add remove padding, code review --- src/MaksIT.LTO.Backup/Application.cs | 84 ++++++--------- src/MaksIT.LTO.Core/Crc32.cs | 87 +++++++++++++++ src/MaksIT.LTO.Core/DriverManager.cs | 33 ------ .../Utilities/ChecksumUtility.cs | 101 ++---------------- .../Utilities/PaddingUtility.cs | 38 +++++++ 5 files changed, 164 insertions(+), 179 deletions(-) create mode 100644 src/MaksIT.LTO.Core/Crc32.cs delete mode 100644 src/MaksIT.LTO.Core/DriverManager.cs create mode 100644 src/MaksIT.LTO.Core/Utilities/PaddingUtility.cs diff --git a/src/MaksIT.LTO.Backup/Application.cs b/src/MaksIT.LTO.Backup/Application.cs index 059c235..50f523e 100644 --- a/src/MaksIT.LTO.Backup/Application.cs +++ b/src/MaksIT.LTO.Backup/Application.cs @@ -244,27 +244,7 @@ public class Application { }); } - private static byte[] AddPadding(byte[] data, int blockSize) { - // Calculate the padding size - int paddingSize = blockSize - (data.Length % blockSize); - if (paddingSize == blockSize) { - paddingSize = 0; - } - // Create a new array with the original data plus padding - byte[] paddedData = new byte[data.Length + paddingSize + 1]; - Array.Copy(data, paddedData, data.Length); - - // Fill the padding with a specific value (e.g., 0x00) - for (int i = data.Length; i < paddedData.Length - 1; i++) { - paddedData[i] = 0x00; - } - - // Append the padding size at the end - paddedData[paddedData.Length - 1] = (byte)paddingSize; - - return paddedData; - } private void WriteFilesToTape(WorkingFolder workingFolder, string descriptorFilePath, uint blockSize) { PathAccessWrapper(workingFolder, (directoryPath) => { @@ -334,7 +314,7 @@ public class Application { var encryptedDescriptorData = AESGCMUtility.EncryptData(descriptorData, _secret); // add padding to the encrypted descriptor data - var paddedDescriptorData = AddPadding(encryptedDescriptorData, (int)blockSize); + var paddedDescriptorData = PaddingUtility.AddPadding(encryptedDescriptorData, (int)blockSize); // calculate the number of blocks needed var descriptorBlocks = (paddedDescriptorData.Length + blockSize - 1) / blockSize; @@ -381,55 +361,53 @@ public class Application { handler.SetPosition(TapeDeviceHandler.TAPE_SPACE_FILEMARKS, 0, 1); Thread.Sleep(2000); - var position = handler.GetPosition(TapeDeviceHandler.TAPE_ABSOLUTE_BLOCK); - if (position.Error != null) + var endOfBackupMarkerPosition = handler.GetPosition(TapeDeviceHandler.TAPE_ABSOLUTE_BLOCK); + if (endOfBackupMarkerPosition.Error != null || endOfBackupMarkerPosition.OffsetLow == null) return null; - var desctiptorBlocks = position.OffsetLow; + _logger.LogInformation($"End of backup marker position: {endOfBackupMarkerPosition.OffsetLow}"); + + var descriptorBlocks = endOfBackupMarkerPosition.OffsetLow; handler.SetPosition(TapeDeviceHandler.TAPE_SPACE_FILEMARKS, 0, 2); Thread.Sleep(2000); - position = handler.GetPosition(TapeDeviceHandler.TAPE_ABSOLUTE_BLOCK); - if (position.Error != null) + var endOfDescriptorMarkerPosition = handler.GetPosition(TapeDeviceHandler.TAPE_ABSOLUTE_BLOCK); + if (endOfDescriptorMarkerPosition.Error != null || endOfDescriptorMarkerPosition.OffsetLow == null) return null; - desctiptorBlocks = position.OffsetLow - desctiptorBlocks; + _logger.LogInformation($"End of descriptor marker position: {endOfDescriptorMarkerPosition.OffsetLow}"); + descriptorBlocks = endOfDescriptorMarkerPosition.OffsetLow - descriptorBlocks; - var padding = handler.ReadData(blockSize); + if (descriptorBlocks == null) + return null; - handler.SetPosition(TapeDeviceHandler.TAPE_SPACE_FILEMARKS, 0, 1); + _logger.LogInformation($"Descriptor blocks to read: {descriptorBlocks}"); + + handler.SetPosition(TapeDeviceHandler.TAPE_ABSOLUTE_BLOCK, 0, (long)(endOfDescriptorMarkerPosition.OffsetLow - descriptorBlocks)); Thread.Sleep(2000); - // read data from descriptorBlocks - var buffer = new List(); - for (var i = 0; i < desctiptorBlocks; i++) { - var data = handler.ReadData(blockSize); - buffer.AddRange(data); + descriptorBlocks -= 2; + + var paddedData = new byte[(descriptorBlocks.Value) * blockSize]; + var buffer = new byte[blockSize]; + + for (var i = 0; i < descriptorBlocks; i++) { + var bytesRead = handler.ReadData(buffer, 0, buffer.Length); + + // Copy the read data into the encryptedData array + Array.Copy(buffer, 0, paddedData, i * blockSize, buffer.Length); } - // Convert buffer to array - var paddedData = buffer.ToArray(); + // i need to remove padding from the data + var encryptedData = PaddingUtility.RemovePadding(paddedData, (int)blockSize); - // Retrieve the padding size from the last byte - int paddingSize = paddedData[^1]; - - // Calculate the length of the original data - int originalDataLength = paddedData.Length - paddingSize - 1; - - // Ensure the padding size is valid - if (paddingSize < 0 || paddingSize >= paddedData.Length || originalDataLength < 0) - return null; - - // Create a new array for the original data - var descriptorData = new byte[originalDataLength]; - Array.Copy(paddedData, descriptorData, originalDataLength); - - descriptorData = AESGCMUtility.DecryptData(descriptorData, _secret); + // decrypt the data + var decryptedData = AESGCMUtility.DecryptData(encryptedData, _secret); // Convert byte array to string and trim ending zeros - var json = Encoding.UTF8.GetString(descriptorData); + var json = Encoding.UTF8.GetString(decryptedData); try { var descriptor = JsonSerializer.Deserialize(json); @@ -514,7 +492,7 @@ public class Application { var encryptedDescriptorData = AESGCMUtility.EncryptData(File.ReadAllBytes(_descriptorFilePath), _secret); - var paddedDescriptorData = AddPadding(encryptedDescriptorData, (int)descriptor.BlockSize); + var paddedDescriptorData = PaddingUtility.AddPadding(encryptedDescriptorData, (int)descriptor.BlockSize); const ulong fileMarkBlocks = 2; diff --git a/src/MaksIT.LTO.Core/Crc32.cs b/src/MaksIT.LTO.Core/Crc32.cs new file mode 100644 index 0000000..4910ad2 --- /dev/null +++ b/src/MaksIT.LTO.Core/Crc32.cs @@ -0,0 +1,87 @@ +using System.Security.Cryptography; + + +namespace MaksIT.LTO.Core; + +public class Crc32 : HashAlgorithm { + public const uint DefaultPolynomial = 0xedb88320; + public const uint DefaultSeed = 0xffffffff; + + private static uint[]? defaultTable; + + private readonly uint seed; + private readonly uint[] table; + private uint hash; + + public Crc32() + : this(DefaultPolynomial, DefaultSeed) { + } + + public Crc32(uint polynomial, uint seed) { + table = InitializeTable(polynomial); + this.seed = hash = seed; + } + + public override void Initialize() { + hash = seed; + } + + protected override void HashCore(byte[] buffer, int start, int length) { + hash = CalculateHash(table, hash, buffer, start, length); + } + + protected override byte[] HashFinal() { + var hashBuffer = UInt32ToBigEndianBytes(~hash); + HashValue = hashBuffer; + return hashBuffer; + } + + public override int HashSize => 32; + + public static uint Compute(byte[] buffer) { + return Compute(DefaultPolynomial, DefaultSeed, buffer); + } + + public static uint Compute(uint seed, byte[] buffer) { + return Compute(DefaultPolynomial, seed, buffer); + } + + public static uint Compute(uint polynomial, uint seed, byte[] buffer) { + return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length); + } + + private static uint[] InitializeTable(uint polynomial) { + if (polynomial == DefaultPolynomial && defaultTable != null) + return defaultTable; + + var createTable = new uint[256]; + for (var i = 0; i < 256; i++) { + var entry = (uint)i; + for (var j = 0; j < 8; j++) + if ((entry & 1) == 1) + entry = (entry >> 1) ^ polynomial; + else + entry >>= 1; + createTable[i] = entry; + } + + if (polynomial == DefaultPolynomial) + defaultTable = createTable; + + return createTable; + } + + private static uint CalculateHash(uint[] table, uint seed, byte[] buffer, int start, int size) { + var crc = seed; + for (var i = start; i < size - start; i++) + crc = (crc >> 8) ^ table[buffer[i] ^ crc & 0xff]; + return crc; + } + + private static byte[] UInt32ToBigEndianBytes(uint x) => [ + (byte)((x >> 24) & 0xff), + (byte)((x >> 16) & 0xff), + (byte)((x >> 8) & 0xff), + (byte)(x & 0xff) + ]; +} diff --git a/src/MaksIT.LTO.Core/DriverManager.cs b/src/MaksIT.LTO.Core/DriverManager.cs deleted file mode 100644 index 0eabe5e..0000000 --- a/src/MaksIT.LTO.Core/DriverManager.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Diagnostics; - - -namespace MaksIT.LTO.Core; -public class DriverManager { - public static void RestartDriver(string deviceName) { - string script = $@" - $device = Get-PnpDevice -FriendlyName '{deviceName}' - Disable-PnpDevice -InstanceId $device.InstanceId -Confirm:$false - Start-Sleep -Seconds 5 - Enable-PnpDevice -InstanceId $device.InstanceId -Confirm:$false - "; - - ProcessStartInfo psi = new ProcessStartInfo { - FileName = "powershell.exe", - Arguments = $"-NoProfile -ExecutionPolicy Bypass -Command \"{script}\"", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true - }; - - using (Process process = Process.Start(psi)) { - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - - Console.WriteLine(output); - if (!string.IsNullOrEmpty(error)) { - Console.WriteLine($"Error: {error}"); - } - } - } -} diff --git a/src/MaksIT.LTO.Core/Utilities/ChecksumUtility.cs b/src/MaksIT.LTO.Core/Utilities/ChecksumUtility.cs index 861f30c..fa2f675 100644 --- a/src/MaksIT.LTO.Core/Utilities/ChecksumUtility.cs +++ b/src/MaksIT.LTO.Core/Utilities/ChecksumUtility.cs @@ -1,19 +1,16 @@ -using System.IO; -using System.Security.Cryptography; - -namespace MaksIT.LTO.Core.Helpers; +namespace MaksIT.LTO.Core.Helpers; public static class ChecksumUtility { public static string CalculateCRC32Checksum(byte[] data) { using var crc32 = new Crc32(); - byte[] hashBytes = crc32.ComputeHash(data); + var hashBytes = crc32.ComputeHash(data); return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); } public static string CalculateCRC32ChecksumFromFile(string filePath) { using var crc32 = new Crc32(); using var stream = File.OpenRead(filePath); - byte[] hashBytes = crc32.ComputeHash(stream); + var hashBytes = crc32.ComputeHash(stream); return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); } @@ -26,105 +23,23 @@ public static class ChecksumUtility { crc32.TransformBlock(buffer, 0, bytesRead, null, 0); } crc32.TransformFinalBlock(buffer, 0, 0); - byte[] hashBytes = crc32.Hash; - return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); + var hashBytes = crc32.Hash; + return BitConverter.ToString(hashBytes ?? Array.Empty()).Replace("-", "").ToLower(); } public static bool VerifyCRC32Checksum(byte[] data, string expectedChecksum) { - string calculatedChecksum = CalculateCRC32Checksum(data); + var calculatedChecksum = CalculateCRC32Checksum(data); return string.Equals(calculatedChecksum, expectedChecksum, StringComparison.OrdinalIgnoreCase); } public static bool VerifyCRC32ChecksumFromFile(string filePath, string expectedChecksum) { - string calculatedChecksum = CalculateCRC32ChecksumFromFile(filePath); + var calculatedChecksum = CalculateCRC32ChecksumFromFile(filePath); return string.Equals(calculatedChecksum, expectedChecksum, StringComparison.OrdinalIgnoreCase); } public static bool VerifyCRC32ChecksumFromFileInChunks(string filePath, string expectedChecksum, int chunkSize = 8192) { - string calculatedChecksum = CalculateCRC32ChecksumFromFileInChunks(filePath, chunkSize); + var calculatedChecksum = CalculateCRC32ChecksumFromFileInChunks(filePath, chunkSize); return string.Equals(calculatedChecksum, expectedChecksum, StringComparison.OrdinalIgnoreCase); } } -public class Crc32 : HashAlgorithm { - public const uint DefaultPolynomial = 0xedb88320; - public const uint DefaultSeed = 0xffffffff; - - private static uint[]? defaultTable; - - private readonly uint seed; - private readonly uint[] table; - private uint hash; - - public Crc32() - : this(DefaultPolynomial, DefaultSeed) { - } - - public Crc32(uint polynomial, uint seed) { - table = InitializeTable(polynomial); - this.seed = hash = seed; - } - - public override void Initialize() { - hash = seed; - } - - protected override void HashCore(byte[] buffer, int start, int length) { - hash = CalculateHash(table, hash, buffer, start, length); - } - - protected override byte[] HashFinal() { - var hashBuffer = UInt32ToBigEndianBytes(~hash); - HashValue = hashBuffer; - return hashBuffer; - } - - public override int HashSize => 32; - - public static uint Compute(byte[] buffer) { - return Compute(DefaultPolynomial, DefaultSeed, buffer); - } - - public static uint Compute(uint seed, byte[] buffer) { - return Compute(DefaultPolynomial, seed, buffer); - } - - public static uint Compute(uint polynomial, uint seed, byte[] buffer) { - return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length); - } - - private static uint[] InitializeTable(uint polynomial) { - if (polynomial == DefaultPolynomial && defaultTable != null) - return defaultTable; - - var createTable = new uint[256]; - for (var i = 0; i < 256; i++) { - var entry = (uint)i; - for (var j = 0; j < 8; j++) - if ((entry & 1) == 1) - entry = (entry >> 1) ^ polynomial; - else - entry >>= 1; - createTable[i] = entry; - } - - if (polynomial == DefaultPolynomial) - defaultTable = createTable; - - return createTable; - } - - private static uint CalculateHash(uint[] table, uint seed, byte[] buffer, int start, int size) { - var crc = seed; - for (var i = start; i < size - start; i++) - crc = (crc >> 8) ^ table[buffer[i] ^ crc & 0xff]; - return crc; - } - - private static byte[] UInt32ToBigEndianBytes(uint x) => [ - (byte)((x >> 24) & 0xff), - (byte)((x >> 16) & 0xff), - (byte)((x >> 8) & 0xff), - (byte)(x & 0xff) - ]; -} diff --git a/src/MaksIT.LTO.Core/Utilities/PaddingUtility.cs b/src/MaksIT.LTO.Core/Utilities/PaddingUtility.cs new file mode 100644 index 0000000..81a932f --- /dev/null +++ b/src/MaksIT.LTO.Core/Utilities/PaddingUtility.cs @@ -0,0 +1,38 @@ +namespace MaksIT.LTO.Core.Utilities; + +public static class PaddingUtility { + + private const byte _specialByte = 0x80; + + public static byte[] AddPadding(byte[] data, int blockSize) { + var paddingSize = blockSize - (data.Length % blockSize); + if (paddingSize == blockSize) { + paddingSize = 0; // no padding needed if already aligned + } + + var paddedData = new byte[data.Length + paddingSize]; + Array.Copy(data, paddedData, data.Length); + + // fill the padding bytes with specialBytes + for (var i = data.Length; i < paddedData.Length; i++) { + paddedData[i] = _specialByte; + } + + return paddedData; + } + + public static byte[] RemovePadding(byte[] paddedData, int blockSize) { + var originalLength = paddedData.Length; + + // find the original length by checking for the padding byte 0x80 + while (originalLength > 0 && paddedData[originalLength - 1] == _specialByte) { + originalLength--; + } + + // create a new array to hold the unpadded data + var unpaddedData = new byte[originalLength]; + Array.Copy(paddedData, unpaddedData, originalLength); + + return unpaddedData; + } +} \ No newline at end of file