File: System\IO\Compression\GZipEncoder.cs
Web Access
Project: src\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.Buffers;
 
namespace System.IO.Compression
{
    /// <summary>
    /// Provides methods and static methods to encode data in a streamless, non-allocating, and performant manner using the GZip data format specification.
    /// </summary>
    public sealed class GZipEncoder : IDisposable
    {
        private readonly DeflateEncoder _deflateEncoder;
        private bool _disposed;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="GZipEncoder"/> class using the default quality.
        /// </summary>
        /// <exception cref="IOException">Failed to create the <see cref="GZipEncoder"/> instance.</exception>
        public GZipEncoder()
            : this(ZLibNative.DefaultQuality, ZLibNative.DefaultWindowLog)
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="GZipEncoder"/> class using the specified quality.
        /// </summary>
        /// <param name="quality">The compression quality value between 0 (no compression) and 9 (maximum compression), or -1 to use the default value.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="quality"/> is not in the valid range (0-9 or -1).</exception>
        /// <exception cref="IOException">Failed to create the <see cref="GZipEncoder"/> instance.</exception>
        public GZipEncoder(int quality)
            : this(quality, ZLibNative.DefaultWindowLog)
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="GZipEncoder"/> class using the specified options.
        /// </summary>
        /// <param name="options">The compression options.</param>
        /// <exception cref="ArgumentNullException"><paramref name="options"/> is null.</exception>
        /// <exception cref="IOException">Failed to create the <see cref="GZipEncoder"/> instance.</exception>
        public GZipEncoder(ZLibCompressionOptions options)
        {
            _deflateEncoder = new DeflateEncoder(options, CompressionFormat.GZip);
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="GZipEncoder"/> class using the specified quality and window size.
        /// </summary>
        /// <param name="quality">The compression quality value between 0 (no compression) and 9 (maximum compression), or -1 to use the default value.</param>
        /// <param name="windowLog">The base-2 logarithm of the window size (8-15), or -1 to use the default value. Larger values result in better compression at the expense of memory usage.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="quality"/> is not in the valid range (0-9 or -1), or <paramref name="windowLog"/> is not in the valid range (8-15 or -1).</exception>
        /// <exception cref="IOException">Failed to create the <see cref="GZipEncoder"/> instance.</exception>
        public GZipEncoder(int quality, int windowLog)
        {
            _deflateEncoder = new DeflateEncoder(quality, windowLog, CompressionFormat.GZip);
        }
 
        /// <summary>
        /// Frees and disposes unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            _disposed = true;
            _deflateEncoder.Dispose();
        }
 
        private void EnsureNotDisposed()
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
        }
 
        /// <summary>
        /// Gets the maximum expected compressed length for the provided input size.
        /// </summary>
        /// <param name="inputLength">The input size to get the maximum expected compressed length from.</param>
        /// <returns>A number representing the maximum compressed length for the provided input size.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="inputLength"/> is negative.</exception>
        public static long GetMaxCompressedLength(long inputLength)
        {
            // compressBound() returns the upper bound for zlib-wrapped deflate, which includes
            // 6 bytes of zlib overhead (2-byte header + 4-byte Adler32 trailer).
            // GZip format uses 18 bytes of overhead (10-byte header + 8-byte CRC32/size trailer),
            // which is 12 bytes more than the zlib overhead already included in compressBound().
            long maxCompressedLength = DeflateEncoder.GetMaxCompressedLength(inputLength);
 
            if (maxCompressedLength > long.MaxValue - 12)
            {
                throw new ArgumentOutOfRangeException(nameof(inputLength));
            }
 
            return maxCompressedLength + 12;
        }
 
        /// <summary>
        /// Compresses a read-only byte span into a destination span.
        /// </summary>
        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
        /// <param name="destination">When this method returns, a byte span where the compressed data is stored.</param>
        /// <param name="bytesConsumed">When this method returns, the total number of bytes that were read from <paramref name="source"/>.</param>
        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramref name="destination"/>.</param>
        /// <param name="isFinalBlock"><see langword="true"/> to finalize the internal stream, which prevents adding more input data when this method returns; <see langword="false"/> to allow the encoder to postpone the production of output until it has processed enough input.</param>
        /// <returns>One of the enumeration values that describes the status with which the span-based operation finished.</returns>
        public OperationStatus Compress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten, bool isFinalBlock)
        {
            EnsureNotDisposed();
            return _deflateEncoder.Compress(source, destination, out bytesConsumed, out bytesWritten, isFinalBlock);
        }
 
        /// <summary>
        /// Compresses an empty read-only span of bytes into its destination, ensuring that output is produced for all the processed input.
        /// </summary>
        /// <param name="destination">When this method returns, a span of bytes where the compressed data will be stored.</param>
        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramref name="destination"/>.</param>
        /// <returns>One of the enumeration values that describes the status with which the operation finished.</returns>
        public OperationStatus Flush(Span<byte> destination, out int bytesWritten)
        {
            EnsureNotDisposed();
            return _deflateEncoder.Flush(destination, out bytesWritten);
        }
 
        /// <summary>
        /// Tries to compress a source byte span into a destination span using the default quality.
        /// </summary>
        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
        /// <param name="destination">When this method returns, a span of bytes where the compressed data is stored.</param>
        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramref name="destination"/>.</param>
        /// <returns><see langword="true"/> if the compression operation was successful; <see langword="false"/> otherwise.</returns>
        public static bool TryCompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
            => TryCompress(source, destination, out bytesWritten, ZLibNative.DefaultQuality, ZLibNative.DefaultWindowLog);
 
        /// <summary>
        /// Tries to compress a source byte span into a destination span using the specified quality.
        /// </summary>
        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
        /// <param name="destination">When this method returns, a span of bytes where the compressed data is stored.</param>
        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramref name="destination"/>.</param>
        /// <param name="quality">The compression quality value between 0 (no compression) and 9 (maximum compression), or -1 to use the default value.</param>
        /// <returns><see langword="true"/> if the compression operation was successful; <see langword="false"/> otherwise.</returns>
        public static bool TryCompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten, int quality)
            => TryCompress(source, destination, out bytesWritten, quality, ZLibNative.DefaultWindowLog);
 
        /// <summary>
        /// Tries to compress a source byte span into a destination span using the specified quality and window size.
        /// </summary>
        /// <param name="source">A read-only span of bytes containing the source data to compress.</param>
        /// <param name="destination">When this method returns, a span of bytes where the compressed data is stored.</param>
        /// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramref name="destination"/>.</param>
        /// <param name="quality">The compression quality value between 0 (no compression) and 9 (maximum compression), or -1 to use the default value.</param>
        /// <param name="windowLog">The base-2 logarithm of the window size (8-15), or -1 to use the default value. Larger values result in better compression at the expense of memory usage.</param>
        /// <returns><see langword="true"/> if the compression operation was successful; <see langword="false"/> otherwise.</returns>
        public static bool TryCompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten, int quality, int windowLog)
        {
            using var encoder = new GZipEncoder(quality, windowLog);
            OperationStatus status = encoder.Compress(source, destination, out int consumed, out bytesWritten, isFinalBlock: true);
 
            bool success = status == OperationStatus.Done && consumed == source.Length;
            if (!success)
            {
                bytesWritten = 0;
            }
 
            return success;
        }
    }
}