< Summary

Information
Class: System.Net.WebSockets.Compression.WebSocketDeflater
Assembly: System.Net.WebSockets
File(s): D:\runner\runtime\src\libraries\System.Net.WebSockets\src\System\Net\WebSockets\Compression\WebSocketDeflater.cs
Line coverage
81%
Covered lines: 101
Uncovered lines: 23
Coverable lines: 124
Total lines: 222
Line coverage: 81.4%
Branch coverage
72%
Covered branches: 26
Total branches: 36
Branch coverage: 72.2%
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%
ReleaseBuffer()100%22100%
Deflate(...)50%2268.18%
DeflatePrivate(...)78.57%141470%
UnsafeDeflate(...)50%44100%
UnsafeFlush(...)75%44100%
Deflate(...)62.5%8860%
CreateDeflater()100%1170%

File(s)

D:\runner\runtime\src\libraries\System.Net.WebSockets\src\System\Net\WebSockets\Compression\WebSocketDeflater.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 compression API.
 12    /// </summary>
 13    internal sealed class WebSocketDeflater : IDisposable
 14    {
 15        private readonly int _windowBits;
 16        private ZLibStreamHandle? _stream;
 17        private readonly bool _persisted;
 18
 19        private byte[]? _buffer;
 20
 797821        internal WebSocketDeflater(int windowBits, bool persisted)
 797822        {
 797823            _windowBits = -windowBits; // Negative for raw deflate
 797824            _persisted = persisted;
 797825        }
 26
 27        public void Dispose()
 797828        {
 797829            _stream?.Dispose();
 797830        }
 31
 32        public void ReleaseBuffer()
 1051233        {
 1051234            if (_buffer is byte[] toReturn)
 266835            {
 266836                _buffer = null;
 266837                ArrayPool<byte>.Shared.Return(toReturn);
 266838            }
 1051239        }
 40
 41        public ReadOnlySpan<byte> Deflate(ReadOnlySpan<byte> payload, bool endOfMessage)
 266842        {
 266843            Debug.Assert(_buffer is null, "Invalid state, ReleaseBuffer not called.");
 44
 45            // For small payloads there might actually be overhead in the compression and the resulting
 46            // output might be larger than the payload. This is why we rent at least 4KB initially.
 47            const int MinInitialBufferLength = 4 * 1024;
 48
 266849            _buffer = ArrayPool<byte>.Shared.Rent(Math.Max(payload.Length, MinInitialBufferLength));
 266850            int position = 0;
 51
 266852            while (true)
 266853            {
 266854                DeflatePrivate(payload, _buffer.AsSpan(position), endOfMessage,
 266855                    out int consumed, out int written, out bool needsMoreOutput);
 266856                position += written;
 57
 266858                if (!needsMoreOutput)
 266859                {
 266860                    Debug.Assert(consumed == payload.Length);
 266861                    break;
 62                }
 63
 064                payload = payload.Slice(consumed);
 65
 66                // Rent a 30% bigger buffer
 067                byte[] newBuffer = ArrayPool<byte>.Shared.Rent((int)(_buffer.Length * 1.3));
 068                _buffer.AsSpan(0, position).CopyTo(newBuffer);
 69
 070                byte[] toReturn = _buffer;
 071                _buffer = newBuffer;
 72
 073                ArrayPool<byte>.Shared.Return(toReturn);
 074            }
 75
 266876            return new ReadOnlySpan<byte>(_buffer, 0, position);
 266877        }
 78
 79        private void DeflatePrivate(ReadOnlySpan<byte> payload, Span<byte> output, bool endOfMessage,
 80            out int consumed, out int written, out bool needsMoreOutput)
 266881        {
 266882            _stream ??= CreateDeflater();
 83
 266884            if (payload.Length == 0)
 165385            {
 165386                consumed = 0;
 165387                written = 0;
 165388            }
 89            else
 101590            {
 101591                UnsafeDeflate(payload, output, out consumed, out written, out needsMoreOutput);
 92
 101593                if (needsMoreOutput)
 094                {
 095                    Debug.Assert(written == output.Length);
 096                    return;
 97                }
 101598            }
 99
 2668100            written += UnsafeFlush(output.Slice(written), out needsMoreOutput);
 101
 2668102            if (needsMoreOutput)
 0103            {
 0104                return;
 105            }
 2668106            Debug.Assert(output.Slice(written - WebSocketInflater.FlushMarkerLength, WebSocketInflater.FlushMarkerLength
 2668107                               .EndsWith(WebSocketInflater.FlushMarker), "The deflated block must always end with a flus
 108
 2668109            if (endOfMessage)
 2546110            {
 111                // As per RFC we need to remove the flush markers
 2546112                written -= WebSocketInflater.FlushMarkerLength;
 2546113            }
 114
 2668115            if (endOfMessage && !_persisted)
 0116            {
 0117                _stream.Dispose();
 0118                _stream = null;
 0119            }
 2668120        }
 121
 122        private unsafe void UnsafeDeflate(ReadOnlySpan<byte> input, Span<byte> output, out int consumed, out int written
 1015123        {
 1015124            Debug.Assert(_stream is not null);
 125
 1015126            fixed (byte* fixedInput = input)
 1015127            fixed (byte* fixedOutput = output)
 1015128            {
 1015129                _stream.NextIn = (IntPtr)fixedInput;
 1015130                _stream.AvailIn = (uint)input.Length;
 131
 1015132                _stream.NextOut = (IntPtr)fixedOutput;
 1015133                _stream.AvailOut = (uint)output.Length;
 134
 135                // The flush is set to Z_NO_FLUSH, which allows deflate to decide
 136                // how much data to accumulate before producing output,
 137                // in order to maximize compression.
 1015138                var errorCode = Deflate(_stream, FlushCode.NoFlush);
 139
 1015140                consumed = input.Length - (int)_stream.AvailIn;
 1015141                written = output.Length - (int)_stream.AvailOut;
 142
 143                // It is important here to also check that we haven't
 144                // exhausted the output buffer because after deflating we're
 145                // always going to issue a flush and a flush with empty output
 146                // is going to throw.
 1015147                needsMoreBuffer = errorCode == ErrorCode.BufError
 1015148                    || _stream.AvailIn > 0
 1015149                    || written == output.Length;
 1015150            }
 1015151        }
 152
 153        private unsafe int UnsafeFlush(Span<byte> output, out bool needsMoreBuffer)
 2668154        {
 2668155            Debug.Assert(_stream is not null);
 2668156            Debug.Assert(_stream.AvailIn == 0);
 2668157            Debug.Assert(output.Length > 0);
 158
 2668159            fixed (byte* fixedOutput = output)
 2668160            {
 2668161                _stream.NextIn = IntPtr.Zero;
 2668162                _stream.AvailIn = 0;
 163
 2668164                _stream.NextOut = (IntPtr)fixedOutput;
 2668165                _stream.AvailOut = (uint)output.Length;
 166
 167                // We need to use Z_BLOCK_FLUSH to instruct the zlib to flush all outstanding
 168                // data but also not to emit a deflate block boundary. After we know that there is no
 169                // more data, we can safely proceed to instruct the library to emit the boundary markers.
 2668170                ErrorCode errorCode = Deflate(_stream, FlushCode.Block);
 2668171                Debug.Assert(errorCode is ErrorCode.Ok or ErrorCode.BufError);
 172
 173                // We need at least 6 bytes to guarantee that we can emit a deflate block boundary.
 2668174                needsMoreBuffer = _stream.AvailOut < 6;
 175
 2668176                if (!needsMoreBuffer)
 2668177                {
 178                    // The flush is set to Z_SYNC_FLUSH, all pending output is flushed
 179                    // to the output buffer and the output is aligned on a byte boundary,
 180                    // so that the decompressor can get all input data available so far.
 181                    // This completes the current deflate block and follows it with an empty
 182                    // stored block that is three bits plus filler bits to the next byte,
 183                    // followed by four bytes (00 00 ff ff).
 2668184                    errorCode = Deflate(_stream, FlushCode.SyncFlush);
 2668185                    Debug.Assert(errorCode == ErrorCode.Ok);
 2668186                }
 187
 2668188                return output.Length - (int)_stream.AvailOut;
 189            }
 2668190        }
 191
 192        private static ErrorCode Deflate(ZLibStreamHandle stream, FlushCode flushCode)
 6351193        {
 6351194            ErrorCode errorCode = stream.Deflate(flushCode);
 195
 6351196            if (errorCode is ErrorCode.Ok or ErrorCode.StreamEnd or ErrorCode.BufError)
 6351197            {
 6351198                return errorCode;
 199            }
 200
 0201            string message = errorCode == ErrorCode.StreamError
 0202                ? SR.ZLibErrorInconsistentStream
 0203                : SR.Format(SR.ZLibErrorUnexpected, (int)errorCode);
 0204            throw new WebSocketException(message);
 6351205        }
 206
 207        private ZLibStreamHandle CreateDeflater()
 2614208        {
 209            try
 2614210            {
 2614211                return ZLibStreamHandle.CreateForDeflate(level: CompressionLevel.DefaultCompression,
 2614212                    windowBits: _windowBits,
 2614213                    memLevel: Deflate_DefaultMemLevel,
 2614214                    strategy: CompressionStrategy.DefaultStrategy);
 215            }
 0216            catch (Exception ex)
 0217            {
 0218                throw new WebSocketException(ex.Message, ex.InnerException);
 219            }
 2614220        }
 221    }
 222}