< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 516
Coverable lines: 516
Total lines: 1122
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 168
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: .ctor(...)100%110%
File 1: .ctor(...)100%110%
File 1: .ctor(...)100%110%
File 1: .ctor(...)100%110%
File 1: SetSourceLength(...)0%220%
File 1: WriteCore(...)0%14140%
File 1: WriteCoreAsync(...)0%14140%
File 1: Write(...)100%110%
File 1: Write(...)100%110%
File 1: WriteByte(...)100%110%
File 1: WriteAsync(...)100%110%
File 1: WriteAsync(...)0%440%
File 1: BeginWrite(...)100%110%
File 1: EndWrite(...)100%110%
File 1: Flush()0%220%
File 1: FlushAsync(...)0%220%
File 1: FlushAsyncInternal(System.Threading.CancellationToken)100%110%
File 2: .ctor(...)100%110%
File 2: Init(...)0%880%
File 2: .ctor(...)0%220%
File 2: .ctor(...)100%110%
File 2: .ctor(...)0%220%
File 2: Seek(...)100%110%
File 2: SetLength(...)100%110%
File 2: Dispose(...)0%880%
File 2: DisposeAsync()0%10100%
File 2: ReleaseStateForDispose()0%12120%
File 2: EnsureNotDisposed()100%110%
File 2: ThrowConcurrentRWOperation()100%110%
File 2: BeginRWOperation(...)0%440%
File 2: EndRWOperation()100%110%
File 2: EnsureNoActiveRWOperation()0%220%
File 3: .ctor(...)100%110%
File 3: .ctor(...)100%110%
File 3: TryDecompress(...)0%18180%
File 3: Read(...)100%110%
File 3: Read(...)0%20200%
File 3: AdvanceToNextFrame(...)0%12120%
File 3: ReadAsync(...)100%110%
File 3: ReadAsync(...)0%20200%
File 3: BeginRead(...)100%110%
File 3: EndRead(...)100%110%
File 3: ReadByte()0%220%
File 3: TryRewindStream(...)0%220%
File 3: ThrowInvalidStream()100%110%
File 3: ThrowTruncatedInvalidData()100%110%

File(s)

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\Zstandard\ZstandardStream.Compress.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.Threading;
 7using System.Threading.Tasks;
 8
 9namespace System.IO.Compression
 10{
 11    public sealed partial class ZstandardStream
 12    {
 13        private ZstandardEncoder? _encoder;
 14
 15        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 16        /// <param name="stream">The stream to which compressed data is written or from which data to decompress is read
 17        /// <param name="compressionLevel">One of the enumeration values that indicates whether to compress data to the 
 18        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <see langword="null"/>.</exception>
 19        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing.</exception>
 20        /// <exception cref="ArgumentOutOfRangeException"><paramref name="compressionLevel"/> is not a valid <see cref="
 021        public ZstandardStream(Stream stream, CompressionLevel compressionLevel) : this(stream, compressionLevel, leaveO
 22
 23        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 24        /// <param name="stream">The stream to which compressed data is written or from which data to decompress is read
 25        /// <param name="compressionLevel">One of the enumeration values that indicates whether to compress data to the 
 26        /// <param name="leaveOpen"><see langword="true" /> to leave the stream open after the <see cref="ZstandardStrea
 27        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <see langword="null"/>.</exception>
 28        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing.</exception>
 29        /// <exception cref="ArgumentOutOfRangeException"><paramref name="compressionLevel"/> is not a valid <see cref="
 030        public ZstandardStream(Stream stream, CompressionLevel compressionLevel, bool leaveOpen)
 031        {
 032            int quality = ZstandardUtils.GetQualityFromCompressionLevel(compressionLevel);
 33
 034            Init(stream, CompressionMode.Compress);
 035            _mode = CompressionMode.Compress;
 036            _leaveOpen = leaveOpen;
 37
 38            try
 039            {
 040                _encoder = new ZstandardEncoder(quality);
 041            }
 042            catch
 043            {
 044                _buffer.Dispose();
 045                throw;
 46            }
 047        }
 48
 49        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 50        /// <param name="stream">The stream to which compressed data is written or from which data to decompress is read
 51        /// <param name="compressionOptions">The options to use for Zstandard compression or decompression.</param>
 52        /// <param name="leaveOpen"><see langword="true" /> to leave the stream open after the <see cref="ZstandardStrea
 53        /// <exception cref="ArgumentNullException"><paramref name="stream"/> or <paramref name="compressionOptions"/> i
 54        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing.</exception>
 055        public ZstandardStream(Stream stream, ZstandardCompressionOptions compressionOptions, bool leaveOpen = false)
 056        {
 057            ArgumentNullException.ThrowIfNull(compressionOptions);
 58
 059            Init(stream, CompressionMode.Compress);
 060            _mode = CompressionMode.Compress;
 061            _leaveOpen = leaveOpen;
 62
 63            try
 064            {
 065                _encoder = new ZstandardEncoder(compressionOptions);
 066            }
 067            catch
 068            {
 069                _buffer.Dispose();
 070                throw;
 71            }
 072        }
 73
 74        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 75        /// <param name="stream">The stream to which compressed data is written.</param>
 76        /// <param name="encoder">The encoder instance to use for compression. The stream will not dispose this encoder;
 77        /// <param name="leaveOpen"><see langword="true" /> to leave the stream open after the <see cref="ZstandardStrea
 78        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <see langword="null"/>.</exception>
 79        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing.</exception>
 080        public ZstandardStream(Stream stream, ZstandardEncoder encoder, bool leaveOpen = false)
 081        {
 082            ArgumentNullException.ThrowIfNull(encoder);
 83
 084            Init(stream, CompressionMode.Compress);
 085            _mode = CompressionMode.Compress;
 086            _leaveOpen = leaveOpen;
 87
 088            _encoder = encoder;
 089            _encoderOwned = false;
 090        }
 91
 92        /// <summary>Sets the length of the uncompressed data that will be compressed by this instance.</summary>
 93        /// <param name="length">The length of the source data in bytes.</param>
 94        /// <remarks>
 95        /// Setting the source length is optional. If set, the information is stored in the header of the compressed dat
 96        /// </remarks>
 97        /// <exception cref="InvalidOperationException">Attempting to set the source size on a decompression stream, or 
 98        /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is negative.</exception>
 99        /// <exception cref="ObjectDisposedException">The encoder has been disposed.</exception>
 100        public void SetSourceLength(long length)
 0101        {
 0102            if (_mode != CompressionMode.Compress)
 0103            {
 0104                throw new InvalidOperationException(SR.CannotWriteToDecompressionStream);
 105            }
 0106            EnsureNotDisposed();
 0107            Debug.Assert(_encoder != null);
 108
 0109            _encoder.SetSourceLength(length);
 0110        }
 111
 112        private void WriteCore(ReadOnlySpan<byte> buffer, bool isFinalBlock = false, bool flush = false, bool throwOnAct
 0113        {
 0114            if (_mode != CompressionMode.Compress)
 0115            {
 0116                throw new InvalidOperationException(SR.CannotWriteToDecompressionStream);
 117            }
 0118            EnsureNotDisposed();
 0119            Debug.Assert(_encoder != null);
 120
 0121            if (!BeginRWOperation(throwOnActiveRwOp))
 0122            {
 123                // we are disposing concurrently with another write, we should avoid throwing during potential stack unw
 124                // best effort at this point is to no-op as we don't guarantee correctness in concurrent scenarios
 0125                return;
 126            }
 127
 128            try
 0129            {
 0130                OperationStatus lastResult = OperationStatus.DestinationTooSmall;
 131
 132                // we don't need extra tracking of ArrayBuffer for write operations as
 133                // we propagate the entire result downstream, so just grab the span
 0134                Span<byte> output = _buffer.AvailableSpan;
 0135                Debug.Assert(!output.IsEmpty, "Internal buffer should be initialized to non-zero size");
 136
 0137                while (lastResult == OperationStatus.DestinationTooSmall)
 0138                {
 139                    int bytesWritten;
 140                    int bytesConsumed;
 141
 0142                    if (flush)
 0143                    {
 0144                        Debug.Assert(buffer.Length == 0);
 0145                        bytesConsumed = 0;
 0146                        lastResult = _encoder.Flush(output, out bytesWritten);
 0147                    }
 148                    else
 0149                    {
 0150                        lastResult = _encoder.Compress(buffer, output, out bytesConsumed, out bytesWritten, isFinalBlock
 0151                    }
 152
 0153                    if (lastResult == OperationStatus.InvalidData)
 0154                    {
 0155                        throw new InvalidDataException(SR.ZstandardStream_Compress_InvalidData);
 156                    }
 0157                    if (bytesWritten > 0)
 0158                    {
 0159                        _stream.Write(output.Slice(0, bytesWritten));
 0160                    }
 0161                    if (bytesConsumed > 0)
 0162                    {
 0163                        buffer = buffer.Slice(bytesConsumed);
 0164                    }
 0165                }
 0166            }
 167            finally
 0168            {
 0169                EndRWOperation();
 0170            }
 0171        }
 172
 173        private async ValueTask WriteCoreAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken, bool is
 0174        {
 0175            if (_mode != CompressionMode.Compress)
 0176            {
 0177                throw new InvalidOperationException(SR.CannotWriteToDecompressionStream);
 178            }
 0179            EnsureNotDisposed();
 0180            Debug.Assert(_encoder != null);
 181
 0182            if (!BeginRWOperation(throwOnActiveRwOp))
 0183            {
 184                // we are disposing concurrently with another write, we should avoid throwing during potential stack unw
 185                // best effort at this point is to no-op as we don't guarantee correctness in concurrent scenarios
 0186                return;
 187            }
 188
 189            try
 0190            {
 0191                OperationStatus lastResult = OperationStatus.DestinationTooSmall;
 192
 193                // we don't need extra tracking of ArrayBuffer for write operations as
 194                // we propagate the entire result downstream, so just grab the memory
 0195                Memory<byte> output = _buffer.AvailableMemory;
 0196                Debug.Assert(!output.IsEmpty, "Internal buffer should be initialized to non-zero size");
 197
 0198                while (lastResult == OperationStatus.DestinationTooSmall)
 0199                {
 200                    int bytesWritten;
 201                    int bytesConsumed;
 202
 0203                    if (flush)
 0204                    {
 0205                        Debug.Assert(buffer.Length == 0);
 0206                        bytesConsumed = 0;
 0207                        lastResult = _encoder.Flush(output.Span, out bytesWritten);
 0208                    }
 209                    else
 0210                    {
 0211                        lastResult = _encoder.Compress(buffer.Span, output.Span, out bytesConsumed, out bytesWritten, is
 0212                    }
 213
 0214                    if (lastResult == OperationStatus.InvalidData)
 0215                    {
 0216                        throw new InvalidDataException(SR.ZstandardStream_Compress_InvalidData);
 217                    }
 0218                    if (bytesWritten > 0)
 0219                    {
 0220                        await _stream.WriteAsync(output.Slice(0, bytesWritten), cancellationToken).ConfigureAwait(false)
 0221                    }
 0222                    if (bytesConsumed > 0)
 0223                    {
 0224                        buffer = buffer.Slice(bytesConsumed);
 0225                    }
 0226                }
 0227            }
 228            finally
 0229            {
 0230                EndRWOperation();
 0231            }
 0232        }
 233
 234        /// <summary>Writes compressed bytes to the underlying stream from the specified byte array.</summary>
 235        /// <param name="buffer">The buffer that contains the data to compress.</param>
 236        /// <param name="offset">The byte offset in <paramref name="buffer" /> from which the bytes will be read.</param
 237        /// <param name="count">The maximum number of bytes to write.</param>
 238        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 239        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 240        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 241        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 242        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 243        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 244        public override void Write(byte[] buffer, int offset, int count)
 0245        {
 0246            ValidateBufferArguments(buffer, offset, count);
 0247            Write(new ReadOnlySpan<byte>(buffer, offset, count));
 0248        }
 249
 250        /// <summary>Writes compressed bytes to the underlying stream from the specified span.</summary>
 251        /// <param name="buffer">The span that contains the data to compress.</param>
 252        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 253        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 254        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 255        public override void Write(ReadOnlySpan<byte> buffer)
 0256        {
 0257            WriteCore(buffer);
 0258        }
 259
 260        /// <summary>Writes a byte to the current position in the stream and advances the position within the stream by 
 261        /// <param name="value">The byte to write to the stream.</param>
 262        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 263        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 264        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 265        public override void WriteByte(byte value)
 0266        {
 0267            Write([value]);
 0268        }
 269
 270        /// <summary>Asynchronously writes compressed bytes to the underlying stream from the specified array.</summary>
 271        /// <param name="buffer">The buffer that contains the data to compress.</param>
 272        /// <param name="offset">The byte offset in <paramref name="buffer" /> from which the bytes will be read.</param
 273        /// <param name="count">The maximum number of bytes to write.</param>
 274        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 275        /// <returns>A task that represents the asynchronous write operation.</returns>
 276        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 277        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 278        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 279        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 280        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 281        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 282        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0283        {
 0284            ValidateBufferArguments(buffer, offset, count);
 0285            return WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask();
 0286        }
 287
 288        /// <summary>Asynchronously writes compressed bytes to the underlying stream from the specified memory.</summary
 289        /// <param name="buffer">The memory that contains the data to compress.</param>
 290        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 291        /// <returns>A task that represents the asynchronous write operation.</returns>
 292        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 293        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 294        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 295        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
 0296        {
 0297            if (_mode != CompressionMode.Compress)
 0298            {
 0299                throw new InvalidOperationException(SR.CannotWriteToDecompressionStream);
 300            }
 301
 0302            EnsureNotDisposed();
 303
 0304            return cancellationToken.IsCancellationRequested ?
 0305                ValueTask.FromCanceled(cancellationToken) :
 0306                WriteCoreAsync(buffer, cancellationToken);
 0307        }
 308
 309        /// <summary>Begins an asynchronous write operation.</summary>
 310        /// <param name="buffer">The buffer to write data from.</param>
 311        /// <param name="offset">The byte offset in <paramref name="buffer"/> from which to begin writing.</param>
 312        /// <param name="count">The maximum number of bytes to write.</param>
 313        /// <param name="callback">An optional asynchronous callback, to be called when the write is complete.</param>
 314        /// <param name="state">A user-provided object that distinguishes this particular asynchronous write request fro
 315        /// <returns>An object that represents the asynchronous write, which could still be pending.</returns>
 316        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 317        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 318        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 319        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 320        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 321        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 322        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? s
 0323        {
 0324            return TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count, CancellationToken.None), callback, state);
 0325        }
 326
 327        /// <summary>Ends an asynchronous write operation.</summary>
 328        /// <param name="asyncResult">A reference to the outstanding asynchronous I/O request.</param>
 329        public override void EndWrite(IAsyncResult asyncResult)
 0330        {
 0331            TaskToAsyncResult.End(asyncResult);
 0332        }
 333
 334        /// <summary>Flushes the internal buffer.</summary>
 335        /// <exception cref="InvalidOperationException">Concurrent write operations are not supported.</exception>
 336        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 337        /// <exception cref="IOException">The flush operation failed.</exception>
 338        public override void Flush()
 0339        {
 0340            EnsureNotDisposed();
 0341            EnsureNoActiveRWOperation();
 342
 0343            if (_mode == CompressionMode.Compress)
 0344            {
 0345                WriteCore(Span<byte>.Empty, flush: true);
 0346                _stream.Flush();
 0347            }
 0348        }
 349
 350        /// <summary>Asynchronously flushes the internal buffer.</summary>
 351        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 352        /// <returns>A task that represents the asynchronous flush operation.</returns>
 353        /// <exception cref="InvalidOperationException">Concurrent write operations are not supported.</exception>
 354        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 355        /// <exception cref="IOException">The flush operation failed.</exception>
 356        public override Task FlushAsync(CancellationToken cancellationToken)
 0357        {
 0358            EnsureNotDisposed();
 0359            EnsureNoActiveRWOperation();
 360
 0361            if (_mode == CompressionMode.Compress)
 0362            {
 0363                return FlushAsyncInternal(cancellationToken);
 364            }
 365
 0366            return Task.CompletedTask;
 367
 368            async Task FlushAsyncInternal(CancellationToken cancellationToken)
 0369            {
 0370                await WriteCoreAsync(Memory<byte>.Empty, cancellationToken, flush: true).ConfigureAwait(false);
 0371                await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
 0372            }
 0373        }
 374    }
 375}

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\Zstandard\ZstandardStream.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.Diagnostics.CodeAnalysis;
 5using System.Net;
 6using System.Threading.Tasks;
 7using System.Threading;
 8
 9namespace System.IO.Compression
 10{
 11    /// <summary>Provides methods and properties used to compress and decompress streams by using the Zstandard data for
 12    [System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
 13    public sealed partial class ZstandardStream : Stream
 14    {
 15        private const int DefaultInternalBufferSize = 65536; // 64KB default buffer
 16        private Stream _stream;
 17        private ArrayBuffer _buffer;
 18        private readonly bool _leaveOpen;
 19        private readonly CompressionMode _mode;
 20        private volatile bool _activeRwOperation;
 21
 22        // Tracks whether the encoder/decoder are owned by this stream instance
 23        // When owned, they are disposed; when not owned, they are reset
 024        private bool _encoderOwned = true;
 25
 26        [MemberNotNull(nameof(_stream))]
 27        [MemberNotNull(nameof(_buffer))]
 28        private void Init(Stream stream, CompressionMode mode)
 029        {
 030            ArgumentNullException.ThrowIfNull(stream);
 31
 032            switch (mode)
 33            {
 34                case CompressionMode.Compress:
 035                    if (!stream.CanWrite)
 036                    {
 037                        throw new ArgumentException(SR.Stream_FalseCanWrite, nameof(stream));
 38                    }
 039                    break;
 40
 41                case CompressionMode.Decompress:
 042                    if (!stream.CanRead)
 043                    {
 044                        throw new ArgumentException(SR.Stream_FalseCanRead, nameof(stream));
 45                    }
 046                    break;
 47
 48                default:
 049                    throw new ArgumentException(SR.ArgumentOutOfRange_Enum, nameof(mode));
 50            }
 51
 052            _stream = stream;
 053            _buffer = new ArrayBuffer(DefaultInternalBufferSize, usePool: true);
 054        }
 55
 56        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 57        /// <param name="stream">The stream to which compressed data is written or from which data to decompress is read
 58        /// <param name="mode">One of the enumeration values that indicates whether to compress data to the stream or de
 59        /// <param name="leaveOpen"><see langword="true" /> to leave the stream open after the <see cref="ZstandardStrea
 60        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <see langword="null"/>.</exception>
 61        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing and <paramref name="m
 062        public ZstandardStream(Stream stream, CompressionMode mode, bool leaveOpen)
 063        {
 064            Init(stream, mode);
 065            _leaveOpen = leaveOpen;
 066            _mode = mode;
 67
 68            try
 069            {
 070                if (mode == CompressionMode.Compress)
 071                {
 072                    _encoder = new ZstandardEncoder();
 073                }
 74                else
 075                {
 076                    _decoder = new ZstandardDecoder();
 077                }
 078            }
 079            catch
 080            {
 081                _buffer.Dispose();
 082                throw;
 83            }
 084        }
 85
 86        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 87        /// <param name="stream">The stream to which compressed data is written or from which data to decompress is read
 88        /// <param name="mode">One of the enumeration values that indicates whether to compress data to the stream or de
 89        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <see langword="null"/>.</exception>
 90        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing and <paramref name="m
 091        public ZstandardStream(Stream stream, CompressionMode mode) : this(stream, mode, leaveOpen: false) { }
 92
 93        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 94        /// <param name="stream">The stream to which compressed data is written or from which data to decompress is read
 95        /// <param name="mode">One of the enumeration values that indicates whether to compress data to the stream or de
 96        /// <param name="dictionary">The compression or decompression dictionary to use.</param>
 97        /// <param name="leaveOpen"><see langword="true" /> to leave the stream open after the <see cref="ZstandardStrea
 98        /// <exception cref="ArgumentNullException"><paramref name="stream"/> or <paramref name="dictionary"/> is null.<
 99        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing and <paramref name="m
 0100        public ZstandardStream(Stream stream, CompressionMode mode, ZstandardDictionary dictionary, bool leaveOpen = fal
 0101        {
 0102            ArgumentNullException.ThrowIfNull(dictionary);
 103
 0104            Init(stream, mode);
 0105            _mode = mode;
 0106            _leaveOpen = leaveOpen;
 107
 108            try
 0109            {
 0110                if (mode == CompressionMode.Compress)
 0111                {
 0112                    _encoder = new ZstandardEncoder(dictionary);
 0113                }
 114                else
 0115                {
 0116                    _decoder = new ZstandardDecoder(dictionary);
 0117                }
 0118            }
 0119            catch
 0120            {
 0121                _buffer.Dispose();
 0122                throw;
 123            }
 0124        }
 125
 126        /// <summary>Gets a reference to the underlying stream.</summary>
 127        /// <value>A stream object that represents the underlying stream.</value>
 128        /// <exception cref="System.ObjectDisposedException">The underlying stream is closed.</exception>
 129        public Stream BaseStream
 130        {
 131            get
 0132            {
 0133                EnsureNotDisposed();
 0134                return _stream;
 0135            }
 136        }
 137
 138        /// <summary>Gets a value indicating whether the stream supports reading while decompressing a file.</summary>
 139        /// <value><see langword="true" /> if the <see cref="CompressionMode" /> value is <c>Decompress,</c> and the und
 0140        public override bool CanRead => _mode == CompressionMode.Decompress && _stream?.CanRead == true;
 141
 142        /// <summary>Gets a value indicating whether the stream supports writing.</summary>
 143        /// <value><see langword="true" /> if the <see cref="CompressionMode" /> value is <c>Compress,</c> and the under
 0144        public override bool CanWrite => _mode == CompressionMode.Compress && _stream?.CanWrite == true;
 145
 146        /// <summary>Gets a value indicating whether the stream supports seeking.</summary>
 147        /// <value><see langword="false" /> in all cases.</value>
 0148        public override bool CanSeek => false;
 149
 150        /// <summary>This property is not supported and always throws a <see cref="NotSupportedException" />.</summary>
 151        /// <exception cref="NotSupportedException">In all cases.</exception>
 0152        public override long Length => throw new NotSupportedException();
 153
 154        /// <summary>This property is not supported and always throws a <see cref="NotSupportedException" />.</summary>
 155        /// <exception cref="NotSupportedException">In all cases.</exception>
 156        public override long Position
 157        {
 0158            get => throw new NotSupportedException();
 0159            set => throw new NotSupportedException();
 160        }
 161
 162        /// <summary>This operation is not supported and always throws a <see cref="NotSupportedException" />.</summary>
 163        /// <param name="offset">The byte offset relative to the <paramref name="origin" /> parameter.</param>
 164        /// <param name="origin">One of the <see cref="SeekOrigin" /> values that indicates the reference point used to 
 165        /// <returns>The new position within the current stream.</returns>
 166        /// <exception cref="NotSupportedException">In all cases.</exception>
 0167        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 168
 169        /// <summary>This operation is not supported and always throws a <see cref="NotSupportedException" />.</summary>
 170        /// <param name="value">The desired length of the current stream in bytes.</param>
 171        /// <exception cref="NotSupportedException">In all cases.</exception>
 0172        public override void SetLength(long value) => throw new NotSupportedException();
 173
 174        /// <summary>Releases the unmanaged resources used by the <see cref="ZstandardStream" /> and optionally releases
 175        /// <param name="disposing"><see langword="true" /> to release both managed and unmanaged resources; <see langwo
 176        protected override void Dispose(bool disposing)
 0177        {
 178            try
 0179            {
 0180                if (disposing && _stream != null)
 0181                {
 0182                    if (_mode == CompressionMode.Compress)
 0183                    {
 0184                        WriteCore(ReadOnlySpan<byte>.Empty, isFinalBlock: true, throwOnActiveRwOp: false);
 0185                    }
 186
 0187                    if (!_leaveOpen)
 0188                    {
 0189                        _stream.Dispose();
 0190                    }
 0191                }
 0192            }
 193            finally
 0194            {
 0195                ReleaseStateForDispose();
 0196                base.Dispose(disposing);
 0197            }
 0198        }
 199
 200        /// <summary>Asynchronously releases the unmanaged resources used by the <see cref="ZstandardStream" />.</summar
 201        /// <returns>A task that represents the asynchronous dispose operation.</returns>
 202        public override async ValueTask DisposeAsync()
 0203        {
 204            try
 0205            {
 0206                if (_stream != null)
 0207                {
 0208                    if (_mode == CompressionMode.Compress)
 0209                    {
 0210                        await WriteCoreAsync(ReadOnlyMemory<byte>.Empty, CancellationToken.None, isFinalBlock: true, thr
 0211                    }
 212
 0213                    if (!_leaveOpen)
 0214                    {
 0215                        await _stream.DisposeAsync().ConfigureAwait(false);
 0216                    }
 0217                }
 0218            }
 219            finally
 0220            {
 0221                ReleaseStateForDispose();
 0222                await base.DisposeAsync().ConfigureAwait(false);
 0223            }
 0224        }
 225
 226        private void ReleaseStateForDispose()
 0227        {
 0228            _stream = null!;
 229
 0230            if (_encoderOwned)
 0231            {
 0232                _encoder?.Dispose();
 0233                _decoder?.Dispose();
 0234            }
 235            else
 0236            {
 0237                _encoder?.Reset();
 0238                _decoder?.Reset();
 0239            }
 240
 241            // only return the buffer if no read/write operation is active
 0242            if (!Interlocked.Exchange(ref _activeRwOperation, true))
 0243            {
 0244                _buffer.Dispose();
 0245            }
 0246        }
 247
 248        private void EnsureNotDisposed()
 0249        {
 0250            ObjectDisposedException.ThrowIf(_stream == null, this);
 0251        }
 252
 253        private static void ThrowConcurrentRWOperation()
 0254        {
 0255            throw new InvalidOperationException(SR.ZstandardStream_ConcurrentRWOperation);
 256        }
 257
 258        private bool BeginRWOperation(bool throwOnActiveRwOp = true)
 0259        {
 0260            if (Interlocked.Exchange(ref _activeRwOperation, true))
 0261            {
 0262                if (!throwOnActiveRwOp)
 0263                {
 0264                    return false;
 265                }
 266
 0267                ThrowConcurrentRWOperation();
 0268            }
 269
 0270            return true;
 0271        }
 272
 273        private void EndRWOperation()
 0274        {
 0275            Interlocked.Exchange(ref _activeRwOperation, false);
 0276        }
 277
 278        private void EnsureNoActiveRWOperation()
 0279        {
 0280            if (_activeRwOperation)
 0281            {
 0282                ThrowConcurrentRWOperation();
 0283            }
 0284        }
 285    }
 286}

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\Zstandard\ZstandardStream.Decompress.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.Diagnostics.CodeAnalysis;
 7using System.Threading;
 8using System.Threading.Tasks;
 9
 10namespace System.IO.Compression
 11{
 12    public sealed partial class ZstandardStream
 13    {
 14        private ZstandardDecoder? _decoder;
 15        private bool _nonEmptyInput;
 16        private bool _endOfStream;
 17
 18        // Length of a Zstandard frame magic number, in bytes.
 19        private const int ZstdFrameMagicLength = 4;
 20
 21        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 22        /// <param name="stream">The stream from which data to decompress is read.</param>
 23        /// <param name="decoder">The decoder instance to use for decompression. The stream will not dispose this decode
 24        /// <param name="leaveOpen"><see langword="true" /> to leave the stream open after the <see cref="ZstandardStrea
 25        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <see langword="null"/>.</exception>
 26        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support reading.</exception>
 027        public ZstandardStream(Stream stream, ZstandardDecoder decoder, bool leaveOpen = false)
 028        {
 029            ArgumentNullException.ThrowIfNull(decoder);
 30
 031            Init(stream, CompressionMode.Decompress);
 032            _mode = CompressionMode.Decompress;
 033            _leaveOpen = leaveOpen;
 34
 035            _decoder = decoder;
 036            _encoderOwned = false;
 037        }
 38
 39        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 40        /// <param name="stream">The stream from which data to decompress is read.</param>
 41        /// <param name="decompressionOptions">The options to use for Zstandard decompression.</param>
 42        /// <param name="leaveOpen"><see langword="true" /> to leave the stream open after the <see cref="ZstandardStrea
 43        /// <exception cref="ArgumentNullException"><paramref name="stream"/> or <paramref name="decompressionOptions"/>
 44        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support reading.</exception>
 045        public ZstandardStream(Stream stream, ZstandardDecompressionOptions decompressionOptions, bool leaveOpen = false
 046        {
 047            ArgumentNullException.ThrowIfNull(decompressionOptions);
 48
 049            Init(stream, CompressionMode.Decompress);
 050            _mode = CompressionMode.Decompress;
 051            _leaveOpen = leaveOpen;
 52
 53            try
 054            {
 055                _decoder = new ZstandardDecoder(decompressionOptions);
 056            }
 057            catch
 058            {
 059                _buffer.Dispose();
 060                throw;
 61            }
 062        }
 63
 64        // Decompresses buffered input for the current frame only. Returns true when there is a result for
 65        // the caller to act on (output was produced, the frame finished, or a zero-byte read should
 66        // return), and false when more input is needed to make progress on the current frame. Crossing a
 67        // frame boundary into the next concatenated frame is handled by Read/ReadAsync once this reports
 68        // OperationStatus.Done.
 69        private bool TryDecompress(Span<byte> destination, out int bytesWritten, out OperationStatus lastResult)
 070        {
 071            Debug.Assert(_decoder != null);
 72
 073            OperationStatus status = _decoder.Decompress(_buffer.ActiveSpan, destination, out int bytesConsumed, out byt
 074            _buffer.Discard(bytesConsumed);
 075            lastResult = status;
 76
 077            if (status == OperationStatus.InvalidData)
 078            {
 079                throw new InvalidDataException(SR.ZstandardStream_Decompress_InvalidData);
 80            }
 81
 082            if (status == OperationStatus.Done)
 083            {
 84                // Reached the end of a frame. ZSTD_decompressStream reports end-of-frame, not end-of-stream:
 85                // a zstd stream may be frames concatenated back-to-back (RFC 8878 section 3), so Read/ReadAsync
 86                // decide whether another frame follows. This may be a zero-output frame (bytesWritten == 0).
 087                return true;
 88            }
 89
 090            if (bytesWritten != 0)
 091            {
 092                return true;
 93            }
 94
 095            if (destination.IsEmpty)
 096            {
 97                // The caller provided a zero-byte buffer.  This is typically done in order to avoid allocating/renting
 98                // a buffer until data is known to be available.  We don't have perfect knowledge here, as _decoder.Deco
 99                // will return DestinationTooSmall whether or not more data is required.  As such, we assume that if the
 100                // any data in our input buffer, it would have been decompressible into at least one byte of output, and
 101                // otherwise we need to do a read on the underlying stream.  This isn't perfect, because having input da
 102                // doesn't necessarily mean it'll decompress into at least one byte of output, but it's a reasonable app
 103                // for the 99% case.  If it's wrong, it just means that a caller using zero-byte reads as a way to delay
 104                // getting a buffer to use for a subsequent call may end up getting one earlier than otherwise preferred
 0105                Debug.Assert(status == OperationStatus.DestinationTooSmall);
 0106                if (_buffer.ActiveLength != 0)
 0107                {
 0108                    return true;
 109                }
 0110            }
 111
 0112            Debug.Assert(
 0113                status == OperationStatus.NeedMoreData ||
 0114                (status == OperationStatus.DestinationTooSmall && destination.IsEmpty && _buffer.ActiveLength == 0), $"{
 115
 0116            return false;
 0117        }
 118
 119        /// <summary>Reads decompressed bytes from the underlying stream and places them in the specified array.</summar
 120        /// <param name="buffer">The byte array to contain the decompressed bytes.</param>
 121        /// <param name="offset">The byte offset in <paramref name="buffer" /> at which the read bytes will be placed.</
 122        /// <param name="count">The maximum number of decompressed bytes to read.</param>
 123        /// <returns>The number of bytes that were read into the byte array.</returns>
 124        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 125        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 126        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 127        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 128        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 129        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 130        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 131        public override int Read(byte[] buffer, int offset, int count)
 0132        {
 0133            ValidateBufferArguments(buffer, offset, count);
 0134            return Read(new Span<byte>(buffer, offset, count));
 0135        }
 136
 137        /// <summary>Reads decompressed bytes from the underlying stream and places them in the specified span.</summary
 138        /// <param name="buffer">The span to contain the decompressed bytes.</param>
 139        /// <returns>The number of bytes that were read into the span.</returns>
 140        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 141        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 142        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 143        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 144        public override int Read(Span<byte> buffer)
 0145        {
 0146            if (_mode != CompressionMode.Decompress)
 0147            {
 0148                throw new InvalidOperationException(SR.CannotReadFromCompressionStream);
 149            }
 150
 0151            EnsureNotDisposed();
 0152            BeginRWOperation();
 153
 154            try
 0155            {
 0156                if (_endOfStream)
 0157                {
 158                    // A previous read reached the end of the final frame (or rejected trailing data). The
 159                    // boundary probe may have left the decoder non-resumable, so never re-enter the decode
 160                    // loop; report end-of-stream to all subsequent reads.
 0161                    return 0;
 162                }
 163
 0164                while (true)
 0165                {
 166                    int bytesWritten;
 167                    OperationStatus lastResult;
 0168                    while (!TryDecompress(buffer, out bytesWritten, out lastResult))
 0169                    {
 0170                        _buffer.EnsureAvailableSpace(1);
 171
 0172                        int bytesRead = _stream.Read(_buffer.AvailableSpan);
 0173                        if (bytesRead <= 0)
 0174                        {
 175                            // The underlying stream ended in the middle of a frame, so the data is truncated.
 176                            // A clean end after a completed frame is reported as Done by TryDecompress and is
 177                            // resolved by the frame-boundary logic below, not here.
 0178                            if (_nonEmptyInput && !buffer.IsEmpty)
 0179                            {
 0180                                ThrowTruncatedInvalidData();
 181                            }
 182
 0183                            return 0;
 184                        }
 185
 0186                        _nonEmptyInput = true;
 187
 0188                        if (bytesRead > _buffer.AvailableLength)
 0189                        {
 0190                            ThrowInvalidStream();
 191                        }
 192
 0193                        _buffer.Commit(bytesRead);
 0194                    }
 195
 0196                    if (lastResult != OperationStatus.Done || bytesWritten != 0)
 0197                    {
 198                        // Output to hand back, or not at a finished-frame boundary: return to the caller.
 0199                        return bytesWritten;
 200                    }
 201
 202                    // A frame finished with no pending output. A zstd stream may be frames concatenated
 203                    // back-to-back (RFC 8878 section 3), so decide whether another frame follows before
 204                    // reporting end-of-stream. async: false completes synchronously (it only ever takes the
 205                    // synchronous read path).
 0206                    ValueTask<bool> advanceTask = AdvanceToNextFrame(async: false, cancellationToken: default);
 0207                    Debug.Assert(advanceTask.IsCompleted, "AdvanceToNextFrame should complete synchronously when async: 
 0208                    if (!advanceTask.GetAwaiter().GetResult())
 0209                    {
 0210                        return bytesWritten;
 211                    }
 0212                }
 213            }
 214            finally
 0215            {
 0216                EndRWOperation();
 0217            }
 0218        }
 219
 220        // Called after TryDecompress reports OperationStatus.Done with no pending output (a finished frame).
 221        // Decides whether another concatenated frame follows by reading up to a frame magic and feeding just
 222        // those bytes to the decoder: a valid magic is accepted (NeedMoreData) so decoding continues, while
 223        // bytes that are not a frame magic identify trailing data after the final frame. Returns true (decoder
 224        // reset and ready) so the caller loops to decode the next frame, or false (stream complete; _endOfStream
 225        // is set and a seekable base stream is rewound to the end of the compressed data) so the caller returns.
 226        // Shared by Read (async: false) and ReadAsync (async: true).
 227        private async ValueTask<bool> AdvanceToNextFrame(bool async, CancellationToken cancellationToken)
 0228        {
 0229            Debug.Assert(_decoder != null);
 230
 0231            if (_buffer.ActiveLength < ZstdFrameMagicLength)
 0232            {
 233                // Not enough buffered to tell a split next-frame magic from end-of-stream; read just enough
 234                // to complete the magic. Limiting the read to exactly the missing bytes (rather than filling
 235                // the available buffer) avoids consuming and hiding trailing bytes past the magic on a
 236                // non-seekable stream, where they can't be rewound; any following frame's body is read by the
 237                // Read/ReadAsync loop afterwards.
 0238                int needed = ZstdFrameMagicLength - _buffer.ActiveLength;
 0239                _buffer.EnsureAvailableSpace(needed);
 0240                int peeked = async
 0241                    ? await _stream.ReadAtLeastAsync(_buffer.AvailableMemory.Slice(0, needed), needed, throwOnEndOfStrea
 0242                    : _stream.ReadAtLeast(_buffer.AvailableSpan.Slice(0, needed), needed, throwOnEndOfStream: false);
 0243                if (peeked > 0)
 0244                {
 0245                    _nonEmptyInput = true;
 0246                    _buffer.Commit(peeked);
 0247                }
 0248            }
 249
 0250            if (_buffer.ActiveLength >= ZstdFrameMagicLength)
 0251            {
 252                // Determine whether another concatenated frame follows by feeding the decoder exactly the next
 253                // frame magic number. The decoder validates the magic against both standard and skippable
 254                // frames: a valid magic leaves it asking for more input (NeedMoreData), so decoding continues,
 255                // while bytes that are not a frame magic produce InvalidData, identifying trailing data after
 256                // the final frame. Feeding only the magic (not the whole buffer) means a frame whose magic is
 257                // valid but whose body is corrupt is not mistaken for trailing data here: the magic is accepted
 258                // and the corrupt body is rejected by the subsequent decode in the Read/ReadAsync loop.
 0259                _decoder.Reset();
 260
 261                // The magic alone never decodes into output, so the decoder needs no real output space; a single
 262                // scratch byte borrowed from the buffer's free region (which the decoder won't write to) satisfies
 263                // the non-empty-destination requirement. A Span local / stackalloc can't be used here because this
 264                // method is async.
 0265                _buffer.EnsureAvailableSpace(1);
 0266                if (_decoder.Decompress(_buffer.ActiveSpan.Slice(0, ZstdFrameMagicLength), _buffer.AvailableSpan.Slice(0
 0267                {
 268                    // A valid magic; the decoder has taken the magic bytes into its session to continue decoding
 269                    // the rest of the frame. Drop them from the buffer and keep decoding.
 0270                    _buffer.Discard(bytesConsumed);
 0271                    return true;
 272                }
 273
 274                // Not a frame: leave the magic bytes buffered so they are included in the trailing-data rewind
 275                // below (on a non-seekable stream they simply remain unconsumed).
 0276            }
 277
 278            // Trailing non-zstd data or end of input after the final frame: the stream is complete. Mark the
 279            // stream ended so subsequent reads short-circuit to 0 without re-entering the (now non-resumable)
 280            // decoder, and leave any trailing bytes on a seekable base stream by rewinding to the end of the
 281            // compressed data, mirroring how DeflateStream handles data after the last gzip member.
 0282            _endOfStream = true;
 0283            if (_stream.CanSeek)
 0284            {
 0285                TryRewindStream(_stream);
 0286            }
 287
 0288            return false;
 0289        }
 290
 291        /// <summary>Asynchronously reads decompressed bytes from the underlying stream and places them in the specified
 292        /// <param name="buffer">The byte array to contain the decompressed bytes.</param>
 293        /// <param name="offset">The byte offset in <paramref name="buffer" /> at which the read bytes will be placed.</
 294        /// <param name="count">The maximum number of decompressed bytes to read.</param>
 295        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 296        /// <returns>A task that represents the asynchronous read operation, which wraps the number of bytes read from t
 297        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 298        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 299        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 300        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 301        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 302        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 303        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 304        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0305        {
 0306            ValidateBufferArguments(buffer, offset, count);
 0307            return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
 0308        }
 309
 310        /// <summary>Asynchronously reads decompressed bytes from the underlying stream and places them in the specified
 311        /// <param name="buffer">The memory to contain the decompressed bytes.</param>
 312        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 313        /// <returns>A task that represents the asynchronous read operation, which wraps the number of bytes read from t
 314        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 315        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 316        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 317        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 318        public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = defaul
 0319        {
 0320            if (_mode != CompressionMode.Decompress)
 0321            {
 0322                throw new InvalidOperationException(SR.CannotReadFromCompressionStream);
 323            }
 324
 0325            EnsureNotDisposed();
 0326            BeginRWOperation();
 327
 328            try
 0329            {
 0330                if (_endOfStream)
 0331                {
 332                    // A previous read reached the end of the final frame (or rejected trailing data). The
 333                    // boundary probe may have left the decoder non-resumable, so never re-enter the decode
 334                    // loop; report end-of-stream to all subsequent reads.
 0335                    return 0;
 336                }
 337
 0338                while (true)
 0339                {
 340                    int bytesWritten;
 341                    OperationStatus lastResult;
 0342                    while (!TryDecompress(buffer.Span, out bytesWritten, out lastResult))
 0343                    {
 0344                        _buffer.EnsureAvailableSpace(1);
 345
 0346                        int bytesRead = await _stream.ReadAsync(_buffer.AvailableMemory, cancellationToken).ConfigureAwa
 0347                        if (bytesRead <= 0)
 0348                        {
 349                            // The underlying stream ended in the middle of a frame, so the data is truncated.
 350                            // A clean end after a completed frame is reported as Done by TryDecompress and is
 351                            // resolved by the frame-boundary logic below, not here.
 0352                            if (_nonEmptyInput && !buffer.IsEmpty)
 0353                            {
 0354                                ThrowTruncatedInvalidData();
 355                            }
 356
 0357                            return 0;
 358                        }
 359
 0360                        _nonEmptyInput = true;
 361
 0362                        if (bytesRead > _buffer.AvailableLength)
 0363                        {
 0364                            ThrowInvalidStream();
 365                        }
 366
 0367                        _buffer.Commit(bytesRead);
 0368                    }
 369
 0370                    if (lastResult != OperationStatus.Done || bytesWritten != 0)
 0371                    {
 372                        // Output to hand back, or not at a finished-frame boundary: return to the caller.
 0373                        return bytesWritten;
 374                    }
 375
 376                    // A frame finished with no pending output. A zstd stream may be frames concatenated
 377                    // back-to-back (RFC 8878 section 3), so decide whether another frame follows before
 378                    // reporting end-of-stream.
 0379                    if (!await AdvanceToNextFrame(async: true, cancellationToken: cancellationToken).ConfigureAwait(fals
 0380                    {
 0381                        return bytesWritten;
 382                    }
 0383                }
 384            }
 385            finally
 0386            {
 0387                EndRWOperation();
 0388            }
 0389        }
 390
 391        /// <summary>Begins an asynchronous read operation.</summary>
 392        /// <param name="buffer">The buffer to read the data into.</param>
 393        /// <param name="offset">The byte offset in <paramref name="buffer"/> at which to begin writing data read from t
 394        /// <param name="count">The maximum number of bytes to read.</param>
 395        /// <param name="callback">An optional asynchronous callback, to be called when the read is complete.</param>
 396        /// <param name="state">A user-provided object that distinguishes this particular asynchronous read request from
 397        /// <returns>An object that represents the asynchronous read, which could still be pending.</returns>
 398        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 399        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 400        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 401        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 402        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 403        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 404        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 405        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? st
 0406        {
 0407            return TaskToAsyncResult.Begin(ReadAsync(buffer, offset, count, CancellationToken.None), callback, state);
 0408        }
 409
 410        /// <summary>Waits for the pending asynchronous read to complete.</summary>
 411        /// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
 412        /// <returns>The number of bytes read from the stream, between zero (0) and the number of bytes you requested. S
 413        public override int EndRead(IAsyncResult asyncResult)
 0414        {
 0415            return TaskToAsyncResult.End<int>(asyncResult);
 0416        }
 417
 418        /// <summary>Reads a byte from the stream and advances the position within the stream by one byte, or returns -1
 419        /// <returns>The unsigned byte cast to an <see cref="int"/>, or -1 if at the end of the stream.</returns>
 420        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 421        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 422        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 423        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 424        public override int ReadByte()
 0425        {
 0426            Span<byte> singleByte = [0];
 0427            int bytesRead = Read(singleByte);
 0428            return bytesRead > 0 ? singleByte[0] : -1;
 0429        }
 430
 431        /// <summary>
 432        /// Rewinds the underlying stream to the exact end of the compressed data if there are unconsumed bytes.
 433        /// This is called when decompression finishes to reset the stream position.
 434        /// </summary>
 435        private void TryRewindStream(Stream stream)
 0436        {
 0437            Debug.Assert(stream != null);
 0438            Debug.Assert(_mode == CompressionMode.Decompress);
 0439            Debug.Assert(stream.CanSeek);
 440
 441            // Check if there are unconsumed bytes in the buffer
 0442            int unconsumedBytes = _buffer.ActiveLength;
 0443            if (unconsumedBytes > 0)
 0444            {
 445                // Rewind the stream to the exact end of the compressed data
 0446                stream.Seek(-unconsumedBytes, SeekOrigin.Current);
 0447                _buffer.DiscardAll();
 0448            }
 0449        }
 450
 451        [DoesNotReturn]
 452        private static void ThrowInvalidStream() =>
 453            // The stream is either malicious or poorly implemented and returned a number of
 454            // bytes larger than the buffer supplied to it.
 0455            throw new InvalidDataException(SR.ZstandardStream_Decompress_InvalidStream);
 456
 457        [DoesNotReturn]
 458        private static void ThrowTruncatedInvalidData() =>
 0459            throw new InvalidDataException(SR.ZstandardStream_Decompress_TruncatedData);
 460    }
 461}

Methods/Properties

.ctor(System.IO.Stream,System.IO.Compression.CompressionLevel)
.ctor(System.IO.Stream,System.IO.Compression.CompressionLevel,System.Boolean)
.ctor(System.IO.Stream,System.IO.Compression.ZstandardCompressionOptions,System.Boolean)
.ctor(System.IO.Stream,System.IO.Compression.ZstandardEncoder,System.Boolean)
SetSourceLength(System.Int64)
WriteCore(System.ReadOnlySpan`1<System.Byte>,System.Boolean,System.Boolean,System.Boolean)
WriteCoreAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken,System.Boolean,System.Boolean,System.Boolean)
Write(System.Byte[],System.Int32,System.Int32)
Write(System.ReadOnlySpan`1<System.Byte>)
WriteByte(System.Byte)
WriteAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
BeginWrite(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)
EndWrite(System.IAsyncResult)
Flush()
FlushAsync(System.Threading.CancellationToken)
FlushAsyncInternal(System.Threading.CancellationToken)
.ctor(System.IO.Stream,System.IO.Compression.CompressionLevel,System.Boolean)
Init(System.IO.Stream,System.IO.Compression.CompressionMode)
.ctor(System.IO.Stream,System.IO.Compression.CompressionMode,System.Boolean)
.ctor(System.IO.Stream,System.IO.Compression.CompressionMode)
.ctor(System.IO.Stream,System.IO.Compression.CompressionMode,System.IO.Compression.ZstandardDictionary,System.Boolean)
BaseStream()
CanRead()
CanWrite()
CanSeek()
Length()
Position()
Position(System.Int64)
Seek(System.Int64,System.IO.SeekOrigin)
SetLength(System.Int64)
Dispose(System.Boolean)
DisposeAsync()
ReleaseStateForDispose()
EnsureNotDisposed()
ThrowConcurrentRWOperation()
BeginRWOperation(System.Boolean)
EndRWOperation()
EnsureNoActiveRWOperation()
.ctor(System.IO.Stream,System.IO.Compression.ZstandardDecoder,System.Boolean)
.ctor(System.IO.Stream,System.IO.Compression.ZstandardDecompressionOptions,System.Boolean)
TryDecompress(System.Span`1<System.Byte>,System.Int32&,System.Buffers.OperationStatus&)
Read(System.Byte[],System.Int32,System.Int32)
Read(System.Span`1<System.Byte>)
AdvanceToNextFrame(System.Boolean,System.Threading.CancellationToken)
ReadAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
BeginRead(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)
EndRead(System.IAsyncResult)
ReadByte()
TryRewindStream(System.IO.Stream)
ThrowInvalidStream()
ThrowTruncatedInvalidData()