diff --git a/src/SharpCompress/Common/CompressionType.cs b/src/SharpCompress/Common/CompressionType.cs
index 69aa0637..76ada667 100644
--- a/src/SharpCompress/Common/CompressionType.cs
+++ b/src/SharpCompress/Common/CompressionType.cs
@@ -28,4 +28,5 @@ public enum CompressionType
Squashed,
Crushed,
Distilled,
+ ZStandard,
}
diff --git a/src/SharpCompress/Common/Zip/ZipCompressionMethod.cs b/src/SharpCompress/Common/Zip/ZipCompressionMethod.cs
index 834197c0..33eaf2b7 100644
--- a/src/SharpCompress/Common/Zip/ZipCompressionMethod.cs
+++ b/src/SharpCompress/Common/Zip/ZipCompressionMethod.cs
@@ -13,7 +13,7 @@ internal enum ZipCompressionMethod
Deflate64 = 9,
BZip2 = 12,
LZMA = 14,
- ZStd = 93,
+ ZStandard = 93,
Xz = 95,
PPMd = 98,
WinzipAes = 0x63, //http://www.winzip.com/aes_info.htm
diff --git a/src/SharpCompress/Common/Zip/ZipEntry.cs b/src/SharpCompress/Common/Zip/ZipEntry.cs
index 10cc1ba2..19f9961b 100644
--- a/src/SharpCompress/Common/Zip/ZipEntry.cs
+++ b/src/SharpCompress/Common/Zip/ZipEntry.cs
@@ -46,6 +46,7 @@ public class ZipEntry : Entry
ZipCompressionMethod.Reduce3 => CompressionType.Reduce3,
ZipCompressionMethod.Reduce4 => CompressionType.Reduce4,
ZipCompressionMethod.Explode => CompressionType.Explode,
+ ZipCompressionMethod.ZStandard => CompressionType.ZStandard,
_ => CompressionType.Unknown,
};
diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs
index b6be4784..f8fd6cd5 100644
--- a/src/SharpCompress/Common/Zip/ZipFilePart.cs
+++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs
@@ -152,7 +152,7 @@ internal abstract class ZipFilePart : FilePart
{
return new XZStream(stream);
}
- case ZipCompressionMethod.ZStd:
+ case ZipCompressionMethod.ZStandard:
{
return new DecompressionStream(stream);
}
diff --git a/src/SharpCompress/Writers/GZip/GZipWriter.cs b/src/SharpCompress/Writers/GZip/GZipWriter.cs
index 95c69a3e..4fbf1bb9 100644
--- a/src/SharpCompress/Writers/GZip/GZipWriter.cs
+++ b/src/SharpCompress/Writers/GZip/GZipWriter.cs
@@ -22,7 +22,7 @@ public sealed class GZipWriter : AbstractWriter
new GZipStream(
destination,
CompressionMode.Compress,
- options?.CompressionLevel ?? CompressionLevel.Default,
+ (CompressionLevel)(options?.CompressionLevel ?? (int)CompressionLevel.Default),
WriterOptions.ArchiveEncoding.GetEncoding()
)
);
diff --git a/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs b/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs
index 35e19faf..0b16b837 100644
--- a/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs
+++ b/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs
@@ -1,24 +1,18 @@
using SharpCompress.Common;
-using SharpCompress.Compressors.Deflate;
+using D = SharpCompress.Compressors.Deflate;
namespace SharpCompress.Writers.GZip;
public class GZipWriterOptions : WriterOptions
{
public GZipWriterOptions()
- : base(CompressionType.GZip) { }
+ : base(CompressionType.GZip, (int)(D.CompressionLevel.Default)) { }
internal GZipWriterOptions(WriterOptions options)
- : base(options.CompressionType)
+ : base(options.CompressionType, (int)(D.CompressionLevel.Default))
{
LeaveStreamOpen = options.LeaveStreamOpen;
ArchiveEncoding = options.ArchiveEncoding;
-
- if (options is GZipWriterOptions writerOptions)
- {
- CompressionLevel = writerOptions.CompressionLevel;
- }
+ CompressionLevel = options.CompressionLevel;
}
-
- public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Default;
}
diff --git a/src/SharpCompress/Writers/WriterOptions.cs b/src/SharpCompress/Writers/WriterOptions.cs
index e71f86f6..972cc83b 100644
--- a/src/SharpCompress/Writers/WriterOptions.cs
+++ b/src/SharpCompress/Writers/WriterOptions.cs
@@ -1,13 +1,41 @@
-using SharpCompress.Common;
+using SharpCompress.Common;
+using D = SharpCompress.Compressors.Deflate;
namespace SharpCompress.Writers;
public class WriterOptions : OptionsBase
{
- public WriterOptions(CompressionType compressionType) => CompressionType = compressionType;
+ public WriterOptions(CompressionType compressionType)
+ {
+ CompressionType = compressionType;
+ CompressionLevel = compressionType switch
+ {
+ CompressionType.ZStandard => 3,
+ CompressionType.Deflate => (int)D.CompressionLevel.Default,
+ CompressionType.Deflate64 => (int)D.CompressionLevel.Default,
+ CompressionType.GZip => (int)D.CompressionLevel.Default,
+ _ => 0
+ };
+ }
+
+ public WriterOptions(CompressionType compressionType, int compressionLevel)
+ {
+ CompressionType = compressionType;
+ CompressionLevel = compressionLevel;
+ }
public CompressionType CompressionType { get; set; }
+ ///
+ /// The compression level to be used when the compression type supports variable levels.
+ /// Valid ranges depend on the compression algorithm:
+ /// - Deflate/GZip: 0-9 (0=no compression, 6=default, 9=best compression)
+ /// - ZStandard: 1-22 (1=fastest, 3=default, 22=best compression)
+ /// Note: BZip2 and LZMA do not support compression levels in this implementation.
+ /// Defaults are set automatically based on compression type in the constructor.
+ ///
+ public int CompressionLevel { get; set; }
+
public static implicit operator WriterOptions(CompressionType compressionType) =>
new(compressionType);
}
diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs
index 82def7a4..b03f72e1 100644
--- a/src/SharpCompress/Writers/Zip/ZipWriter.cs
+++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs
@@ -18,7 +18,7 @@ namespace SharpCompress.Writers.Zip;
public class ZipWriter : AbstractWriter
{
private readonly CompressionType compressionType;
- private readonly CompressionLevel compressionLevel;
+ private readonly int compressionLevel;
private readonly List entries = new();
private readonly string zipComment;
private long streamPosition;
@@ -36,7 +36,7 @@ public class ZipWriter : AbstractWriter
}
compressionType = zipWriterOptions.CompressionType;
- compressionLevel = zipWriterOptions.DeflateCompressionLevel;
+ compressionLevel = zipWriterOptions.CompressionLevel;
if (WriterOptions.LeaveStreamOpen)
{
@@ -69,6 +69,7 @@ public class ZipWriter : AbstractWriter
CompressionType.BZip2 => ZipCompressionMethod.BZip2,
CompressionType.LZMA => ZipCompressionMethod.LZMA,
CompressionType.PPMd => ZipCompressionMethod.PPMd,
+ CompressionType.ZStandard => ZipCompressionMethod.ZStandard,
_ => throw new InvalidFormatException("Invalid compression method: " + compressionType),
};
@@ -117,7 +118,7 @@ public class ZipWriter : AbstractWriter
OutputStream.NotNull(),
entry,
compression,
- options.DeflateCompressionLevel ?? compressionLevel
+ options.CompressionLevel ?? compressionLevel
);
}
@@ -312,7 +313,7 @@ public class ZipWriter : AbstractWriter
private readonly Stream writeStream;
private readonly ZipWriter writer;
private readonly ZipCompressionMethod zipCompressionMethod;
- private readonly CompressionLevel compressionLevel;
+ private readonly int compressionLevel;
private SharpCompressStream? counting;
private ulong decompressed;
@@ -325,7 +326,7 @@ public class ZipWriter : AbstractWriter
Stream originalStream,
ZipCentralDirectoryEntry entry,
ZipCompressionMethod zipCompressionMethod,
- CompressionLevel compressionLevel
+ int compressionLevel
)
{
this.writer = writer;
@@ -363,7 +364,7 @@ public class ZipWriter : AbstractWriter
}
case ZipCompressionMethod.Deflate:
{
- return new DeflateStream(counting, CompressionMode.Compress, compressionLevel);
+ return new DeflateStream(counting, CompressionMode.Compress, (CompressionLevel)compressionLevel);
}
case ZipCompressionMethod.BZip2:
{
@@ -389,6 +390,10 @@ public class ZipWriter : AbstractWriter
counting.Write(writer.PpmdProperties.Properties, 0, 2);
return new PpmdStream(writer.PpmdProperties, counting, true);
}
+ case ZipCompressionMethod.ZStandard:
+ {
+ return new ZstdSharp.CompressionStream(counting, (int)writer.WriterOptions.CompressionLevel);
+ }
default:
{
throw new NotSupportedException("CompressionMethod: " + zipCompressionMethod);
diff --git a/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs b/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs
index a718f3b5..c40306b7 100644
--- a/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs
+++ b/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs
@@ -9,9 +9,29 @@ public class ZipWriterEntryOptions
public CompressionType? CompressionType { get; set; }
///
- /// When CompressionType.Deflate is used, this property is referenced. Defaults to CompressionLevel.Default.
+ /// The compression level to be used when the compression type supports variable levels.
+ /// Valid ranges depend on the compression algorithm:
+ /// - Deflate/GZip: 0-9 (0=no compression, 6=default, 9=best compression)
+ /// - ZStandard: 1-22 (1=fastest, 3=default, 22=best compression)
+ /// When null, uses the archive's default compression level for the specified compression type.
+ /// Note: BZip2 and LZMA do not support compression levels in this implementation.
///
- public CompressionLevel? DeflateCompressionLevel { get; set; }
+ public int? CompressionLevel { get; set; }
+
+ ///
+ /// When CompressionType.Deflate is used, this property is referenced.
+ /// Valid range: 0-9 (0=no compression, 6=default, 9=best compression).
+ /// When null, uses the archive's default compression level.
+ ///
+ ///
+ /// This property is deprecated. Use instead.
+ ///
+ [Obsolete("Use CompressionLevel property instead. This property will be removed in a future version.")]
+ public CompressionLevel? DeflateCompressionLevel
+ {
+ get => CompressionLevel.HasValue ? (CompressionLevel)Math.Min(CompressionLevel.Value, 9) : null;
+ set => CompressionLevel = value.HasValue ? (int)value.Value : null;
+ }
public string? EntryComment { get; set; }
diff --git a/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs b/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs
index fcbc9db3..a2516d21 100644
--- a/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs
+++ b/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs
@@ -1,31 +1,66 @@
+using System;
using SharpCompress.Common;
using SharpCompress.Compressors.Deflate;
+using D = SharpCompress.Compressors.Deflate;
namespace SharpCompress.Writers.Zip;
public class ZipWriterOptions : WriterOptions
{
- public ZipWriterOptions(CompressionType compressionType)
- : base(compressionType) { }
+ public ZipWriterOptions(CompressionType compressionType, CompressionLevel compressionLevel = D.CompressionLevel.Default)
+ : base(compressionType, (int)compressionLevel) { }
internal ZipWriterOptions(WriterOptions options)
: base(options.CompressionType)
{
LeaveStreamOpen = options.LeaveStreamOpen;
ArchiveEncoding = options.ArchiveEncoding;
+ CompressionLevel = options.CompressionLevel;
if (options is ZipWriterOptions writerOptions)
{
UseZip64 = writerOptions.UseZip64;
- DeflateCompressionLevel = writerOptions.DeflateCompressionLevel;
ArchiveComment = writerOptions.ArchiveComment;
}
}
///
- /// When CompressionType.Deflate is used, this property is referenced. Defaults to CompressionLevel.Default.
+ /// Sets the compression level for Deflate compression (0-9).
+ /// This is a convenience method that sets the CompressionLevel property for Deflate compression.
///
- public CompressionLevel DeflateCompressionLevel { get; set; } = CompressionLevel.Default;
+ /// Deflate compression level (0=no compression, 6=default, 9=best compression)
+ public void SetDeflateCompressionLevel(CompressionLevel level)
+ {
+ CompressionLevel = (int)level;
+ }
+
+ ///
+ /// Sets the compression level for ZStandard compression (1-22).
+ /// This is a convenience method that sets the CompressionLevel property for ZStandard compression.
+ ///
+ /// ZStandard compression level (1=fastest, 3=default, 22=best compression)
+ /// Thrown when level is not between 1 and 22
+ public void SetZStandardCompressionLevel(int level)
+ {
+ if (level < 1 || level > 22)
+ throw new ArgumentOutOfRangeException(nameof(level), "ZStandard compression level must be between 1 and 22");
+
+ CompressionLevel = level;
+ }
+
+ ///
+ /// Legacy property for Deflate compression levels.
+ /// Valid range: 0-9 (0=no compression, 6=default, 9=best compression).
+ ///
+ ///
+ /// This property is deprecated. Use or instead.
+ ///
+ [Obsolete("Use CompressionLevel property or SetDeflateCompressionLevel method instead. This property will be removed in a future version.")]
+ public CompressionLevel DeflateCompressionLevel
+ {
+ get => (CompressionLevel)Math.Min(CompressionLevel, 9);
+ set => CompressionLevel = (int)value;
+ }
public string? ArchiveComment { get; set; }
diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs
index 15deb061..6796c914 100644
--- a/tests/SharpCompress.Test/ArchiveTests.cs
+++ b/tests/SharpCompress.Test/ArchiveTests.cs
@@ -4,8 +4,12 @@ using System.IO;
using System.Linq;
using SharpCompress.Archives;
using SharpCompress.Common;
+using SharpCompress.Compressors.Xz;
+using SharpCompress.Crypto;
using SharpCompress.IO;
using SharpCompress.Readers;
+using SharpCompress.Writers;
+using SharpCompress.Writers.Zip;
using Xunit;
namespace SharpCompress.Test;
@@ -339,4 +343,182 @@ public class ArchiveTests : ReaderTests
}
}
}
+
+ ///
+ /// Calculates CRC32 for the given data using SharpCompress implementation
+ ///
+ protected static uint CalculateCrc32(byte[] data) => Crc32.Compute(data);
+
+ ///
+ /// Creates a writer with the specified compression type and level
+ ///
+ protected static IWriter CreateWriterWithLevel(Stream stream, CompressionType compressionType, int? compressionLevel = null)
+ {
+ var writerOptions = new ZipWriterOptions(compressionType);
+ if (compressionLevel.HasValue)
+ {
+ writerOptions.CompressionLevel = compressionLevel.Value;
+ }
+ return WriterFactory.Open(stream, ArchiveType.Zip, writerOptions);
+ }
+
+ ///
+ /// Verifies archive content against expected files with CRC32 validation
+ ///
+ protected void VerifyArchiveContent(MemoryStream zipStream, Dictionary expectedFiles)
+ {
+ zipStream.Position = 0;
+ using var archive = ArchiveFactory.Open(zipStream);
+ Assert.Equal(expectedFiles.Count, archive.Entries.Count());
+
+ foreach (var entry in archive.Entries.Where(e => !e.IsDirectory))
+ {
+ using var entryStream = entry.OpenEntryStream();
+ using var extractedStream = new MemoryStream();
+ entryStream.CopyTo(extractedStream);
+ var extractedData = extractedStream.ToArray();
+
+ Assert.True(expectedFiles.ContainsKey(entry.Key.NotNull()), $"Unexpected entry: {entry.Key}");
+
+ var (expectedData, expectedCrc) = expectedFiles[entry.Key.NotNull()];
+ var actualCrc = CalculateCrc32(extractedData);
+
+ Assert.Equal(expectedCrc, actualCrc);
+ Assert.Equal(expectedData.Length, extractedData.Length);
+
+ // For large files, spot check rather than full comparison for performance
+ if (expectedData.Length > 1024 * 1024)
+ {
+ VerifyDataSpotCheck(expectedData, extractedData);
+ }
+ else
+ {
+ Assert.Equal(expectedData, extractedData);
+ }
+ }
+ }
+
+ ///
+ /// Performs efficient spot checks on large data arrays
+ ///
+ protected static void VerifyDataSpotCheck(byte[] expected, byte[] actual)
+ {
+ // Check first, middle, and last 1KB
+ Assert.Equal(expected.Take(1024), actual.Take(1024));
+ var mid = expected.Length / 2;
+ Assert.Equal(expected.Skip(mid).Take(1024), actual.Skip(mid).Take(1024));
+ Assert.Equal(expected.Skip(Math.Max(0, expected.Length - 1024)), actual.Skip(Math.Max(0, actual.Length - 1024)));
+ }
+
+ ///
+ /// Verifies compression ratio meets expectations
+ ///
+ protected void VerifyCompressionRatio(long originalSize, long compressedSize, double maxRatio, string context)
+ {
+ var compressionRatio = (double)compressedSize / originalSize;
+ Assert.True(compressionRatio < maxRatio,
+ $"Expected better compression for {context}. Original: {originalSize}, Compressed: {compressedSize}, Ratio: {compressionRatio:P}");
+ }
+
+ ///
+ /// Creates a memory-based archive with specified files and compression
+ ///
+ protected MemoryStream CreateMemoryArchive(Dictionary files, CompressionType compressionType, int? compressionLevel = null)
+ {
+ var zipStream = new MemoryStream();
+ using (var writer = CreateWriterWithLevel(zipStream, compressionType, compressionLevel))
+ {
+ foreach (var kvp in files)
+ {
+ writer.Write(kvp.Key, new MemoryStream(kvp.Value));
+ }
+ }
+ return zipStream;
+ }
+
+ ///
+ /// Verifies streaming CRC calculation for large data
+ ///
+ protected void VerifyStreamingCrc(Stream entryStream, uint expectedCrc, long expectedLength)
+ {
+ using var crcStream = new Crc32Stream(Stream.Null);
+ const int bufferSize = 64 * 1024;
+ var buffer = new byte[bufferSize];
+ int totalBytesRead = 0;
+ int bytesRead;
+
+ while ((bytesRead = entryStream.Read(buffer, 0, bufferSize)) > 0)
+ {
+ crcStream.Write(buffer, 0, bytesRead);
+ totalBytesRead += bytesRead;
+ }
+
+ var actualCrc = crcStream.Crc;
+ Assert.Equal(expectedCrc, actualCrc);
+ Assert.Equal(expectedLength, totalBytesRead);
+ }
+
+ ///
+ /// Creates and verifies a basic archive with compression testing
+ ///
+ protected void CreateAndVerifyBasicArchive(
+ Dictionary testFiles,
+ CompressionType compressionType,
+ int? compressionLevel = null,
+ double maxCompressionRatio = 0.8)
+ {
+ // Calculate expected CRCs
+ var expectedFiles = testFiles.ToDictionary(
+ kvp => kvp.Key,
+ kvp => (data: kvp.Value, crc: CalculateCrc32(kvp.Value))
+ );
+
+ // Create archive
+ using var zipStream = CreateMemoryArchive(testFiles, compressionType, compressionLevel);
+
+ // Verify compression occurred if expected
+ if (compressionType != CompressionType.None)
+ {
+ var originalSize = testFiles.Values.Sum(data => (long)data.Length);
+ VerifyCompressionRatio(originalSize, zipStream.Length, maxCompressionRatio, compressionType.ToString());
+ }
+
+ // Verify content
+ VerifyArchiveContent(zipStream, expectedFiles);
+ }
+
+ ///
+ /// Verifies archive entries have correct compression type
+ ///
+ protected void VerifyCompressionType(MemoryStream zipStream, CompressionType expectedCompressionType)
+ {
+ zipStream.Position = 0;
+ using var archive = ArchiveFactory.Open(zipStream);
+
+ foreach (var entry in archive.Entries.Where(e => !e.IsDirectory))
+ {
+ Assert.Equal(expectedCompressionType, entry.CompressionType);
+ }
+ }
+
+ ///
+ /// Extracts and verifies a single entry from archive
+ ///
+ protected (byte[] data, uint crc) ExtractAndVerifyEntry(MemoryStream zipStream, string entryName)
+ {
+ zipStream.Position = 0;
+ using var archive = ArchiveFactory.Open(zipStream);
+
+ var entry = archive.Entries.FirstOrDefault(e => e.Key == entryName && !e.IsDirectory);
+ Assert.NotNull(entry);
+
+ using var entryStream = entry.OpenEntryStream();
+ using var extractedStream = new MemoryStream();
+ entryStream.CopyTo(extractedStream);
+
+ var extractedData = extractedStream.ToArray();
+ var crc = CalculateCrc32(extractedData);
+
+ return (extractedData, crc);
+ }
}
diff --git a/tests/SharpCompress.Test/Zip/TestPseudoTextStream.cs b/tests/SharpCompress.Test/Zip/TestPseudoTextStream.cs
new file mode 100644
index 00000000..b9687a5c
--- /dev/null
+++ b/tests/SharpCompress.Test/Zip/TestPseudoTextStream.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SharpCompress.Test.Zip;
+///
+/// Generates pseudo English-style text for testing - Nanook
+///
+internal class TestPseudoTextStream : Stream
+{
+ private static readonly char[] _vowels = { 'a', 'e', 'i', 'o', 'u' };
+ private static readonly char[] _consonants = "bcdfghjklmnpqrstvwxyz".ToCharArray();
+
+ private long _position = 0;
+
+ 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 => _position;
+ set => throw new NotSupportedException();
+ }
+
+ public override void Flush() => throw new NotSupportedException();
+
+ public static byte[] Create(int size)
+ {
+ byte[] data = new byte[size];
+ using (TestPseudoTextStream rts = new TestPseudoTextStream())
+ {
+ int bufferSize = 64 * 1024; // 64k blocks
+ byte[] buffer = new byte[bufferSize];
+ int bytesRead = 0;
+ int totalBytesRead = 0;
+
+ while (totalBytesRead < size)
+ {
+ bytesRead = rts.Read(buffer, 0, Math.Min(bufferSize, size - totalBytesRead));
+ Array.Copy(buffer, 0, data, totalBytesRead, bytesRead);
+ totalBytesRead += bytesRead;
+ }
+ }
+
+ return data;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ int bytesRead = 0;
+ while (bytesRead < count)
+ {
+ string word = GenerateDeterministicWord(_position + bytesRead);
+ byte[] wordBytes = System.Text.Encoding.ASCII.GetBytes(word + " ");
+
+ int bytesToCopy = Math.Min(wordBytes.Length, count - bytesRead);
+ Array.Copy(wordBytes, 0, buffer, offset + bytesRead, bytesToCopy);
+
+ bytesRead += bytesToCopy;
+ _position += bytesToCopy;
+ }
+
+ return bytesRead;
+ }
+
+ private string GenerateDeterministicWord(long seed)
+ {
+ int length = (int)(seed % 7) + 2; // 2 to 8 letters
+ int vowelCount = (int)((seed / 7) % 4) + 2; // 2 to 5 vowels
+
+ System.Text.StringBuilder word = new System.Text.StringBuilder(length);
+ int vowelsAdded = 0;
+
+ for (int i = 0; i < length; i++)
+ {
+ if (vowelsAdded < vowelCount && ((seed >> i) & 1) == 0)
+ {
+ word.Append(_vowels[(int)(seed >> (i + 1)) % _vowels.Length]);
+ vowelsAdded++;
+ }
+ else
+ {
+ word.Append(_consonants[(int)(seed >> (i + 1)) % _consonants.Length]);
+ }
+ }
+
+ return word.ToString();
+ }
+
+ 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();
+}
diff --git a/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcTests.cs b/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcTests.cs
new file mode 100644
index 00000000..36869dea
--- /dev/null
+++ b/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcTests.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using SharpCompress.Archives.Zip;
+using SharpCompress.Common;
+using SharpCompress.Compressors.Deflate;
+using SharpCompress.Compressors.Xz;
+using SharpCompress.Crypto;
+using SharpCompress.Writers;
+using SharpCompress.Writers.Zip;
+using Xunit;
+
+namespace SharpCompress.Test.Zip;
+
+public class ZipTypesLevelsWithCrcRatioTests : ArchiveTests
+{
+ public ZipTypesLevelsWithCrcRatioTests() => UseExtensionInsteadOfNameToVerify = true;
+
+ [Theory]
+ [InlineData(CompressionType.Deflate, 1, 1, 0.11f)] // was 0.8f, actual 0.104
+ [InlineData(CompressionType.Deflate, 3, 1, 0.08f)] // was 0.8f, actual 0.078
+ [InlineData(CompressionType.Deflate, 6, 1, 0.05f)] // was 0.8f, actual ~0.042
+ [InlineData(CompressionType.Deflate, 9, 1, 0.04f)] // was 0.7f, actual 0.038
+ [InlineData(CompressionType.ZStandard, 1, 1, 0.025f)] // was 0.8f, actual 0.023
+ [InlineData(CompressionType.ZStandard, 3, 1, 0.015f)] // was 0.7f, actual 0.013
+ [InlineData(CompressionType.ZStandard, 9, 1, 0.006f)] // was 0.7f, actual 0.005
+ [InlineData(CompressionType.ZStandard, 22, 1, 0.005f)] // was 0.7f, actual 0.004
+ [InlineData(CompressionType.BZip2, 0, 1, 0.035f)] // was 0.8f, actual 0.033
+ [InlineData(CompressionType.LZMA, 0, 1, 0.005f)] // was 0.8f, actual 0.004
+ [InlineData(CompressionType.None, 0, 1, 1.001f)] // was 1.1f, actual 1.000
+ [InlineData(CompressionType.Deflate, 6, 2, 0.045f)] // was 0.8f, actual 0.042
+ [InlineData(CompressionType.ZStandard, 3, 2, 0.012f)] // was 0.7f, actual 0.010
+ [InlineData(CompressionType.BZip2, 0, 2, 0.035f)] // was 0.8f, actual 0.032
+ [InlineData(CompressionType.Deflate, 9, 3, 0.04f)] // was 0.7f, actual 0.038
+ [InlineData(CompressionType.ZStandard, 9, 3, 0.003f)] // was 0.7f, actual 0.002
+ public void Zip_Create_Archive_With_3_Files_Crc32_Test(CompressionType compressionType, int compressionLevel, int sizeMb, float expectedRatio)
+ {
+ const int OneMiB = 1024 * 1024;
+ var baseSize = sizeMb * OneMiB;
+
+ // Generate test content for files with sizes based on the sizeMb parameter
+ var file1Data = TestPseudoTextStream.Create(baseSize);
+ var file2Data = TestPseudoTextStream.Create(baseSize * 2);
+ var file3Data = TestPseudoTextStream.Create(baseSize * 3);
+
+ var expectedFiles = new Dictionary
+ {
+ [$"file1_{sizeMb}MiB.txt"] = (file1Data, CalculateCrc32(file1Data)),
+ [$"data/file2_{sizeMb * 2}MiB.txt"] = (file2Data, CalculateCrc32(file2Data)),
+ [$"deep/nested/file3_{sizeMb * 3}MiB.txt"] = (file3Data, CalculateCrc32(file3Data))
+ };
+
+ // Create zip archive in memory
+ using var zipStream = new MemoryStream();
+ using (var writer = CreateWriterWithLevel(zipStream, compressionType, compressionLevel))
+ {
+ writer.Write($"file1_{sizeMb}MiB.txt", new MemoryStream(file1Data));
+ writer.Write($"data/file2_{sizeMb * 2}MiB.txt", new MemoryStream(file2Data));
+ writer.Write($"deep/nested/file3_{sizeMb * 3}MiB.txt", new MemoryStream(file3Data));
+ }
+
+ // Calculate and output actual compression ratio
+ var originalSize = file1Data.Length + file2Data.Length + file3Data.Length;
+ var actualRatio = (double)zipStream.Length / originalSize;
+ //Debug.WriteLine($"Zip_Create_Archive_With_3_Files_Crc32_Test: {compressionType} Level={compressionLevel} Size={sizeMb}MB Expected={expectedRatio:F3} Actual={actualRatio:F3}");
+
+ // Verify compression occurred (except for None compression type)
+ if (compressionType != CompressionType.None)
+ {
+ Assert.True(zipStream.Length < originalSize, $"Compression failed: compressed={zipStream.Length}, original={originalSize}");
+ }
+
+ // Verify compression ratio
+ VerifyCompressionRatio(originalSize, zipStream.Length, expectedRatio, $"{compressionType} level {compressionLevel}");
+
+ // Verify archive content and CRC32
+ VerifyArchiveContent(zipStream, expectedFiles);
+
+ // Verify compression type is correctly set
+ VerifyCompressionType(zipStream, compressionType);
+ }
+
+ [Theory]
+ [InlineData(CompressionType.Deflate, 1, 4, 0.11f)] // was 0.8, actual 0.105
+ [InlineData(CompressionType.Deflate, 3, 4, 0.08f)] // was 0.8, actual 0.077
+ [InlineData(CompressionType.Deflate, 6, 4, 0.045f)] // was 0.8, actual 0.042
+ [InlineData(CompressionType.Deflate, 9, 4, 0.04f)] // was 0.8, actual 0.037
+ [InlineData(CompressionType.ZStandard, 1, 4, 0.025f)] // was 0.8, actual 0.022
+ [InlineData(CompressionType.ZStandard, 3, 4, 0.012f)] // was 0.8, actual 0.010
+ [InlineData(CompressionType.ZStandard, 9, 4, 0.003f)] // was 0.8, actual 0.002
+ [InlineData(CompressionType.ZStandard, 22, 4, 0.003f)] // was 0.8, actual 0.002
+ [InlineData(CompressionType.BZip2, 0, 4, 0.035f)] // was 0.8, actual 0.032
+ [InlineData(CompressionType.LZMA, 0, 4, 0.003f)] // was 0.8, actual 0.002
+ public void Zip_WriterFactory_Crc32_Test(CompressionType compressionType, int compressionLevel, int sizeMb, float expectedRatio)
+ {
+ var fileSize = sizeMb * 1024 * 1024;
+
+ var testData = TestPseudoTextStream.Create(fileSize);
+ var expectedCrc = CalculateCrc32(testData);
+
+ // Create archive with specified compression level
+ using var zipStream = new MemoryStream();
+ var writerOptions = new ZipWriterOptions(compressionType) { CompressionLevel = compressionLevel };
+
+ using (var writer = WriterFactory.Open(zipStream, ArchiveType.Zip, writerOptions))
+ {
+ writer.Write($"{compressionType}_level_{compressionLevel}_{sizeMb}MiB.txt", new MemoryStream(testData));
+ }
+
+ // Calculate and output actual compression ratio
+ var actualRatio = (double)zipStream.Length / testData.Length;
+ //Debug.WriteLine($"Zip_WriterFactory_Crc32_Test: {compressionType} Level={compressionLevel} Size={sizeMb}MB Expected={expectedRatio:F3} Actual={actualRatio:F3}");
+
+ VerifyCompressionRatio(testData.Length, zipStream.Length, expectedRatio, $"{compressionType} level {compressionLevel}");
+
+ // Verify the archive
+ zipStream.Position = 0;
+ using var archive = ZipArchive.Open(zipStream);
+
+ var entry = archive.Entries.Single(e => !e.IsDirectory);
+ using var entryStream = entry.OpenEntryStream();
+ using var extractedStream = new MemoryStream();
+ entryStream.CopyTo(extractedStream);
+
+ var extractedData = extractedStream.ToArray();
+ var actualCrc = CalculateCrc32(extractedData);
+
+ Assert.Equal(compressionType, entry.CompressionType);
+ Assert.Equal(expectedCrc, actualCrc);
+ Assert.Equal(testData.Length, extractedData.Length);
+ Assert.Equal(testData, extractedData);
+ }
+
+ [Theory]
+ [InlineData(CompressionType.Deflate, 1, 2, 0.11f)] // was 0.8, actual 0.104
+ [InlineData(CompressionType.Deflate, 3, 2, 0.08f)] // was 0.8, actual 0.077
+ [InlineData(CompressionType.Deflate, 6, 2, 0.045f)] // was 0.8, actual 0.042
+ [InlineData(CompressionType.Deflate, 9, 2, 0.04f)] // was 0.7, actual 0.038
+ [InlineData(CompressionType.ZStandard, 1, 2, 0.025f)] // was 0.8, actual 0.023
+ [InlineData(CompressionType.ZStandard, 3, 2, 0.015f)] // was 0.7, actual 0.012
+ [InlineData(CompressionType.ZStandard, 9, 2, 0.006f)] // was 0.7, actual 0.005
+ [InlineData(CompressionType.ZStandard, 22, 2, 0.005f)] // was 0.7, actual 0.004
+ [InlineData(CompressionType.BZip2, 0, 2, 0.035f)] // was 0.8, actual 0.032
+ [InlineData(CompressionType.LZMA, 0, 2, 0.005f)] // was 0.8, actual 0.004
+ public void Zip_ZipArchiveOpen_Crc32_Test(CompressionType compressionType, int compressionLevel, int sizeMb, float expectedRatio)
+ {
+ var fileSize = sizeMb * 1024 * 1024;
+
+ var testData = TestPseudoTextStream.Create(fileSize);
+ var expectedCrc = CalculateCrc32(testData);
+
+ // Create archive with specified compression and level
+ using var zipStream = new MemoryStream();
+ using (var writer = CreateWriterWithLevel(zipStream, compressionType, compressionLevel))
+ {
+ writer.Write($"{compressionType}_{compressionLevel}_{sizeMb}MiB.txt", new MemoryStream(testData));
+ }
+
+ // Calculate and output actual compression ratio
+ var actualRatio = (double)zipStream.Length / testData.Length;
+ //Debug.WriteLine($"Zip_ZipArchiveOpen_Crc32_Test: {compressionType} Level={compressionLevel} Size={sizeMb}MB Expected={expectedRatio:F3} Actual={actualRatio:F3}");
+
+ // Verify the archive
+ zipStream.Position = 0;
+ using var archive = ZipArchive.Open(zipStream);
+
+ var entry = archive.Entries.Single(e => !e.IsDirectory);
+ using var entryStream = entry.OpenEntryStream();
+ using var extractedStream = new MemoryStream();
+ entryStream.CopyTo(extractedStream);
+
+ var extractedData = extractedStream.ToArray();
+ var actualCrc = CalculateCrc32(extractedData);
+
+ Assert.Equal(compressionType, entry.CompressionType);
+ Assert.Equal(expectedCrc, actualCrc);
+ Assert.Equal(testData.Length, extractedData.Length);
+
+ // For smaller files, verify full content; for larger, spot check
+ if (testData.Length <= sizeMb * 2)
+ {
+ Assert.Equal(testData, extractedData);
+ }
+ else
+ {
+ VerifyDataSpotCheck(testData, extractedData);
+ }
+
+ VerifyCompressionRatio(testData.Length, zipStream.Length, expectedRatio, $"{compressionType} Level {compressionLevel}");
+ }
+
+}