diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs
index 77dc4abb..16eb8e1a 100644
--- a/src/SharpCompress/Common/Zip/ZipFilePart.cs
+++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs
@@ -13,8 +13,8 @@ using SharpCompress.Compressors.PPMd;
using SharpCompress.Compressors.Reduce;
using SharpCompress.Compressors.Shrink;
using SharpCompress.Compressors.Xz;
+using SharpCompress.Compressors.ZStandard;
using SharpCompress.IO;
-using ZstdSharp;
namespace SharpCompress.Common.Zip;
diff --git a/src/SharpCompress/Compressors/LZMA/Registry.cs b/src/SharpCompress/Compressors/LZMA/Registry.cs
index eb3e3bdd..d71abded 100644
--- a/src/SharpCompress/Compressors/LZMA/Registry.cs
+++ b/src/SharpCompress/Compressors/LZMA/Registry.cs
@@ -7,7 +7,7 @@ using SharpCompress.Compressors.Deflate;
using SharpCompress.Compressors.Filters;
using SharpCompress.Compressors.LZMA.Utilites;
using SharpCompress.Compressors.PPMd;
-using ZstdSharp;
+using SharpCompress.Compressors.ZStandard;
namespace SharpCompress.Compressors.LZMA;
diff --git a/src/SharpCompress/Compressors/ZStandard/BitOperations.cs b/src/SharpCompress/Compressors/ZStandard/BitOperations.cs
new file mode 100644
index 00000000..fc8e3108
--- /dev/null
+++ b/src/SharpCompress/Compressors/ZStandard/BitOperations.cs
@@ -0,0 +1,311 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if !NETCOREAPP3_0_OR_GREATER
+
+using System.Runtime.CompilerServices;
+using static SharpCompress.Compressors.ZStandard.UnsafeHelper;
+
+// Some routines inspired by the Stanford Bit Twiddling Hacks by Sean Eron Anderson:
+// http://graphics.stanford.edu/~seander/bithacks.html
+
+namespace System.Numerics
+{
+ ///
+ /// Utility methods for intrinsic bit-twiddling operations.
+ /// The methods use hardware intrinsics when available on the underlying platform,
+ /// otherwise they use optimized software fallbacks.
+ ///
+ public static unsafe class BitOperations
+ {
+ // hack: should be public because of inline
+ public static readonly byte* TrailingZeroCountDeBruijn = GetArrayPointer(
+ new byte[]
+ {
+ 00,
+ 01,
+ 28,
+ 02,
+ 29,
+ 14,
+ 24,
+ 03,
+ 30,
+ 22,
+ 20,
+ 15,
+ 25,
+ 17,
+ 04,
+ 08,
+ 31,
+ 27,
+ 13,
+ 23,
+ 21,
+ 19,
+ 16,
+ 07,
+ 26,
+ 12,
+ 18,
+ 06,
+ 11,
+ 05,
+ 10,
+ 09,
+ }
+ );
+
+ // hack: should be public because of inline
+ public static readonly byte* Log2DeBruijn = GetArrayPointer(
+ new byte[]
+ {
+ 00,
+ 09,
+ 01,
+ 10,
+ 13,
+ 21,
+ 02,
+ 29,
+ 11,
+ 14,
+ 16,
+ 18,
+ 22,
+ 25,
+ 03,
+ 30,
+ 08,
+ 12,
+ 20,
+ 28,
+ 15,
+ 17,
+ 24,
+ 07,
+ 19,
+ 27,
+ 23,
+ 06,
+ 26,
+ 05,
+ 04,
+ 31,
+ }
+ );
+
+ ///
+ /// Returns the integer (floor) log of the specified value, base 2.
+ /// Note that by convention, input value 0 returns 0 since log(0) is undefined.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Log2(uint value)
+ {
+ // The 0->0 contract is fulfilled by setting the LSB to 1.
+ // Log(1) is 0, and setting the LSB for values > 1 does not change the log2 result.
+ value |= 1;
+
+ // value lzcnt actual expected
+ // ..0001 31 31-31 0
+ // ..0010 30 31-30 1
+ // 0010.. 2 31-2 29
+ // 0100.. 1 31-1 30
+ // 1000.. 0 31-0 31
+
+ // Fallback contract is 0->0
+ // No AggressiveInlining due to large method size
+ // Has conventional contract 0->0 (Log(0) is undefined)
+
+ // Fill trailing zeros with ones, eg 00010010 becomes 00011111
+ value |= value >> 01;
+ value |= value >> 02;
+ value |= value >> 04;
+ value |= value >> 08;
+ value |= value >> 16;
+
+ // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check
+ return Log2DeBruijn[
+ // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_1100_0100_1010_1100_1101_1101u
+ (int)((value * 0x07C4ACDDu) >> 27)
+ ];
+ }
+
+ ///
+ /// Returns the integer (floor) log of the specified value, base 2.
+ /// Note that by convention, input value 0 returns 0 since log(0) is undefined.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Log2(ulong value)
+ {
+ value |= 1;
+
+ uint hi = (uint)(value >> 32);
+
+ if (hi == 0)
+ {
+ return Log2((uint)value);
+ }
+
+ return 32 + Log2(hi);
+ }
+
+ ///
+ /// Count the number of trailing zero bits in an integer value.
+ /// Similar in behavior to the x86 instruction TZCNT.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int TrailingZeroCount(int value) => TrailingZeroCount((uint)value);
+
+ ///
+ /// Count the number of trailing zero bits in an integer value.
+ /// Similar in behavior to the x86 instruction TZCNT.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int TrailingZeroCount(uint value)
+ {
+ // Unguarded fallback contract is 0->0, BSF contract is 0->undefined
+ if (value == 0)
+ {
+ return 32;
+ }
+
+ // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check
+ return TrailingZeroCountDeBruijn[
+ // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_0111_1100_1011_0101_0011_0001u
+ (int)(((value & (uint)-(int)value) * 0x077CB531u) >> 27)
+ ]; // Multi-cast mitigates redundant conv.u8
+ }
+
+ ///
+ /// Count the number of trailing zero bits in a mask.
+ /// Similar in behavior to the x86 instruction TZCNT.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int TrailingZeroCount(long value) => TrailingZeroCount((ulong)value);
+
+ ///
+ /// Count the number of trailing zero bits in a mask.
+ /// Similar in behavior to the x86 instruction TZCNT.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int TrailingZeroCount(ulong value)
+ {
+ uint lo = (uint)value;
+
+ if (lo == 0)
+ {
+ return 32 + TrailingZeroCount((uint)(value >> 32));
+ }
+
+ return TrailingZeroCount(lo);
+ }
+
+ ///
+ /// Rotates the specified value left by the specified number of bits.
+ /// Similar in behavior to the x86 instruction ROL.
+ ///
+ /// The value to rotate.
+ /// The number of bits to rotate by.
+ /// Any value outside the range [0..31] is treated as congruent mod 32.
+ /// The rotated value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint RotateLeft(uint value, int offset) =>
+ (value << offset) | (value >> (32 - offset));
+
+ ///
+ /// Rotates the specified value left by the specified number of bits.
+ /// Similar in behavior to the x86 instruction ROL.
+ ///
+ /// The value to rotate.
+ /// The number of bits to rotate by.
+ /// Any value outside the range [0..63] is treated as congruent mod 64.
+ /// The rotated value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ulong RotateLeft(ulong value, int offset) =>
+ (value << offset) | (value >> (64 - offset));
+
+ ///
+ /// Rotates the specified value right by the specified number of bits.
+ /// Similar in behavior to the x86 instruction ROR.
+ ///
+ /// The value to rotate.
+ /// The number of bits to rotate by.
+ /// Any value outside the range [0..31] is treated as congruent mod 32.
+ /// The rotated value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint RotateRight(uint value, int offset) =>
+ (value >> offset) | (value << (32 - offset));
+
+ ///
+ /// Rotates the specified value right by the specified number of bits.
+ /// Similar in behavior to the x86 instruction ROR.
+ ///
+ /// The value to rotate.
+ /// The number of bits to rotate by.
+ /// Any value outside the range [0..63] is treated as congruent mod 64.
+ /// The rotated value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ulong RotateRight(ulong value, int offset) =>
+ (value >> offset) | (value << (64 - offset));
+
+ ///
+ /// Count the number of leading zero bits in a mask.
+ /// Similar in behavior to the x86 instruction LZCNT.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LeadingZeroCount(uint value)
+ {
+ // Unguarded fallback contract is 0->31, BSR contract is 0->undefined
+ if (value == 0)
+ {
+ return 32;
+ }
+
+ // No AggressiveInlining due to large method size
+ // Has conventional contract 0->0 (Log(0) is undefined)
+
+ // Fill trailing zeros with ones, eg 00010010 becomes 00011111
+ value |= value >> 01;
+ value |= value >> 02;
+ value |= value >> 04;
+ value |= value >> 08;
+ value |= value >> 16;
+
+ // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check
+ return 31
+ ^ Log2DeBruijn[
+ // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here
+ (int)((value * 0x07C4ACDDu) >> 27)
+ ];
+ }
+
+ ///
+ /// Count the number of leading zero bits in a mask.
+ /// Similar in behavior to the x86 instruction LZCNT.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LeadingZeroCount(ulong value)
+ {
+ uint hi = (uint)(value >> 32);
+
+ if (hi == 0)
+ {
+ return 32 + LeadingZeroCount((uint)value);
+ }
+
+ return LeadingZeroCount(hi);
+ }
+ }
+}
+
+#endif
diff --git a/src/SharpCompress/Compressors/ZStandard/CompressionStream.cs b/src/SharpCompress/Compressors/ZStandard/CompressionStream.cs
new file mode 100644
index 00000000..92de03b3
--- /dev/null
+++ b/src/SharpCompress/Compressors/ZStandard/CompressionStream.cs
@@ -0,0 +1,301 @@
+using System;
+using System.Buffers;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using SharpCompress.Compressors.ZStandard.Unsafe;
+
+namespace SharpCompress.Compressors.ZStandard;
+
+public class CompressionStream : Stream
+{
+ private readonly Stream innerStream;
+ private readonly byte[] outputBuffer;
+ private readonly bool preserveCompressor;
+ private readonly bool leaveOpen;
+ private Compressor? compressor;
+ private ZSTD_outBuffer_s output;
+
+ public CompressionStream(
+ Stream stream,
+ int level = Compressor.DefaultCompressionLevel,
+ int bufferSize = 0,
+ bool leaveOpen = true
+ )
+ : this(stream, new Compressor(level), bufferSize, false, leaveOpen) { }
+
+ public CompressionStream(
+ Stream stream,
+ Compressor compressor,
+ int bufferSize = 0,
+ bool preserveCompressor = true,
+ bool leaveOpen = true
+ )
+ {
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream));
+
+ if (!stream.CanWrite)
+ throw new ArgumentException("Stream is not writable", nameof(stream));
+
+ if (bufferSize < 0)
+ throw new ArgumentOutOfRangeException(nameof(bufferSize));
+
+ innerStream = stream;
+ this.compressor = compressor;
+ this.preserveCompressor = preserveCompressor;
+ this.leaveOpen = leaveOpen;
+
+ var outputBufferSize =
+ bufferSize > 0
+ ? bufferSize
+ : (int)Unsafe.Methods.ZSTD_CStreamOutSize().EnsureZstdSuccess();
+ outputBuffer = ArrayPool.Shared.Rent(outputBufferSize);
+ output = new ZSTD_outBuffer_s { pos = 0, size = (nuint)outputBufferSize };
+ }
+
+ public void SetParameter(ZSTD_cParameter parameter, int value)
+ {
+ EnsureNotDisposed();
+ compressor.NotNull().SetParameter(parameter, value);
+ }
+
+ public int GetParameter(ZSTD_cParameter parameter)
+ {
+ EnsureNotDisposed();
+ return compressor.NotNull().GetParameter(parameter);
+ }
+
+ public void LoadDictionary(byte[] dict)
+ {
+ EnsureNotDisposed();
+ compressor.NotNull().LoadDictionary(dict);
+ }
+
+ ~CompressionStream() => Dispose(false);
+
+#if !NETSTANDARD2_0 && !NETFRAMEWORK
+ public override async ValueTask DisposeAsync()
+#else
+ public async Task DisposeAsync()
+#endif
+ {
+ if (compressor == null)
+ return;
+
+ try
+ {
+ await FlushInternalAsync(ZSTD_EndDirective.ZSTD_e_end).ConfigureAwait(false);
+ }
+ finally
+ {
+ ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (compressor == null)
+ return;
+
+ try
+ {
+ if (disposing)
+ FlushInternal(ZSTD_EndDirective.ZSTD_e_end);
+ }
+ finally
+ {
+ ReleaseUnmanagedResources();
+ }
+ }
+
+ private void ReleaseUnmanagedResources()
+ {
+ if (!preserveCompressor)
+ {
+ compressor.NotNull().Dispose();
+ }
+ compressor = null;
+
+ if (outputBuffer != null)
+ {
+ ArrayPool.Shared.Return(outputBuffer);
+ }
+
+ if (!leaveOpen)
+ {
+ innerStream.Dispose();
+ }
+ }
+
+ public override void Flush() => FlushInternal(ZSTD_EndDirective.ZSTD_e_flush);
+
+ public override async Task FlushAsync(CancellationToken cancellationToken) =>
+ await FlushInternalAsync(ZSTD_EndDirective.ZSTD_e_flush, cancellationToken)
+ .ConfigureAwait(false);
+
+ private void FlushInternal(ZSTD_EndDirective directive) => WriteInternal(null, directive);
+
+ private async Task FlushInternalAsync(
+ ZSTD_EndDirective directive,
+ CancellationToken cancellationToken = default
+ ) => await WriteInternalAsync(null, directive, cancellationToken).ConfigureAwait(false);
+
+ public override void Write(byte[] buffer, int offset, int count) =>
+ Write(new ReadOnlySpan(buffer, offset, count));
+
+#if !NETSTANDARD2_0 && !NETFRAMEWORK
+ public override void Write(ReadOnlySpan buffer) =>
+ WriteInternal(buffer, ZSTD_EndDirective.ZSTD_e_continue);
+#else
+ public void Write(ReadOnlySpan buffer) =>
+ WriteInternal(buffer, ZSTD_EndDirective.ZSTD_e_continue);
+#endif
+
+ private void WriteInternal(ReadOnlySpan buffer, ZSTD_EndDirective directive)
+ {
+ EnsureNotDisposed();
+
+ var input = new ZSTD_inBuffer_s
+ {
+ pos = 0,
+ size = buffer != null ? (nuint)buffer.Length : 0,
+ };
+ nuint remaining;
+ do
+ {
+ output.pos = 0;
+ remaining = CompressStream(ref input, buffer, directive);
+
+ var written = (int)output.pos;
+ if (written > 0)
+ innerStream.Write(outputBuffer, 0, written);
+ } while (
+ directive == ZSTD_EndDirective.ZSTD_e_continue ? input.pos < input.size : remaining > 0
+ );
+ }
+
+#if !NETSTANDARD2_0 && !NETFRAMEWORK
+ private async ValueTask WriteInternalAsync(
+ ReadOnlyMemory? buffer,
+ ZSTD_EndDirective directive,
+ CancellationToken cancellationToken = default
+ )
+#else
+ private async Task WriteInternalAsync(
+ ReadOnlyMemory? buffer,
+ ZSTD_EndDirective directive,
+ CancellationToken cancellationToken = default
+ )
+#endif
+
+ {
+ EnsureNotDisposed();
+
+ var input = new ZSTD_inBuffer_s
+ {
+ pos = 0,
+ size = buffer.HasValue ? (nuint)buffer.Value.Length : 0,
+ };
+ nuint remaining;
+ do
+ {
+ output.pos = 0;
+ remaining = CompressStream(
+ ref input,
+ buffer.HasValue ? buffer.Value.Span : null,
+ directive
+ );
+
+ var written = (int)output.pos;
+ if (written > 0)
+ await innerStream
+ .WriteAsync(outputBuffer, 0, written, cancellationToken)
+ .ConfigureAwait(false);
+ } while (
+ directive == ZSTD_EndDirective.ZSTD_e_continue ? input.pos < input.size : remaining > 0
+ );
+ }
+
+#if !NETSTANDARD2_0 && !NETFRAMEWORK
+
+ public override Task WriteAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken
+ ) => WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask();
+
+ public override async ValueTask WriteAsync(
+ ReadOnlyMemory buffer,
+ CancellationToken cancellationToken = default
+ ) =>
+ await WriteInternalAsync(buffer, ZSTD_EndDirective.ZSTD_e_continue, cancellationToken)
+ .ConfigureAwait(false);
+#else
+
+ public override Task WriteAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken
+ ) => WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken);
+
+ public async Task WriteAsync(
+ ReadOnlyMemory buffer,
+ CancellationToken cancellationToken = default
+ ) =>
+ await WriteInternalAsync(buffer, ZSTD_EndDirective.ZSTD_e_continue, cancellationToken)
+ .ConfigureAwait(false);
+#endif
+
+ internal unsafe nuint CompressStream(
+ ref ZSTD_inBuffer_s input,
+ ReadOnlySpan inputBuffer,
+ ZSTD_EndDirective directive
+ )
+ {
+ fixed (byte* inputBufferPtr = inputBuffer)
+ fixed (byte* outputBufferPtr = outputBuffer)
+ {
+ input.src = inputBufferPtr;
+ output.dst = outputBufferPtr;
+ return compressor
+ .NotNull()
+ .CompressStream(ref input, ref output, directive)
+ .EnsureZstdSuccess();
+ }
+ }
+
+ public override bool CanRead => false;
+ public override bool CanSeek => false;
+ public override bool CanWrite => true;
+
+ public override long Length => throw new NotSupportedException();
+
+ public override long Position
+ {
+ get => throw new NotSupportedException();
+ set => throw new NotSupportedException();
+ }
+
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+
+ public override void SetLength(long value) => throw new NotSupportedException();
+
+ public override int Read(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException();
+
+ private void EnsureNotDisposed()
+ {
+ if (compressor == null)
+ throw new ObjectDisposedException(nameof(CompressionStream));
+ }
+
+ public void SetPledgedSrcSize(ulong pledgedSrcSize)
+ {
+ EnsureNotDisposed();
+ compressor.NotNull().SetPledgedSrcSize(pledgedSrcSize);
+ }
+}
diff --git a/src/SharpCompress/Compressors/ZStandard/Compressor.cs b/src/SharpCompress/Compressors/ZStandard/Compressor.cs
new file mode 100644
index 00000000..66860601
--- /dev/null
+++ b/src/SharpCompress/Compressors/ZStandard/Compressor.cs
@@ -0,0 +1,204 @@
+using System;
+using SharpCompress.Compressors.ZStandard.Unsafe;
+
+namespace SharpCompress.Compressors.ZStandard;
+
+public unsafe class Compressor : IDisposable
+{
+ ///
+ /// Minimum negative compression level allowed
+ ///
+ public static int MinCompressionLevel => Unsafe.Methods.ZSTD_minCLevel();
+
+ ///
+ /// Maximum compression level available
+ ///
+ public static int MaxCompressionLevel => Unsafe.Methods.ZSTD_maxCLevel();
+
+ ///
+ /// Default compression level
+ ///
+ ///
+ public const int DefaultCompressionLevel = 3;
+
+ private int level = DefaultCompressionLevel;
+
+ private readonly SafeCctxHandle handle;
+
+ public int Level
+ {
+ get => level;
+ set
+ {
+ if (level != value)
+ {
+ level = value;
+ SetParameter(ZSTD_cParameter.ZSTD_c_compressionLevel, value);
+ }
+ }
+ }
+
+ public void SetParameter(ZSTD_cParameter parameter, int value)
+ {
+ using var cctx = handle.Acquire();
+ Unsafe.Methods.ZSTD_CCtx_setParameter(cctx, parameter, value).EnsureZstdSuccess();
+ }
+
+ public int GetParameter(ZSTD_cParameter parameter)
+ {
+ using var cctx = handle.Acquire();
+ int value;
+ Unsafe.Methods.ZSTD_CCtx_getParameter(cctx, parameter, &value).EnsureZstdSuccess();
+ return value;
+ }
+
+ public void LoadDictionary(byte[] dict)
+ {
+ var dictReadOnlySpan = new ReadOnlySpan(dict);
+ LoadDictionary(dictReadOnlySpan);
+ }
+
+ public void LoadDictionary(ReadOnlySpan dict)
+ {
+ using var cctx = handle.Acquire();
+ fixed (byte* dictPtr = dict)
+ Unsafe
+ .Methods.ZSTD_CCtx_loadDictionary(cctx, dictPtr, (nuint)dict.Length)
+ .EnsureZstdSuccess();
+ }
+
+ public Compressor(int level = DefaultCompressionLevel)
+ {
+ handle = SafeCctxHandle.Create();
+ Level = level;
+ }
+
+ public static int GetCompressBound(int length) =>
+ (int)Unsafe.Methods.ZSTD_compressBound((nuint)length);
+
+ public static ulong GetCompressBoundLong(ulong length) =>
+ Unsafe.Methods.ZSTD_compressBound((nuint)length);
+
+ public Span Wrap(ReadOnlySpan src)
+ {
+ var dest = new byte[GetCompressBound(src.Length)];
+ var length = Wrap(src, dest);
+ return new Span(dest, 0, length);
+ }
+
+ public int Wrap(byte[] src, byte[] dest, int offset) =>
+ Wrap(src, new Span(dest, offset, dest.Length - offset));
+
+ public int Wrap(ReadOnlySpan src, Span dest)
+ {
+ fixed (byte* srcPtr = src)
+ fixed (byte* destPtr = dest)
+ {
+ using var cctx = handle.Acquire();
+ return (int)
+ Unsafe
+ .Methods.ZSTD_compress2(
+ cctx,
+ destPtr,
+ (nuint)dest.Length,
+ srcPtr,
+ (nuint)src.Length
+ )
+ .EnsureZstdSuccess();
+ }
+ }
+
+ public int Wrap(ArraySegment src, ArraySegment dest) =>
+ Wrap((ReadOnlySpan)src, dest);
+
+ public int Wrap(
+ byte[] src,
+ int srcOffset,
+ int srcLength,
+ byte[] dst,
+ int dstOffset,
+ int dstLength
+ ) =>
+ Wrap(
+ new ReadOnlySpan(src, srcOffset, srcLength),
+ new Span(dst, dstOffset, dstLength)
+ );
+
+ public bool TryWrap(byte[] src, byte[] dest, int offset, out int written) =>
+ TryWrap(src, new Span(dest, offset, dest.Length - offset), out written);
+
+ public bool TryWrap(ReadOnlySpan src, Span dest, out int written)
+ {
+ fixed (byte* srcPtr = src)
+ fixed (byte* destPtr = dest)
+ {
+ nuint returnValue;
+ using (var cctx = handle.Acquire())
+ {
+ returnValue = Unsafe.Methods.ZSTD_compress2(
+ cctx,
+ destPtr,
+ (nuint)dest.Length,
+ srcPtr,
+ (nuint)src.Length
+ );
+ }
+
+ if (returnValue == unchecked(0 - (nuint)ZSTD_ErrorCode.ZSTD_error_dstSize_tooSmall))
+ {
+ written = default;
+ return false;
+ }
+
+ returnValue.EnsureZstdSuccess();
+ written = (int)returnValue;
+ return true;
+ }
+ }
+
+ public bool TryWrap(ArraySegment src, ArraySegment dest, out int written) =>
+ TryWrap((ReadOnlySpan)src, dest, out written);
+
+ public bool TryWrap(
+ byte[] src,
+ int srcOffset,
+ int srcLength,
+ byte[] dst,
+ int dstOffset,
+ int dstLength,
+ out int written
+ ) =>
+ TryWrap(
+ new ReadOnlySpan(src, srcOffset, srcLength),
+ new Span(dst, dstOffset, dstLength),
+ out written
+ );
+
+ public void Dispose()
+ {
+ handle.Dispose();
+ GC.SuppressFinalize(this);
+ }
+
+ internal nuint CompressStream(
+ ref ZSTD_inBuffer_s input,
+ ref ZSTD_outBuffer_s output,
+ ZSTD_EndDirective directive
+ )
+ {
+ fixed (ZSTD_inBuffer_s* inputPtr = &input)
+ fixed (ZSTD_outBuffer_s* outputPtr = &output)
+ {
+ using var cctx = handle.Acquire();
+ return Unsafe
+ .Methods.ZSTD_compressStream2(cctx, outputPtr, inputPtr, directive)
+ .EnsureZstdSuccess();
+ }
+ }
+
+ public void SetPledgedSrcSize(ulong pledgedSrcSize)
+ {
+ using var cctx = handle.Acquire();
+ Unsafe.Methods.ZSTD_CCtx_setPledgedSrcSize(cctx, pledgedSrcSize).EnsureZstdSuccess();
+ }
+}
diff --git a/src/SharpCompress/Compressors/ZStandard/Constants.cs b/src/SharpCompress/Compressors/ZStandard/Constants.cs
new file mode 100644
index 00000000..cce84fc0
--- /dev/null
+++ b/src/SharpCompress/Compressors/ZStandard/Constants.cs
@@ -0,0 +1,8 @@
+namespace SharpCompress.Compressors.ZStandard;
+
+internal class Constants
+{
+ //NOTE: https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element#remarks
+ //NOTE: https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/System.Private.CoreLib/src/System/Array.cs#L27
+ public const ulong MaxByteArrayLength = 0x7FFFFFC7;
+}
diff --git a/src/SharpCompress/Compressors/ZStandard/DecompressionStream.cs b/src/SharpCompress/Compressors/ZStandard/DecompressionStream.cs
new file mode 100644
index 00000000..9864a805
--- /dev/null
+++ b/src/SharpCompress/Compressors/ZStandard/DecompressionStream.cs
@@ -0,0 +1,293 @@
+using System;
+using System.Buffers;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using SharpCompress.Compressors.ZStandard.Unsafe;
+
+namespace SharpCompress.Compressors.ZStandard;
+
+public class DecompressionStream : Stream
+{
+ private readonly Stream innerStream;
+ private readonly byte[] inputBuffer;
+ private readonly int inputBufferSize;
+ private readonly bool preserveDecompressor;
+ private readonly bool leaveOpen;
+ private readonly bool checkEndOfStream;
+ private Decompressor? decompressor;
+ private ZSTD_inBuffer_s input;
+ private nuint lastDecompressResult = 0;
+ private bool contextDrained = true;
+
+ public DecompressionStream(
+ Stream stream,
+ int bufferSize = 0,
+ bool checkEndOfStream = true,
+ bool leaveOpen = true
+ )
+ : this(stream, new Decompressor(), bufferSize, checkEndOfStream, false, leaveOpen) { }
+
+ public DecompressionStream(
+ Stream stream,
+ Decompressor decompressor,
+ int bufferSize = 0,
+ bool checkEndOfStream = true,
+ bool preserveDecompressor = true,
+ bool leaveOpen = true
+ )
+ {
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream));
+
+ if (!stream.CanRead)
+ throw new ArgumentException("Stream is not readable", nameof(stream));
+
+ if (bufferSize < 0)
+ throw new ArgumentOutOfRangeException(nameof(bufferSize));
+
+ innerStream = stream;
+ this.decompressor = decompressor;
+ this.preserveDecompressor = preserveDecompressor;
+ this.leaveOpen = leaveOpen;
+ this.checkEndOfStream = checkEndOfStream;
+
+ inputBufferSize =
+ bufferSize > 0
+ ? bufferSize
+ : (int)Unsafe.Methods.ZSTD_DStreamInSize().EnsureZstdSuccess();
+ inputBuffer = ArrayPool.Shared.Rent(inputBufferSize);
+ input = new ZSTD_inBuffer_s { pos = (nuint)inputBufferSize, size = (nuint)inputBufferSize };
+ }
+
+ public void SetParameter(ZSTD_dParameter parameter, int value)
+ {
+ EnsureNotDisposed();
+ decompressor.NotNull().SetParameter(parameter, value);
+ }
+
+ public int GetParameter(ZSTD_dParameter parameter)
+ {
+ EnsureNotDisposed();
+ return decompressor.NotNull().GetParameter(parameter);
+ }
+
+ public void LoadDictionary(byte[] dict)
+ {
+ EnsureNotDisposed();
+ decompressor.NotNull().LoadDictionary(dict);
+ }
+
+ ~DecompressionStream() => Dispose(false);
+
+ protected override void Dispose(bool disposing)
+ {
+ if (decompressor == null)
+ return;
+
+ if (!preserveDecompressor)
+ {
+ decompressor.Dispose();
+ }
+ decompressor = null;
+
+ if (inputBuffer != null)
+ {
+ ArrayPool.Shared.Return(inputBuffer);
+ }
+
+ if (!leaveOpen)
+ {
+ innerStream.Dispose();
+ }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count) =>
+ Read(new Span(buffer, offset, count));
+
+#if !NETSTANDARD2_0 && !NETFRAMEWORK
+ public override int Read(Span buffer)
+#else
+ public int Read(Span buffer)
+#endif
+ {
+ EnsureNotDisposed();
+
+ // Guard against infinite loop (output.pos would never become non-zero)
+ if (buffer.Length == 0)
+ {
+ return 0;
+ }
+
+ var output = new ZSTD_outBuffer_s { pos = 0, size = (nuint)buffer.Length };
+ while (true)
+ {
+ // If there is still input available, or there might be data buffered in the decompressor context, flush that out
+ while (input.pos < input.size || !contextDrained)
+ {
+ nuint oldInputPos = input.pos;
+ nuint result = DecompressStream(ref output, buffer);
+ if (output.pos > 0 || oldInputPos != input.pos)
+ {
+ // Keep result from last decompress call that made some progress, so we known if we're at end of frame
+ lastDecompressResult = result;
+ }
+ // If decompression filled the output buffer, there might still be data buffered in the decompressor context
+ contextDrained = output.pos < output.size;
+ // If we have data to return, return it immediately, so we won't stall on Read
+ if (output.pos > 0)
+ {
+ return (int)output.pos;
+ }
+ }
+
+ // Otherwise, read some more input
+ int bytesRead;
+ if ((bytesRead = innerStream.Read(inputBuffer, 0, inputBufferSize)) == 0)
+ {
+ if (checkEndOfStream && lastDecompressResult != 0)
+ {
+ throw new EndOfStreamException("Premature end of stream");
+ }
+
+ return 0;
+ }
+
+ input.size = (nuint)bytesRead;
+ input.pos = 0;
+ }
+ }
+
+#if !NETSTANDARD2_0 && !NETFRAMEWORK
+ public override Task ReadAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken
+ ) => ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask();
+
+ public override async ValueTask ReadAsync(
+ Memory buffer,
+ CancellationToken cancellationToken = default
+ )
+#else
+
+ public override Task ReadAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken
+ ) => ReadAsync(new Memory(buffer, offset, count), cancellationToken);
+
+ public async Task ReadAsync(
+ Memory buffer,
+ CancellationToken cancellationToken = default
+ )
+#endif
+ {
+ EnsureNotDisposed();
+
+ // Guard against infinite loop (output.pos would never become non-zero)
+ if (buffer.Length == 0)
+ {
+ return 0;
+ }
+
+ var output = new ZSTD_outBuffer_s { pos = 0, size = (nuint)buffer.Length };
+ while (true)
+ {
+ // If there is still input available, or there might be data buffered in the decompressor context, flush that out
+ while (input.pos < input.size || !contextDrained)
+ {
+ nuint oldInputPos = input.pos;
+ nuint result = DecompressStream(ref output, buffer.Span);
+ if (output.pos > 0 || oldInputPos != input.pos)
+ {
+ // Keep result from last decompress call that made some progress, so we known if we're at end of frame
+ lastDecompressResult = result;
+ }
+ // If decompression filled the output buffer, there might still be data buffered in the decompressor context
+ contextDrained = output.pos < output.size;
+ // If we have data to return, return it immediately, so we won't stall on Read
+ if (output.pos > 0)
+ {
+ return (int)output.pos;
+ }
+ }
+
+ // Otherwise, read some more input
+ int bytesRead;
+ if (
+ (
+ bytesRead = await innerStream
+ .ReadAsync(inputBuffer, 0, inputBufferSize, cancellationToken)
+ .ConfigureAwait(false)
+ ) == 0
+ )
+ {
+ if (checkEndOfStream && lastDecompressResult != 0)
+ {
+ throw new EndOfStreamException("Premature end of stream");
+ }
+
+ return 0;
+ }
+
+ input.size = (nuint)bytesRead;
+ input.pos = 0;
+ }
+ }
+
+ private unsafe nuint DecompressStream(ref ZSTD_outBuffer_s output, Span outputBuffer)
+ {
+ fixed (byte* inputBufferPtr = inputBuffer)
+ fixed (byte* outputBufferPtr = outputBuffer)
+ {
+ input.src = inputBufferPtr;
+ output.dst = outputBufferPtr;
+ return decompressor.NotNull().DecompressStream(ref input, ref output);
+ }
+ }
+
+ 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 => throw new NotSupportedException();
+ set => throw new NotSupportedException();
+ }
+
+ public override void Flush() => throw new NotSupportedException();
+
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+
+ public override void SetLength(long value) => throw new NotSupportedException();
+
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException();
+
+ private void EnsureNotDisposed()
+ {
+ if (decompressor == null)
+ throw new ObjectDisposedException(nameof(DecompressionStream));
+ }
+
+#if NETSTANDARD2_0 || NETFRAMEWORK
+ public virtual Task DisposeAsync()
+ {
+ try
+ {
+ Dispose();
+ return Task.CompletedTask;
+ }
+ catch (Exception exc)
+ {
+ return Task.FromException(exc);
+ }
+ }
+#endif
+}
diff --git a/src/SharpCompress/Compressors/ZStandard/Decompressor.cs b/src/SharpCompress/Compressors/ZStandard/Decompressor.cs
new file mode 100644
index 00000000..8a63c4d9
--- /dev/null
+++ b/src/SharpCompress/Compressors/ZStandard/Decompressor.cs
@@ -0,0 +1,176 @@
+using System;
+using SharpCompress.Compressors.ZStandard.Unsafe;
+
+namespace SharpCompress.Compressors.ZStandard;
+
+public unsafe class Decompressor : IDisposable
+{
+ private readonly SafeDctxHandle handle;
+
+ public Decompressor()
+ {
+ handle = SafeDctxHandle.Create();
+ }
+
+ public void SetParameter(ZSTD_dParameter parameter, int value)
+ {
+ using var dctx = handle.Acquire();
+ Unsafe.Methods.ZSTD_DCtx_setParameter(dctx, parameter, value).EnsureZstdSuccess();
+ }
+
+ public int GetParameter(ZSTD_dParameter parameter)
+ {
+ using var dctx = handle.Acquire();
+ int value;
+ Unsafe.Methods.ZSTD_DCtx_getParameter(dctx, parameter, &value).EnsureZstdSuccess();
+ return value;
+ }
+
+ public void LoadDictionary(byte[] dict)
+ {
+ var dictReadOnlySpan = new ReadOnlySpan(dict);
+ this.LoadDictionary(dictReadOnlySpan);
+ }
+
+ public void LoadDictionary(ReadOnlySpan dict)
+ {
+ using var dctx = handle.Acquire();
+ fixed (byte* dictPtr = dict)
+ Unsafe
+ .Methods.ZSTD_DCtx_loadDictionary(dctx, dictPtr, (nuint)dict.Length)
+ .EnsureZstdSuccess();
+ }
+
+ public static ulong GetDecompressedSize(ReadOnlySpan src)
+ {
+ fixed (byte* srcPtr = src)
+ return Unsafe
+ .Methods.ZSTD_decompressBound(srcPtr, (nuint)src.Length)
+ .EnsureContentSizeOk();
+ }
+
+ public static ulong GetDecompressedSize(ArraySegment src) =>
+ GetDecompressedSize((ReadOnlySpan)src);
+
+ public static ulong GetDecompressedSize(byte[] src, int srcOffset, int srcLength) =>
+ GetDecompressedSize(new ReadOnlySpan(src, srcOffset, srcLength));
+
+ public Span Unwrap(ReadOnlySpan src, int maxDecompressedSize = int.MaxValue)
+ {
+ var expectedDstSize = GetDecompressedSize(src);
+ if (expectedDstSize > (ulong)maxDecompressedSize)
+ throw new ZstdException(
+ ZSTD_ErrorCode.ZSTD_error_dstSize_tooSmall,
+ $"Decompressed content size {expectedDstSize} is greater than {nameof(maxDecompressedSize)} {maxDecompressedSize}"
+ );
+ if (expectedDstSize > Constants.MaxByteArrayLength)
+ throw new ZstdException(
+ ZSTD_ErrorCode.ZSTD_error_dstSize_tooSmall,
+ $"Decompressed content size {expectedDstSize} is greater than max possible byte array size {Constants.MaxByteArrayLength}"
+ );
+
+ var dest = new byte[expectedDstSize];
+ var length = Unwrap(src, dest);
+ return new Span(dest, 0, length);
+ }
+
+ public int Unwrap(byte[] src, byte[] dest, int offset) =>
+ Unwrap(src, new Span(dest, offset, dest.Length - offset));
+
+ public int Unwrap(ReadOnlySpan src, Span dest)
+ {
+ fixed (byte* srcPtr = src)
+ fixed (byte* destPtr = dest)
+ {
+ using var dctx = handle.Acquire();
+ return (int)
+ Unsafe
+ .Methods.ZSTD_decompressDCtx(
+ dctx,
+ destPtr,
+ (nuint)dest.Length,
+ srcPtr,
+ (nuint)src.Length
+ )
+ .EnsureZstdSuccess();
+ }
+ }
+
+ public int Unwrap(
+ byte[] src,
+ int srcOffset,
+ int srcLength,
+ byte[] dst,
+ int dstOffset,
+ int dstLength
+ ) =>
+ Unwrap(
+ new ReadOnlySpan(src, srcOffset, srcLength),
+ new Span(dst, dstOffset, dstLength)
+ );
+
+ public bool TryUnwrap(byte[] src, byte[] dest, int offset, out int written) =>
+ TryUnwrap(src, new Span(dest, offset, dest.Length - offset), out written);
+
+ public bool TryUnwrap(ReadOnlySpan src, Span dest, out int written)
+ {
+ fixed (byte* srcPtr = src)
+ fixed (byte* destPtr = dest)
+ {
+ nuint returnValue;
+ using (var dctx = handle.Acquire())
+ {
+ returnValue = Unsafe.Methods.ZSTD_decompressDCtx(
+ dctx,
+ destPtr,
+ (nuint)dest.Length,
+ srcPtr,
+ (nuint)src.Length
+ );
+ }
+
+ if (returnValue == unchecked(0 - (nuint)ZSTD_ErrorCode.ZSTD_error_dstSize_tooSmall))
+ {
+ written = default;
+ return false;
+ }
+
+ returnValue.EnsureZstdSuccess();
+ written = (int)returnValue;
+ return true;
+ }
+ }
+
+ public bool TryUnwrap(
+ byte[] src,
+ int srcOffset,
+ int srcLength,
+ byte[] dst,
+ int dstOffset,
+ int dstLength,
+ out int written
+ ) =>
+ TryUnwrap(
+ new ReadOnlySpan(src, srcOffset, srcLength),
+ new Span(dst, dstOffset, dstLength),
+ out written
+ );
+
+ public void Dispose()
+ {
+ handle.Dispose();
+ GC.SuppressFinalize(this);
+ }
+
+ internal nuint DecompressStream(ref ZSTD_inBuffer_s input, ref ZSTD_outBuffer_s output)
+ {
+ fixed (ZSTD_inBuffer_s* inputPtr = &input)
+ fixed (ZSTD_outBuffer_s* outputPtr = &output)
+ {
+ using var dctx = handle.Acquire();
+ return Unsafe
+ .Methods.ZSTD_decompressStream(dctx, outputPtr, inputPtr)
+ .EnsureZstdSuccess();
+ }
+ }
+}
diff --git a/src/SharpCompress/Compressors/ZStandard/JobThreadPool.cs b/src/SharpCompress/Compressors/ZStandard/JobThreadPool.cs
new file mode 100644
index 00000000..e783940d
--- /dev/null
+++ b/src/SharpCompress/Compressors/ZStandard/JobThreadPool.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace SharpCompress.Compressors.ZStandard;
+
+internal unsafe class JobThreadPool : IDisposable
+{
+ private int numThreads;
+ private readonly List threads;
+ private readonly BlockingCollection queue;
+
+ private struct Job
+ {
+ public void* function;
+ public void* opaque;
+ }
+
+ private class JobThread
+ {
+ private Thread Thread { get; }
+ public CancellationTokenSource CancellationTokenSource { get; }
+
+ public JobThread(Thread thread)
+ {
+ CancellationTokenSource = new CancellationTokenSource();
+ Thread = thread;
+ }
+
+ public void Start()
+ {
+ Thread.Start(this);
+ }
+
+ public void Cancel()
+ {
+ CancellationTokenSource.Cancel();
+ }
+
+ public void Join()
+ {
+ Thread.Join();
+ }
+ }
+
+ private void Worker(object? obj)
+ {
+ if (obj is not JobThread poolThread)
+ return;
+
+ var cancellationToken = poolThread.CancellationTokenSource.Token;
+ while (!queue.IsCompleted && !cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ if (queue.TryTake(out var job, -1, cancellationToken))
+ ((delegate* managed)job.function)(job.opaque);
+ }
+ catch (InvalidOperationException) { }
+ catch (OperationCanceledException) { }
+ }
+ }
+
+ public JobThreadPool(int num, int queueSize)
+ {
+ numThreads = num;
+ queue = new BlockingCollection(queueSize + 1);
+ threads = new List(num);
+ for (var i = 0; i < numThreads; i++)
+ CreateThread();
+ }
+
+ private void CreateThread()
+ {
+ var poolThread = new JobThread(new Thread(Worker));
+ threads.Add(poolThread);
+ poolThread.Start();
+ }
+
+ public void Resize(int num)
+ {
+ lock (threads)
+ {
+ if (num < numThreads)
+ {
+ for (var i = numThreads - 1; i >= num; i--)
+ {
+ threads[i].Cancel();
+ threads.RemoveAt(i);
+ }
+ }
+ else
+ {
+ for (var i = numThreads; i < num; i++)
+ CreateThread();
+ }
+ }
+
+ numThreads = num;
+ }
+
+ public void Add(void* function, void* opaque)
+ {
+ queue.Add(new Job { function = function, opaque = opaque });
+ }
+
+ public bool TryAdd(void* function, void* opaque)
+ {
+ return queue.TryAdd(new Job { function = function, opaque = opaque });
+ }
+
+ public void Join(bool cancel = true)
+ {
+ queue.CompleteAdding();
+ List jobThreads;
+ lock (threads)
+ jobThreads = new List(threads);
+
+ if (cancel)
+ {
+ foreach (var thread in jobThreads)
+ thread.Cancel();
+ }
+
+ foreach (var thread in jobThreads)
+ thread.Join();
+ }
+
+ public void Dispose()
+ {
+ queue.Dispose();
+ }
+
+ public int Size()
+ {
+ // todo not implemented
+ // https://github.com/dotnet/runtime/issues/24200
+ return 0;
+ }
+}
diff --git a/src/SharpCompress/Compressors/ZStandard/SafeHandles.cs b/src/SharpCompress/Compressors/ZStandard/SafeHandles.cs
new file mode 100644
index 00000000..3b49bdce
--- /dev/null
+++ b/src/SharpCompress/Compressors/ZStandard/SafeHandles.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Runtime.InteropServices;
+using SharpCompress.Compressors.ZStandard.Unsafe;
+
+namespace SharpCompress.Compressors.ZStandard;
+
+///
+/// Provides the base class for ZstdSharp implementations.
+///
+///
+/// Even though ZstdSharp is a managed library, its internals are using unmanaged
+/// memory and we are using safe handles in the library's high-level API to ensure
+/// proper disposal of unmanaged resources and increase safety.
+///
+///
+///
+internal abstract unsafe class SafeZstdHandle : SafeHandle
+{
+ ///
+ /// Parameterless constructor is hidden. Use the static Create factory
+ /// method to create a new safe handle instance.
+ ///
+ protected SafeZstdHandle()
+ : base(IntPtr.Zero, true) { }
+
+ public sealed override bool IsInvalid => handle == IntPtr.Zero;
+}
+
+///
+/// Safely wraps an unmanaged Zstd compression context.
+///
+internal sealed unsafe class SafeCctxHandle : SafeZstdHandle
+{
+ ///
+ private SafeCctxHandle() { }
+
+ ///
+ /// Creates a new instance of .
+ ///
+ ///
+ /// Creation failed.
+ public static SafeCctxHandle Create()
+ {
+ var safeHandle = new SafeCctxHandle();
+ bool success = false;
+ try
+ {
+ var cctx = Unsafe.Methods.ZSTD_createCCtx();
+ if (cctx == null)
+ throw new ZstdException(ZSTD_ErrorCode.ZSTD_error_GENERIC, "Failed to create cctx");
+ safeHandle.SetHandle((IntPtr)cctx);
+ success = true;
+ }
+ finally
+ {
+ if (!success)
+ {
+ safeHandle.SetHandleAsInvalid();
+ }
+ }
+ return safeHandle;
+ }
+
+ ///
+ /// Acquires a reference to the safe handle.
+ ///
+ ///
+ /// A instance that can be implicitly converted to a pointer
+ /// to .
+ ///
+ public SafeHandleHolder Acquire() => new(this);
+
+ protected override bool ReleaseHandle()
+ {
+ return Unsafe.Methods.ZSTD_freeCCtx((ZSTD_CCtx_s*)handle) == 0;
+ }
+}
+
+///
+/// Safely wraps an unmanaged Zstd compression context.
+///
+internal sealed unsafe class SafeDctxHandle : SafeZstdHandle
+{
+ ///
+ private SafeDctxHandle() { }
+
+ ///
+ /// Creates a new instance of .
+ ///
+ ///
+ /// Creation failed.
+ public static SafeDctxHandle Create()
+ {
+ var safeHandle = new SafeDctxHandle();
+ bool success = false;
+ try
+ {
+ var dctx = Unsafe.Methods.ZSTD_createDCtx();
+ if (dctx == null)
+ throw new ZstdException(ZSTD_ErrorCode.ZSTD_error_GENERIC, "Failed to create dctx");
+ safeHandle.SetHandle((IntPtr)dctx);
+ success = true;
+ }
+ finally
+ {
+ if (!success)
+ {
+ safeHandle.SetHandleAsInvalid();
+ }
+ }
+ return safeHandle;
+ }
+
+ ///
+ /// Acquires a reference to the safe handle.
+ ///
+ ///
+ /// A instance that can be implicitly converted to a pointer
+ /// to .
+ ///
+ public SafeHandleHolder Acquire() => new(this);
+
+ protected override bool ReleaseHandle()
+ {
+ return Unsafe.Methods.ZSTD_freeDCtx((ZSTD_DCtx_s*)handle) == 0;
+ }
+}
+
+///
+/// Provides a convenient interface to safely acquire pointers of a specific type
+/// from a , by utilizing blocks.
+///
+/// The type of pointers to return.
+///
+/// Safe handle holders can be d to decrement the safe handle's
+/// reference count, and can be implicitly converted to pointers to .
+///
+internal unsafe ref struct SafeHandleHolder
+ where T : unmanaged
+{
+ private readonly SafeHandle _handle;
+
+ private bool _refAdded;
+
+ public SafeHandleHolder(SafeHandle safeHandle)
+ {
+ _handle = safeHandle;
+ _refAdded = false;
+ safeHandle.DangerousAddRef(ref _refAdded);
+ }
+
+ public static implicit operator T*(SafeHandleHolder holder) =>
+ (T*)holder._handle.DangerousGetHandle();
+
+ public void Dispose()
+ {
+ if (_refAdded)
+ {
+ _handle.DangerousRelease();
+ _refAdded = false;
+ }
+ }
+}
diff --git a/src/SharpCompress/Compressors/ZStandard/SynchronizationWrapper.cs b/src/SharpCompress/Compressors/ZStandard/SynchronizationWrapper.cs
new file mode 100644
index 00000000..406cacd4
--- /dev/null
+++ b/src/SharpCompress/Compressors/ZStandard/SynchronizationWrapper.cs
@@ -0,0 +1,22 @@
+using System.Threading;
+
+namespace SharpCompress.Compressors.ZStandard;
+
+internal static unsafe class SynchronizationWrapper
+{
+ private static object UnwrapObject(void** obj) => UnmanagedObject.Unwrap