< Summary

Information
Class: System.IO.Compression.DeflateEncoder
Assembly: System.IO.Compression
File(s): D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\DeflateEncoder.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 162
Coverable lines: 162
Total lines: 343
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 66
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%110%
.ctor(...)100%110%
.ctor(...)100%110%
.ctor(...)100%110%
.ctor(...)0%220%
.ctor(...)0%220%
ValidateQuality(...)0%220%
ValidateWindowLog(...)0%220%
Dispose()0%220%
EnsureNotDisposed()100%110%
GetMaxCompressedLength(...)0%12120%
Compress(...)0%28280%
Flush(...)0%12120%
TryCompress(...)100%110%
TryCompress(...)100%110%
TryCompress(...)0%440%

File(s)

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\DeflateEncoder.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Runtime.InteropServices;
 7
 8namespace System.IO.Compression
 9{
 10    /// <summary>
 11    /// Provides methods and static methods to encode data in a streamless, non-allocating, and performant manner using 
 12    /// </summary>
 13    public sealed class DeflateEncoder : IDisposable
 14    {
 15        private ZLibNative.ZLibStreamHandle? _state;
 16        private bool _disposed;
 17        private bool _finished;
 18
 19        /// <summary>
 20        /// Initializes a new instance of the <see cref="DeflateEncoder"/> class using the default quality.
 21        /// </summary>
 22        /// <exception cref="IOException">Failed to create the <see cref="DeflateEncoder"/> instance.</exception>
 23        public DeflateEncoder()
 024            : this(ZLibNative.DefaultQuality)
 025        {
 026        }
 27
 28        /// <summary>
 29        /// Initializes a new instance of the <see cref="DeflateEncoder"/> class using the specified quality.
 30        /// </summary>
 31        /// <param name="quality">The compression quality value between 0 (no compression) and 9 (maximum compression), 
 32        /// <exception cref="ArgumentOutOfRangeException"><paramref name="quality"/> is not in the valid range (0-9 or -
 33        /// <exception cref="IOException">Failed to create the <see cref="DeflateEncoder"/> instance.</exception>
 34        public DeflateEncoder(int quality)
 035            : this(quality, ZLibNative.DefaultWindowLog)
 036        {
 037        }
 38
 39        /// <summary>
 40        /// Initializes a new instance of the <see cref="DeflateEncoder"/> class using the specified options.
 41        /// </summary>
 42        /// <param name="options">The compression options.</param>
 43        /// <exception cref="ArgumentNullException"><paramref name="options"/> is null.</exception>
 44        /// <exception cref="IOException">Failed to create the <see cref="DeflateEncoder"/> instance.</exception>
 45        public DeflateEncoder(ZLibCompressionOptions options)
 046            : this(options, CompressionFormat.Deflate)
 047        {
 048        }
 49
 50        /// <summary>
 51        /// Initializes a new instance of the <see cref="DeflateEncoder"/> class using the specified quality and window 
 52        /// </summary>
 53        /// <param name="quality">The compression quality value between 0 (no compression) and 9 (maximum compression), 
 54        /// <param name="windowLog2">The base-2 logarithm of the window size (8-15), or -1 to use the default value. Lar
 55        /// <exception cref="ArgumentOutOfRangeException"><paramref name="quality"/> is not in the valid range (0-9 or -
 56        /// <exception cref="IOException">Failed to create the <see cref="DeflateEncoder"/> instance.</exception>
 57        public DeflateEncoder(int quality, int windowLog2)
 058            : this(quality, windowLog2, CompressionFormat.Deflate)
 059        {
 060        }
 61
 62        /// <summary>
 63        /// Internal constructor that accepts quality, windowLog2 (8-15), and format.
 64        /// Validates both parameters and transforms windowLog2 to windowBits based on format.
 65        /// </summary>
 066        internal DeflateEncoder(int quality, int windowLog2, CompressionFormat format)
 067        {
 068            ValidateQuality(quality);
 069            ValidateWindowLog(windowLog2);
 70
 071            int windowBits = CompressionFormatHelper.ResolveWindowBits(windowLog2, format);
 72
 073            int memLevel = quality == (int)ZLibNative.CompressionLevel.NoCompression
 074                ? ZLibNative.Deflate_NoCompressionMemLevel
 075                : ZLibNative.Deflate_DefaultMemLevel;
 76
 077            _state = ZLibNative.ZLibStreamHandle.CreateForDeflate(
 078                (ZLibNative.CompressionLevel)quality,
 079                windowBits,
 080                memLevel,
 081                ZLibNative.CompressionStrategy.DefaultStrategy);
 082        }
 83
 84        /// <summary>
 85        /// Internal constructor that accepts ZLibCompressionOptions and format.
 86        /// </summary>
 087        internal DeflateEncoder(ZLibCompressionOptions options, CompressionFormat format)
 088        {
 089            ArgumentNullException.ThrowIfNull(options);
 90
 091            int windowBits = CompressionFormatHelper.ResolveWindowBits(options.WindowLog2, format);
 92
 093            int memLevel = options.CompressionLevel == (int)ZLibNative.CompressionLevel.NoCompression
 094                ? ZLibNative.Deflate_NoCompressionMemLevel
 095                : ZLibNative.Deflate_DefaultMemLevel;
 96
 097            _state = ZLibNative.ZLibStreamHandle.CreateForDeflate(
 098                (ZLibNative.CompressionLevel)options.CompressionLevel,
 099                windowBits,
 0100                memLevel,
 0101                (ZLibNative.CompressionStrategy)options.CompressionStrategy);
 0102        }
 103
 104        private static void ValidateQuality(int quality)
 0105        {
 0106            if (quality != -1)
 0107            {
 0108                ArgumentOutOfRangeException.ThrowIfLessThan(quality, ZLibNative.MinQuality, nameof(quality));
 0109                ArgumentOutOfRangeException.ThrowIfGreaterThan(quality, ZLibNative.MaxQuality, nameof(quality));
 0110            }
 0111        }
 112
 113        private static void ValidateWindowLog(int windowLog2)
 0114        {
 0115            if (windowLog2 != -1)
 0116            {
 0117                ArgumentOutOfRangeException.ThrowIfLessThan(windowLog2, ZLibNative.MinWindowLog, nameof(windowLog2));
 0118                ArgumentOutOfRangeException.ThrowIfGreaterThan(windowLog2, ZLibNative.MaxWindowLog, nameof(windowLog2));
 0119            }
 0120        }
 121
 122        /// <summary>
 123        /// Frees and disposes unmanaged resources.
 124        /// </summary>
 125        public void Dispose()
 0126        {
 0127            _disposed = true;
 0128            _state?.Dispose();
 0129            _state = null;
 0130        }
 131
 132        private void EnsureNotDisposed()
 0133        {
 0134            ObjectDisposedException.ThrowIf(_disposed, this);
 0135        }
 136
 137        /// <summary>
 138        /// Gets the maximum expected compressed length for the provided input size.
 139        /// </summary>
 140        /// <param name="inputLength">The input size to get the maximum expected compressed length from.</param>
 141        /// <returns>A number representing the maximum compressed length for the provided input size.</returns>
 142        /// <exception cref="ArgumentOutOfRangeException"><paramref name="inputLength"/> is negative.</exception>
 143        public static long GetMaxCompressedLength(long inputLength)
 0144        {
 0145            ArgumentOutOfRangeException.ThrowIfNegative(inputLength);
 146
 147            // For inputs up to 2 GiB, delegate to the native compressBound() function, which returns
 148            // the exact upper bound for the zlib implementation linked into the current process
 149            // (either classic zlib or zlib-ng, depending on platform and build flags). The 2^31
 150            // threshold keeps the value within the uint P/Invoke signature on all platforms.
 151
 152            // Browser/WASI builds do not link the native compression library,
 153            // so fall through to the managed formula on those platforms.
 0154            if (inputLength <= (1L << 31) && !OperatingSystem.IsBrowser() && !OperatingSystem.IsWasi())
 0155            {
 0156                return Interop.ZLib.compressBound((uint)inputLength);
 157            }
 158
 159            // For larger inputs, compute the bound in managed code using zlib-ng's quick-strategy
 160            // formula. It is strictly larger than classic zlib's compressBound(), so it is a safe
 161            // upper bound regardless of which implementation is linked at runtime.
 162            // See: src/native/external/zlib-ng/compress.c and zutil.h.
 163            // Use ulong to avoid overflow; reject inputs whose bound does not fit in long.
 0164            ulong sourceLength = (ulong)inputLength;
 0165            ulong maxCompressedLength = sourceLength
 0166                + (sourceLength == 0 ? 1u : 0u)
 0167                + (sourceLength < 9 ? 1u : 0u)
 0168                + ((sourceLength + 7) >> 3)
 0169                + 3   // DEFLATE_BLOCK_OVERHEAD: (3 + 15 + 6) >> 3
 0170                + 6;  // ZLIB_WRAPLEN: zlib header (2 bytes) + Adler32 trailer (4 bytes)
 171
 0172            if (maxCompressedLength > long.MaxValue)
 0173            {
 0174                throw new ArgumentOutOfRangeException(nameof(inputLength));
 175            }
 176
 0177            return (long)maxCompressedLength;
 0178        }
 179
 180        /// <summary>
 181        /// Compresses a read-only byte span into a destination span.
 182        /// </summary>
 183        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
 184        /// <param name="destination">When this method returns, a byte span where the compressed data is stored.</param>
 185        /// <param name="bytesConsumed">When this method returns, the total number of bytes that were read from <paramre
 186        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramre
 187        /// <param name="isFinalBlock"><see langword="true"/> to finalize the internal stream, which prevents adding mor
 188        /// <returns>One of the enumeration values that describes the status with which the span-based operation finishe
 189        public OperationStatus Compress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out in
 0190        {
 0191            EnsureNotDisposed();
 0192            Debug.Assert(_state is not null);
 193
 0194            bytesConsumed = 0;
 0195            bytesWritten = 0;
 196
 0197            if (_finished)
 0198            {
 0199                return OperationStatus.Done;
 200            }
 201
 0202            if (source.IsEmpty && !isFinalBlock)
 0203            {
 0204                return OperationStatus.Done;
 205            }
 206
 0207            if (destination.IsEmpty && (source.Length > 0 || isFinalBlock))
 0208            {
 0209                return OperationStatus.DestinationTooSmall;
 210            }
 211
 0212            ZLibNative.FlushCode flushCode = isFinalBlock ? ZLibNative.FlushCode.Finish : ZLibNative.FlushCode.NoFlush;
 213
 214            unsafe
 0215            {
 0216                fixed (byte* inputPtr = &MemoryMarshal.GetReference(source))
 0217                fixed (byte* outputPtr = &MemoryMarshal.GetReference(destination))
 0218                {
 0219                    _state.NextIn = (IntPtr)inputPtr;
 0220                    _state.AvailIn = (uint)source.Length;
 0221                    _state.NextOut = (IntPtr)outputPtr;
 0222                    _state.AvailOut = (uint)destination.Length;
 223
 0224                    ZLibNative.ErrorCode errorCode = _state.Deflate(flushCode);
 225
 0226                    bytesConsumed = source.Length - (int)_state.AvailIn;
 0227                    bytesWritten = destination.Length - (int)_state.AvailOut;
 228
 0229                    OperationStatus status = errorCode switch
 0230                    {
 0231                        ZLibNative.ErrorCode.Ok when isFinalBlock => OperationStatus.DestinationTooSmall,
 0232                        ZLibNative.ErrorCode.Ok => _state.AvailIn == 0
 0233                            ? OperationStatus.Done
 0234                            : OperationStatus.DestinationTooSmall,
 0235                        ZLibNative.ErrorCode.StreamEnd => OperationStatus.Done,
 0236                        ZLibNative.ErrorCode.BufError => _state.AvailOut == 0
 0237                            ? OperationStatus.DestinationTooSmall
 0238                            : OperationStatus.Done,
 0239                        _ => throw new ZLibException(SR.ZLibErrorUnexpected, "deflate", (int)errorCode, _state.GetErrorM
 0240                    };
 241
 242                    // Track if compression is finished
 0243                    if (isFinalBlock && errorCode == ZLibNative.ErrorCode.StreamEnd)
 0244                    {
 0245                        _finished = true;
 0246                    }
 247
 0248                    return status;
 249                }
 250            }
 0251        }
 252
 253        /// <summary>
 254        /// Compresses an empty read-only span of bytes into its destination, ensuring that output is produced for all t
 255        /// </summary>
 256        /// <param name="destination">When this method returns, a span of bytes where the compressed data will be stored
 257        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramre
 258        /// <returns>One of the enumeration values that describes the status with which the operation finished.</returns
 259        public OperationStatus Flush(Span<byte> destination, out int bytesWritten)
 0260        {
 0261            EnsureNotDisposed();
 0262            Debug.Assert(_state is not null);
 263
 0264            bytesWritten = 0;
 265
 0266            if (_finished)
 0267            {
 0268                return OperationStatus.Done;
 269            }
 270
 271            unsafe
 0272            {
 0273                fixed (byte* outputPtr = &MemoryMarshal.GetReference(destination))
 0274                {
 0275                    _state.NextIn = IntPtr.Zero;
 0276                    _state.AvailIn = 0;
 0277                    _state.NextOut = (IntPtr)outputPtr;
 0278                    _state.AvailOut = (uint)destination.Length;
 279
 0280                    ZLibNative.ErrorCode errorCode = _state.Deflate(ZLibNative.FlushCode.SyncFlush);
 281
 0282                    bytesWritten = destination.Length - (int)_state.AvailOut;
 283
 0284                    return errorCode switch
 0285                    {
 0286                        ZLibNative.ErrorCode.Ok => _state.AvailOut == 0
 0287                            ? OperationStatus.DestinationTooSmall
 0288                            : OperationStatus.Done,
 0289                        ZLibNative.ErrorCode.StreamEnd => OperationStatus.Done,
 0290                        ZLibNative.ErrorCode.BufError => _state.AvailOut == 0
 0291                            ? OperationStatus.DestinationTooSmall
 0292                            : OperationStatus.Done,
 0293                        _ => throw new ZLibException(SR.ZLibErrorUnexpected, "deflate", (int)errorCode, _state.GetErrorM
 0294                    };
 295                }
 296            }
 0297        }
 298
 299        /// <summary>
 300        /// Tries to compress a source byte span into a destination span using the default quality.
 301        /// </summary>
 302        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
 303        /// <param name="destination">When this method returns, a span of bytes where the compressed data is stored.</pa
 304        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramre
 305        /// <returns><see langword="true"/> if the compression operation was successful; <see langword="false"/> otherwi
 306        public static bool TryCompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
 0307            => TryCompress(source, destination, out bytesWritten, ZLibNative.DefaultQuality, ZLibNative.DefaultWindowLog
 308
 309        /// <summary>
 310        /// Tries to compress a source byte span into a destination span using the specified quality.
 311        /// </summary>
 312        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
 313        /// <param name="destination">When this method returns, a span of bytes where the compressed data is stored.</pa
 314        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramre
 315        /// <param name="quality">The compression quality value between 0 (no compression) and 9 (maximum compression), 
 316        /// <returns><see langword="true"/> if the compression operation was successful; <see langword="false"/> otherwi
 317        public static bool TryCompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten, int qual
 0318            => TryCompress(source, destination, out bytesWritten, quality, ZLibNative.DefaultWindowLog);
 319
 320        /// <summary>
 321        /// Tries to compress a source byte span into a destination span using the specified quality and window size.
 322        /// </summary>
 323        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
 324        /// <param name="destination">When this method returns, a span of bytes where the compressed data is stored.</pa
 325        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramre
 326        /// <param name="quality">The compression quality value between 0 (no compression) and 9 (maximum compression), 
 327        /// <param name="windowLog2">The base-2 logarithm of the window size (8-15), or -1 to use the default value. Lar
 328        /// <returns><see langword="true"/> if the compression operation was successful; <see langword="false"/> otherwi
 329        public static bool TryCompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten, int qual
 0330        {
 0331            using var encoder = new DeflateEncoder(quality, windowLog2);
 0332            OperationStatus status = encoder.Compress(source, destination, out int consumed, out bytesWritten, isFinalBl
 333
 0334            bool success = status == OperationStatus.Done && consumed == source.Length;
 0335            if (!success)
 0336            {
 0337                bytesWritten = 0;
 0338            }
 339
 0340            return success;
 0341        }
 342    }
 343}