< Summary

Line coverage
69%
Covered lines: 132
Uncovered lines: 57
Coverable lines: 189
Total lines: 372
Line coverage: 69.8%
Branch coverage
50%
Covered branches: 31
Total branches: 62
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%11100%
Finished()100%11100%
Inflate(...)0%220%
Inflate(...)50%6687.5%
Inflate(...)50%2285.71%
InflateVerified(...)70%101083.33%
ReadOutput(...)62.5%8875%
ResetStreamForLeftoverInput()0%660%
IsGzipStream()50%22100%
NeedsInput()100%11100%
NonEmptyInput()100%110%
GetAvailableInput()100%110%
MarkEndOfStream()100%110%
SetInput(...)50%44100%
SetInput(...)100%2293.33%
Dispose(...)50%6678.57%
Dispose()100%11100%
Finalize()100%110%
ReadInflateOutput(...)100%11100%
Inflate(...)37.5%8853.33%
UnsetInput()100%110%
DeallocateInputBufferHandle(...)75%4485.71%
CreateInflater(...)50%22100%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/Inflater.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 a wrapper around the ZLib decompression API.
 12    /// </summary>
 13    internal sealed class Inflater : IDisposable
 14    {
 15        private const int MinWindowBits = -15;                      // WindowBits must be between -8..-15 to ignore the 
 16        private const int MaxWindowBits = 47;                       // zlib headers, 24..31 for GZip headers, or 40..47 
 17
 18        private bool _nonEmptyInput;                                // Whether there is any non empty input
 19        private bool _finished;                                     // Whether the end of the stream has been reached
 20        private bool _isDisposed;                                   // Prevents multiple disposals
 21        private readonly int _windowBits;                           // The WindowBits parameter passed to Inflater const
 22        private readonly ZLibNative.ZLibStreamHandle _zlibStream;   // The handle to the primary underlying zlib stream
 23        private MemoryHandle _inputBufferHandle;                    // The handle to the buffer that provides input to _
 24        private readonly long _uncompressedSize;
 25        private long _currentInflatedCount;
 26        private int _gzipConcatProbeBytes;                          // Count of leftover bytes speculatively consumed as
 27        private bool _endOfStream;                                  // Set once an unconfirmed GZip concatenation probe 
 28
 175229        private object SyncLock => this;                    // Used to make writing to unmanaged structures atomic
 30
 43831        private Inflater(int windowBits, long uncompressedSize, ZLibNative.ZLibStreamHandle zlibStream)
 43832        {
 43833            _finished = false;
 43834            _nonEmptyInput = false;
 43835            _isDisposed = false;
 43836            _windowBits = windowBits;
 43837            _uncompressedSize = uncompressedSize;
 43838            _zlibStream = zlibStream;
 43839        }
 40
 41        public int AvailableOutput => (int)_zlibStream.AvailOut;
 42
 43        /// <summary>
 44        /// Returns true if the end of the stream has been reached.
 45        /// </summary>
 137846        public bool Finished() => _finished;
 47
 48        public unsafe bool Inflate(out byte b)
 049        {
 050            fixed (byte* bufPtr = &b)
 051            {
 052                int bytesRead = InflateVerified(bufPtr, 1);
 053                Debug.Assert(bytesRead == 0 || bytesRead == 1);
 054                return bytesRead != 0;
 55            }
 056        }
 57
 58        public unsafe int Inflate(byte[] bytes, int offset, int length)
 6459        {
 60            // If Inflate is called on an invalid or unready inflater, return 0 to indicate no bytes have been read.
 6461            if (length == 0)
 062                return 0;
 63
 6464            Debug.Assert(null != bytes, "Can't pass in a null output buffer!");
 6465            fixed (byte* bufPtr = bytes)
 6466            {
 6467                return InflateVerified(bufPtr + offset, length);
 68            }
 6469        }
 70
 71        public unsafe int Inflate(Span<byte> destination)
 118672        {
 73            // If Inflate is called on an invalid or unready inflater, return 0 to indicate no bytes have been read.
 118674            if (destination.Length == 0)
 075                return 0;
 76
 118677            fixed (byte* bufPtr = &MemoryMarshal.GetReference(destination))
 118678            {
 118679                return InflateVerified(bufPtr, destination.Length);
 80            }
 118681        }
 82
 83        public unsafe int InflateVerified(byte* bufPtr, int length)
 125084        {
 85            // Once a lone GZip ID1 (0x1F) probe has been confirmed as trailing data at the end of the
 86            // stream, the inflater is terminally finished. Returning 0 here prevents DeflateStream from
 87            // re-reading (and re-consuming) the byte that was already rewound in the base stream.
 125088            if (_endOfStream)
 089            {
 090                return 0;
 91            }
 92
 93            // State is valid; attempt inflation
 94            try
 125095            {
 125096                int bytesRead = 0;
 125097                if (_uncompressedSize == -1)
 098                {
 099                    ReadOutput(bufPtr, length, out bytesRead);
 0100                }
 101                else
 1250102                {
 1250103                    if (_uncompressedSize > _currentInflatedCount)
 876104                    {
 876105                        length = (int)Math.Min(length, _uncompressedSize - _currentInflatedCount);
 876106                        ReadOutput(bufPtr, length, out bytesRead);
 876107                        _currentInflatedCount += bytesRead;
 876108                    }
 109                    else
 374110                    {
 374111                        _finished = true;
 374112                        _zlibStream.AvailIn = 0;
 374113                    }
 1250114                }
 1250115                return bytesRead;
 116            }
 117            finally
 1250118            {
 119                // Before returning, make sure to release input buffer if necessary:
 1250120                if (0 == _zlibStream.AvailIn && IsInputBufferHandleAllocated)
 438121                {
 438122                    DeallocateInputBufferHandle(resetStreamHandle: true);
 438123                }
 1250124            }
 1250125        }
 126
 127        private unsafe void ReadOutput(byte* bufPtr, int length, out int bytesRead)
 876128        {
 876129            if (ReadInflateOutput(bufPtr, length, ZLibNative.FlushCode.NoFlush, out bytesRead) == ZLibNative.ErrorCode.S
 438130            {
 438131                if (!NeedsInput() && IsGzipStream() && IsInputBufferHandleAllocated)
 0132                {
 0133                    _finished = ResetStreamForLeftoverInput();
 0134                }
 135                else
 438136                {
 438137                    _finished = true;
 438138                }
 438139            }
 876140        }
 141
 142        /// <summary>
 143        /// If this stream has some input leftover that hasn't been processed then we should
 144        /// check if it is another GZip file concatenated with this one.
 145        ///
 146        /// Returns false if the leftover input is another GZip data stream.
 147        /// </summary>
 148        private unsafe bool ResetStreamForLeftoverInput()
 0149        {
 0150            Debug.Assert(!NeedsInput());
 0151            Debug.Assert(IsGzipStream());
 0152            Debug.Assert(IsInputBufferHandleAllocated);
 153
 0154            lock (SyncLock)
 0155            {
 156                // Re-evaluating the leftover input supersedes any previously recorded probe.
 0157                _gzipConcatProbeBytes = 0;
 158
 0159                byte* nextInPointer = (byte*)_zlibStream.NextIn;
 0160                uint nextAvailIn = _zlibStream.AvailIn;
 161
 162                // Check the leftover bytes to see if they start with the gzip header ID bytes
 0163                if (*nextInPointer != ZLibNative.GZip_Header_ID1 || (nextAvailIn > 1 && *(nextInPointer + 1) != ZLibNati
 0164                {
 0165                    return true;
 166                }
 167
 168                // A single leftover 0x1F (GZip ID1) is ambiguous: it may be the first byte of a
 169                // concatenated member whose ID2 byte hasn't been read yet, or it may be trailing content
 170                // after the member. We optimistically treat it as the start of a concatenated member
 171                // (resetting below), but remember it as an unconfirmed probe so that DeflateStream can
 172                // rewind it if the base stream turns out to have no further data.
 0173                _gzipConcatProbeBytes = nextAvailIn == 1 ? 1 : 0;
 174
 175                // Reset our existing zstream.
 0176                _zlibStream.InflateReset2_(_windowBits);
 177
 0178                _finished = false;
 0179            }
 180
 0181            return false;
 0182        }
 183
 748184        internal bool IsGzipStream() => _windowBits >= 24 && _windowBits <= 31;
 185
 1720186        public bool NeedsInput() => _zlibStream.AvailIn == 0;
 187
 0188        public bool NonEmptyInput() => _nonEmptyInput;
 189
 0190        internal int GetAvailableInput() => (int)_zlibStream.AvailIn;
 191
 192        /// <summary>
 193        /// Number of bytes that were speculatively consumed as a possible concatenated GZip member
 194        /// header but haven't been confirmed as such (either 0, or 1 for a lone trailing GZip ID1 byte).
 195        /// </summary>
 0196        internal int UnconfirmedGZipProbeBytes => _gzipConcatProbeBytes;
 197
 198        /// <summary>
 199        /// Whether the inflater consumed a lone GZip ID1 (0x1F) byte as an unconfirmed concatenated
 200        /// member probe. If the base stream has no further data, that byte was actually trailing
 201        /// content and should be rewound.
 202        /// </summary>
 0203        internal bool HasUnconfirmedGZipProbe => _gzipConcatProbeBytes > 0;
 204
 205        /// <summary>
 206        /// Whether an unconfirmed GZip concatenation probe has been resolved as trailing data at the end of the stream.
 207        /// </summary>
 438208        internal bool EndOfStreamReached => _endOfStream;
 209
 210        /// <summary>
 211        /// Marks the inflater as terminally finished after an unconfirmed GZip concatenation probe has
 212        /// been rewound, so that subsequent reads don't re-consume the rewound byte from the base stream.
 213        /// </summary>
 0214        internal void MarkEndOfStream() => _endOfStream = true;
 215
 216        public void SetInput(byte[] inputBuffer, int startIndex, int count)
 406217        {
 406218            Debug.Assert(NeedsInput(), "We have something left in previous input!");
 406219            Debug.Assert(inputBuffer != null);
 406220            Debug.Assert(startIndex >= 0 && count >= 0 && count + startIndex <= inputBuffer.Length);
 406221            Debug.Assert(!IsInputBufferHandleAllocated);
 222
 406223            SetInput(inputBuffer.AsMemory(startIndex, count));
 406224        }
 225
 226        public unsafe void SetInput(ReadOnlyMemory<byte> inputBuffer)
 438227        {
 438228            Debug.Assert(NeedsInput(), "We have something left in previous input!");
 438229            Debug.Assert(!IsInputBufferHandleAllocated);
 230
 438231            if (inputBuffer.IsEmpty)
 0232                return;
 233
 438234            lock (SyncLock)
 438235            {
 438236                _inputBufferHandle = inputBuffer.Pin();
 438237                _zlibStream.NextIn = (IntPtr)_inputBufferHandle.Pointer;
 438238                _zlibStream.AvailIn = (uint)inputBuffer.Length;
 438239                _finished = false;
 438240                _nonEmptyInput = true;
 241
 242                // Feeding new input resolves any pending lone-0x1F probe: the byte wasn't trailing data.
 438243                _gzipConcatProbeBytes = 0;
 438244            }
 438245        }
 246
 247        private void Dispose(bool disposing)
 438248        {
 438249            if (!_isDisposed)
 438250            {
 438251                if (disposing)
 438252                {
 438253                    _zlibStream.Dispose();
 438254                }
 255
 438256                if (IsInputBufferHandleAllocated)
 0257                {
 258                    // Unpin the input buffer, but avoid modifying the ZLibStreamHandle (which may have been disposed of
 0259                    DeallocateInputBufferHandle(resetStreamHandle: false);
 0260                }
 261
 438262                _isDisposed = true;
 438263            }
 438264        }
 265
 266        public void Dispose()
 438267        {
 438268            Dispose(true);
 438269            GC.SuppressFinalize(this);
 438270        }
 271
 272        ~Inflater()
 0273        {
 0274            Dispose(false);
 0275        }
 276
 277        /// <summary>
 278        /// Wrapper around the ZLib inflate function, configuring the stream appropriately.
 279        /// </summary>
 280        private unsafe ZLibNative.ErrorCode ReadInflateOutput(byte* bufPtr, int length, ZLibNative.FlushCode flushCode, 
 876281        {
 876282            lock (SyncLock)
 876283            {
 876284                _zlibStream.NextOut = (IntPtr)bufPtr;
 876285                _zlibStream.AvailOut = (uint)length;
 286
 876287                ZLibNative.ErrorCode errC = Inflate(flushCode);
 876288                bytesRead = length - (int)_zlibStream.AvailOut;
 289
 876290                return errC;
 291            }
 876292        }
 293
 294        /// <summary>
 295        /// Wrapper around the ZLib inflate function
 296        /// </summary>
 297        private ZLibNative.ErrorCode Inflate(ZLibNative.FlushCode flushCode)
 876298        {
 299            ZLibNative.ErrorCode errC;
 300            try
 876301            {
 876302                errC = _zlibStream.Inflate(flushCode);
 876303            }
 0304            catch (Exception cause) // could not load the Zlib DLL correctly
 0305            {
 0306                throw new ZLibException(SR.ZLibErrorDLLLoadError, cause);
 307            }
 876308            switch (errC)
 309            {
 310                case ZLibNative.ErrorCode.Ok:           // progress has been made inflating
 311                case ZLibNative.ErrorCode.StreamEnd:    // The end of the input stream has been reached
 438312                    return errC;
 313
 314                case ZLibNative.ErrorCode.BufError:     // No room in the output buffer - inflate() can be called again 
 438315                    return errC;
 316
 317                case ZLibNative.ErrorCode.MemError:     // Not enough memory to complete the operation
 0318                    throw new ZLibException(SR.ZLibErrorNotEnoughMemory, "inflate_", (int)errC, _zlibStream.GetErrorMess
 319
 320                case ZLibNative.ErrorCode.DataError:    // The input data was corrupted (input stream not conforming to 
 0321                    throw new InvalidDataException(SR.UnsupportedCompression);
 322
 323                case ZLibNative.ErrorCode.StreamError:  //the stream structure was inconsistent (for example if next_in 
 0324                    throw new ZLibException(SR.ZLibErrorInconsistentStream, "inflate_", (int)errC, _zlibStream.GetErrorM
 325
 326                default:
 0327                    throw new ZLibException(SR.ZLibErrorUnexpected, "inflate_", (int)errC, _zlibStream.GetErrorMessage()
 328            }
 876329        }
 330
 331        /// <summary>
 332        /// Discards any unconsumed input previously set via SetInput, releasing the pinned reference (if any).
 333        /// Must be called if an in-progress operation is abandoned (e.g. due to an exception or cancellation) so
 334        /// the inflater doesn't retain a dangling reference to a buffer the caller may have since reused or freed.
 335        /// </summary>
 0336        internal void UnsetInput() => DeallocateInputBufferHandle(resetStreamHandle: true);
 337
 338        /// <summary>
 339        /// Frees the GCHandle being used to store the input buffer
 340        /// </summary>
 341        private void DeallocateInputBufferHandle(bool resetStreamHandle)
 438342        {
 438343            lock (SyncLock)
 438344            {
 438345                if (!IsInputBufferHandleAllocated)
 0346                {
 0347                    return;
 348                }
 349
 438350                if (resetStreamHandle)
 438351                {
 438352                    _zlibStream.AvailIn = 0;
 438353                    _zlibStream.NextIn = ZLibNative.ZNullPtr;
 438354                }
 355
 438356                _inputBufferHandle.Dispose();
 438357            }
 438358        }
 359
 360        public static Inflater CreateInflater(int windowBits, long uncompressedSize = -1)
 438361        {
 438362            Debug.Assert(windowBits >= MinWindowBits && windowBits <= MaxWindowBits);
 363
 438364            ZLibNative.ZLibStreamHandle zlibStream = ZLibNative.ZLibStreamHandle.CreateForInflate(windowBits);
 365
 438366            return new Inflater(windowBits, uncompressedSize, zlibStream);
 438367        }
 368
 2970369        private unsafe bool IsInputBufferHandleAllocated => _inputBufferHandle.Pointer != default;
 370    }
 371}
 372