< Summary

Information
Class: System.Net.WebSockets.Compression.WebSocketInflater
Assembly: System.Net.WebSockets
File(s): D:\runner\runtime\src\libraries\System.Net.WebSockets\src\System\Net\WebSockets\Compression\WebSocketInflater.cs
Line coverage
85%
Covered lines: 128
Uncovered lines: 21
Coverable lines: 149
Total lines: 281
Line coverage: 85.9%
Branch coverage
79%
Covered branches: 46
Total branches: 58
Branch coverage: 79.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%11100%
Dispose()100%22100%
Prepare(...)100%22100%
AddBytes(...)100%88100%
Inflate(...)88.88%1818100%
Finish(...)57.14%141450%
ReleaseBuffer()100%22100%
IsFinished(...)50%2277.77%
Inflate(...)70%101085.71%
CreateInflater()100%1157.14%

File(s)

D:\runner\runtime\src\libraries\System.Net.WebSockets\src\System\Net\WebSockets\Compression\WebSocketInflater.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 static System.IO.Compression.ZLibNative;
 7
 8namespace System.Net.WebSockets.Compression
 9{
 10    /// <summary>
 11    /// Provides a wrapper around the ZLib decompression API.
 12    /// </summary>
 13    internal sealed class WebSocketInflater : IDisposable
 14    {
 15        internal const int FlushMarkerLength = 4;
 924916        internal static ReadOnlySpan<byte> FlushMarker => [0x00, 0x00, 0xFF, 0xFF];
 17
 18        private readonly int _windowBits;
 19        private ZLibStreamHandle? _stream;
 20        private readonly bool _persisted;
 21
 22        /// <summary>
 23        /// There is no way of knowing, when decoding data, if the underlying inflater
 24        /// has flushed all outstanding data to consumer other than to provide a buffer
 25        /// and see whether any bytes are written. There are cases when the consumers
 26        /// provide a buffer exactly the size of the uncompressed data and in this case
 27        /// to avoid requiring another read we will use this field.
 28        /// </summary>
 29        private byte? _remainingByte;
 30
 31        /// <summary>
 32        /// The last added bytes to the inflater were part of the final
 33        /// payload for the message being sent.
 34        /// </summary>
 35        private bool _endOfMessage;
 36
 37        private byte[]? _buffer;
 38
 39        /// <summary>
 40        /// The position for the next unconsumed byte in the inflate buffer.
 41        /// </summary>
 42        private int _position;
 43
 44        /// <summary>
 45        /// How many unconsumed bytes are left in the inflate buffer.
 46        /// </summary>
 47        private int _available;
 48
 797849        internal WebSocketInflater(int windowBits, bool persisted)
 797850        {
 797851            _windowBits = -windowBits; // Negative for raw deflate
 797852            _persisted = persisted;
 797853        }
 54
 94455        public Memory<byte> Memory => _buffer.AsMemory(_position + _available);
 56
 1418957        public Span<byte> Span => _buffer.AsSpan(_position + _available);
 58
 59        public void Dispose()
 797860        {
 797861            _stream?.Dispose();
 797862            ReleaseBuffer();
 797863        }
 64
 65        /// <summary>
 66        /// Initializes the inflater by allocating a buffer so the websocket can receive directly onto it.
 67        /// </summary>
 68        /// <param name="payloadLength">the length of the message payload</param>
 69        /// <param name="userBufferLength">the length of the buffer where the payload will be inflated</param>
 70        public void Prepare(long payloadLength, int userBufferLength)
 764271        {
 764272            if (_buffer is not null)
 208473            {
 208474                Debug.Assert(_available > 0);
 75
 208476                _buffer.AsSpan(_position, _available).CopyTo(_buffer);
 208477                _position = 0;
 208478            }
 79            else
 555880            {
 81                // Rent a buffer as close to the size of the user buffer as possible.
 82                // If the payload is smaller than the user buffer, rent only as much as we need.
 555883                _buffer = ArrayPool<byte>.Shared.Rent((int)Math.Min(userBufferLength, payloadLength));
 555884            }
 764285        }
 86
 87        public void AddBytes(int totalBytesReceived, bool endOfMessage)
 7456688        {
 7456689            Debug.Assert(totalBytesReceived == 0 || _buffer is not null, "Prepare must be called.");
 90
 7456691            _available += totalBytesReceived;
 7456692            _endOfMessage = endOfMessage;
 93
 7456694            if (endOfMessage)
 658195            {
 658196                if (_buffer is null)
 633997                {
 633998                    Debug.Assert(_available == 0);
 99
 6339100                    _buffer = ArrayPool<byte>.Shared.Rent(FlushMarkerLength);
 6339101                    _available = FlushMarkerLength;
 6339102                    FlushMarker.CopyTo(_buffer);
 6339103                }
 104                else
 242105                {
 242106                    if (_buffer.Length < _available + FlushMarkerLength)
 19107                    {
 19108                        byte[] newBuffer = ArrayPool<byte>.Shared.Rent(_available + FlushMarkerLength);
 19109                        _buffer.AsSpan(0, _available).CopyTo(newBuffer);
 110
 19111                        byte[] toReturn = _buffer;
 19112                        _buffer = newBuffer;
 113
 19114                        ArrayPool<byte>.Shared.Return(toReturn);
 19115                    }
 116
 242117                    FlushMarker.CopyTo(_buffer.AsSpan(_available));
 242118                    _available += FlushMarkerLength;
 242119                }
 6581120            }
 74566121        }
 122
 123        /// <summary>
 124        /// Inflates the last receive payload into the provided buffer.
 125        /// </summary>
 126        public unsafe bool Inflate(Span<byte> output, out int written)
 68775127        {
 68775128            _stream ??= CreateInflater();
 129
 68775130            bool streamEnded = false;
 131
 68775132            if (_available > 0 && output.Length > 0)
 14022133            {
 134                int consumed;
 135
 14022136                fixed (byte* bufferPtr = _buffer)
 14022137                {
 14022138                    _stream.NextIn = (IntPtr)(bufferPtr + _position);
 14022139                    _stream.AvailIn = (uint)_available;
 140
 14022141                    written = Inflate(_stream, output, FlushCode.NoFlush, out streamEnded);
 13774142                    consumed = _available - (int)_stream.AvailIn;
 13774143                }
 144
 13774145                _position += consumed;
 13774146                _available -= consumed;
 13774147            }
 148            else
 54753149            {
 54753150                written = 0;
 54753151            }
 152
 68527153            if (_available == 0)
 65731154            {
 65731155                ReleaseBuffer();
 65731156                return _endOfMessage ? Finish(output, ref written) : true;
 157            }
 158
 2796159            if (streamEnded && _available > 0)
 54160            {
 161                // zlib reached the end of the DEFLATE stream (a BFINAL=1 final block) while compressed
 162                // bytes still remain that it will never consume. permessage-deflate messages are not
 163                // expected to contain a final block; continuing would make no forward progress (the
 164                // inflater would report empty results forever and hang the caller's receive loop), so
 165                // reject the message.
 54166                throw new WebSocketException(SR.net_WebSockets_DataAfterBFinal);
 167            }
 168
 2742169            return false;
 68473170        }
 171
 172        /// <summary>
 173        /// Finishes the decoding by flushing any outstanding data to the output.
 174        /// </summary>
 175        /// <returns>true if the flush completed, false to indicate that there is more outstanding data.</returns>
 176        private bool Finish(Span<byte> output, ref int written)
 109177        {
 109178            Debug.Assert(_stream is not null && _stream.AvailIn == 0);
 109179            Debug.Assert(_available == 0);
 180
 109181            if (_remainingByte is not null)
 0182            {
 0183                if (output.Length == written)
 0184                {
 0185                    return false;
 186                }
 0187                output[written] = _remainingByte.GetValueOrDefault();
 0188                _remainingByte = null;
 0189                written += 1;
 0190            }
 191
 192            // If we have more space in the output, try to inflate
 109193            if (output.Length > written)
 103194            {
 103195                written += Inflate(_stream, output[written..], FlushCode.SyncFlush, out _);
 103196            }
 197
 198            // After inflate, if we have more space in the output then it means that we
 199            // have finished. Otherwise we need to manually check for more data.
 109200            if (written < output.Length || IsFinished(_stream, out _remainingByte))
 109201            {
 109202                if (!_persisted)
 0203                {
 0204                    _stream.Dispose();
 0205                    _stream = null;
 0206                }
 109207                return true;
 208            }
 209
 0210            return false;
 109211        }
 212
 213        private void ReleaseBuffer()
 73709214        {
 73709215            if (_buffer is byte[] toReturn)
 11897216            {
 11897217                _buffer = null;
 11897218                _available = 0;
 11897219                _position = 0;
 220
 11897221                ArrayPool<byte>.Shared.Return(toReturn);
 11897222            }
 73709223        }
 224
 225        private static bool IsFinished(ZLibStreamHandle stream, out byte? remainingByte)
 6226        {
 227            // There is no other way to make sure that we've consumed all data
 228            // but to try to inflate again with at least one byte of output buffer.
 6229            byte b = 0;
 6230            if (Inflate(stream, new Span<byte>(ref b), FlushCode.SyncFlush, out _) == 0)
 6231            {
 6232                remainingByte = null;
 6233                return true;
 234            }
 235
 0236            remainingByte = b;
 0237            return false;
 6238        }
 239
 240        private static unsafe int Inflate(ZLibStreamHandle stream, Span<byte> destination, FlushCode flushCode, out bool
 14131241        {
 14131242            Debug.Assert(destination.Length > 0);
 243            ErrorCode errorCode;
 244
 14131245            fixed (byte* bufPtr = destination)
 14131246            {
 14131247                stream.NextOut = (IntPtr)bufPtr;
 14131248                stream.AvailOut = (uint)destination.Length;
 249
 14131250                errorCode = stream.Inflate(flushCode);
 251
 14131252                if (errorCode is ErrorCode.Ok or ErrorCode.StreamEnd or ErrorCode.BufError)
 13883253                {
 13883254                    streamEnded = errorCode == ErrorCode.StreamEnd;
 13883255                    return destination.Length - (int)stream.AvailOut;
 256                }
 248257            }
 258
 248259            string message = errorCode switch
 248260            {
 0261                ErrorCode.MemError => SR.ZLibErrorNotEnoughMemory,
 248262                ErrorCode.DataError => SR.ZLibUnsupportedCompression,
 0263                ErrorCode.StreamError => SR.ZLibErrorInconsistentStream,
 0264                _ => SR.Format(SR.ZLibErrorUnexpected, (int)errorCode)
 248265            };
 248266            throw new WebSocketException(message);
 13883267        }
 268
 269        private ZLibStreamHandle CreateInflater()
 4321270        {
 271            try
 4321272            {
 4321273                return ZLibStreamHandle.CreateForInflate(_windowBits);
 274            }
 0275            catch (Exception ex)
 0276            {
 0277                throw new WebSocketException(ex.Message, ex.InnerException);
 278            }
 4321279        }
 280    }
 281}