< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 189
Coverable lines: 189
Total lines: 372
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 62
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%
Finished()100%110%
Inflate(...)0%220%
Inflate(...)0%660%
Inflate(...)0%220%
InflateVerified(...)0%10100%
ReadOutput(...)0%880%
ResetStreamForLeftoverInput()0%660%
IsGzipStream()0%220%
NeedsInput()100%110%
NonEmptyInput()100%110%
GetAvailableInput()100%110%
MarkEndOfStream()100%110%
SetInput(...)0%440%
SetInput(...)0%220%
Dispose(...)0%660%
Dispose()100%110%
Finalize()100%110%
ReadInflateOutput(...)100%110%
Inflate(...)0%880%
UnsetInput()100%110%
DeallocateInputBufferHandle(...)0%440%
CreateInflater(...)0%220%

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
 029        private object SyncLock => this;                    // Used to make writing to unmanaged structures atomic
 30
 031        private Inflater(int windowBits, long uncompressedSize, ZLibNative.ZLibStreamHandle zlibStream)
 032        {
 033            _finished = false;
 034            _nonEmptyInput = false;
 035            _isDisposed = false;
 036            _windowBits = windowBits;
 037            _uncompressedSize = uncompressedSize;
 038            _zlibStream = zlibStream;
 039        }
 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>
 046        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)
 059        {
 60            // If Inflate is called on an invalid or unready inflater, return 0 to indicate no bytes have been read.
 061            if (length == 0)
 062                return 0;
 63
 064            Debug.Assert(null != bytes, "Can't pass in a null output buffer!");
 065            fixed (byte* bufPtr = bytes)
 066            {
 067                return InflateVerified(bufPtr + offset, length);
 68            }
 069        }
 70
 71        public unsafe int Inflate(Span<byte> destination)
 072        {
 73            // If Inflate is called on an invalid or unready inflater, return 0 to indicate no bytes have been read.
 074            if (destination.Length == 0)
 075                return 0;
 76
 077            fixed (byte* bufPtr = &MemoryMarshal.GetReference(destination))
 078            {
 079                return InflateVerified(bufPtr, destination.Length);
 80            }
 081        }
 82
 83        public unsafe int InflateVerified(byte* bufPtr, int length)
 084        {
 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.
 088            if (_endOfStream)
 089            {
 090                return 0;
 91            }
 92
 93            // State is valid; attempt inflation
 94            try
 095            {
 096                int bytesRead = 0;
 097                if (_uncompressedSize == -1)
 098                {
 099                    ReadOutput(bufPtr, length, out bytesRead);
 0100                }
 101                else
 0102                {
 0103                    if (_uncompressedSize > _currentInflatedCount)
 0104                    {
 0105                        length = (int)Math.Min(length, _uncompressedSize - _currentInflatedCount);
 0106                        ReadOutput(bufPtr, length, out bytesRead);
 0107                        _currentInflatedCount += bytesRead;
 0108                    }
 109                    else
 0110                    {
 0111                        _finished = true;
 0112                        _zlibStream.AvailIn = 0;
 0113                    }
 0114                }
 0115                return bytesRead;
 116            }
 117            finally
 0118            {
 119                // Before returning, make sure to release input buffer if necessary:
 0120                if (0 == _zlibStream.AvailIn && IsInputBufferHandleAllocated)
 0121                {
 0122                    DeallocateInputBufferHandle(resetStreamHandle: true);
 0123                }
 0124            }
 0125        }
 126
 127        private unsafe void ReadOutput(byte* bufPtr, int length, out int bytesRead)
 0128        {
 0129            if (ReadInflateOutput(bufPtr, length, ZLibNative.FlushCode.NoFlush, out bytesRead) == ZLibNative.ErrorCode.S
 0130            {
 0131                if (!NeedsInput() && IsGzipStream() && IsInputBufferHandleAllocated)
 0132                {
 0133                    _finished = ResetStreamForLeftoverInput();
 0134                }
 135                else
 0136                {
 0137                    _finished = true;
 0138                }
 0139            }
 0140        }
 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
 0184        internal bool IsGzipStream() => _windowBits >= 24 && _windowBits <= 31;
 185
 0186        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>
 0208        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)
 0217        {
 0218            Debug.Assert(NeedsInput(), "We have something left in previous input!");
 0219            Debug.Assert(inputBuffer != null);
 0220            Debug.Assert(startIndex >= 0 && count >= 0 && count + startIndex <= inputBuffer.Length);
 0221            Debug.Assert(!IsInputBufferHandleAllocated);
 222
 0223            SetInput(inputBuffer.AsMemory(startIndex, count));
 0224        }
 225
 226        public unsafe void SetInput(ReadOnlyMemory<byte> inputBuffer)
 0227        {
 0228            Debug.Assert(NeedsInput(), "We have something left in previous input!");
 0229            Debug.Assert(!IsInputBufferHandleAllocated);
 230
 0231            if (inputBuffer.IsEmpty)
 0232                return;
 233
 0234            lock (SyncLock)
 0235            {
 0236                _inputBufferHandle = inputBuffer.Pin();
 0237                _zlibStream.NextIn = (IntPtr)_inputBufferHandle.Pointer;
 0238                _zlibStream.AvailIn = (uint)inputBuffer.Length;
 0239                _finished = false;
 0240                _nonEmptyInput = true;
 241
 242                // Feeding new input resolves any pending lone-0x1F probe: the byte wasn't trailing data.
 0243                _gzipConcatProbeBytes = 0;
 0244            }
 0245        }
 246
 247        private void Dispose(bool disposing)
 0248        {
 0249            if (!_isDisposed)
 0250            {
 0251                if (disposing)
 0252                {
 0253                    _zlibStream.Dispose();
 0254                }
 255
 0256                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
 0262                _isDisposed = true;
 0263            }
 0264        }
 265
 266        public void Dispose()
 0267        {
 0268            Dispose(true);
 0269            GC.SuppressFinalize(this);
 0270        }
 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, 
 0281        {
 0282            lock (SyncLock)
 0283            {
 0284                _zlibStream.NextOut = (IntPtr)bufPtr;
 0285                _zlibStream.AvailOut = (uint)length;
 286
 0287                ZLibNative.ErrorCode errC = Inflate(flushCode);
 0288                bytesRead = length - (int)_zlibStream.AvailOut;
 289
 0290                return errC;
 291            }
 0292        }
 293
 294        /// <summary>
 295        /// Wrapper around the ZLib inflate function
 296        /// </summary>
 297        private ZLibNative.ErrorCode Inflate(ZLibNative.FlushCode flushCode)
 0298        {
 299            ZLibNative.ErrorCode errC;
 300            try
 0301            {
 0302                errC = _zlibStream.Inflate(flushCode);
 0303            }
 0304            catch (Exception cause) // could not load the Zlib DLL correctly
 0305            {
 0306                throw new ZLibException(SR.ZLibErrorDLLLoadError, cause);
 307            }
 0308            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
 0312                    return errC;
 313
 314                case ZLibNative.ErrorCode.BufError:     // No room in the output buffer - inflate() can be called again 
 0315                    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            }
 0329        }
 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)
 0342        {
 0343            lock (SyncLock)
 0344            {
 0345                if (!IsInputBufferHandleAllocated)
 0346                {
 0347                    return;
 348                }
 349
 0350                if (resetStreamHandle)
 0351                {
 0352                    _zlibStream.AvailIn = 0;
 0353                    _zlibStream.NextIn = ZLibNative.ZNullPtr;
 0354                }
 355
 0356                _inputBufferHandle.Dispose();
 0357            }
 0358        }
 359
 360        public static Inflater CreateInflater(int windowBits, long uncompressedSize = -1)
 0361        {
 0362            Debug.Assert(windowBits >= MinWindowBits && windowBits <= MaxWindowBits);
 363
 0364            ZLibNative.ZLibStreamHandle zlibStream = ZLibNative.ZLibStreamHandle.CreateForInflate(windowBits);
 365
 0366            return new Inflater(windowBits, uncompressedSize, zlibStream);
 0367        }
 368
 0369        private unsafe bool IsInputBufferHandleAllocated => _inputBufferHandle.Pointer != default;
 370    }
 371}
 372