From 3d4ebd4314b4711c142cecf41f67475092c54237 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 24 Mar 2026 09:05:23 -0400 Subject: [PATCH] Migrate some Transform tasks to extensions appropriately --- .../SabreTools.IO.Extensions.Test.csproj | 1 + .../StreamExtensionsTests.cs | 141 +++++++++++ SabreTools.IO.Extensions/StreamExtensions.cs | 235 ++++++++++++++++++ SabreTools.IO.Test/SabreTools.IO.Test.csproj | 1 - SabreTools.IO.Test/TransformTests.cs | 141 ----------- SabreTools.IO/Transform.cs | 226 ----------------- 6 files changed, 377 insertions(+), 368 deletions(-) diff --git a/SabreTools.IO.Extensions.Test/SabreTools.IO.Extensions.Test.csproj b/SabreTools.IO.Extensions.Test/SabreTools.IO.Extensions.Test.csproj index d5dff60..a72b023 100644 --- a/SabreTools.IO.Extensions.Test/SabreTools.IO.Extensions.Test.csproj +++ b/SabreTools.IO.Extensions.Test/SabreTools.IO.Extensions.Test.csproj @@ -28,6 +28,7 @@ + \ No newline at end of file diff --git a/SabreTools.IO.Extensions.Test/StreamExtensionsTests.cs b/SabreTools.IO.Extensions.Test/StreamExtensionsTests.cs index ae37555..bfbdb47 100644 --- a/SabreTools.IO.Extensions.Test/StreamExtensionsTests.cs +++ b/SabreTools.IO.Extensions.Test/StreamExtensionsTests.cs @@ -1,9 +1,11 @@ using System; using System.IO; using System.Text; +using SabreTools.Matching; using Xunit; #pragma warning disable IDE0017 // Object initialization can be simplified +#pragma warning disable IDE0230 // Use UTF-8 string literal namespace SabreTools.IO.Extensions.Test { public class StreamExtensionsTests @@ -137,6 +139,80 @@ namespace SabreTools.IO.Extensions.Test #endregion + #region InterleaveWith + + [Fact] + public void Interleave_EvenNotExists_False() + { + string even = "NOT A REAL PATH"; + string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); + string output = Guid.NewGuid().ToString(); + + bool actual = even.InterleaveWith(odd, output, 1); + Assert.False(actual); + } + + [Fact] + public void Interleave_OddNotExists_False() + { + string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); + string odd = "NOT A REAL PATH"; + string output = Guid.NewGuid().ToString(); + + bool actual = even.InterleaveWith(odd, output, 1); + Assert.False(actual); + } + + [Fact] + public void Interleave_InvalidValue_False() + { + string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); + string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); + string output = Guid.NewGuid().ToString(); + + bool actual = even.InterleaveWith(odd, output, -1); + Assert.False(actual); + } + + [Theory] + [InlineData(1, "TThhiiss ddooeessnn''tt mmaattcchh aannyytthhiinngg")] + [InlineData(2, "ThThisis d doeoesnsn't't m matatchch a anynyththiningg")] + [InlineData(4, "ThisThis doe doesn'tsn't mat match ach anythnythinging")] + [InlineData(8, "This doeThis doesn't matsn't match anythch anythinging")] + public void Interleave_SameLength_True(int blockSize, string expected) + { + string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); + string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); + string output = Guid.NewGuid().ToString(); + + bool actual = even.InterleaveWith(odd, output, blockSize); + Assert.True(actual); + + string text = File.ReadAllText(output); + Assert.Equal(expected, text); + + File.Delete(output); + } + + [Fact] + public void Interleave_DifferentLength_True() + { + string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); + string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "file-to-compress.bin"); + + string output = Guid.NewGuid().ToString(); + + bool actual = even.InterleaveWith(odd, output, 1); + Assert.True(actual); + + string text = File.ReadAllText(output); + Assert.Equal("TThhiiss diose sjnu'stt maa tfcihl ea ntyhtahti nhgas a known set of hashes to make sure that everything with hashing is still working as anticipated.", text); + + File.Delete(output); + } + + #endregion + #region ReadFrom [Theory] @@ -475,6 +551,71 @@ namespace SabreTools.IO.Extensions.Test #endregion + #region Swap + + [Fact] + public void Swap_EmptyFileName_False() + { + string input = string.Empty; + string output = string.Empty; + bool actual = input.Swap(output, SwapOperation.Byteswap); + Assert.False(actual); + } + + [Fact] + public void Swap_InvalidFile_False() + { + string input = "INVALID"; + string output = string.Empty; + bool actual = input.Swap(output, SwapOperation.Byteswap); + Assert.False(actual); + } + + [Fact] + public void Swap_InvalidType_False() + { + string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); + string output = Guid.NewGuid().ToString(); + + bool actual = input.Swap(output, (SwapOperation)int.MaxValue); + Assert.False(actual); + } + + [Fact] + public void Swap_Valid_True() + { + string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); + string output = Guid.NewGuid().ToString(); + + // Bitswap + bool actual = input.Swap(output, SwapOperation.Bitswap); + Assert.True(actual); + byte[] actualBytes = File.ReadAllBytes(output); + Assert.True(new byte[] { 0x2A, 0x16, 0x96, 0xCE, 0x04, 0x26, 0xF6, 0xA6, 0xCE, 0x76, 0xE4, 0x2E, 0x04, 0xB6, 0x86, 0x2E, 0xC6, 0x16, 0x04, 0x86, 0x76, 0x9E, 0x2E, 0x16, 0x96, 0x76, 0xE6 }.EqualsExactly(actualBytes)); + + // Byteswap + actual = input.Swap(output, SwapOperation.Byteswap); + Assert.True(actual); + actualBytes = File.ReadAllBytes(output); + Assert.True(new byte[] { 0x68, 0x54, 0x73, 0x69, 0x64, 0x20, 0x65, 0x6F, 0x6E, 0x73, 0x74, 0x27, 0x6D, 0x20, 0x74, 0x61, 0x68, 0x63, 0x61, 0x20, 0x79, 0x6E, 0x68, 0x74, 0x6E, 0x69, 0x67 }.EqualsExactly(actualBytes)); + + // Wordswap + actual = input.Swap(output, SwapOperation.Wordswap); + Assert.True(actual); + actualBytes = File.ReadAllBytes(output); + Assert.True(new byte[] { 0x69, 0x73, 0x54, 0x68, 0x6F, 0x65, 0x20, 0x64, 0x27, 0x74, 0x73, 0x6E, 0x61, 0x74, 0x20, 0x6D, 0x20, 0x61, 0x63, 0x68, 0x74, 0x68, 0x6E, 0x79, 0x69, 0x6E, 0x67 }.EqualsExactly(actualBytes)); + + // WordByteswap + actual = input.Swap(output, SwapOperation.WordByteswap); + Assert.True(actual); + actualBytes = File.ReadAllBytes(output); + Assert.True(new byte[] { 0x73, 0x69, 0x68, 0x54, 0x65, 0x6F, 0x64, 0x20, 0x74, 0x27, 0x6E, 0x73, 0x74, 0x61, 0x6D, 0x20, 0x61, 0x20, 0x68, 0x63, 0x68, 0x74, 0x79, 0x6E, 0x69, 0x6E, 0x67 }.EqualsExactly(actualBytes)); + + File.Delete(output); + } + + #endregion + /// /// Represents a hidden non-seekable stream /// diff --git a/SabreTools.IO.Extensions/StreamExtensions.cs b/SabreTools.IO.Extensions/StreamExtensions.cs index bc7597e..82c9687 100644 --- a/SabreTools.IO.Extensions/StreamExtensions.cs +++ b/SabreTools.IO.Extensions/StreamExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using SabreTools.Numerics.Extensions; @@ -81,6 +82,88 @@ namespace SabreTools.IO.Extensions } } + #region InterleaveWith + + /// + /// Interleave two files into a single output + /// + /// First file to interleave + /// Second file to interleave + /// Path to the output file + /// Number of bytes read before switching input + /// True if the files were interleaved successfully, false otherwise + public static bool InterleaveWith(this string even, string odd, string output, int blockSize) + { + // If either file does not exist + if (!File.Exists(even) || !File.Exists(odd)) + return false; + + try + { + // Get the input and output streams + using var evenStream = File.Open(even, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var oddStream = File.Open(odd, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var outputStream = File.Open(output, FileMode.Create, FileAccess.Write, FileShare.None); + + // Interleave the streams + return evenStream.InterleaveWith(oddStream, outputStream, blockSize); + } + catch + { + // Absorb all errors for now + return false; + } + } + + /// + /// Interleave two streams into a single output + /// + /// First stream to interleave + /// Second stream to interleave + /// Output stream + /// Number of bytes read before switching input + /// A filled stream on success, null otherwise + /// + /// Thrown if is non-positive. + /// + public static bool InterleaveWith(this Stream even, Stream odd, Stream output, int blockSize) + { + // If either stream is unreadable + if (!even.CanRead || !odd.CanRead) + return false; + + // If the output is unwritable + if (!output.CanWrite) + return false; + + // If the block size is invalid + if (blockSize <= 0) + throw new ArgumentOutOfRangeException(nameof(blockSize)); + + try + { + // Alternate between inputs during reading + bool useEven = true; + while (even.Position < even.Length || odd.Position < odd.Length) + { + byte[] read = new byte[blockSize]; + int actual = (useEven ? even : odd).Read(read, 0, blockSize); + output.Write(read, 0, actual); + output.Flush(); + useEven = !useEven; + } + + return true; + } + catch + { + // Absorb all errors for now + return false; + } + } + + #endregion + /// /// Read a number of bytes from an offset in a stream, if possible /// @@ -144,6 +227,8 @@ namespace SabreTools.IO.Extensions return data.ReadStringsFrom(charLimit); } + #region SeekIfPossible + /// /// Seek to a specific point in the stream, if possible /// @@ -182,6 +267,8 @@ namespace SabreTools.IO.Extensions } } + #endregion + /// /// Check if a segment is valid in the stream /// @@ -200,5 +287,153 @@ namespace SabreTools.IO.Extensions return true; } + + #region Swap + + /// + /// Transform an input file using the given rule + /// + /// Input file name + /// Output file name + /// Transform operation to carry out + /// True if the file was transformed properly, false otherwise + public static bool Swap(this string input, string output, SwapOperation operation) + { + // If the file does not exist + if (!File.Exists(input)) + return false; + + // Create the output directory if it doesn't already + string? outputDirectory = Path.GetDirectoryName(Path.GetFullPath(output)); + if (outputDirectory is not null && !Directory.Exists(outputDirectory)) + Directory.CreateDirectory(outputDirectory); + + try + { + // Get the input and output streams + using var inputStream = File.Open(input, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var outputStream = File.Open(output, FileMode.Create, FileAccess.Write, FileShare.None); + + // Transform the stream + return Swap(inputStream, outputStream, operation); + } + catch + { + // Absorb all errors for now + return false; + } + } + + /// + /// Transform an input stream using the given rule + /// + /// Input stream + /// Output stream + /// Transform operation to carry out + /// True if the file was transformed properly, false otherwise + /// + /// Thrown if is not a recognized value. + /// + public static bool Swap(this Stream input, Stream output, SwapOperation operation) + { + // If the input is unreadable + if (!input.CanRead) + return false; + + // If the output is unwritable + if (!output.CanWrite) + return false; + + // If the operation is not defined + if (!Enum.IsDefined(typeof(SwapOperation), operation)) + return false; + + try + { + // Determine the cutoff boundary for the operation + long endBoundary = operation switch + { + SwapOperation.Bitswap => input.Length, + SwapOperation.Byteswap => input.Length - (input.Length % 2), + SwapOperation.Wordswap => input.Length - (input.Length % 4), + SwapOperation.WordByteswap => input.Length - (input.Length % 4), + _ => throw new ArgumentOutOfRangeException(nameof(operation)), + }; + + // Loop over the input and process in blocks + byte[] buffer = new byte[4]; + int pos = 0; + while (input.Position < endBoundary) + { + byte b = (byte)input.ReadByte(); + switch (operation) + { + case SwapOperation.Bitswap: + uint r = b; + int s = 7; + for (b >>= 1; b != 0; b >>= 1) + { + r <<= 1; + r |= (byte)(b & 1); + s--; + } + + r <<= s; + buffer[pos] = (byte)r; + break; + case SwapOperation.Byteswap: + if (pos % 2 == 1) + buffer[pos - 1] = b; + else + buffer[pos + 1] = b; + + break; + case SwapOperation.Wordswap: + buffer[(pos + 2) % 4] = b; + break; + case SwapOperation.WordByteswap: + buffer[3 - pos] = b; + break; + default: + buffer[pos] = b; + break; + } + + // Set the buffer position to default write to + pos = (pos + 1) % 4; + + // If the buffer pointer has been reset + if (pos == 0) + { + output.Write(buffer, 0, buffer.Length); + output.Flush(); + buffer = new byte[4]; + } + } + + // If there's anything more in the buffer + if (pos > 0) + output.Write(buffer, 0, pos); + + // If the stream still has data + if (input.Position < input.Length) + { + int remaining = (int)(input.Length - input.Position); + byte[] bytes = new byte[remaining]; + int read = input.Read(bytes, 0, remaining); + output.Write(bytes, 0, read); + output.Flush(); + } + + return true; + } + catch + { + // Absorb all errors for now + return false; + } + } + + #endregion } } diff --git a/SabreTools.IO.Test/SabreTools.IO.Test.csproj b/SabreTools.IO.Test/SabreTools.IO.Test.csproj index 94523a9..43276a4 100644 --- a/SabreTools.IO.Test/SabreTools.IO.Test.csproj +++ b/SabreTools.IO.Test/SabreTools.IO.Test.csproj @@ -30,7 +30,6 @@ - \ No newline at end of file diff --git a/SabreTools.IO.Test/TransformTests.cs b/SabreTools.IO.Test/TransformTests.cs index fd8c63b..308dd8e 100644 --- a/SabreTools.IO.Test/TransformTests.cs +++ b/SabreTools.IO.Test/TransformTests.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.IO; -using SabreTools.Matching; using Xunit; -#pragma warning disable IDE0230 // Use UTF-8 string literal namespace SabreTools.IO.Test { public class TransformTests @@ -48,80 +46,6 @@ namespace SabreTools.IO.Test #endregion - #region Interleave - - [Fact] - public void Interleave_EvenNotExists_False() - { - string even = "NOT A REAL PATH"; - string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); - string output = Guid.NewGuid().ToString(); - - bool actual = Transform.Interleave(even, odd, output, 1); - Assert.False(actual); - } - - [Fact] - public void Interleave_OddNotExists_False() - { - string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); - string odd = "NOT A REAL PATH"; - string output = Guid.NewGuid().ToString(); - - bool actual = Transform.Interleave(even, odd, output, 1); - Assert.False(actual); - } - - [Fact] - public void Interleave_InvalidValue_False() - { - string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); - string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); - string output = Guid.NewGuid().ToString(); - - bool actual = Transform.Interleave(even, odd, output, -1); - Assert.False(actual); - } - - [Theory] - [InlineData(1, "TThhiiss ddooeessnn''tt mmaattcchh aannyytthhiinngg")] - [InlineData(2, "ThThisis d doeoesnsn't't m matatchch a anynyththiningg")] - [InlineData(4, "ThisThis doe doesn'tsn't mat match ach anythnythinging")] - [InlineData(8, "This doeThis doesn't matsn't match anythch anythinging")] - public void Interleave_SameLength_True(int blockSize, string expected) - { - string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); - string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); - string output = Guid.NewGuid().ToString(); - - bool actual = Transform.Interleave(even, odd, output, blockSize); - Assert.True(actual); - - string text = File.ReadAllText(output); - Assert.Equal(expected, text); - - File.Delete(output); - } - - [Fact] - public void Interleave_DifferentLength_True() - { - string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); - string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "file-to-compress.bin"); - - string output = Guid.NewGuid().ToString(); - - bool actual = Transform.Interleave(even, odd, output, 1); - Assert.True(actual); - - string text = File.ReadAllText(output); - Assert.Equal("TThhiiss diose sjnu'stt maa tfcihl ea ntyhtahti nhgas a known set of hashes to make sure that everything with hashing is still working as anticipated.", text); - - File.Delete(output); - } - - #endregion - #region BlockSplit [Fact] @@ -235,70 +159,5 @@ namespace SabreTools.IO.Test } #endregion - - #region Swap - - [Fact] - public void Swap_EmptyFileName_False() - { - string input = string.Empty; - string output = string.Empty; - bool actual = Transform.Swap(input, output, SwapOperation.Byteswap); - Assert.False(actual); - } - - [Fact] - public void Swap_InvalidFile_False() - { - string input = "INVALID"; - string output = string.Empty; - bool actual = Transform.Swap(input, output, SwapOperation.Byteswap); - Assert.False(actual); - } - - [Fact] - public void Swap_InvalidType_False() - { - string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); - string output = Guid.NewGuid().ToString(); - - bool actual = Transform.Swap(input, output, (SwapOperation)int.MaxValue); - Assert.False(actual); - } - - [Fact] - public void Swap_Valid_True() - { - string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); - string output = Guid.NewGuid().ToString(); - - // Bitswap - bool actual = Transform.Swap(input, output, SwapOperation.Bitswap); - Assert.True(actual); - byte[] actualBytes = File.ReadAllBytes(output); - Assert.True(new byte[] { 0x2A, 0x16, 0x96, 0xCE, 0x04, 0x26, 0xF6, 0xA6, 0xCE, 0x76, 0xE4, 0x2E, 0x04, 0xB6, 0x86, 0x2E, 0xC6, 0x16, 0x04, 0x86, 0x76, 0x9E, 0x2E, 0x16, 0x96, 0x76, 0xE6 }.EqualsExactly(actualBytes)); - - // Byteswap - actual = Transform.Swap(input, output, SwapOperation.Byteswap); - Assert.True(actual); - actualBytes = File.ReadAllBytes(output); - Assert.True(new byte[] { 0x68, 0x54, 0x73, 0x69, 0x64, 0x20, 0x65, 0x6F, 0x6E, 0x73, 0x74, 0x27, 0x6D, 0x20, 0x74, 0x61, 0x68, 0x63, 0x61, 0x20, 0x79, 0x6E, 0x68, 0x74, 0x6E, 0x69, 0x67 }.EqualsExactly(actualBytes)); - - // Wordswap - actual = Transform.Swap(input, output, SwapOperation.Wordswap); - Assert.True(actual); - actualBytes = File.ReadAllBytes(output); - Assert.True(new byte[] { 0x69, 0x73, 0x54, 0x68, 0x6F, 0x65, 0x20, 0x64, 0x27, 0x74, 0x73, 0x6E, 0x61, 0x74, 0x20, 0x6D, 0x20, 0x61, 0x63, 0x68, 0x74, 0x68, 0x6E, 0x79, 0x69, 0x6E, 0x67 }.EqualsExactly(actualBytes)); - - // WordByteswap - actual = Transform.Swap(input, output, SwapOperation.WordByteswap); - Assert.True(actual); - actualBytes = File.ReadAllBytes(output); - Assert.True(new byte[] { 0x73, 0x69, 0x68, 0x54, 0x65, 0x6F, 0x64, 0x20, 0x74, 0x27, 0x6E, 0x73, 0x74, 0x61, 0x6D, 0x20, 0x61, 0x20, 0x68, 0x63, 0x68, 0x74, 0x79, 0x6E, 0x69, 0x6E, 0x67 }.EqualsExactly(actualBytes)); - - File.Delete(output); - } - - #endregion } } diff --git a/SabreTools.IO/Transform.cs b/SabreTools.IO/Transform.cs index 324b670..f5c108b 100644 --- a/SabreTools.IO/Transform.cs +++ b/SabreTools.IO/Transform.cs @@ -63,84 +63,6 @@ namespace SabreTools.IO } } - /// - /// Interleave two files into a single output - /// - /// First file to interleave - /// Second file to interleave - /// Path to the output file - /// Number of bytes read before switching input - /// True if the files were interleaved successfully, false otherwise - public static bool Interleave(string even, string odd, string output, int blockSize) - { - // If either file does not exist - if (!File.Exists(even) || !File.Exists(odd)) - return false; - - try - { - // Get the input and output streams - using var evenStream = File.Open(even, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var oddStream = File.Open(odd, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var outputStream = File.Open(output, FileMode.Create, FileAccess.Write, FileShare.None); - - // Interleave the streams - return Interleave(evenStream, oddStream, outputStream, blockSize); - } - catch - { - // Absorb all errors for now - return false; - } - } - - /// - /// Interleave two streams into a single output - /// - /// First stream to interleave - /// Second stream to interleave - /// Output stream - /// Number of bytes read before switching input - /// A filled stream on success, null otherwise - /// - /// Thrown if is non-positive. - /// - public static bool Interleave(Stream even, Stream odd, Stream output, int blockSize) - { - // If either stream is unreadable - if (!even.CanRead || !odd.CanRead) - return false; - - // If the output is unwritable - if (!output.CanWrite) - return false; - - // If the block size is invalid - if (blockSize <= 0) - throw new ArgumentOutOfRangeException(nameof(blockSize)); - - try - { - // Alternate between inputs during reading - bool useEven = true; - while (even.Position < even.Length || odd.Position < odd.Length) - { - byte[] read = new byte[blockSize]; - int actual = (useEven ? even : odd).Read(read, 0, blockSize); - output.Write(read, 0, actual); - output.Flush(); - useEven = !useEven; - } - - return true; - } - catch - { - // Absorb all errors for now - return false; - } - } - #endregion #region Split @@ -309,153 +231,5 @@ namespace SabreTools.IO } #endregion - - #region Swap - - /// - /// Transform an input file using the given rule - /// - /// Input file name - /// Output file name - /// Transform operation to carry out - /// True if the file was transformed properly, false otherwise - public static bool Swap(string input, string output, SwapOperation operation) - { - // If the file does not exist - if (!File.Exists(input)) - return false; - - // Create the output directory if it doesn't already - string? outputDirectory = Path.GetDirectoryName(Path.GetFullPath(output)); - if (outputDirectory is not null && !Directory.Exists(outputDirectory)) - Directory.CreateDirectory(outputDirectory); - - try - { - // Get the input and output streams - using var inputStream = File.Open(input, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var outputStream = File.Open(output, FileMode.Create, FileAccess.Write, FileShare.None); - - // Transform the stream - return Swap(inputStream, outputStream, operation); - } - catch - { - // Absorb all errors for now - return false; - } - } - - /// - /// Transform an input stream using the given rule - /// - /// Input stream - /// Output stream - /// Transform operation to carry out - /// True if the file was transformed properly, false otherwise - /// - /// Thrown if is not a recognized value. - /// - public static bool Swap(Stream input, Stream output, SwapOperation operation) - { - // If the input is unreadable - if (!input.CanRead) - return false; - - // If the output is unwritable - if (!output.CanWrite) - return false; - - // If the operation is not defined - if (!Enum.IsDefined(typeof(SwapOperation), operation)) - return false; - - try - { - // Determine the cutoff boundary for the operation - long endBoundary = operation switch - { - SwapOperation.Bitswap => input.Length, - SwapOperation.Byteswap => input.Length - (input.Length % 2), - SwapOperation.Wordswap => input.Length - (input.Length % 4), - SwapOperation.WordByteswap => input.Length - (input.Length % 4), - _ => throw new ArgumentOutOfRangeException(nameof(operation)), - }; - - // Loop over the input and process in blocks - byte[] buffer = new byte[4]; - int pos = 0; - while (input.Position < endBoundary) - { - byte b = (byte)input.ReadByte(); - switch (operation) - { - case SwapOperation.Bitswap: - uint r = b; - int s = 7; - for (b >>= 1; b != 0; b >>= 1) - { - r <<= 1; - r |= (byte)(b & 1); - s--; - } - - r <<= s; - buffer[pos] = (byte)r; - break; - case SwapOperation.Byteswap: - if (pos % 2 == 1) - buffer[pos - 1] = b; - else - buffer[pos + 1] = b; - - break; - case SwapOperation.Wordswap: - buffer[(pos + 2) % 4] = b; - break; - case SwapOperation.WordByteswap: - buffer[3 - pos] = b; - break; - default: - buffer[pos] = b; - break; - } - - // Set the buffer position to default write to - pos = (pos + 1) % 4; - - // If the buffer pointer has been reset - if (pos == 0) - { - output.Write(buffer, 0, buffer.Length); - output.Flush(); - buffer = new byte[4]; - } - } - - // If there's anything more in the buffer - if (pos > 0) - output.Write(buffer, 0, pos); - - // If the stream still has data - if (input.Position < input.Length) - { - int remaining = (int)(input.Length - input.Position); - byte[] bytes = new byte[remaining]; - int read = input.Read(bytes, 0, remaining); - output.Write(bytes, 0, read); - output.Flush(); - } - - return true; - } - catch - { - // Absorb all errors for now - return false; - } - } - - #endregion } }