Skip to content

Commit

Permalink
Changes in 1.2.13.4.
Browse files Browse the repository at this point in the history
- Made ZlibStatus public.
- Replaced MemoryZlib static class with:
  - Checks inside of a `Checks` static class.
  - Compression methods inside of a new ZlibEncoder class.*
  - Decompression methods inside of a new ZlibDecoder class.*
- Added Crc32 and Status members to the ZlibResult structure.
* The new ZlibEncoder has new TryCompress methods while the new ZlibDecoder has new TryDecompress methods.
AraHaan committed Jul 15, 2024
1 parent b409a4d commit 2c453fd
Showing 12 changed files with 425 additions and 189 deletions.
10 changes: 8 additions & 2 deletions src/ZlibSharp/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -3,11 +3,17 @@
<Import Project="../../Directory.Build.props" />

<PropertyGroup>
<Version>1.2.13.3</Version>
<Version>1.2.13.4</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageReleaseNotes>
A bug fix to the calculation of the adler32 hash when compressing data. Also added strong name signing as well.
- Made ZlibStatus public.
- Replaced MemoryZlib static class with:
- Checks inside of a `Checks` static class.
- Compression methods inside of a new ZlibEncoder class.*
- Decompression methods inside of a new ZlibDecoder class.*
- Added Crc32 and Status members to the ZlibResult structure.
* The new ZlibEncoder has new TryCompress methods while the new ZlibDecoder has new TryDecompress methods.
</PackageReleaseNotes>
<Description>
A high performance .NET zlib wrapper that is span based and offers more functionality over System.IO.Compression by allowing more developer control over how the data is compressed and by avoiding .NET's streams entirely.
4 changes: 4 additions & 0 deletions src/ZlibSharp/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<Project>

<PropertyGroup>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="elskom.zlib.redist.win" Version="1.2.13" />
<PackageReference Include="elskom.zlib.redist.linux" Version="1.2.13" />
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021~2022, Els_kom org.
// Copyright (c) 2021~2022, Els_kom org.
// https://github.com/Elskom/
// All rights reserved.
// license: MIT, see LICENSE for more details.
@@ -9,9 +9,9 @@ namespace ZlibSharp;
using Exceptions;

/// <summary>
/// Zlib Memory Compression and Decompression Class.
/// Common zlib checks.
/// </summary>
public static unsafe class MemoryZlib
public static unsafe class Checks
{
/// <summary>
/// Gets or sets the native zlib version to use.
@@ -21,90 +21,6 @@ public static unsafe class MemoryZlib
/// </remarks>
public static string NativeZlibVersion { get; set; } = "1.2.13";

/// <summary>
/// Compresses data using the user specified compression level.
/// </summary>
/// <param name="sourcePath">The path to the file to compress.</param>
/// <param name="dest">The compressed data buffer.</param>
/// <param name="compressionLevel">The compression level to use to compress the file.</param>
/// <param name="windowBits">The window bits to use to compress the data.</param>
/// <param name="strategy">The compression strategy to use to compress the data.</param>
/// <exception cref="NotPackableException">
/// Thrown when zlib errors internally in any way.
/// </exception>
/// <returns>
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ZlibResult Compress(string sourcePath, Span<byte> dest, ZlibCompressionLevel compressionLevel = ZlibCompressionLevel.DefaultCompression, ZlibWindowBits windowBits = ZlibWindowBits.Zlib, ZlibCompressionStrategy strategy = ZlibCompressionStrategy.Default)
=> Compress(File.ReadAllBytes(sourcePath), dest, compressionLevel, windowBits, strategy);

/// <summary>
/// Compresses data using the user specified compression level.
/// </summary>
/// <param name="source">The input data buffer.</param>
/// <param name="dest">The compressed data buffer.</param>
/// <param name="compressionLevel">The compression level to use to compress the data.</param>
/// <param name="windowBits">The window bits to use to compress the data.</param>
/// <param name="strategy">The compression strategy to use to compress the data.</param>
/// <exception cref="NotPackableException">
/// Thrown when zlib errors internally in any way.
/// </exception>
/// <returns>
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ZlibResult Compress(ReadOnlySpan<byte> source, Span<byte> dest, ZlibCompressionLevel compressionLevel = ZlibCompressionLevel.DefaultCompression, ZlibWindowBits windowBits = ZlibWindowBits.Zlib, ZlibCompressionStrategy strategy = ZlibCompressionStrategy.Default)
{
var bytesWritten = ZlibHelper.Compress(source, dest, compressionLevel, windowBits, strategy, out var adler32);
return new(bytesWritten, 0, adler32);
}

/// <summary>
/// Decompresses a file.
/// </summary>
/// <param name="sourcePath">The path to the file to decompress.</param>
/// <param name="dest">The decompressed data buffer.</param>
/// <param name="windowBits">The window bits to use to decompress the data.</param>
/// <exception cref="NotUnpackableException">
/// Thrown when zlib errors internally in any way.
/// </exception>
/// <returns>
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ZlibResult Decompress(string sourcePath, Span<byte> dest, ZlibWindowBits windowBits = ZlibWindowBits.Zlib)
=> Decompress(File.ReadAllBytes(sourcePath), dest, windowBits);

/// <summary>
/// Decompresses data.
/// </summary>
/// <param name="source">The compressed input data.</param>
/// <param name="dest">The decompressed data buffer.</param>
/// <param name="windowBits">The window bits to use to decompress the data.</param>
/// <exception cref="NotUnpackableException">
/// Thrown when zlib errors internally in any way.
/// </exception>
/// <returns>
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ZlibResult Decompress(ReadOnlySpan<byte> source, Span<byte> dest, ZlibWindowBits windowBits = ZlibWindowBits.Zlib)
{
var bytesRead = ZlibHelper.Decompress(source, dest, out var bytesWritten, out var adler32, windowBits);
return new ZlibResult(bytesWritten, bytesRead, adler32);
}

// NEW: GZip compression check.

/// <summary>
/// Check data for compression by gzip.
/// </summary>
@@ -169,8 +85,8 @@ public static bool IsCompressedByZlib(string path)
/// </summary>
/// <returns>The version string to this version of ZlibSharp.</returns>
public static string ZlibSharpVersion()
// => typeof(MemoryZlib).Assembly.GetName().Version!.ToString(3);
=> typeof(MemoryZlib).Assembly.GetName().Version!.ToString(4);
// => typeof(Checks).Assembly.GetName().Version!.ToString(3);
=> typeof(Checks).Assembly.GetName().Version!.ToString(4);

/// <summary>
/// Gets the version to the imported native zlib library.
@@ -229,7 +145,12 @@ public static ulong ZlibGetCrc32(ReadOnlySpan<byte> data)
public static uint GetCompressedSize(ReadOnlySpan<byte> source, ZlibCompressionLevel compressionLevel = ZlibCompressionLevel.DefaultCompression, ZlibWindowBits windowBits = ZlibWindowBits.Zlib)
{
var discard = new byte[source.Length];
var result = Compress(source, discard, compressionLevel, windowBits);
var encoder = new ZlibEncoder()
{
CompressionLevel = compressionLevel,
WindowBits = windowBits,
};
var result = encoder.Compress(source, discard);
return result.BytesWritten;
}

@@ -245,7 +166,8 @@ public static uint GetCompressedSize(ReadOnlySpan<byte> source, ZlibCompressionL
public static uint GetDecompressedSize(ReadOnlySpan<byte> source, ZlibWindowBits windowBits = ZlibWindowBits.Zlib)
{
var discard = new byte[Array.MaxLength];
var result = Decompress(source, discard, windowBits);
var decoder = new ZlibDecoder(windowBits);
var result = decoder.Decompress(source, discard);
return result.BytesWritten;
}
}
26 changes: 13 additions & 13 deletions src/ZlibSharp/ZlibSharp/Internal/UnsafeNativeMethods.cs
Original file line number Diff line number Diff line change
@@ -14,27 +14,27 @@ internal static class UnsafeNativeMethods

[DefaultDllImportSearchPaths(DllImportSearchPath.LegacyBehavior)]
[DllImport("zlib", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, EntryPoint = "deflateInit2_")]
private static extern unsafe ZlibPInvokeResult deflateInit2__private(ZStream* zs, ZlibCompressionLevel compressionLevel, ZlibCompressionMethod method, ZlibWindowBits windowBits, int memLevel, ZlibCompressionStrategy strategy, byte *version, int streamSize);
private static extern unsafe ZlibStatus deflateInit2__private(ZStream* zs, ZlibCompressionLevel compressionLevel, ZlibCompressionMethod method, ZlibWindowBits windowBits, int memLevel, ZlibCompressionStrategy strategy, byte *version, int streamSize);

[DefaultDllImportSearchPaths(DllImportSearchPath.LegacyBehavior)]
[DllImport("zlib", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, EntryPoint = "inflateInit2_")]
private static extern unsafe ZlibPInvokeResult inflateInit2__private(ZStream* zs, ZlibWindowBits windowBits, byte* version, int streamSize);
private static extern unsafe ZlibStatus inflateInit2__private(ZStream* zs, ZlibWindowBits windowBits, byte* version, int streamSize);

[DefaultDllImportSearchPaths(DllImportSearchPath.LegacyBehavior)]
[DllImport("zlib", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, EntryPoint = "inflate")]
private static extern unsafe ZlibPInvokeResult inflate_private(ZStream* zs, ZlibFlushStrategy flush);
private static extern unsafe ZlibStatus inflate_private(ZStream* zs, ZlibFlushStrategy flush);

[DefaultDllImportSearchPaths(DllImportSearchPath.LegacyBehavior)]
[DllImport("zlib", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, EntryPoint = "deflate")]
private static extern unsafe ZlibPInvokeResult deflate_private(ZStream* zs, ZlibFlushStrategy flush);
private static extern unsafe ZlibStatus deflate_private(ZStream* zs, ZlibFlushStrategy flush);

[DefaultDllImportSearchPaths(DllImportSearchPath.LegacyBehavior)]
[DllImport("zlib", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, EntryPoint = "inflateEnd")]
private static extern unsafe ZlibPInvokeResult inflateEnd_private(ZStream* zs);
private static extern unsafe ZlibStatus inflateEnd_private(ZStream* zs);

[DefaultDllImportSearchPaths(DllImportSearchPath.LegacyBehavior)]
[DllImport("zlib", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, EntryPoint = "deflateEnd")]
private static extern unsafe ZlibPInvokeResult deflateEnd_private(ZStream* zs);
private static extern unsafe ZlibStatus deflateEnd_private(ZStream* zs);

[DefaultDllImportSearchPaths(DllImportSearchPath.LegacyBehavior)]
[DllImport("zlib", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, EntryPoint = "adler32")]
@@ -45,7 +45,7 @@ internal static class UnsafeNativeMethods
private static extern unsafe ulong crc32_private(ulong crc, byte* buf, uint len);

private static void ThrowInvalidOperationException()
=> throw new InvalidOperationException($"Zlib version '{MemoryZlib.NativeZlibVersion}' not found. Please install the proper '{RuntimeInformation.ProcessArchitecture}' version and then try again.");
=> throw new InvalidOperationException($"Zlib version '{Checks.NativeZlibVersion}' not found. Please install the proper '{RuntimeInformation.ProcessArchitecture}' version and then try again.");

[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "P/Invoke.")]
internal static unsafe byte* zlibVersion()
@@ -64,7 +64,7 @@ private static void ThrowInvalidOperationException()
}

[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "P/Invoke.")]
internal static unsafe ZlibPInvokeResult deflateInit2_(ZStream* zs, ZlibCompressionLevel compressionLevel, ZlibWindowBits windowBits, ZlibCompressionStrategy strategy)
internal static unsafe ZlibStatus deflateInit2_(ZStream* zs, ZlibCompressionLevel compressionLevel, ZlibWindowBits windowBits, ZlibCompressionStrategy strategy)
{
try
{
@@ -80,7 +80,7 @@ internal static unsafe ZlibPInvokeResult deflateInit2_(ZStream* zs, ZlibCompress
}

[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "P/Invoke.")]
internal static unsafe ZlibPInvokeResult inflateInit2_(ZStream* zs, ZlibWindowBits windowBits)
internal static unsafe ZlibStatus inflateInit2_(ZStream* zs, ZlibWindowBits windowBits)
{
try
{
@@ -96,7 +96,7 @@ internal static unsafe ZlibPInvokeResult inflateInit2_(ZStream* zs, ZlibWindowBi
}

[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "P/Invoke.")]
internal static unsafe ZlibPInvokeResult inflate(ZStream* zs, ZlibFlushStrategy flush)
internal static unsafe ZlibStatus inflate(ZStream* zs, ZlibFlushStrategy flush)
{
try
{
@@ -112,7 +112,7 @@ internal static unsafe ZlibPInvokeResult inflate(ZStream* zs, ZlibFlushStrategy
}

[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "P/Invoke.")]
internal static unsafe ZlibPInvokeResult deflate(ZStream* zs, ZlibFlushStrategy flush)
internal static unsafe ZlibStatus deflate(ZStream* zs, ZlibFlushStrategy flush)
{
try
{
@@ -128,7 +128,7 @@ internal static unsafe ZlibPInvokeResult deflate(ZStream* zs, ZlibFlushStrategy
}

[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "P/Invoke.")]
internal static unsafe ZlibPInvokeResult inflateEnd(ZStream* zs)
internal static unsafe ZlibStatus inflateEnd(ZStream* zs)
{
try
{
@@ -144,7 +144,7 @@ internal static unsafe ZlibPInvokeResult inflateEnd(ZStream* zs)
}

[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "P/Invoke.")]
internal static unsafe ZlibPInvokeResult deflateEnd(ZStream* zs)
internal static unsafe ZlibStatus deflateEnd(ZStream* zs)
{
try
{
80 changes: 43 additions & 37 deletions src/ZlibSharp/ZlibSharp/Internal/ZlibHelper.cs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ internal static unsafe class ZlibHelper
{
private static bool zlibResolverAdded;

internal static uint Compress(ReadOnlySpan<byte> source, Span<byte> dest, ZlibCompressionLevel compressionLevel, ZlibWindowBits windowBits, ZlibCompressionStrategy strategy, out uint adler32)
internal static uint Compress(ReadOnlySpan<byte> source, Span<byte> dest, ZlibCompressionLevel compressionLevel, ZlibWindowBits windowBits, ZlibCompressionStrategy strategy, out uint adler32, out uint crc32, out ZlibStatus status)
{
if (!zlibResolverAdded)
{
@@ -33,24 +33,25 @@ internal static uint Compress(ReadOnlySpan<byte> source, Span<byte> dest, ZlibCo
streamPtr->avail_in = (uint)source.Length;
streamPtr->next_out = destPtr;
streamPtr->avail_out = (uint)dest.Length;
InitializeDeflate(streamPtr, compressionLevel, windowBits, strategy);
while (UnsafeNativeMethods.deflate(streamPtr, ZlibFlushStrategy.NoFlush) == ZlibPInvokeResult.Ok)
status = InitializeDeflate(streamPtr, compressionLevel, windowBits, strategy);
while ((status = UnsafeNativeMethods.deflate(streamPtr, ZlibFlushStrategy.NoFlush)) == ZlibStatus.Ok)
{
if (streamPtr->avail_in == 0)
{
_ = UnsafeNativeMethods.deflate(streamPtr, ZlibFlushStrategy.Finish);
status = UnsafeNativeMethods.deflate(streamPtr, ZlibFlushStrategy.Finish);
}
}

adler32 = unchecked((uint)(MemoryZlib.ZlibGetAdler32(source) & 0xFFFFFFFF));
DeflateEnd(streamPtr);
adler32 = unchecked((uint)(Checks.ZlibGetAdler32(source) & 0xFFFFFFFF));
crc32 = unchecked((uint)(Checks.ZlibGetCrc32(source) & 0xFFFFFFFF));
status = DeflateEnd(streamPtr);
return (uint)stream.total_out.Value;
}
}

//Decompress returns avail_in, allowing users to reallocate and continue decompressing remaining data
//should Dest buffer be under-allocated
internal static uint Decompress(ReadOnlySpan<byte> source, Span<byte> dest, out uint bytesWritten, out uint adler32, ZlibWindowBits windowBits)
internal static uint Decompress(ReadOnlySpan<byte> source, Span<byte> dest, out uint bytesWritten, out uint adler32, out uint crc32, out ZlibStatus status, ZlibWindowBits windowBits)
{
if (!zlibResolverAdded)
{
@@ -71,64 +72,69 @@ internal static uint Decompress(ReadOnlySpan<byte> source, Span<byte> dest, out
streamPtr->avail_in = (uint)source.Length;
streamPtr->next_out = destPtr;
streamPtr->avail_out = (uint)dest.Length;
InitializeInflate(streamPtr, windowBits);
while (Inflate(streamPtr, ZlibFlushStrategy.NoFlush) == ZlibPInvokeResult.Ok)
status = InitializeInflate(streamPtr, windowBits);
while ((status = Inflate(streamPtr, ZlibFlushStrategy.NoFlush)) == ZlibStatus.Ok)
{
if (streamPtr->avail_in == 0)
{
_ = Inflate(streamPtr, ZlibFlushStrategy.Finish);
status = Inflate(streamPtr, ZlibFlushStrategy.Finish);
}
}

bytesWritten = (uint)streamPtr->total_out.Value;
adler32 = unchecked((uint)(MemoryZlib.ZlibGetAdler32(dest) & 0xFFFFFFFF));
InflateEnd(streamPtr);
adler32 = unchecked((uint)(Checks.ZlibGetAdler32(dest) & 0xFFFFFFFF));
crc32 = unchecked((uint)(Checks.ZlibGetCrc32(dest) & 0xFFFFFFFF));
status = InflateEnd(streamPtr);
return streamPtr->avail_in;
}
}

private static void InitializeInflate(ZStream* streamPtr, ZlibWindowBits windowBits)
private static ZlibStatus InitializeInflate(ZStream* streamPtr, ZlibWindowBits windowBits)
{
var result = UnsafeNativeMethods.inflateInit2_(streamPtr, windowBits);
if (result is not ZlibPInvokeResult.Ok)
{
throw new NotUnpackableException($"{nameof(InitializeInflate)} failed - ({result}) {Marshal.PtrToStringUTF8((nint)streamPtr->msg)}");
}
return result is not ZlibStatus.Ok
? throw new NotUnpackableException($"{nameof(InitializeInflate)} failed - ({result}) {Marshal.PtrToStringUTF8((nint)streamPtr->msg)}")
: result;
}

private static void InitializeDeflate(ZStream* streamPtr, ZlibCompressionLevel compressionLevel, ZlibWindowBits windowBits, ZlibCompressionStrategy strategy)
private static ZlibStatus InitializeDeflate(ZStream* streamPtr, ZlibCompressionLevel compressionLevel, ZlibWindowBits windowBits, ZlibCompressionStrategy strategy)
{
var result = UnsafeNativeMethods.deflateInit2_(streamPtr, compressionLevel, windowBits, strategy);
if (result is not ZlibPInvokeResult.Ok)
{
throw new NotPackableException($"{nameof(InitializeDeflate)} failed - ({result}) {Marshal.PtrToStringUTF8((nint)streamPtr->msg)}");
}
return result is not ZlibStatus.Ok
? throw new NotPackableException($"{nameof(InitializeDeflate)} failed - ({result}) {Marshal.PtrToStringUTF8((nint)streamPtr->msg)}")
: result;
}

private static ZlibStatus Deflate(ZStream* streamPtr, ZlibFlushStrategy flush)
{
var result = UnsafeNativeMethods.deflate(streamPtr, flush);
return result is not ZlibStatus.Ok and not ZlibStatus.StreamEnd
? throw new NotUnpackableException($"{nameof(Deflate)} failed - ({result}) {Marshal.PtrToStringUTF8((nint)streamPtr->msg)}")
: result;
}

private static ZlibPInvokeResult Inflate(ZStream* streamPtr, ZlibFlushStrategy flush)
private static ZlibStatus Inflate(ZStream* streamPtr, ZlibFlushStrategy flush)
{
var result = UnsafeNativeMethods.inflate(streamPtr, flush);
return result is not ZlibPInvokeResult.Ok and not ZlibPInvokeResult.StreamEnd
return result is not ZlibStatus.Ok and not ZlibStatus.StreamEnd
? throw new NotUnpackableException($"{nameof(Inflate)} failed - ({result}) {Marshal.PtrToStringUTF8((nint)streamPtr->msg)}")
: result;
}

private static void InflateEnd(ZStream* streamPtr)
private static ZlibStatus InflateEnd(ZStream* streamPtr)
{
var result = UnsafeNativeMethods.inflateEnd(streamPtr);
if (result is not ZlibPInvokeResult.Ok)
{
throw new NotUnpackableException($"{nameof(InflateEnd)} failed - ({result}) {Marshal.PtrToStringUTF8((nint)streamPtr->msg)}");
}
return result is not ZlibStatus.Ok
? throw new NotUnpackableException($"{nameof(InflateEnd)} failed - ({result}) {Marshal.PtrToStringUTF8((nint)streamPtr->msg)}")
: result;
}

private static void DeflateEnd(ZStream* streamPtr)
private static ZlibStatus DeflateEnd(ZStream* streamPtr)
{
var result = UnsafeNativeMethods.deflateEnd(streamPtr);
if (result is not ZlibPInvokeResult.Ok)
{
throw new NotPackableException($"{nameof(DeflateEnd)} failed - ({result}) {Marshal.PtrToStringUTF8((nint)streamPtr->msg)}");
}
return result is not ZlibStatus.Ok
? throw new NotPackableException($"{nameof(DeflateEnd)} failed - ({result}) {Marshal.PtrToStringUTF8((nint)streamPtr->msg)}")
: result;
}

private static void AddNativeResolver()
@@ -151,17 +157,17 @@ private static void AddNativeResolver()
{
// pick up zlib from "sudo apt install zlib1g" or
// "sudo apt install zlib1g-dev".
_ = NativeLibrary.TryLoad($"libz.so.{MemoryZlib.NativeZlibVersion}", assembly, path, out handle);
_ = NativeLibrary.TryLoad($"libz.so.{Checks.NativeZlibVersion}", assembly, path, out handle);
}
}
else if (OperatingSystem.IsMacOS() || OperatingSystem.IsMacCatalyst())
{
if (!NativeLibrary.TryLoad("libz.dylib", assembly, path, out handle))
{
if (!NativeLibrary.TryLoad($"libz.{MemoryZlib.NativeZlibVersion}.dylib", assembly, path, out handle))
if (!NativeLibrary.TryLoad($"libz.{Checks.NativeZlibVersion}.dylib", assembly, path, out handle))
{
// fall back on homebrew zlib.
_ = NativeLibrary.TryLoad($"/usr/local/Cellar/zlib/{MemoryZlib.NativeZlibVersion}/lib/libz.{MemoryZlib.NativeZlibVersion}.dylib", out handle);
_ = NativeLibrary.TryLoad($"/usr/local/Cellar/zlib/{Checks.NativeZlibVersion}/lib/libz.{Checks.NativeZlibVersion}.dylib", out handle);
}
}
}
19 changes: 0 additions & 19 deletions src/ZlibSharp/ZlibSharp/Internal/ZlibPInvokeResult.cs

This file was deleted.

102 changes: 102 additions & 0 deletions src/ZlibSharp/ZlibSharp/ZlibDecoder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) 2021~2022, Els_kom org.
// https://github.com/Elskom/
// All rights reserved.
// license: MIT, see LICENSE for more details.

namespace ZlibSharp;

using Internal;
using Exceptions;

/// <summary>
/// Zlib Memory Decompression class.
/// </summary>
/// <param name="WindowBits"> Gets or sets the window bits to use to decompress the data. </param>
public record ZlibDecoder(ZlibWindowBits WindowBits)
{
public ZlibDecoder() : this(ZlibWindowBits.Zlib)
{
}

/// <summary>
/// Decompresses a file.
/// </summary>
/// <param name="sourcePath">The path to the file to decompress.</param>
/// <param name="dest">The decompressed data buffer.</param>
/// <param name="result">
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
/// </param>
/// <returns>
/// <see langword="true"/> if the compression was a success, <see langword="false"/> otherwise.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryDecompress(string sourcePath, Span<byte> dest, out ZlibResult? result)
=> this.TryDecompress(File.ReadAllBytes(sourcePath), dest, out result);

/// <summary>
/// Decompresses data.
/// </summary>
/// <param name="source">The compressed input data.</param>
/// <param name="dest">The decompressed data buffer.</param>
/// <param name="result">
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
/// </param>
/// <returns>
/// <see langword="true"/> if the compression was a success, <see langword="false"/> otherwise.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryDecompress(ReadOnlySpan<byte> source, Span<byte> dest, out ZlibResult? result)
{
try
{
result = this.Decompress(source, dest);
return true;
}
catch (NotUnpackableException)
{
result = null;
return false;
}
}

/// <summary>
/// Decompresses a file.
/// </summary>
/// <param name="sourcePath">The path to the file to decompress.</param>
/// <param name="dest">The decompressed data buffer.</param>
/// <exception cref="NotUnpackableException">
/// Thrown when zlib errors internally in any way.
/// </exception>
/// <returns>
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ZlibResult Decompress(string sourcePath, Span<byte> dest)
=> this.Decompress(File.ReadAllBytes(sourcePath), dest);

/// <summary>
/// Decompresses data.
/// </summary>
/// <param name="source">The compressed input data.</param>
/// <param name="dest">The decompressed data buffer.</param>
/// <exception cref="NotUnpackableException">
/// Thrown when zlib errors internally in any way.
/// </exception>
/// <returns>
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ZlibResult Decompress(ReadOnlySpan<byte> source, Span<byte> dest)
{
var bytesRead = ZlibHelper.Decompress(source, dest, out var bytesWritten, out var adler32, out var crc32, out var status, this.WindowBits);
return new ZlibResult(bytesWritten, bytesRead, adler32, crc32, status);
}
}
127 changes: 127 additions & 0 deletions src/ZlibSharp/ZlibSharp/ZlibEncoder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright (c) 2021~2022, Els_kom org.
// https://github.com/Elskom/
// All rights reserved.
// license: MIT, see LICENSE for more details.

namespace ZlibSharp;

using Internal;
using Exceptions;

/// <summary>
/// Zlib Memory Compression class.
/// </summary>
public record ZlibEncoder
{
public ZlibEncoder() : this(ZlibCompressionLevel.DefaultCompression, ZlibWindowBits.Zlib, ZlibCompressionStrategy.Default)
{
}

public ZlibEncoder(ZlibCompressionLevel compressionLevel, ZlibWindowBits windowBits, ZlibCompressionStrategy strategy)
{
this.CompressionLevel = compressionLevel;
this.WindowBits = windowBits;
this.Strategy = strategy;
}

/// <summary>
/// Gets or sets the compression level to use to compress the file.
/// </summary>
public ZlibCompressionLevel CompressionLevel { get; set; }

/// <summary>
/// Gets or sets the window bits to use to compress the data.
/// </summary>
public ZlibWindowBits WindowBits { get; set; }

/// <summary>
/// Gets or sets the compression strategy to use to compress the data.
/// </summary>
public ZlibCompressionStrategy Strategy { get; set; }

/// <summary>
/// Tries to compress a file using the user specified compression level.
/// </summary>
/// <param name="sourcePath">The path to the file to compress.</param>
/// <param name="dest">The compressed data buffer.</param>
/// <param name="result">
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
///
/// If the compression failed this is set to <see langword="null"/>.
/// </param>
/// <returns>
/// <see langword="true"/> if the compression was a success, <see langword="false"/> otherwise.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryCompress(string sourcePath, Span<byte> dest, out ZlibResult? result)
=> this.TryCompress(File.ReadAllBytes(sourcePath), dest, out result);

/// <summary>
/// Tries to compress the data using the user specified compression level.
/// </summary>
/// <param name="source">The input data buffer.</param>
/// <param name="dest">The compressed data buffer.</param>
/// <param name="result">
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
///
/// If the compression failed this is set to <see langword="null"/>.
/// </param>
/// <returns>
/// <see langword="true"/> if the compression was a success, <see langword="false"/> otherwise.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryCompress(ReadOnlySpan<byte> source, Span<byte> dest, out ZlibResult? result)
{
try
{
result = this.Compress(source, dest);
return true;
}
catch (NotPackableException)
{
result = null;
return false;
}
}

/// <summary>
/// Compresses a file using the user specified compression level.
/// </summary>
/// <param name="sourcePath">The path to the file to compress.</param>
/// <param name="dest">The compressed data buffer.</param>
/// <exception cref="NotPackableException">
/// Thrown when zlib errors internally in any way.
/// </exception>
/// <returns>
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ZlibResult Compress(string sourcePath, Span<byte> dest)
=> this.Compress(File.ReadAllBytes(sourcePath), dest);

/// <summary>
/// Compresses data using the user specified compression level.
/// </summary>
/// <param name="source">The input data buffer.</param>
/// <param name="dest">The compressed data buffer.</param>
/// <exception cref="NotPackableException">
/// Thrown when zlib errors internally in any way.
/// </exception>
/// <returns>
/// The zlib result structure that contains the amount of bytes read, written,
/// and the adler32 hash of the data that can be used to compare the integrity
/// of the compressed/decompressed results.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ZlibResult Compress(ReadOnlySpan<byte> source, Span<byte> dest)
{
var bytesWritten = ZlibHelper.Compress(source, dest, this.CompressionLevel, this.WindowBits, this.Strategy, out var adler32, out var crc32, out var status);
return new(bytesWritten, 0, adler32, crc32, status);
}
}
14 changes: 13 additions & 1 deletion src/ZlibSharp/ZlibSharp/ZlibResult.cs
Original file line number Diff line number Diff line change
@@ -12,11 +12,13 @@ namespace ZlibSharp;
/// </summary>
public readonly struct ZlibResult
{
internal ZlibResult(uint bytesWritten, uint bytesRead, uint adler32)
internal ZlibResult(uint bytesWritten, uint bytesRead, uint adler32, uint crc32, ZlibStatus status)
{
this.BytesWritten = bytesWritten;
this.BytesRead = bytesRead;
this.Adler32 = adler32;
this.Crc32 = crc32;
this.Status = status;
}

/// <summary>
@@ -37,4 +39,14 @@ internal ZlibResult(uint bytesWritten, uint bytesRead, uint adler32)
/// The Adler32 checksum of the compressed/decompressed data.
/// </summary>
public uint Adler32 { get; }

/// <summary>
/// The Crc32 checksum of the compressed/decompressed data.
/// </summary>
public uint Crc32 { get; }

/// <summary>
/// The resulting status code from zlib.
/// </summary>
public ZlibStatus Status { get; }
}
49 changes: 49 additions & 0 deletions src/ZlibSharp/ZlibSharp/ZlibStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2021~2022, Els_kom org.
// https://github.com/Elskom/
// All rights reserved.
// license: MIT, see LICENSE for more details.

namespace ZlibSharp;

/// <summary>
/// The possible status codes from zlib.
/// </summary>
public enum ZlibStatus
{
/// <summary>
/// Version error.
/// </summary>
VersionError = -6,
/// <summary>
/// Buffer Error (usually under-allocated buffers).
/// </summary>
BufError,
/// <summary>
/// A memory error (out of memory).
/// </summary>
MemError,
/// <summary>
/// A data error (the data is possibly corrupted).
/// </summary>
DataError,
/// <summary>
/// A stream error.
/// </summary>
StreamError,
/// <summary>
/// Some other error.
/// </summary>
ErrNo,
/// <summary>
/// Everything is alright.
/// </summary>
Ok,
/// <summary>
/// End of stream.
/// </summary>
StreamEnd,
/// <summary>
/// Need dictionary.
/// </summary>
NeedDict,
}
4 changes: 4 additions & 0 deletions tests/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<Project>

<PropertyGroup>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="*-*" />
<PackageReference Include="xunit" Version="*-*" />
75 changes: 49 additions & 26 deletions tests/Test.cs
Original file line number Diff line number Diff line change
@@ -12,14 +12,18 @@ public class Test
{
private readonly byte[] sourceString, sourceStringCompressed, sourceBuffer;
private readonly int lengthOfCompressed;
private readonly ZlibEncoder encoder;
private readonly ZlibDecoder decoder;

public Test()
{
encoder = new ZlibEncoder();
decoder = new ZlibDecoder();
sourceString = File.ReadAllBytes("SourceText.txt");
// var destBuffer = new byte[sourceString.Length];
lengthOfCompressed = (int)MemoryZlib.GetCompressedSize(sourceString);
lengthOfCompressed = (int)Checks.GetCompressedSize(sourceString);
sourceStringCompressed = new byte[lengthOfCompressed];
_ = MemoryZlib.Compress(sourceString, sourceStringCompressed);
_ = encoder.Compress(sourceString, sourceStringCompressed);
// destBuffer.AsSpan(0, lengthOfCompressed).CopyTo(sourceStringCompressed);
sourceBuffer = new byte[sourceString.Length];
}
@@ -28,37 +32,54 @@ public Test()
public void CompressFileWorks()
{
var destBuffer = new byte[sourceString.Length];
var result = MemoryZlib.Compress("SourceText.txt", destBuffer, ZlibCompressionLevel.Level7);
encoder.CompressionLevel = ZlibCompressionLevel.Level7;
var result = encoder.Compress("SourceText.txt", destBuffer);
_ = result.BytesWritten.Should().BeGreaterThan(0);
_ = result.Adler32.Should().BeGreaterThan(0);
_ = result.Crc32.Should().BeGreaterThan(0);

// overwrite destBuffer to test TryCompress.
destBuffer = new byte[sourceString.Length];
encoder.TryCompress("SourceText.txt", destBuffer, out _).Should().BeTrue();
}

[Fact]
public void DecompressFileWorks()
{
var destBuffer = new byte[sourceString.Length];
var result = MemoryZlib.Decompress("CompressedText.txt", destBuffer);
var result = decoder.Decompress("CompressedText.txt", destBuffer);
_ = result.BytesRead.Should().BeGreaterThan(0);
_ = destBuffer.Should().Equal(sourceString);

// overwrite destBuffer to test TryDecompress.
destBuffer = new byte[sourceString.Length];
decoder.TryDecompress("CompressedText.txt", destBuffer, out _).Should().BeTrue();
}

[Fact]
public void DecompressionWorks()
{
var result = MemoryZlib.Decompress(sourceStringCompressed, sourceBuffer);
var result = decoder.Decompress(sourceStringCompressed, sourceBuffer);
_ = result.BytesRead.Should().Be(0);
_ = result.Adler32.Should().BeGreaterThan(0);
_ = result.Crc32.Should().BeGreaterThan(0);
_ = sourceBuffer.Should().Equal(sourceString);

// Test TryDecompress as well to ensure it returns true here.
decoder.TryDecompress(sourceStringCompressed, sourceBuffer, out _).Should().BeTrue();
}

[Fact]
public void DecompressionToUnderAllocatedFailure()
{
const int undersizedBufferLength = 69;
_ = undersizedBufferLength.Should().BeLessThan((int)MemoryZlib.GetDecompressedSize(sourceStringCompressed));
_ = undersizedBufferLength.Should().BeLessThan((int)Checks.GetDecompressedSize(sourceStringCompressed));
var undersizedDestBuffer = new byte[undersizedBufferLength];
Assert.Throws<NotUnpackableException>(
[ExcludeFromCodeCoverage] () => _ = MemoryZlib.Decompress(sourceStringCompressed, undersizedDestBuffer));
[ExcludeFromCodeCoverage] () => _ = decoder.Decompress(sourceStringCompressed, undersizedDestBuffer));

// Test TryDecompress as well to ensure it returns false here.
decoder.TryDecompress(sourceStringCompressed, undersizedDestBuffer, out _).Should().BeFalse();
}

[Fact]
@@ -67,73 +88,75 @@ public void CompressionToUnderAllocatedBufferReturnsNonZeroValue()
const int undersizedBufferLength = 69;
_ = undersizedBufferLength.Should().BeLessThan(lengthOfCompressed);
var undersizedDestBuffer = new byte[undersizedBufferLength];
_ = MemoryZlib.Compress(sourceStringCompressed, undersizedDestBuffer).BytesWritten.Should().NotBe(0);
_ = encoder.Compress(sourceStringCompressed, undersizedDestBuffer).BytesWritten.Should().NotBe(0);
encoder.TryCompress(sourceStringCompressed, undersizedDestBuffer, out _).Should().BeTrue();
}

[Fact]
public void DecompressionToOverAllocatedBufferShouldHaveBytesWrittenEqualToSourceStringLength()
{
const uint oversizeBy = 69;
var sourceLength = MemoryZlib.GetDecompressedSize(sourceStringCompressed);
var sourceLength = Checks.GetDecompressedSize(sourceStringCompressed);
var oversizedDestBuffer = new byte[sourceLength + oversizeBy];
var result = MemoryZlib.Decompress(sourceStringCompressed, oversizedDestBuffer);
var result = decoder.Decompress(sourceStringCompressed, oversizedDestBuffer);
_ = result.BytesRead.Should().Be(0);
_ = result.BytesWritten.Should().Be(sourceLength);
}

[Fact]
public void ZlibSharpVersionWorks()
=> _ = MemoryZlib.ZlibSharpVersion().Should().Be("1.2.13.3");
=> _ = Checks.ZlibSharpVersion().Should().Be("1.2.13.4");

[Fact]
public void ZlibVersionWorks()
=> _ = MemoryZlib.ZlibVersion().Should().Be(MemoryZlib.NativeZlibVersion);
=> _ = Checks.ZlibVersion().Should().Be(Checks.NativeZlibVersion);

[Fact]
public void IsCompressedByZlibWorksAndIsFalse()
=> _ = MemoryZlib.IsCompressedByZlib("SourceText.txt").Should().BeFalse();
=> _ = Checks.IsCompressedByZlib("SourceText.txt").Should().BeFalse();

[Fact]
public void IsCompressedByZlibWorksAndIsTrue()
=> _ = MemoryZlib.IsCompressedByZlib("CompressedText.txt").Should().BeTrue();
=> _ = Checks.IsCompressedByZlib("CompressedText.txt").Should().BeTrue();

[Fact]
public void IsCompressedByZlibFailure()
=> _ = Assert.Throws<ArgumentNullException>(
[ExcludeFromCodeCoverage] () => _ = MemoryZlib.IsCompressedByZlib(Array.Empty<byte>()));
[ExcludeFromCodeCoverage] () => _ = Checks.IsCompressedByZlib(Array.Empty<byte>()));

[Fact]
public void IsCompressedByGZipWorksAndIsFalse()
=> _ = MemoryZlib.IsCompressedByGZip("SourceText.txt").Should().BeFalse();
=> _ = Checks.IsCompressedByGZip("SourceText.txt").Should().BeFalse();

[Fact]
public void IsCompressedByGZipWorksAndIsTrue()
{
var destBuffer = new byte[MemoryZlib.GetCompressedSize(sourceString)];
_ = MemoryZlib.Compress(sourceString, destBuffer, windowBits: ZlibWindowBits.GZip);
_ = MemoryZlib.IsCompressedByGZip(destBuffer).Should().BeTrue();
var destBuffer = new byte[Checks.GetCompressedSize(sourceString)];
encoder.WindowBits = ZlibWindowBits.GZip;
_ = encoder.Compress(sourceString, destBuffer);
_ = Checks.IsCompressedByGZip(destBuffer).Should().BeTrue();
}

[Fact]
public void IsCompressedByGZipFailure()
=> _ = Assert.Throws<ArgumentNullException>(
[ExcludeFromCodeCoverage] () => _ = MemoryZlib.IsCompressedByGZip(Array.Empty<byte>()));
[ExcludeFromCodeCoverage] () => _ = Checks.IsCompressedByGZip(Array.Empty<byte>()));

[Fact]
public void NativeZlibVersionWorks()
{
var testZlibVersion = "1.2.13";
var originalVersion = MemoryZlib.NativeZlibVersion;
MemoryZlib.NativeZlibVersion = testZlibVersion;
_ = MemoryZlib.NativeZlibVersion.Should().Be(testZlibVersion);
MemoryZlib.NativeZlibVersion = originalVersion;
var originalVersion = Checks.NativeZlibVersion;
Checks.NativeZlibVersion = testZlibVersion;
_ = Checks.NativeZlibVersion.Should().Be(testZlibVersion);
Checks.NativeZlibVersion = originalVersion;
}

[Fact]
public void GetAdler32Works()
=> _ = MemoryZlib.ZlibGetAdler32(File.ReadAllBytes("SourceText.txt")).Should().Be(2150767711UL);
=> _ = Checks.ZlibGetAdler32(File.ReadAllBytes("SourceText.txt")).Should().Be(2150767711UL);

[Fact]
public void GetCrc32Works()
=> _ = MemoryZlib.ZlibGetCrc32(File.ReadAllBytes("SourceText.txt")).Should().Be(739290345UL);
=> _ = Checks.ZlibGetCrc32(File.ReadAllBytes("SourceText.txt")).Should().Be(739290345UL);
}

0 comments on commit 2c453fd

Please sign in to comment.