File: src\runtime\src\libraries\Common\src\System\IO\Compression\ZLibNative.cs
Web Access
Project: src\runtime\src\libraries\System.IO.Compression\src\System.IO.Compression.csproj (System.IO.Compression)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Security;

namespace System.IO.Compression
{
    /// <summary>
    /// This class provides declaration for constants and PInvokes as well as some basic tools for exposing the
    /// native System.IO.Compression.Native.dll (effectively, ZLib) library to managed code.
    ///
    /// <para>See also: How to choose a compression level (in comments to <see cref="CompressionLevel" />.)</para>
    /// </summary>
    /// <seealso href="https://www.zlib.net/manual.html">ZLib manual</seealso>
    internal static partial class ZLibNative
    {
        // This is the NULL pointer for using with ZLib pointers;
        // we prefer it to IntPtr.Zero to mimic the definition of Z_NULL in zlib.h:
        internal static readonly IntPtr ZNullPtr = IntPtr.Zero;

        public enum FlushCode : int
        {
            NoFlush = 0,
            SyncFlush = 2,
            Finish = 4,
            Block = 5
        }

        public enum ErrorCode : int
        {
            Ok = 0,
            StreamEnd = 1,
            StreamError = -2,
            DataError = -3,
            MemError = -4,
            BufError = -5,
            VersionError = -6
        }

        /// <summary>
        /// <para><strong>From the ZLib manual:</strong><br />
        /// <see cref="CompressionStrategy" /> is used to tune the compression algorithm.<br />
        /// Use the value <see cref="DefaultStrategy" /> for normal data, <see cref="Filtered" /> for data produced by a filter (or predictor),
        /// <see cref="HuffmanOnly" /> to force Huffman encoding only (no string match), or <see cref="RunLengthEncoding" /> to limit match distances to one
        /// (run-length encoding). Filtered data consists mostly of small values with a somewhat random distribution. In this case, the
        /// compression algorithm is tuned to compress them better. The effect of <see cref="Filtered" /> is to force more Huffman coding and
        /// less string matching; it is somewhat intermediate between <see cref="DefaultStrategy" /> and <see cref="HuffmanOnly" />.
        /// <see cref="RunLengthEncoding" /> is designed to be almost as fast as <see cref="HuffmanOnly" />, but give better compression for PNG image data.
        /// The strategy parameter only affects the compression ratio but not the correctness of the compressed output even if it is not set
        /// appropriately. <see cref="Fixed" /> prevents the use of dynamic Huffman codes, allowing for a simpler decoder for special applications.</para>
        ///
        /// <para><strong>For .NET Framework use:</strong><br />
        /// We have investigated compression scenarios for a bunch of different frequently occurring compression data and found that in all
        /// cases we investigated so far, <see cref="DefaultStrategy" /> provided best results</para>
        ///
        /// <para>See also: How to choose a compression level (in comments to <see cref="CompressionLevel" />.)</para>
        /// </summary>
        public enum CompressionStrategy : int
        {
            DefaultStrategy = 0,
            Filtered = 1,
            HuffmanOnly = 2,
            RunLengthEncoding = 3,
            Fixed = 4
        }

        /// <summary>
        /// In version 2.2.1, zlib-ng provides only the <see cref="Deflated" /> <see cref="CompressionMethod" />.
        /// </summary>
        public enum CompressionMethod : int
        {
            Deflated = 8
        }

        /// <summary>
        /// <para><strong>From the ZLib manual:</strong><br />
        /// ZLib's <c>windowBits</c> parameter is the base two logarithm of the window size (the size of the history buffer).
        /// It should be in the range 8..15 for this version of the library. Larger values of this parameter result in better compression
        /// at the expense of memory usage. The default value is 15 if <c>deflateInit</c> is used instead.</para>
        ///
        /// <para><strong>Note</strong>: <c>windowBits</c> can also be -8..-15 for raw deflate. In this case, -windowBits determines the window size.
        /// <c>Deflate</c> will then generate raw deflate data with no ZLib header or trailer, and will not compute an adler32 check value.</para>
        ///
        /// <para>See also: How to choose a compression level (in comments to <see cref="CompressionLevel" />.)</para>
        /// </summary>
        public const int Deflate_DefaultWindowBits = -15; // Legal values are 8..15 and -8..-15. 15 is the window size,
                                                          // negative val causes deflate to produce raw deflate data (no zlib header).

        /// <summary>
        /// <para><strong>From the ZLib manual:</strong><br />
        /// ZLib's <c>windowBits</c> parameter is the base two logarithm of the window size (the size of the history buffer).
        /// It should be in the range 8..15 for this version of the library. Larger values of this parameter result in better compression
        /// at the expense of memory usage. The default value is 15 if <c>deflateInit</c> is used instead.</para>
        /// </summary>
        public const int ZLib_DefaultWindowBits = 15;

        /// <summary>
        /// <para>ZLib's <c>windowBits</c> parameter is the base two logarithm of the window size (the size of the history buffer).
        /// For GZip header encoding, <c>windowBits</c> should be equal to a value between 8..15 (to specify Window Size) added to
        /// 16. The range of values for GZip encoding is therefore 24..31.</para>
        /// <para><strong>Note</strong>:<br />
        /// The GZip header will have no file name, no extra data, no comment, no modification time (set to zero), no header crc, and
        /// the operating system will be set based on the OS that the ZLib library was compiled to. <c>ZStream.adler</c>
        /// is a crc32 instead of an adler32.</para>
        /// </summary>
        public const int GZip_DefaultWindowBits = 31;

        /// <summary>
        /// The minimum value for the base-2 logarithm of the history buffer (window) size.
        /// A value of 8 corresponds to a 256-byte window.
        /// </summary>
        public const int MinWindowLog = 8;

        /// <summary>
        /// The maximum value for the base-2 logarithm of the history buffer (window) size.
        /// A value of 15 corresponds to a 32KB window, which provides the best compression ratio.
        /// </summary>
        public const int MaxWindowLog = 15;

        /// <summary>
        /// The default value for the base-2 logarithm of the history buffer (window) size.
        /// Defaults to <see cref="MaxWindowLog"/> (15) for optimal compression.
        /// </summary>
        public const int DefaultWindowLog = MaxWindowLog;

        /// <summary>
        /// The minimum compression quality level. A value of 0 means no compression (store only).
        /// </summary>
        public const int MinQuality = 0;

        /// <summary>
        /// The maximum compression quality level. A value of 9 provides the best compression ratio
        /// but is the slowest.
        /// </summary>
        public const int MaxQuality = 9;

        /// <summary>
        /// The default compression quality level. A value of 6 provides a good balance between
        /// compression ratio and speed.
        /// </summary>
        public const int DefaultQuality = 6;

        /// <summary>
        /// <para><strong>From the ZLib manual:</strong><br />
        /// The <c>memLevel</c> parameter specifies how much memory should be allocated for the internal compression state.
        /// <c>memLevel</c> = 1 uses minimum memory but is slow and reduces compression ratio; <c>memLevel</c> = 9 uses maximum
        /// memory for optimal speed. The default value is 8.</para>
        ///
        /// <para>See also: How to choose a compression level (in comments to <see cref="CompressionLevel" />.)</para>
        /// </summary>
        public const int Deflate_DefaultMemLevel = 8;     // Memory usage by deflate. Legal range: [1..9]. 8 is ZLib default.
                                                          // More is faster and better compression with more memory usage.
        public const int Deflate_NoCompressionMemLevel = 7;

        public const byte GZip_Header_ID1 = 31;
        public const byte GZip_Header_ID2 = 139;

        public sealed class ZLibStreamHandle : SafeHandle
        {
            public enum State
            {
                NotInitialized,
                InitializedForDeflate,
                InitializedForInflate,
                Disposed
            }

            private ZStream _zStream;

            private volatile State _initializationState;

            public ZLibStreamHandle()
                : base(new IntPtr(-1), true)
            {
                _initializationState = State.NotInitialized;
                SetHandle(IntPtr.Zero);
            }

            public static ZLibStreamHandle CreateForDeflate(CompressionLevel level, int windowBits,
                int memLevel, CompressionStrategy strategy)
            {
                ZLibStreamHandle zLibStreamHandle = new ZLibStreamHandle();

                try
                {
                    zLibStreamHandle.DeflateInit2_(level, windowBits, memLevel, strategy);
                }
                catch (Exception)
                {
                    zLibStreamHandle.Dispose();
                    throw;
                }

                return zLibStreamHandle;
            }

            public static ZLibStreamHandle CreateForInflate(int windowBits)
            {
                ZLibStreamHandle zLibStreamHandle = new ZLibStreamHandle();

                try
                {
                    zLibStreamHandle.InflateInit2_(windowBits);
                }
                catch (Exception)
                {
                    zLibStreamHandle.Dispose();
                    throw;
                }

                return zLibStreamHandle;
            }

            public override bool IsInvalid
            {
                get { return handle == new IntPtr(-1); }
            }

            public State InitializationState
            {
                get { return _initializationState; }
            }

            protected override bool ReleaseHandle() =>
                InitializationState switch
                {
                    State.NotInitialized => true,
                    State.InitializedForDeflate => (DeflateEnd() == ErrorCode.Ok),
                    State.InitializedForInflate => (InflateEnd() == ErrorCode.Ok),
                    State.Disposed => true,
                    _ => false,  // This should never happen. Did we forget one of the State enum values in the switch?
                };

            public IntPtr NextIn
            {
                get { return _zStream.nextIn; }
                set { _zStream.nextIn = value; }
            }

            public uint AvailIn
            {
                get { return _zStream.availIn; }
                set { _zStream.availIn = value; }
            }

            public IntPtr NextOut
            {
                get { return _zStream.nextOut; }
                set { _zStream.nextOut = value; }
            }

            public uint AvailOut
            {
                get { return _zStream.availOut; }
                set { _zStream.availOut = value; }
            }

            private void EnsureState(State requiredState)
            {
                if (InitializationState != requiredState)
                    throw new InvalidOperationException("InitializationState != " + requiredState.ToString());
            }

            private void EnsureNativeHandleInitialized(ErrorCode zlibErrorCode, string zlibErrorContext)
            {
                Debug.Assert(zlibErrorContext is "deflateInit2_" or "inflateInit2_");

                if (zlibErrorCode is not ErrorCode.Ok)
                {
                    string zlibErrorMessage = GetErrorMessage();
                    string exceptionMessage = zlibErrorCode switch
                    {
                        // Not enough memory
                        ErrorCode.MemError => SR.ZLibErrorNotEnoughMemory,

                        // zlib library is incompatible with the version assumed
                        ErrorCode.VersionError => SR.ZLibErrorVersionMismatch,

                        // Parameters are invalid
                        ErrorCode.StreamError => SR.ZLibErrorIncorrectInitParameters,

                        _ => SR.Format(SR.ZLibErrorUnexpected, (int)zlibErrorCode)
                    };

                    throw new ZLibException(exceptionMessage, zlibErrorContext, (int)zlibErrorCode, zlibErrorMessage);
                }
            }

            private unsafe void DeflateInit2_(CompressionLevel level, int windowBits, int memLevel, CompressionStrategy strategy)
            {
                Debug.Assert(InitializationState == State.NotInitialized);

                ErrorCode errC;

                try
                {
                    fixed (ZStream* stream = &_zStream)
                    {
                        errC = Interop.ZLib.DeflateInit2_(stream, level, CompressionMethod.Deflated, windowBits, memLevel, strategy);
                    }
                }
                catch (Exception e) // Could not load the ZLib dll
                {
                    throw new ZLibException(SR.ZLibErrorDLLLoadError, e);
                }

                EnsureNativeHandleInitialized(errC, "deflateInit2_");
                _initializationState = State.InitializedForDeflate;
            }

            public unsafe ErrorCode Deflate(FlushCode flush)
            {
                bool refAdded = false;
                try
                {
                    DangerousAddRef(ref refAdded);
                    EnsureState(State.InitializedForDeflate);

                    fixed (ZStream* stream = &_zStream)
                    {
                        return Interop.ZLib.Deflate(stream, flush);
                    }
                }
                finally
                {
                    if (refAdded)
                    {
                        DangerousRelease();
                    }
                }
            }

            public unsafe ErrorCode DeflateReset()
            {
                bool refAdded = false;
                try
                {
                    DangerousAddRef(ref refAdded);
                    EnsureState(State.InitializedForDeflate);

                    fixed (ZStream* stream = &_zStream)
                    {
                        return Interop.ZLib.DeflateReset(stream);
                    }
                }
                finally
                {
                    if (refAdded)
                    {
                        DangerousRelease();
                    }
                }
            }

            private unsafe ErrorCode DeflateEnd()
            {
                EnsureState(State.InitializedForDeflate);

                fixed (ZStream* stream = &_zStream)
                {
                    ErrorCode errC = Interop.ZLib.DeflateEnd(stream);
                    _initializationState = State.Disposed;

                    return errC;
                }
            }

            private unsafe void InflateInit2_(int windowBits)
            {
                Debug.Assert(InitializationState == State.NotInitialized);

                ErrorCode errC;

                try
                {
                    fixed (ZStream* stream = &_zStream)
                    {
                        errC = Interop.ZLib.InflateInit2_(stream, windowBits);
                    }
                }
                catch (Exception e) // Could not load the ZLib dll
                {
                    throw new ZLibException(SR.ZLibErrorDLLLoadError, e);
                }

                EnsureNativeHandleInitialized(errC, "inflateInit2_");
                _initializationState = State.InitializedForInflate;
            }

            public unsafe ErrorCode InflateReset2_(int windowBits)
            {
                bool refAdded = false;
                try
                {
                    DangerousAddRef(ref refAdded);
                    EnsureState(State.InitializedForInflate);

                    fixed (ZStream* stream = &_zStream)
                    {
                        return Interop.ZLib.InflateReset2_(stream, windowBits);
                    }
                }
                finally
                {
                    if (refAdded)
                    {
                        DangerousRelease();
                    }
                }
            }

            public unsafe ErrorCode Inflate(FlushCode flush)
            {
                bool refAdded = false;
                try
                {
                    DangerousAddRef(ref refAdded);
                    EnsureState(State.InitializedForInflate);

                    fixed (ZStream* stream = &_zStream)
                    {
                        return Interop.ZLib.Inflate(stream, flush);
                    }
                }
                finally
                {
                    if (refAdded)
                    {
                        DangerousRelease();
                    }
                }
            }

            private unsafe ErrorCode InflateEnd()
            {
                EnsureState(State.InitializedForInflate);

                fixed (ZStream* stream = &_zStream)
                {
                    ErrorCode errC = Interop.ZLib.InflateEnd(stream);
                    _initializationState = State.Disposed;

                    return errC;
                }
            }

            // This can work even after XxflateEnd(). Gets the error message from the native library.
            public unsafe string GetErrorMessage() => Utf8StringMarshaller.ConvertToManaged(_zStream.msg) ?? string.Empty;
        }
    }
}