< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 484
Coverable lines: 484
Total lines: 1079
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            Init(stream, CompressionMode.Compress);
 033            _mode = CompressionMode.Compress;
 034            _leaveOpen = leaveOpen;
 35
 036            _encoder = new ZstandardEncoder(ZstandardUtils.GetQualityFromCompressionLevel(compressionLevel));
 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 to which compressed data is written or from which data to decompress is read
 41        /// <param name="compressionOptions">The options to use for Zstandard compression or 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"/> is <see langword="null"/>.</exception>
 44        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing.</exception>
 045        public ZstandardStream(Stream stream, ZstandardCompressionOptions compressionOptions, bool leaveOpen = false)
 046        {
 047            Init(stream, CompressionMode.Compress);
 048            _mode = CompressionMode.Compress;
 049            _leaveOpen = leaveOpen;
 50
 051            _encoder = new ZstandardEncoder(compressionOptions);
 052        }
 53
 54        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 55        /// <param name="stream">The stream to which compressed data is written.</param>
 56        /// <param name="encoder">The encoder instance to use for compression. The stream will not dispose this encoder;
 57        /// <param name="leaveOpen"><see langword="true" /> to leave the stream open after the <see cref="ZstandardStrea
 58        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <see langword="null"/>.</exception>
 59        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing.</exception>
 060        public ZstandardStream(Stream stream, ZstandardEncoder encoder, bool leaveOpen = false)
 061        {
 062            ArgumentNullException.ThrowIfNull(encoder);
 63
 064            Init(stream, CompressionMode.Compress);
 065            _mode = CompressionMode.Compress;
 066            _leaveOpen = leaveOpen;
 67
 068            _encoder = encoder;
 069            _encoderOwned = false;
 070        }
 71
 72        /// <summary>Sets the length of the uncompressed data that will be compressed by this instance.</summary>
 73        /// <param name="length">The length of the source data in bytes.</param>
 74        /// <remarks>
 75        /// Setting the source length is optional. If set, the information is stored in the header of the compressed dat
 76        /// </remarks>
 77        /// <exception cref="InvalidOperationException">Attempting to set the source size on a decompression stream, or 
 78        /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is negative.</exception>
 79        /// <exception cref="ObjectDisposedException">The encoder has been disposed.</exception>
 80        public void SetSourceLength(long length)
 081        {
 082            if (_mode != CompressionMode.Compress)
 083            {
 084                throw new InvalidOperationException(SR.CannotWriteToDecompressionStream);
 85            }
 086            EnsureNotDisposed();
 087            Debug.Assert(_encoder != null);
 88
 089            _encoder.SetSourceLength(length);
 090        }
 91
 92        private void WriteCore(ReadOnlySpan<byte> buffer, bool isFinalBlock = false, bool flush = false, bool throwOnAct
 093        {
 094            if (_mode != CompressionMode.Compress)
 095            {
 096                throw new InvalidOperationException(SR.CannotWriteToDecompressionStream);
 97            }
 098            EnsureNotDisposed();
 099            Debug.Assert(_encoder != null);
 100
 0101            if (!BeginRWOperation(throwOnActiveRwOp))
 0102            {
 103                // we are disposing concurrently with another write, we should avoid throwing during potential stack unw
 104                // best effort at this point is to no-op as we don't guarantee correctness in concurrent scenarios
 0105                return;
 106            }
 107
 108            try
 0109            {
 0110                OperationStatus lastResult = OperationStatus.DestinationTooSmall;
 111
 112                // we don't need extra tracking of ArrayBuffer for write operations as
 113                // we propagate the entire result downstream, so just grab the span
 0114                Span<byte> output = _buffer.AvailableSpan;
 0115                Debug.Assert(!output.IsEmpty, "Internal buffer should be initialized to non-zero size");
 116
 0117                while (lastResult == OperationStatus.DestinationTooSmall)
 0118                {
 119                    int bytesWritten;
 120                    int bytesConsumed;
 121
 0122                    if (flush)
 0123                    {
 0124                        Debug.Assert(buffer.Length == 0);
 0125                        bytesConsumed = 0;
 0126                        lastResult = _encoder.Flush(output, out bytesWritten);
 0127                    }
 128                    else
 0129                    {
 0130                        lastResult = _encoder.Compress(buffer, output, out bytesConsumed, out bytesWritten, isFinalBlock
 0131                    }
 132
 0133                    if (lastResult == OperationStatus.InvalidData)
 0134                    {
 0135                        throw new InvalidDataException(SR.ZstandardStream_Compress_InvalidData);
 136                    }
 0137                    if (bytesWritten > 0)
 0138                    {
 0139                        _stream.Write(output.Slice(0, bytesWritten));
 0140                    }
 0141                    if (bytesConsumed > 0)
 0142                    {
 0143                        buffer = buffer.Slice(bytesConsumed);
 0144                    }
 0145                }
 0146            }
 147            finally
 0148            {
 0149                EndRWOperation();
 0150            }
 0151        }
 152
 153        private async ValueTask WriteCoreAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken, bool is
 0154        {
 0155            if (_mode != CompressionMode.Compress)
 0156            {
 0157                throw new InvalidOperationException(SR.CannotWriteToDecompressionStream);
 158            }
 0159            EnsureNotDisposed();
 0160            Debug.Assert(_encoder != null);
 161
 0162            if (!BeginRWOperation(throwOnActiveRwOp))
 0163            {
 164                // we are disposing concurrently with another write, we should avoid throwing during potential stack unw
 165                // best effort at this point is to no-op as we don't guarantee correctness in concurrent scenarios
 0166                return;
 167            }
 168
 169            try
 0170            {
 0171                OperationStatus lastResult = OperationStatus.DestinationTooSmall;
 172
 173                // we don't need extra tracking of ArrayBuffer for write operations as
 174                // we propagate the entire result downstream, so just grab the memory
 0175                Memory<byte> output = _buffer.AvailableMemory;
 0176                Debug.Assert(!output.IsEmpty, "Internal buffer should be initialized to non-zero size");
 177
 0178                while (lastResult == OperationStatus.DestinationTooSmall)
 0179                {
 180                    int bytesWritten;
 181                    int bytesConsumed;
 182
 0183                    if (flush)
 0184                    {
 0185                        Debug.Assert(buffer.Length == 0);
 0186                        bytesConsumed = 0;
 0187                        lastResult = _encoder.Flush(output.Span, out bytesWritten);
 0188                    }
 189                    else
 0190                    {
 0191                        lastResult = _encoder.Compress(buffer.Span, output.Span, out bytesConsumed, out bytesWritten, is
 0192                    }
 193
 0194                    if (lastResult == OperationStatus.InvalidData)
 0195                    {
 0196                        throw new InvalidDataException(SR.ZstandardStream_Compress_InvalidData);
 197                    }
 0198                    if (bytesWritten > 0)
 0199                    {
 0200                        await _stream.WriteAsync(output.Slice(0, bytesWritten), cancellationToken).ConfigureAwait(false)
 0201                    }
 0202                    if (bytesConsumed > 0)
 0203                    {
 0204                        buffer = buffer.Slice(bytesConsumed);
 0205                    }
 0206                }
 0207            }
 208            finally
 0209            {
 0210                EndRWOperation();
 0211            }
 0212        }
 213
 214        /// <summary>Writes compressed bytes to the underlying stream from the specified byte array.</summary>
 215        /// <param name="buffer">The buffer that contains the data to compress.</param>
 216        /// <param name="offset">The byte offset in <paramref name="buffer" /> from which the bytes will be read.</param
 217        /// <param name="count">The maximum number of bytes to write.</param>
 218        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 219        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 220        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 221        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 222        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 223        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 224        public override void Write(byte[] buffer, int offset, int count)
 0225        {
 0226            ValidateBufferArguments(buffer, offset, count);
 0227            Write(new ReadOnlySpan<byte>(buffer, offset, count));
 0228        }
 229
 230        /// <summary>Writes compressed bytes to the underlying stream from the specified span.</summary>
 231        /// <param name="buffer">The span that contains the data to compress.</param>
 232        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 233        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 234        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 235        public override void Write(ReadOnlySpan<byte> buffer)
 0236        {
 0237            WriteCore(buffer);
 0238        }
 239
 240        /// <summary>Writes a byte to the current position in the stream and advances the position within the stream by 
 241        /// <param name="value">The byte to write to the stream.</param>
 242        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 243        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 244        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 245        public override void WriteByte(byte value)
 0246        {
 0247            Write([value]);
 0248        }
 249
 250        /// <summary>Asynchronously writes compressed bytes to the underlying stream from the specified array.</summary>
 251        /// <param name="buffer">The buffer that contains the data to compress.</param>
 252        /// <param name="offset">The byte offset in <paramref name="buffer" /> from which the bytes will be read.</param
 253        /// <param name="count">The maximum number of bytes to write.</param>
 254        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 255        /// <returns>A task that represents the asynchronous write operation.</returns>
 256        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 257        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 258        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 259        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 260        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 261        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 262        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0263        {
 0264            ValidateBufferArguments(buffer, offset, count);
 0265            return WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask();
 0266        }
 267
 268        /// <summary>Asynchronously writes compressed bytes to the underlying stream from the specified memory.</summary
 269        /// <param name="buffer">The memory that contains the data to compress.</param>
 270        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 271        /// <returns>A task that represents the asynchronous write operation.</returns>
 272        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 273        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 274        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 275        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
 0276        {
 0277            if (_mode != CompressionMode.Compress)
 0278            {
 0279                throw new InvalidOperationException(SR.CannotWriteToDecompressionStream);
 280            }
 281
 0282            EnsureNotDisposed();
 283
 0284            return cancellationToken.IsCancellationRequested ?
 0285                ValueTask.FromCanceled(cancellationToken) :
 0286                WriteCoreAsync(buffer, cancellationToken);
 0287        }
 288
 289        /// <summary>Begins an asynchronous write operation.</summary>
 290        /// <param name="buffer">The buffer to write data from.</param>
 291        /// <param name="offset">The byte offset in <paramref name="buffer"/> from which to begin writing.</param>
 292        /// <param name="count">The maximum number of bytes to write.</param>
 293        /// <param name="callback">An optional asynchronous callback, to be called when the write is complete.</param>
 294        /// <param name="state">A user-provided object that distinguishes this particular asynchronous write request fro
 295        /// <returns>An object that represents the asynchronous write, which could still be pending.</returns>
 296        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 297        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 298        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 299        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 300        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 301        /// <exception cref="IOException">Failed to compress data to the underlying stream.</exception>
 302        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? s
 0303        {
 0304            return TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count, CancellationToken.None), callback, state);
 0305        }
 306
 307        /// <summary>Ends an asynchronous write operation.</summary>
 308        /// <param name="asyncResult">A reference to the outstanding asynchronous I/O request.</param>
 309        public override void EndWrite(IAsyncResult asyncResult)
 0310        {
 0311            TaskToAsyncResult.End(asyncResult);
 0312        }
 313
 314        /// <summary>Flushes the internal buffer.</summary>
 315        /// <exception cref="InvalidOperationException">Concurrent write operations are not supported.</exception>
 316        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 317        /// <exception cref="IOException">The flush operation failed.</exception>
 318        public override void Flush()
 0319        {
 0320            EnsureNotDisposed();
 0321            EnsureNoActiveRWOperation();
 322
 0323            if (_mode == CompressionMode.Compress)
 0324            {
 0325                WriteCore(Span<byte>.Empty, flush: true);
 0326                _stream.Flush();
 0327            }
 0328        }
 329
 330        /// <summary>Asynchronously flushes the internal buffer.</summary>
 331        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 332        /// <returns>A task that represents the asynchronous flush operation.</returns>
 333        /// <exception cref="InvalidOperationException">Concurrent write operations are not supported.</exception>
 334        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 335        /// <exception cref="IOException">The flush operation failed.</exception>
 336        public override Task FlushAsync(CancellationToken cancellationToken)
 0337        {
 0338            EnsureNotDisposed();
 0339            EnsureNoActiveRWOperation();
 340
 0341            if (_mode == CompressionMode.Compress)
 0342            {
 0343                return FlushAsyncInternal(cancellationToken);
 344            }
 345
 0346            return Task.CompletedTask;
 347
 348            async Task FlushAsyncInternal(CancellationToken cancellationToken)
 0349            {
 0350                await WriteCoreAsync(Memory<byte>.Empty, cancellationToken, flush: true).ConfigureAwait(false);
 0351                await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
 0352            }
 0353        }
 354    }
 355}

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    [System.Runtime.Versioning.UnsupportedOSPlatform("wasi")]
 14    public sealed partial class ZstandardStream : Stream
 15    {
 16        private const int DefaultInternalBufferSize = 65536; // 64KB default buffer
 17        private Stream _stream;
 18        private ArrayBuffer _buffer;
 19        private readonly bool _leaveOpen;
 20        private readonly CompressionMode _mode;
 21        private volatile bool _activeRwOperation;
 22
 23        // Tracks whether the encoder/decoder are owned by this stream instance
 24        // When owned, they are disposed; when not owned, they are reset
 025        private bool _encoderOwned = true;
 26
 27        [MemberNotNull(nameof(_stream))]
 28        [MemberNotNull(nameof(_buffer))]
 29        private void Init(Stream stream, CompressionMode mode)
 030        {
 031            ArgumentNullException.ThrowIfNull(stream);
 32
 033            switch (mode)
 34            {
 35                case CompressionMode.Compress:
 036                    if (!stream.CanWrite)
 037                    {
 038                        throw new ArgumentException(SR.Stream_FalseCanWrite, nameof(stream));
 39                    }
 040                    break;
 41
 42                case CompressionMode.Decompress:
 043                    if (!stream.CanRead)
 044                    {
 045                        throw new ArgumentException(SR.Stream_FalseCanRead, nameof(stream));
 46                    }
 047                    break;
 48
 49                default:
 050                    throw new ArgumentException(SR.ArgumentOutOfRange_Enum, nameof(mode));
 51            }
 52
 053            _stream = stream;
 054            _buffer = new ArrayBuffer(DefaultInternalBufferSize, usePool: true);
 055        }
 56
 57        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 58        /// <param name="stream">The stream to which compressed data is written or from which data to decompress is read
 59        /// <param name="mode">One of the enumeration values that indicates whether to compress data to the stream or de
 60        /// <param name="leaveOpen"><see langword="true" /> to leave the stream open after the <see cref="ZstandardStrea
 61        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <see langword="null"/>.</exception>
 62        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing and <paramref name="m
 063        public ZstandardStream(Stream stream, CompressionMode mode, bool leaveOpen)
 064        {
 065            Init(stream, mode);
 066            _leaveOpen = leaveOpen;
 067            _mode = mode;
 68
 069            if (mode == CompressionMode.Compress)
 070            {
 071                _encoder = new ZstandardEncoder();
 072            }
 73            else
 074            {
 075                _decoder = new ZstandardDecoder();
 076            }
 077        }
 78
 79        /// <summary>Initializes a new instance of the <see cref="ZstandardStream" /> class by using the specified strea
 80        /// <param name="stream">The stream to which compressed data is written or from which data to decompress is read
 81        /// <param name="mode">One of the enumeration values that indicates whether to compress data to the stream or de
 82        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <see langword="null"/>.</exception>
 83        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing and <paramref name="m
 084        public ZstandardStream(Stream stream, CompressionMode mode) : this(stream, mode, leaveOpen: false) { }
 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        /// <param name="dictionary">The compression or decompression dictionary to use.</param>
 90        /// <param name="leaveOpen"><see langword="true" /> to leave the stream open after the <see cref="ZstandardStrea
 91        /// <exception cref="ArgumentNullException"><paramref name="stream"/> or <paramref name="dictionary"/> is null.<
 92        /// <exception cref="ArgumentException"><paramref name="stream"/> does not support writing and <paramref name="m
 093        public ZstandardStream(Stream stream, CompressionMode mode, ZstandardDictionary dictionary, bool leaveOpen = fal
 094        {
 095            ArgumentNullException.ThrowIfNull(dictionary);
 96
 097            Init(stream, mode);
 098            _mode = mode;
 099            _leaveOpen = leaveOpen;
 100
 0101            if (mode == CompressionMode.Compress)
 0102            {
 0103                _encoder = new ZstandardEncoder(dictionary);
 0104            }
 105            else
 0106            {
 0107                _decoder = new ZstandardDecoder(dictionary);
 0108            }
 0109        }
 110
 111        /// <summary>Gets a reference to the underlying stream.</summary>
 112        /// <value>A stream object that represents the underlying stream.</value>
 113        /// <exception cref="System.ObjectDisposedException">The underlying stream is closed.</exception>
 114        public Stream BaseStream
 115        {
 116            get
 0117            {
 0118                EnsureNotDisposed();
 0119                return _stream;
 0120            }
 121        }
 122
 123        /// <summary>Gets a value indicating whether the stream supports reading while decompressing a file.</summary>
 124        /// <value><see langword="true" /> if the <see cref="CompressionMode" /> value is <c>Decompress,</c> and the und
 0125        public override bool CanRead => _mode == CompressionMode.Decompress && _stream?.CanRead == true;
 126
 127        /// <summary>Gets a value indicating whether the stream supports writing.</summary>
 128        /// <value><see langword="true" /> if the <see cref="CompressionMode" /> value is <c>Compress,</c> and the under
 0129        public override bool CanWrite => _mode == CompressionMode.Compress && _stream?.CanWrite == true;
 130
 131        /// <summary>Gets a value indicating whether the stream supports seeking.</summary>
 132        /// <value><see langword="false" /> in all cases.</value>
 0133        public override bool CanSeek => false;
 134
 135        /// <summary>This property is not supported and always throws a <see cref="NotSupportedException" />.</summary>
 136        /// <exception cref="NotSupportedException">In all cases.</exception>
 0137        public override long Length => throw new NotSupportedException();
 138
 139        /// <summary>This property is not supported and always throws a <see cref="NotSupportedException" />.</summary>
 140        /// <exception cref="NotSupportedException">In all cases.</exception>
 141        public override long Position
 142        {
 0143            get => throw new NotSupportedException();
 0144            set => throw new NotSupportedException();
 145        }
 146
 147        /// <summary>This operation is not supported and always throws a <see cref="NotSupportedException" />.</summary>
 148        /// <param name="offset">The byte offset relative to the <paramref name="origin" /> parameter.</param>
 149        /// <param name="origin">One of the <see cref="SeekOrigin" /> values that indicates the reference point used to 
 150        /// <returns>The new position within the current stream.</returns>
 151        /// <exception cref="NotSupportedException">In all cases.</exception>
 0152        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 153
 154        /// <summary>This operation is not supported and always throws a <see cref="NotSupportedException" />.</summary>
 155        /// <param name="value">The desired length of the current stream in bytes.</param>
 156        /// <exception cref="NotSupportedException">In all cases.</exception>
 0157        public override void SetLength(long value) => throw new NotSupportedException();
 158
 159        /// <summary>Releases the unmanaged resources used by the <see cref="ZstandardStream" /> and optionally releases
 160        /// <param name="disposing"><see langword="true" /> to release both managed and unmanaged resources; <see langwo
 161        protected override void Dispose(bool disposing)
 0162        {
 163            try
 0164            {
 0165                if (disposing && _stream != null)
 0166                {
 0167                    if (_mode == CompressionMode.Compress)
 0168                    {
 0169                        WriteCore(ReadOnlySpan<byte>.Empty, isFinalBlock: true, throwOnActiveRwOp: false);
 0170                    }
 171
 0172                    if (!_leaveOpen)
 0173                    {
 0174                        _stream.Dispose();
 0175                    }
 0176                }
 0177            }
 178            finally
 0179            {
 0180                ReleaseStateForDispose();
 0181                base.Dispose(disposing);
 0182            }
 0183        }
 184
 185        /// <summary>Asynchronously releases the unmanaged resources used by the <see cref="ZstandardStream" />.</summar
 186        /// <returns>A task that represents the asynchronous dispose operation.</returns>
 187        public override async ValueTask DisposeAsync()
 0188        {
 189            try
 0190            {
 0191                if (_stream != null)
 0192                {
 0193                    if (_mode == CompressionMode.Compress)
 0194                    {
 0195                        await WriteCoreAsync(ReadOnlyMemory<byte>.Empty, CancellationToken.None, isFinalBlock: true, thr
 0196                    }
 197
 0198                    if (!_leaveOpen)
 0199                    {
 0200                        await _stream.DisposeAsync().ConfigureAwait(false);
 0201                    }
 0202                }
 0203            }
 204            finally
 0205            {
 0206                ReleaseStateForDispose();
 0207                await base.DisposeAsync().ConfigureAwait(false);
 0208            }
 0209        }
 210
 211        private void ReleaseStateForDispose()
 0212        {
 0213            _stream = null!;
 214
 0215            if (_encoderOwned)
 0216            {
 0217                _encoder?.Dispose();
 0218                _decoder?.Dispose();
 0219            }
 220            else
 0221            {
 0222                _encoder?.Reset();
 0223                _decoder?.Reset();
 0224            }
 225
 226            // only return the buffer if no read/write operation is active
 0227            if (!Interlocked.Exchange(ref _activeRwOperation, true))
 0228            {
 0229                _buffer.Dispose();
 0230            }
 0231        }
 232
 233        private void EnsureNotDisposed()
 0234        {
 0235            ObjectDisposedException.ThrowIf(_stream == null, this);
 0236        }
 237
 238        private static void ThrowConcurrentRWOperation()
 0239        {
 0240            throw new InvalidOperationException(SR.ZstandardStream_ConcurrentRWOperation);
 241        }
 242
 243        private bool BeginRWOperation(bool throwOnActiveRwOp = true)
 0244        {
 0245            if (Interlocked.Exchange(ref _activeRwOperation, true))
 0246            {
 0247                if (!throwOnActiveRwOp)
 0248                {
 0249                    return false;
 250                }
 251
 0252                ThrowConcurrentRWOperation();
 0253            }
 254
 0255            return true;
 0256        }
 257
 258        private void EndRWOperation()
 0259        {
 0260            Interlocked.Exchange(ref _activeRwOperation, false);
 0261        }
 262
 263        private void EnsureNoActiveRWOperation()
 0264        {
 0265            if (_activeRwOperation)
 0266            {
 0267                ThrowConcurrentRWOperation();
 0268            }
 0269        }
 270    }
 271}

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
 053            _decoder = new ZstandardDecoder(decompressionOptions);
 054        }
 55
 56        // Decompresses buffered input for the current frame only. Returns true when there is a result for
 57        // the caller to act on (output was produced, the frame finished, or a zero-byte read should
 58        // return), and false when more input is needed to make progress on the current frame. Crossing a
 59        // frame boundary into the next concatenated frame is handled by Read/ReadAsync once this reports
 60        // OperationStatus.Done.
 61        private bool TryDecompress(Span<byte> destination, out int bytesWritten, out OperationStatus lastResult)
 062        {
 063            Debug.Assert(_decoder != null);
 64
 065            OperationStatus status = _decoder.Decompress(_buffer.ActiveSpan, destination, out int bytesConsumed, out byt
 066            _buffer.Discard(bytesConsumed);
 067            lastResult = status;
 68
 069            if (status == OperationStatus.InvalidData)
 070            {
 071                throw new InvalidDataException(SR.ZstandardStream_Decompress_InvalidData);
 72            }
 73
 074            if (status == OperationStatus.Done)
 075            {
 76                // Reached the end of a frame. ZSTD_decompressStream reports end-of-frame, not end-of-stream:
 77                // a zstd stream may be frames concatenated back-to-back (RFC 8878 section 3), so Read/ReadAsync
 78                // decide whether another frame follows. This may be a zero-output frame (bytesWritten == 0).
 079                return true;
 80            }
 81
 082            if (bytesWritten != 0)
 083            {
 084                return true;
 85            }
 86
 087            if (destination.IsEmpty)
 088            {
 89                // The caller provided a zero-byte buffer.  This is typically done in order to avoid allocating/renting
 90                // a buffer until data is known to be available.  We don't have perfect knowledge here, as _decoder.Deco
 91                // will return DestinationTooSmall whether or not more data is required.  As such, we assume that if the
 92                // any data in our input buffer, it would have been decompressible into at least one byte of output, and
 93                // otherwise we need to do a read on the underlying stream.  This isn't perfect, because having input da
 94                // doesn't necessarily mean it'll decompress into at least one byte of output, but it's a reasonable app
 95                // for the 99% case.  If it's wrong, it just means that a caller using zero-byte reads as a way to delay
 96                // getting a buffer to use for a subsequent call may end up getting one earlier than otherwise preferred
 097                Debug.Assert(status == OperationStatus.DestinationTooSmall);
 098                if (_buffer.ActiveLength != 0)
 099                {
 0100                    return true;
 101                }
 0102            }
 103
 0104            Debug.Assert(
 0105                status == OperationStatus.NeedMoreData ||
 0106                (status == OperationStatus.DestinationTooSmall && destination.IsEmpty && _buffer.ActiveLength == 0), $"{
 107
 0108            return false;
 0109        }
 110
 111        /// <summary>Reads decompressed bytes from the underlying stream and places them in the specified array.</summar
 112        /// <param name="buffer">The byte array to contain the decompressed bytes.</param>
 113        /// <param name="offset">The byte offset in <paramref name="buffer" /> at which the read bytes will be placed.</
 114        /// <param name="count">The maximum number of decompressed bytes to read.</param>
 115        /// <returns>The number of bytes that were read into the byte array.</returns>
 116        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 117        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 118        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 119        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 120        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 121        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 122        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 123        public override int Read(byte[] buffer, int offset, int count)
 0124        {
 0125            ValidateBufferArguments(buffer, offset, count);
 0126            return Read(new Span<byte>(buffer, offset, count));
 0127        }
 128
 129        /// <summary>Reads decompressed bytes from the underlying stream and places them in the specified span.</summary
 130        /// <param name="buffer">The span to contain the decompressed bytes.</param>
 131        /// <returns>The number of bytes that were read into the span.</returns>
 132        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 133        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 134        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 135        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 136        public override int Read(Span<byte> buffer)
 0137        {
 0138            if (_mode != CompressionMode.Decompress)
 0139            {
 0140                throw new InvalidOperationException(SR.CannotReadFromCompressionStream);
 141            }
 142
 0143            EnsureNotDisposed();
 0144            BeginRWOperation();
 145
 146            try
 0147            {
 0148                if (_endOfStream)
 0149                {
 150                    // A previous read reached the end of the final frame (or rejected trailing data). The
 151                    // boundary probe may have left the decoder non-resumable, so never re-enter the decode
 152                    // loop; report end-of-stream to all subsequent reads.
 0153                    return 0;
 154                }
 155
 0156                while (true)
 0157                {
 158                    int bytesWritten;
 159                    OperationStatus lastResult;
 0160                    while (!TryDecompress(buffer, out bytesWritten, out lastResult))
 0161                    {
 0162                        _buffer.EnsureAvailableSpace(1);
 163
 0164                        int bytesRead = _stream.Read(_buffer.AvailableSpan);
 0165                        if (bytesRead <= 0)
 0166                        {
 167                            // The underlying stream ended in the middle of a frame, so the data is truncated.
 168                            // A clean end after a completed frame is reported as Done by TryDecompress and is
 169                            // resolved by the frame-boundary logic below, not here.
 0170                            if (_nonEmptyInput && !buffer.IsEmpty)
 0171                            {
 0172                                ThrowTruncatedInvalidData();
 173                            }
 174
 0175                            return 0;
 176                        }
 177
 0178                        _nonEmptyInput = true;
 179
 0180                        if (bytesRead > _buffer.AvailableLength)
 0181                        {
 0182                            ThrowInvalidStream();
 183                        }
 184
 0185                        _buffer.Commit(bytesRead);
 0186                    }
 187
 0188                    if (lastResult != OperationStatus.Done || bytesWritten != 0)
 0189                    {
 190                        // Output to hand back, or not at a finished-frame boundary: return to the caller.
 0191                        return bytesWritten;
 192                    }
 193
 194                    // A frame finished with no pending output. A zstd stream may be frames concatenated
 195                    // back-to-back (RFC 8878 section 3), so decide whether another frame follows before
 196                    // reporting end-of-stream. async: false completes synchronously (it only ever takes the
 197                    // synchronous read path).
 0198                    ValueTask<bool> advanceTask = AdvanceToNextFrame(async: false, cancellationToken: default);
 0199                    Debug.Assert(advanceTask.IsCompleted, "AdvanceToNextFrame should complete synchronously when async: 
 0200                    if (!advanceTask.GetAwaiter().GetResult())
 0201                    {
 0202                        return bytesWritten;
 203                    }
 0204                }
 205            }
 206            finally
 0207            {
 0208                EndRWOperation();
 0209            }
 0210        }
 211
 212        // Called after TryDecompress reports OperationStatus.Done with no pending output (a finished frame).
 213        // Decides whether another concatenated frame follows by reading up to a frame magic and feeding just
 214        // those bytes to the decoder: a valid magic is accepted (NeedMoreData) so decoding continues, while
 215        // bytes that are not a frame magic identify trailing data after the final frame. Returns true (decoder
 216        // reset and ready) so the caller loops to decode the next frame, or false (stream complete; _endOfStream
 217        // is set and a seekable base stream is rewound to the end of the compressed data) so the caller returns.
 218        // Shared by Read (async: false) and ReadAsync (async: true).
 219        private async ValueTask<bool> AdvanceToNextFrame(bool async, CancellationToken cancellationToken)
 0220        {
 0221            Debug.Assert(_decoder != null);
 222
 0223            if (_buffer.ActiveLength < ZstdFrameMagicLength)
 0224            {
 225                // Not enough buffered to tell a split next-frame magic from end-of-stream; read just enough
 226                // to complete the magic. Limiting the read to exactly the missing bytes (rather than filling
 227                // the available buffer) avoids consuming and hiding trailing bytes past the magic on a
 228                // non-seekable stream, where they can't be rewound; any following frame's body is read by the
 229                // Read/ReadAsync loop afterwards.
 0230                int needed = ZstdFrameMagicLength - _buffer.ActiveLength;
 0231                _buffer.EnsureAvailableSpace(needed);
 0232                int peeked = async
 0233                    ? await _stream.ReadAtLeastAsync(_buffer.AvailableMemory.Slice(0, needed), needed, throwOnEndOfStrea
 0234                    : _stream.ReadAtLeast(_buffer.AvailableSpan.Slice(0, needed), needed, throwOnEndOfStream: false);
 0235                if (peeked > 0)
 0236                {
 0237                    _nonEmptyInput = true;
 0238                    _buffer.Commit(peeked);
 0239                }
 0240            }
 241
 0242            if (_buffer.ActiveLength >= ZstdFrameMagicLength)
 0243            {
 244                // Determine whether another concatenated frame follows by feeding the decoder exactly the next
 245                // frame magic number. The decoder validates the magic against both standard and skippable
 246                // frames: a valid magic leaves it asking for more input (NeedMoreData), so decoding continues,
 247                // while bytes that are not a frame magic produce InvalidData, identifying trailing data after
 248                // the final frame. Feeding only the magic (not the whole buffer) means a frame whose magic is
 249                // valid but whose body is corrupt is not mistaken for trailing data here: the magic is accepted
 250                // and the corrupt body is rejected by the subsequent decode in the Read/ReadAsync loop.
 0251                _decoder.Reset();
 252
 253                // The magic alone never decodes into output, so the decoder needs no real output space; a single
 254                // scratch byte borrowed from the buffer's free region (which the decoder won't write to) satisfies
 255                // the non-empty-destination requirement. A Span local / stackalloc can't be used here because this
 256                // method is async.
 0257                _buffer.EnsureAvailableSpace(1);
 0258                if (_decoder.Decompress(_buffer.ActiveSpan.Slice(0, ZstdFrameMagicLength), _buffer.AvailableSpan.Slice(0
 0259                {
 260                    // A valid magic; the decoder has taken the magic bytes into its session to continue decoding
 261                    // the rest of the frame. Drop them from the buffer and keep decoding.
 0262                    _buffer.Discard(bytesConsumed);
 0263                    return true;
 264                }
 265
 266                // Not a frame: leave the magic bytes buffered so they are included in the trailing-data rewind
 267                // below (on a non-seekable stream they simply remain unconsumed).
 0268            }
 269
 270            // Trailing non-zstd data or end of input after the final frame: the stream is complete. Mark the
 271            // stream ended so subsequent reads short-circuit to 0 without re-entering the (now non-resumable)
 272            // decoder, and leave any trailing bytes on a seekable base stream by rewinding to the end of the
 273            // compressed data, mirroring how DeflateStream handles data after the last gzip member.
 0274            _endOfStream = true;
 0275            if (_stream.CanSeek)
 0276            {
 0277                TryRewindStream(_stream);
 0278            }
 279
 0280            return false;
 0281        }
 282
 283        /// <summary>Asynchronously reads decompressed bytes from the underlying stream and places them in the specified
 284        /// <param name="buffer">The byte array to contain the decompressed bytes.</param>
 285        /// <param name="offset">The byte offset in <paramref name="buffer" /> at which the read bytes will be placed.</
 286        /// <param name="count">The maximum number of decompressed bytes to read.</param>
 287        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 288        /// <returns>A task that represents the asynchronous read operation, which wraps the number of bytes read from t
 289        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 290        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 291        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 292        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 293        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 294        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 295        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 296        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0297        {
 0298            ValidateBufferArguments(buffer, offset, count);
 0299            return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
 0300        }
 301
 302        /// <summary>Asynchronously reads decompressed bytes from the underlying stream and places them in the specified
 303        /// <param name="buffer">The memory to contain the decompressed bytes.</param>
 304        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 305        /// <returns>A task that represents the asynchronous read operation, which wraps the number of bytes read from t
 306        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 307        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 308        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 309        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 310        public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = defaul
 0311        {
 0312            if (_mode != CompressionMode.Decompress)
 0313            {
 0314                throw new InvalidOperationException(SR.CannotReadFromCompressionStream);
 315            }
 316
 0317            EnsureNotDisposed();
 0318            BeginRWOperation();
 319
 320            try
 0321            {
 0322                if (_endOfStream)
 0323                {
 324                    // A previous read reached the end of the final frame (or rejected trailing data). The
 325                    // boundary probe may have left the decoder non-resumable, so never re-enter the decode
 326                    // loop; report end-of-stream to all subsequent reads.
 0327                    return 0;
 328                }
 329
 0330                while (true)
 0331                {
 332                    int bytesWritten;
 333                    OperationStatus lastResult;
 0334                    while (!TryDecompress(buffer.Span, out bytesWritten, out lastResult))
 0335                    {
 0336                        _buffer.EnsureAvailableSpace(1);
 337
 0338                        int bytesRead = await _stream.ReadAsync(_buffer.AvailableMemory, cancellationToken).ConfigureAwa
 0339                        if (bytesRead <= 0)
 0340                        {
 341                            // The underlying stream ended in the middle of a frame, so the data is truncated.
 342                            // A clean end after a completed frame is reported as Done by TryDecompress and is
 343                            // resolved by the frame-boundary logic below, not here.
 0344                            if (_nonEmptyInput && !buffer.IsEmpty)
 0345                            {
 0346                                ThrowTruncatedInvalidData();
 347                            }
 348
 0349                            return 0;
 350                        }
 351
 0352                        _nonEmptyInput = true;
 353
 0354                        if (bytesRead > _buffer.AvailableLength)
 0355                        {
 0356                            ThrowInvalidStream();
 357                        }
 358
 0359                        _buffer.Commit(bytesRead);
 0360                    }
 361
 0362                    if (lastResult != OperationStatus.Done || bytesWritten != 0)
 0363                    {
 364                        // Output to hand back, or not at a finished-frame boundary: return to the caller.
 0365                        return bytesWritten;
 366                    }
 367
 368                    // A frame finished with no pending output. A zstd stream may be frames concatenated
 369                    // back-to-back (RFC 8878 section 3), so decide whether another frame follows before
 370                    // reporting end-of-stream.
 0371                    if (!await AdvanceToNextFrame(async: true, cancellationToken: cancellationToken).ConfigureAwait(fals
 0372                    {
 0373                        return bytesWritten;
 374                    }
 0375                }
 376            }
 377            finally
 0378            {
 0379                EndRWOperation();
 0380            }
 0381        }
 382
 383        /// <summary>Begins an asynchronous read operation.</summary>
 384        /// <param name="buffer">The buffer to read the data into.</param>
 385        /// <param name="offset">The byte offset in <paramref name="buffer"/> at which to begin writing data read from t
 386        /// <param name="count">The maximum number of bytes to read.</param>
 387        /// <param name="callback">An optional asynchronous callback, to be called when the read is complete.</param>
 388        /// <param name="state">A user-provided object that distinguishes this particular asynchronous read request from
 389        /// <returns>An object that represents the asynchronous read, which could still be pending.</returns>
 390        /// <exception cref="ArgumentNullException"><paramref name="buffer" /> is <see langword="null" />.</exception>
 391        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset" /> or <paramref name="count" /> is les
 392        /// <exception cref="ArgumentException">The <paramref name="buffer" /> length minus the index starting point is 
 393        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 394        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 395        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 396        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 397        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? st
 0398        {
 0399            return TaskToAsyncResult.Begin(ReadAsync(buffer, offset, count, CancellationToken.None), callback, state);
 0400        }
 401
 402        /// <summary>Waits for the pending asynchronous read to complete.</summary>
 403        /// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
 404        /// <returns>The number of bytes read from the stream, between zero (0) and the number of bytes you requested. S
 405        public override int EndRead(IAsyncResult asyncResult)
 0406        {
 0407            return TaskToAsyncResult.End<int>(asyncResult);
 0408        }
 409
 410        /// <summary>Reads a byte from the stream and advances the position within the stream by one byte, or returns -1
 411        /// <returns>The unsigned byte cast to an <see cref="int"/>, or -1 if at the end of the stream.</returns>
 412        /// <exception cref="InvalidOperationException">The <see cref="CompressionMode" /> value was <see cref="Compress
 413        /// <exception cref="InvalidDataException">The data is in an invalid format.</exception>
 414        /// <exception cref="ObjectDisposedException">The stream is disposed.</exception>
 415        /// <exception cref="IOException">Failed to decompress data from the underlying stream.</exception>
 416        public override int ReadByte()
 0417        {
 0418            Span<byte> singleByte = [0];
 0419            int bytesRead = Read(singleByte);
 0420            return bytesRead > 0 ? singleByte[0] : -1;
 0421        }
 422
 423        /// <summary>
 424        /// Rewinds the underlying stream to the exact end of the compressed data if there are unconsumed bytes.
 425        /// This is called when decompression finishes to reset the stream position.
 426        /// </summary>
 427        private void TryRewindStream(Stream stream)
 0428        {
 0429            Debug.Assert(stream != null);
 0430            Debug.Assert(_mode == CompressionMode.Decompress);
 0431            Debug.Assert(stream.CanSeek);
 432
 433            // Check if there are unconsumed bytes in the buffer
 0434            int unconsumedBytes = _buffer.ActiveLength;
 0435            if (unconsumedBytes > 0)
 0436            {
 437                // Rewind the stream to the exact end of the compressed data
 0438                stream.Seek(-unconsumedBytes, SeekOrigin.Current);
 0439                _buffer.Discard(unconsumedBytes);
 0440            }
 0441        }
 442
 443        [DoesNotReturn]
 444        private static void ThrowInvalidStream() =>
 445            // The stream is either malicious or poorly implemented and returned a number of
 446            // bytes larger than the buffer supplied to it.
 0447            throw new InvalidDataException(SR.ZstandardStream_Decompress_InvalidStream);
 448
 449        [DoesNotReturn]
 450        private static void ThrowTruncatedInvalidData() =>
 0451            throw new InvalidDataException(SR.ZstandardStream_Decompress_TruncatedData);
 452    }
 453}

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()