< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 162
Coverable lines: 162
Total lines: 346
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 56
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%220%
Compress(...)0%28280%
Flush(...)0%12120%
Reset()100%110%
TryCompress(...)100%110%
TryCompress(...)100%110%
TryCompress(...)0%440%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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            // This method does not know which windowLog2 or quality the caller will use, so
 148            // compute the maximum of the bounds used by zlib-ng's deflateBound(). The code below mimics the logic
 149            // to compute the maximum possible size returned by deflateBound. Our API doesn't expose all knobs
 150            // so z_stream's strstart and gzhead are always null. We also don't handle the s390 corner case.
 0151            ulong sourceLength = (ulong)inputLength;
 152            const ulong wrapLength = 6; // GZIP_WRAPLEN is 18, GZipEncoder compensates for the rest
 0153            ulong maxCompressedLength = sourceLength + ((sourceLength + 7) >> 3) + ((sourceLength + 63) >> 6) + 5;
 0154            ulong storedBlockBound = sourceLength + (sourceLength >> 5) + (sourceLength >> 7) + (sourceLength >> 11) + 7
 155
 0156            maxCompressedLength = Math.Max(maxCompressedLength, storedBlockBound) + wrapLength;
 157
 0158            if (maxCompressedLength > long.MaxValue)
 0159            {
 0160                throw new ArgumentOutOfRangeException(nameof(inputLength));
 161            }
 162
 0163            return (long)maxCompressedLength;
 0164        }
 165
 166        /// <summary>
 167        /// Compresses a read-only byte span into a destination span.
 168        /// </summary>
 169        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
 170        /// <param name="destination">When this method returns, a byte span where the compressed data is stored.</param>
 171        /// <param name="bytesConsumed">When this method returns, the total number of bytes that were read from <paramre
 172        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramre
 173        /// <param name="isFinalBlock"><see langword="true"/> to finalize the internal stream, which prevents adding mor
 174        /// <returns>One of the enumeration values that describes the status with which the span-based operation finishe
 175        public OperationStatus Compress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out in
 0176        {
 0177            EnsureNotDisposed();
 0178            Debug.Assert(_state is not null);
 179
 0180            bytesConsumed = 0;
 0181            bytesWritten = 0;
 182
 0183            if (_finished)
 0184            {
 0185                return OperationStatus.Done;
 186            }
 187
 0188            if (source.IsEmpty && !isFinalBlock)
 0189            {
 0190                return OperationStatus.Done;
 191            }
 192
 0193            if (destination.IsEmpty && (source.Length > 0 || isFinalBlock))
 0194            {
 0195                return OperationStatus.DestinationTooSmall;
 196            }
 197
 0198            ZLibNative.FlushCode flushCode = isFinalBlock ? ZLibNative.FlushCode.Finish : ZLibNative.FlushCode.NoFlush;
 199
 200            unsafe
 0201            {
 0202                fixed (byte* inputPtr = &MemoryMarshal.GetReference(source))
 0203                fixed (byte* outputPtr = &MemoryMarshal.GetReference(destination))
 0204                {
 0205                    _state.NextIn = (IntPtr)inputPtr;
 0206                    _state.AvailIn = (uint)source.Length;
 0207                    _state.NextOut = (IntPtr)outputPtr;
 0208                    _state.AvailOut = (uint)destination.Length;
 209
 0210                    ZLibNative.ErrorCode errorCode = _state.Deflate(flushCode);
 211
 0212                    bytesConsumed = source.Length - (int)_state.AvailIn;
 0213                    bytesWritten = destination.Length - (int)_state.AvailOut;
 214
 0215                    OperationStatus status = errorCode switch
 0216                    {
 0217                        ZLibNative.ErrorCode.Ok when isFinalBlock => OperationStatus.DestinationTooSmall,
 0218                        ZLibNative.ErrorCode.Ok => _state.AvailIn == 0
 0219                            ? OperationStatus.Done
 0220                            : OperationStatus.DestinationTooSmall,
 0221                        ZLibNative.ErrorCode.StreamEnd => OperationStatus.Done,
 0222                        ZLibNative.ErrorCode.BufError => _state.AvailOut == 0
 0223                            ? OperationStatus.DestinationTooSmall
 0224                            : OperationStatus.Done,
 0225                        _ => throw new ZLibException(SR.ZLibErrorUnexpected, "deflate", (int)errorCode, _state.GetErrorM
 0226                    };
 227
 228                    // Track if compression is finished
 0229                    if (isFinalBlock && errorCode == ZLibNative.ErrorCode.StreamEnd)
 0230                    {
 0231                        _finished = true;
 0232                    }
 233
 0234                    return status;
 235                }
 236            }
 0237        }
 238
 239        /// <summary>
 240        /// Compresses an empty read-only span of bytes into its destination, ensuring that output is produced for all t
 241        /// </summary>
 242        /// <param name="destination">When this method returns, a span of bytes where the compressed data will be stored
 243        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramre
 244        /// <returns>One of the enumeration values that describes the status with which the operation finished.</returns
 245        public OperationStatus Flush(Span<byte> destination, out int bytesWritten)
 0246        {
 0247            EnsureNotDisposed();
 0248            Debug.Assert(_state is not null);
 249
 0250            bytesWritten = 0;
 251
 0252            if (_finished)
 0253            {
 0254                return OperationStatus.Done;
 255            }
 256
 257            unsafe
 0258            {
 0259                fixed (byte* outputPtr = &MemoryMarshal.GetReference(destination))
 0260                {
 0261                    _state.NextIn = IntPtr.Zero;
 0262                    _state.AvailIn = 0;
 0263                    _state.NextOut = (IntPtr)outputPtr;
 0264                    _state.AvailOut = (uint)destination.Length;
 265
 0266                    ZLibNative.ErrorCode errorCode = _state.Deflate(ZLibNative.FlushCode.SyncFlush);
 267
 0268                    bytesWritten = destination.Length - (int)_state.AvailOut;
 269
 0270                    return errorCode switch
 0271                    {
 0272                        ZLibNative.ErrorCode.Ok => _state.AvailOut == 0
 0273                            ? OperationStatus.DestinationTooSmall
 0274                            : OperationStatus.Done,
 0275                        ZLibNative.ErrorCode.StreamEnd => OperationStatus.Done,
 0276                        ZLibNative.ErrorCode.BufError => _state.AvailOut == 0
 0277                            ? OperationStatus.DestinationTooSmall
 0278                            : OperationStatus.Done,
 0279                        _ => throw new ZLibException(SR.ZLibErrorUnexpected, "deflate", (int)errorCode, _state.GetErrorM
 0280                    };
 281                }
 282            }
 0283        }
 284
 285        /// <summary>
 286        /// Resets the encoder to its initial state so the same instance can be reused for a new, independent compressio
 287        /// </summary>
 288        /// <remarks>
 289        /// The encoder keeps the compression quality and window size it was created with. Any pending output or unflush
 290        /// </remarks>
 291        /// <exception cref="ObjectDisposedException">The encoder has been disposed.</exception>
 292        public void Reset()
 0293        {
 0294            EnsureNotDisposed();
 0295            Debug.Assert(_state is not null);
 296
 0297            _state.DeflateReset();
 0298            _finished = false;
 0299        }
 300
 301        /// <summary>
 302        /// Tries to compress a source byte span into a destination span using the default quality.
 303        /// </summary>
 304        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
 305        /// <param name="destination">When this method returns, a span of bytes where the compressed data is stored.</pa
 306        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramre
 307        /// <returns><see langword="true"/> if the compression operation was successful; <see langword="false"/> otherwi
 308        public static bool TryCompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
 0309            => TryCompress(source, destination, out bytesWritten, ZLibNative.DefaultQuality, ZLibNative.DefaultWindowLog
 310
 311        /// <summary>
 312        /// Tries to compress a source byte span into a destination span using the specified quality.
 313        /// </summary>
 314        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
 315        /// <param name="destination">When this method returns, a span of bytes where the compressed data is stored.</pa
 316        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramre
 317        /// <param name="quality">The compression quality value between 0 (no compression) and 9 (maximum compression), 
 318        /// <returns><see langword="true"/> if the compression operation was successful; <see langword="false"/> otherwi
 319        public static bool TryCompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten, int qual
 0320            => TryCompress(source, destination, out bytesWritten, quality, ZLibNative.DefaultWindowLog);
 321
 322        /// <summary>
 323        /// Tries to compress a source byte span into a destination span using the specified quality and window size.
 324        /// </summary>
 325        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
 326        /// <param name="destination">When this method returns, a span of bytes where the compressed data is stored.</pa
 327        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramre
 328        /// <param name="quality">The compression quality value between 0 (no compression) and 9 (maximum compression), 
 329        /// <param name="windowLog2">The base-2 logarithm of the window size (8-15), or -1 to use the default value. Lar
 330        /// <returns><see langword="true"/> if the compression operation was successful; <see langword="false"/> otherwi
 331        public static bool TryCompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten, int qual
 0332        {
 0333            using var encoder = new DeflateEncoder(quality, windowLog2);
 0334            OperationStatus status = encoder.Compress(source, destination, out int consumed, out bytesWritten, isFinalBl
 335
 0336            bool success = status == OperationStatus.Done && consumed == source.Length;
 0337            if (!success)
 0338            {
 0339                bytesWritten = 0;
 0340            }
 341
 0342            return success;
 0343        }
 344    }
 345}
 346