< Summary

Line coverage
71%
Covered lines: 904
Uncovered lines: 365
Coverable lines: 1269
Total lines: 2227
Line coverage: 71.2%
Branch coverage
66%
Covered branches: 402
Total branches: 608
Branch coverage: 66.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: .cctor()100%11100%
File 1: .ctor(...)30%202039.21%
File 1: .ctor(...)100%44100%
File 1: Dispose()50%22100%
File 1: DisposeCore()70%1010100%
File 1: DisposeSafe(...)66.66%1212100%
File 1: SendAsync(...)0%440%
File 1: SendAsync(...)100%22100%
File 1: SendAsync(...)50%101077.77%
File 1: ReceiveAsync(...)0%440%
File 1: ReceiveAsync(...)50%44100%
File 1: CloseAsync(...)25%4466.66%
File 1: CloseOutputAsync(...)50%22100%
File 1: CloseOutputAsyncCore(...)87.5%88100%
File 1: Abort()0%220%
File 1: OnAborted()50%22100%
File 1: OnAbortedCore()70%1010100%
File 1: SendFrameAsync(...)83.33%66100%
File 1: SendFrameLockAcquiredNonCancelableAsync(...)43.75%161666.66%
File 1: WaitForWriteTaskAsync(...)20%101040.74%
File 1: SendFrameFallbackAsync(...)16.66%121252.77%
File 1: WriteFrameToSendBuffer(...)100%1616100%
File 1: WriteHeader(...)85.71%141485.29%
File 1: WriteRandomMask(...)100%11100%
File 1: ReceiveAsyncPrivate()83.03%11211289.69%
File 1: GetReceiveResult(...)50%2283.33%
File 1: HandleReceivedCloseAsync(...)92.3%262694.54%
File 1: WaitForServerToCloseConnectionAsync(...)58.33%121275%
File 1: HandleReceivedPingPongAsync(...)73.07%262680.48%
File 1: IsValidCloseStatus(...)80%101090.9%
File 1: CloseWithReceiveErrorAndThrowAsync(...)66.66%66100%
File 1: TryParseMessageHeaderFromReceiveBuffer(...)100%4242100%
File 1: CloseAsyncPrivate(...)70%303093.54%
File 1: SendCloseFrameAsync(...)91.66%1212100%
File 1: ConsumeFromBuffer(...)50%44100%
File 1: EnsureBufferContainsAsync()75%121295.23%
File 1: ThrowEOFUnexpected()100%11100%
File 1: AllocateSendBuffer(...)100%11100%
File 1: ReleaseSendBuffer()50%22100%
File 1: CombineMaskBytes(...)100%11100%
File 1: ApplyMask(...)50%44100%
File 1: ApplyMask(...)81.25%1616100%
File 1: CreateOperationCanceledException(...)100%110%
File 1: ThrowIfDisposed()100%11100%
File 1: ThrowIfInvalidState(...)50%4450%
File 1: TryValidateUtf8(...)100%4040100%
File 1: LogExceptions(...)0%220%
File 1: LogExceptions(...)0%440%
File 1: LogFaulted(System.Threading.Tasks.Task,System.Object)0%220%
File 1: ThrowIfInvalidMessageType(...)75%4450%
File 1: ThrowInvalidMessageType(System.Net.WebSockets.WebSocketMessageType,System.String)100%110%
File 2: HeartBeat()0%220%
File 2: UnsolicitedPongHeartBeat()0%220%
File 2: TrySendKeepAliveFrameAsync(...)0%880%
File 2: KeepAlivePingHeartBeat()0%18180%
File 2: SendPingAsync(...)0%220%
File 2: .ctor(...)100%110%
File 2: TimeSpanToMs(System.TimeSpan)100%110%
File 2: OnDataReceived()100%110%
File 2: OnPongResponseReceived(...)0%12120%
File 2: OnNextPingRequestCore()100%110%
File 2: OnKeepAliveFaulted(...)100%110%
File 2: OnKeepAliveFaultedCore(...)0%14140%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.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.Buffers.Binary;
 6using System.Diagnostics;
 7using System.IO;
 8using System.Net.WebSockets.Compression;
 9using System.Numerics;
 10using System.Runtime.CompilerServices;
 11using System.Runtime.ExceptionServices;
 12using System.Runtime.InteropServices;
 13using System.Security.Cryptography;
 14using System.Text;
 15using System.Text.Unicode;
 16using System.Threading;
 17using System.Threading.Tasks;
 18
 19namespace System.Net.WebSockets
 20{
 21    /// <summary>A managed implementation of a web socket that sends and receives data via a <see cref="Stream"/>.</summ
 22    /// <remarks>
 23    /// Thread-safety:
 24    /// - It's acceptable to call ReceiveAsync and SendAsync in parallel.  One of each may run concurrently.
 25    /// - It's acceptable to have a pending ReceiveAsync while CloseOutputAsync or CloseAsync is called.
 26    /// - Attempting to invoke any other operations in parallel may corrupt the instance.  Attempting to invoke
 27    ///   a send operation while another is in progress or a receive operation while another is in progress will
 28    ///   result in an exception.
 29    /// </remarks>
 30    internal sealed partial class ManagedWebSocket : WebSocket
 31    {
 32        /// <summary>Encoding for the payload of text messages: UTF-8 encoding that throws if invalid bytes are discover
 133        private static readonly UTF8Encoding s_textEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, t
 34
 35        /// <summary>The maximum size in bytes of a message frame header that includes mask bytes.</summary>
 36        internal const int MaxMessageHeaderLength = 14;
 37        /// <summary>The maximum size of a control message payload.</summary>
 38        private const int MaxControlPayloadLength = 125;
 39        /// <summary>Length of the mask XOR'd with the payload data.</summary>
 40        private const int MaskLength = 4;
 41
 42        /// <summary>The stream used to communicate with the remote server.</summary>
 43        private readonly Stream _stream;
 44        /// <summary>
 45        /// true if this is the server-side of the connection; false if it's client.
 46        /// This impacts masking behavior: clients always mask payloads they send and
 47        /// expect to always receive unmasked payloads, whereas servers always send
 48        /// unmasked payloads and expect to always receive masked payloads.
 49        /// </summary>
 50        private readonly bool _isServer;
 51        /// <summary>The agreed upon subprotocol with the server.</summary>
 52        private readonly string? _subprotocol;
 53        /// <summary>Timer used to send periodic pings to the server, at the interval specified</summary>
 54        private readonly Timer? _keepAliveTimer;
 55        /// <summary>Buffer used for reading data from the network.</summary>
 56        private readonly Memory<byte> _receiveBuffer;
 57        /// <summary>
 58        /// Tracks the state of the validity of the UTF-8 encoding of text payloads.  Text may be split across fragments
 59        /// </summary>
 864260        private readonly Utf8MessageState _utf8TextState = new Utf8MessageState();
 61        /// <summary>
 62        /// Mutex used to ensure that calls to SendFrameAsync don't run concurrently.  We don't support multiple concurr
 63        /// but this is needed to support SendAsync concurrently with keep-alive pings and CloseAsync.
 64        /// </summary>
 864265        private readonly AsyncMutex _sendMutex = new AsyncMutex();
 66        /// <summary>
 67        /// Mutex used to ensure that calls to ReceiveAsyncPrivate don't run concurrently.  We don't support multiple co
 68        /// but this is needed to support SendAsync concurrently with keep-alive pings and CloseAsync.
 69        /// </summary>
 864270        private readonly AsyncMutex _receiveMutex = new AsyncMutex();
 71
 72        // We maintain the current WebSocketState in _state.  However, we separately maintain _sentCloseFrame and _recei
 73        // as there isn't a strict ordering between CloseSent and CloseReceived.  If we receive a close frame from the s
 74        // transition to CloseReceived even if we're currently in CloseSent, and if we send a close frame, we need to tr
 75        // CloseSent even if we're currently in CloseReceived.
 76
 77        /// <summary>The current state of the web socket in the protocol.</summary>
 864278        private WebSocketState _state = WebSocketState.Open;
 79        /// <summary>true if Dispose has been called; otherwise, false.</summary>
 80        private bool _disposed;
 81        /// <summary>Whether we've ever sent a close frame.</summary>
 82        private bool _sentCloseFrame;
 83        /// <summary>Whether we've ever received a close frame.</summary>
 84        private bool _receivedCloseFrame;
 85        /// <summary>The reason for the close, as sent by the server, or null if not yet closed.</summary>
 86        private WebSocketCloseStatus? _closeStatus;
 87        /// <summary>A description of the close reason as sent by the server, or null if not yet closed.</summary>
 88        private string? _closeStatusDescription;
 89
 90        /// <summary>
 91        /// The last header received in a ReceiveAsync.  If ReceiveAsync got a header but then
 92        /// returned fewer bytes than was indicated in the header, subsequent ReceiveAsync calls
 93        /// will use the data from the header to construct the subsequent receive results, and
 94        /// the payload length in this header will be decremented to indicate the number of bytes
 95        /// remaining to be received for that header.  As a result, between fragments, the payload
 96        /// length in this header should be 0.
 97        /// </summary>
 864298        private MessageHeader _lastReceiveHeader = new MessageHeader { Opcode = MessageOpcode.Text, Fin = true, Processe
 99        /// <summary>The offset of the next available byte in the _receiveBuffer.</summary>
 100        private int _receiveBufferOffset;
 101        /// <summary>The number of bytes available in the _receiveBuffer.</summary>
 102        private int _receiveBufferCount;
 103        /// <summary>
 104        /// When dealing with partially read fragments of binary/text messages, a mask previously received may still
 105        /// apply, and the first new byte received may not correspond to the 0th position in the mask.  This value is
 106        /// the next offset into the mask that should be applied.
 107        /// </summary>
 108        private int _receivedMaskOffsetOffset;
 109        /// <summary>
 110        /// Temporary send buffer.  This should be released back to the ArrayPool once it's
 111        /// no longer needed for the current send operation.  It is stored as an instance
 112        /// field to minimize needing to pass it around and to avoid it becoming a field on
 113        /// various async state machine objects.
 114        /// </summary>
 115        private byte[]? _sendBuffer;
 116        /// <summary>
 117        /// Whether the last SendAsync had endOfMessage==false. We need to track this so that we
 118        /// can send the subsequent message with a continuation opcode if the last message was a fragment.
 119        /// </summary>
 120        private bool _lastSendWasFragment;
 121        /// <summary>
 122        /// Whether the last SendAsync had <seealso cref="WebSocketMessageFlags.DisableCompression" /> flag set.
 123        /// </summary>
 124        private bool _lastSendHadDisableCompression;
 125
 126        /// <summary>Lock used to protect update and check-and-update operations on _state.</summary>
 56604127        private object StateUpdateLock => _sendMutex;
 128
 129        private readonly WebSocketInflater? _inflater;
 130        private readonly WebSocketDeflater? _deflater;
 131
 132        private readonly KeepAlivePingState? _keepAlivePingState;
 133
 134        /// <summary>Initializes the websocket.</summary>
 135        /// <param name="stream">The connected Stream.</param>
 136        /// <param name="isServer">true if this is the server-side of the connection; false if this is the client-side o
 137        /// <param name="subprotocol">The agreed upon subprotocol for the connection.</param>
 138        /// <param name="keepAliveInterval">The interval to use for keep-alive pings.</param>
 139        /// <param name="keepAliveTimeout">The timeout to use when waiting for keep-alive pong response.</param>
 8642140        internal ManagedWebSocket(Stream stream, bool isServer, string? subprotocol, TimeSpan keepAliveInterval, TimeSpa
 8642141        {
 8642142            Debug.Assert(StateUpdateLock != null, $"Expected {nameof(StateUpdateLock)} to be non-null");
 8642143            Debug.Assert(stream != null, $"Expected non-null {nameof(stream)}");
 8642144            Debug.Assert(stream.CanRead, $"Expected readable {nameof(stream)}");
 8642145            Debug.Assert(stream.CanWrite, $"Expected writeable {nameof(stream)}");
 8642146            Debug.Assert(keepAliveInterval == Timeout.InfiniteTimeSpan || keepAliveInterval >= TimeSpan.Zero, $"Invalid 
 8642147            Debug.Assert(keepAliveTimeout == Timeout.InfiniteTimeSpan || keepAliveTimeout >= TimeSpan.Zero, $"Invalid {n
 148
 8642149            _stream = stream;
 8642150            _isServer = isServer;
 8642151            _subprotocol = subprotocol;
 152
 8642153            if (NetEventSource.Log.IsEnabled())
 0154            {
 0155                NetEventSource.Associate(this, stream);
 0156                NetEventSource.Associate(this, _sendMutex);
 0157                NetEventSource.Associate(this, _receiveMutex);
 0158            }
 159
 160            // Create a buffer just large enough to handle received packet headers (at most 14 bytes) and
 161            // control payloads (at most 125 bytes).  Message payloads are read directly into the buffer
 162            // supplied to ReceiveAsync.
 163            const int ReceiveBufferMinLength = MaxControlPayloadLength;
 8642164            _receiveBuffer = new byte[ReceiveBufferMinLength];
 165
 166            // Now that we're opened, initiate the keep alive timer to send periodic pings.
 167            // We use a weak reference from the timer to the web socket to avoid a cycle
 168            // that could keep the web socket rooted in erroneous cases.
 8642169            if (keepAliveInterval > TimeSpan.Zero)
 0170            {
 0171                long heartBeatIntervalMs = (long)keepAliveInterval.TotalMilliseconds;
 0172                if (keepAliveTimeout > TimeSpan.Zero)
 0173                {
 0174                    _keepAlivePingState = new KeepAlivePingState(keepAliveInterval, keepAliveTimeout, this);
 0175                    heartBeatIntervalMs = _keepAlivePingState.HeartBeatIntervalMs;
 176
 0177                    if (NetEventSource.Log.IsEnabled())
 0178                    {
 0179                        NetEventSource.Associate(this, _keepAlivePingState);
 180
 0181                        NetEventSource.Trace(this,
 0182                            $"Enabling Ping/Pong Keep-Alive strategy: ping delay={_keepAlivePingState.DelayMs}ms, timeou
 0183                    }
 0184                }
 0185                else if (NetEventSource.Log.IsEnabled())
 0186                {
 0187                    NetEventSource.Trace(this, $"Enabling Unsolicited Pong Keep-Alive strategy: heartbeat={heartBeatInte
 0188                }
 189
 0190                _keepAliveTimer = new Timer(static s =>
 0191                {
 0192                    var wr = (WeakReference<ManagedWebSocket>)s!;
 0193                    if (wr.TryGetTarget(out ManagedWebSocket? thisRef))
 0194                    {
 0195                        thisRef.HeartBeat();
 0196                    }
 0197                }, new WeakReference<ManagedWebSocket>(this), heartBeatIntervalMs, heartBeatIntervalMs);
 0198            }
 8642199        }
 200
 201        /// <summary>Initializes the websocket.</summary>
 202        /// <param name="stream">The connected Stream.</param>
 203        /// <param name="options">The options with which the websocket must be created.</param>
 204        internal ManagedWebSocket(Stream stream, WebSocketCreationOptions options)
 8642205            : this(stream, options.IsServer, options.SubProtocol, options.KeepAliveInterval, options.KeepAliveTimeout)
 8642206        {
 8642207            var deflateOptions = options.DangerousDeflateOptions;
 208
 8642209            if (deflateOptions is not null)
 2025210            {
 2025211                if (options.IsServer)
 265212                {
 265213                    _inflater = new WebSocketInflater(deflateOptions.ClientMaxWindowBits, deflateOptions.ClientContextTa
 265214                    _deflater = new WebSocketDeflater(deflateOptions.ServerMaxWindowBits, deflateOptions.ServerContextTa
 265215                }
 216                else
 1760217                {
 1760218                    _inflater = new WebSocketInflater(deflateOptions.ServerMaxWindowBits, deflateOptions.ServerContextTa
 1760219                    _deflater = new WebSocketDeflater(deflateOptions.ClientMaxWindowBits, deflateOptions.ClientContextTa
 1760220                }
 2025221            }
 8642222        }
 223
 224        public override void Dispose()
 8677225        {
 8677226            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 227
 8677228            lock (StateUpdateLock)
 8677229            {
 8677230                DisposeCore();
 8677231            }
 8677232        }
 233
 234        private void DisposeCore()
 9930235        {
 9930236            Debug.Assert(Monitor.IsEntered(StateUpdateLock), $"Expected {nameof(StateUpdateLock)} to be held");
 237
 9930238            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"{nameof(_disposed)}={_disposed}");
 239
 9930240            if (!_disposed)
 8642241            {
 8642242                _disposed = true;
 8642243                _keepAliveTimer?.Dispose();
 8642244                _stream.Dispose();
 245
 8642246                WebSocketState state = _state;
 8642247                if (state < WebSocketState.Aborted)
 1345248                {
 1345249                    _state = WebSocketState.Closed;
 1345250                }
 251
 8642252                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_stat
 253
 8642254                DisposeSafe(_inflater, _receiveMutex);
 8642255                DisposeSafe(_deflater, _sendMutex);
 8642256            }
 9930257        }
 258
 259        private static void DisposeSafe(IDisposable? resource, AsyncMutex mutex)
 17284260        {
 17284261            if (resource is not null)
 4050262            {
 4050263                Task lockTask = mutex.EnterAsync(CancellationToken.None);
 264
 4050265                if (lockTask.IsCompleted)
 2855266                {
 2855267                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(mutex);
 268
 2855269                    resource.Dispose();
 2855270                    mutex.Exit();
 271
 2855272                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(mutex);
 2855273                }
 274                else
 1195275                {
 1195276                    lockTask.GetAwaiter().UnsafeOnCompleted(() =>
 1195277                    {
 1195278                        if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(mutex);
 1195279
 1195280                        resource.Dispose();
 1195281                        mutex.Exit();
 1195282
 1195283                        if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(mutex);
 2390284                    });
 1195285                }
 4050286            }
 17284287        }
 288
 0289        public override WebSocketCloseStatus? CloseStatus => _closeStatus;
 290
 0291        public override string? CloseStatusDescription => _closeStatusDescription;
 292
 11253293        public override WebSocketState State => _state;
 294
 0295        public override string? SubProtocol => _subprotocol;
 296
 297        public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, C
 0298        {
 0299            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 300
 0301            ThrowIfInvalidMessageType(messageType);
 302
 0303            WebSocketValidate.ValidateArraySegment(buffer, nameof(buffer));
 304
 0305            return SendAsync(buffer, messageType, endOfMessage ? WebSocketMessageFlags.EndOfMessage : default, cancellat
 0306        }
 307
 308        public override ValueTask SendAsync(ReadOnlyMemory<byte> buffer, WebSocketMessageType messageType, bool endOfMes
 236309            SendAsync(buffer, messageType, endOfMessage ? WebSocketMessageFlags.EndOfMessage : default, cancellationToke
 310
 311        public override ValueTask SendAsync(ReadOnlyMemory<byte> buffer, WebSocketMessageType messageType, WebSocketMess
 236312        {
 236313            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 314
 236315            ThrowIfInvalidMessageType(messageType);
 316
 317            try
 236318            {
 236319                ThrowIfInvalidState(WebSocketStateHelper.ValidSendStates);
 219320            }
 17321            catch (Exception exc)
 17322            {
 17323                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 17324                return ValueTask.FromException(exc);
 325            }
 326
 219327            bool endOfMessage = (messageFlags & WebSocketMessageFlags.EndOfMessage) != 0;
 219328            bool disableCompression = (messageFlags & WebSocketMessageFlags.DisableCompression) != 0;
 329            MessageOpcode opcode;
 330
 219331            if (_lastSendWasFragment)
 0332            {
 0333                if (_lastSendHadDisableCompression != disableCompression)
 0334                {
 0335                    throw new ArgumentException(SR.net_WebSockets_Argument_MessageFlagsHasDifferentCompressionOptions, n
 336                }
 0337                opcode = MessageOpcode.Continuation;
 0338            }
 339            else
 219340            {
 219341                opcode = messageType == WebSocketMessageType.Binary ? MessageOpcode.Binary : MessageOpcode.Text;
 219342            }
 343
 219344            ValueTask t = SendFrameAsync(opcode, endOfMessage, disableCompression, buffer, cancellationToken);
 219345            _lastSendWasFragment = !endOfMessage;
 219346            _lastSendHadDisableCompression = disableCompression;
 347
 219348            return t;
 236349        }
 350
 351        public override Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellat
 0352        {
 0353            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 354
 0355            WebSocketValidate.ValidateArraySegment(buffer, nameof(buffer));
 356
 357            try
 0358            {
 0359                ThrowIfInvalidState(WebSocketStateHelper.ValidReceiveStates);
 360
 0361                return ReceiveAsyncPrivate<WebSocketReceiveResult>(buffer, cancellationToken).AsTask();
 362            }
 0363            catch (Exception exc)
 0364            {
 0365                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 0366                return Task.FromException<WebSocketReceiveResult>(exc);
 367            }
 0368        }
 369
 370        public override ValueTask<ValueWebSocketReceiveResult> ReceiveAsync(Memory<byte> buffer, CancellationToken cance
 22941371        {
 22941372            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 373
 374            try
 22941375            {
 22941376                ThrowIfInvalidState(WebSocketStateHelper.ValidReceiveStates);
 377
 22893378                return ReceiveAsyncPrivate<ValueWebSocketReceiveResult>(buffer, cancellationToken);
 379            }
 48380            catch (Exception exc)
 48381            {
 48382                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 48383                return ValueTask.FromException<ValueWebSocketReceiveResult>(exc);
 384            }
 22941385        }
 386
 387        public override Task CloseAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken c
 5618388        {
 5618389            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 390
 5618391            WebSocketValidate.ValidateCloseStatus(closeStatus, statusDescription);
 392
 393            try
 5618394            {
 5618395                ThrowIfInvalidState(WebSocketStateHelper.ValidCloseStates);
 5618396            }
 0397            catch (Exception exc)
 0398            {
 0399                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 0400                return Task.FromException(exc);
 401            }
 402
 5618403            return CloseAsyncPrivate(closeStatus, statusDescription, cancellationToken);
 5618404        }
 405
 406        public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationT
 126407        {
 126408            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 409
 126410            WebSocketValidate.ValidateCloseStatus(closeStatus, statusDescription);
 126411            return CloseOutputAsyncCore(closeStatus, statusDescription, enterReceiveMutex: true, cancellationToken: canc
 126412        }
 413
 414        private async Task CloseOutputAsyncCore(WebSocketCloseStatus closeStatus, string? statusDescription, bool enterR
 2718415        {
 2718416            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 417
 2718418            ThrowIfInvalidState(WebSocketStateHelper.ValidCloseOutputStates);
 419
 2684420            await SendCloseFrameAsync(closeStatus, statusDescription, cancellationToken).ConfigureAwait(false);
 421
 422            // Polite EOF wait under the receive mutex; avoids racing the receive loop on the stream.
 2684423            if (!_isServer && _receivedCloseFrame)
 1193424            {
 1193425                await WaitForServerToCloseConnectionAsync(enterReceiveMutex, cancellationToken).ConfigureAwait(false);
 1193426            }
 427
 428            // If we already received a close frame, since we've now also sent one, we're now closed.
 2684429            lock (StateUpdateLock)
 2684430            {
 2684431                Debug.Assert(_sentCloseFrame);
 2684432                if (_receivedCloseFrame)
 1218433                {
 1218434                    DisposeCore();
 1218435                }
 2684436            }
 2684437        }
 438
 439        public override void Abort()
 0440        {
 0441            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 442
 0443            OnAborted();
 0444            Dispose(); // forcibly tear down connection
 0445        }
 446
 447        private void OnAborted()
 8543448        {
 8543449            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 450
 8543451            lock (StateUpdateLock)
 8543452            {
 8543453                OnAbortedCore();
 8543454            }
 8543455        }
 456
 457        private void OnAbortedCore()
 8543458        {
 8543459            Debug.Assert(Monitor.IsEntered(StateUpdateLock), $"Expected {nameof(StateUpdateLock)} to be held");
 460
 8543461            WebSocketState state = _state;
 8543462            if (state is not WebSocketState.Closed and not WebSocketState.Aborted)
 7297463            {
 7297464                _state = state is not WebSocketState.None and not WebSocketState.Connecting ?
 7297465                    WebSocketState.Aborted :
 7297466                    WebSocketState.Closed;
 7297467            }
 468
 8543469            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_state}")
 8543470        }
 471
 472        /// <summary>Sends a websocket frame to the network.</summary>
 473        /// <param name="opcode">The opcode for the message.</param>
 474        /// <param name="endOfMessage">The value of the FIN bit for the message.</param>
 475        /// <param name="disableCompression">Disables compression for the message.</param>
 476        /// <param name="payloadBuffer">The buffer containing the payload data from the message.</param>
 477        /// <param name="cancellationToken">The CancellationToken to use to cancel the websocket.</param>
 478        private ValueTask SendFrameAsync(MessageOpcode opcode, bool endOfMessage, bool disableCompression, ReadOnlyMemor
 37439479        {
 37439480            if (NetEventSource.Log.IsEnabled()) NetEventSource.SendFrameAsyncStarted(this, opcode.ToString(), payloadBuf
 481
 482            // If a cancelable cancellation token was provided, that would require registering with it, which means more
 483            // pass around (the CancellationTokenRegistration), so if it is cancelable, just immediately go to the fallb
 484            // Similarly, it should be rare that there are multiple outstanding calls to SendFrameAsync, but if there ar
 485            // fall back to the fallback path.
 37439486            Task lockTask = _sendMutex.EnterAsync(cancellationToken);
 37439487            return cancellationToken.CanBeCanceled || !lockTask.IsCompletedSuccessfully ?
 37439488                SendFrameFallbackAsync(opcode, endOfMessage, disableCompression, payloadBuffer, lockTask, cancellationTo
 37439489                SendFrameLockAcquiredNonCancelableAsync(opcode, endOfMessage, disableCompression, payloadBuffer);
 37439490        }
 491
 492        /// <summary>Sends a websocket frame to the network. The caller must hold the sending lock.</summary>
 493        /// <param name="opcode">The opcode for the message.</param>
 494        /// <param name="endOfMessage">The value of the FIN bit for the message.</param>
 495        /// <param name="disableCompression">Disables compression for the message.</param>
 496        /// <param name="payloadBuffer">The buffer containing the payload data fro the message.</param>
 497        private ValueTask SendFrameLockAcquiredNonCancelableAsync(MessageOpcode opcode, bool endOfMessage, bool disableC
 3681498        {
 3681499            Debug.Assert(_sendMutex.IsHeld, $"Caller should hold the {nameof(_sendMutex)}");
 500
 3681501            if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_sendMutex);
 502
 503            // If we get here, the cancellation token is not cancelable so we don't have to worry about it,
 504            // and we own the semaphore, so we don't need to asynchronously wait for it.
 3681505            ValueTask writeTask = default;
 3681506            bool releaseSendBufferAndSemaphore = true;
 507            try
 3681508            {
 509                // Write the payload synchronously to the buffer, then write that buffer out to the network.
 3681510                int sendBytes = WriteFrameToSendBuffer(opcode, endOfMessage, disableCompression, payloadBuffer.Span);
 3681511                writeTask = _stream.WriteAsync(new ReadOnlyMemory<byte>(_sendBuffer, 0, sendBytes));
 512
 513                // If the operation happens to complete synchronously (or, more specifically, by
 514                // the time we get from the previous line to here), release the semaphore, return
 515                // the task, and we're done.
 3681516                if (writeTask.IsCompleted)
 3681517                {
 3681518                    writeTask.GetAwaiter().GetResult();
 3681519                    Task flushTask = _stream.FlushAsync();
 3681520                    if (flushTask.IsCompleted)
 290521                    {
 290522                        flushTask.GetAwaiter().GetResult();
 290523                        return ValueTask.CompletedTask;
 524                    }
 525                    else
 3391526                    {
 3391527                        releaseSendBufferAndSemaphore = false;
 3391528                        return WaitForWriteTaskAsync(new ValueTask(flushTask), shouldFlush: false);
 529                    }
 530                }
 531
 532                // Up until this point, if an exception occurred (such as when accessing _stream or when
 533                // calling GetResult), we want to release the semaphore and the send buffer. After this point,
 534                // both need to be held until writeTask completes.
 0535                releaseSendBufferAndSemaphore = false;
 0536            }
 0537            catch (Exception exc)
 0538            {
 0539                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 540
 0541                return ValueTask.FromException(
 0542                    exc is OperationCanceledException ? exc :
 0543                    _state == WebSocketState.Aborted ? CreateOperationCanceledException(exc) :
 0544                    ExceptionDispatchInfo.SetCurrentStackTrace(new WebSocketException(WebSocketError.ConnectionClosedPre
 545            }
 546            finally
 3681547            {
 3681548                if (releaseSendBufferAndSemaphore)
 290549                {
 290550                    ReleaseSendBuffer();
 290551                    _sendMutex.Exit();
 552
 290553                    if (NetEventSource.Log.IsEnabled())
 0554                    {
 0555                        NetEventSource.MutexExited(_sendMutex);
 0556                        NetEventSource.SendFrameAsyncCompleted(this);
 0557                    }
 290558                }
 3681559            }
 560
 0561            return WaitForWriteTaskAsync(writeTask, shouldFlush: true);
 3681562        }
 563
 564        private async ValueTask WaitForWriteTaskAsync(ValueTask writeTask, bool shouldFlush)
 3391565        {
 566            try
 3391567            {
 3391568                await writeTask.ConfigureAwait(false);
 3391569                if (shouldFlush)
 0570                {
 0571                    await _stream.FlushAsync().ConfigureAwait(false);
 0572                }
 3391573            }
 0574            catch (Exception exc)
 0575            {
 0576                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 577
 0578                if (exc is OperationCanceledException)
 0579                {
 0580                    throw;
 581                }
 582
 0583                throw _state == WebSocketState.Aborted ?
 0584                    CreateOperationCanceledException(exc) :
 0585                    new WebSocketException(WebSocketError.ConnectionClosedPrematurely, exc);
 586            }
 587            finally
 3391588            {
 3391589                ReleaseSendBuffer();
 3391590                _sendMutex.Exit();
 591
 3391592                if (NetEventSource.Log.IsEnabled())
 0593                {
 0594                    NetEventSource.MutexExited(_sendMutex);
 0595                    NetEventSource.SendFrameAsyncCompleted(this);
 0596                }
 3391597            }
 3391598        }
 599
 600        private async ValueTask SendFrameFallbackAsync(MessageOpcode opcode, bool endOfMessage, bool disableCompression,
 33758601        {
 602            // Register for cancellation before waiting on the lock so that if cancellation races with
 603            // acquiring the mutex, we still abort the connection, just as we would if cancellation raced
 604            // with the write itself. Without this, a cancellation that fires while we're waiting to enter
 605            // the mutex would propagate out without transitioning the WebSocket to the Aborted state.
 33758606            using (cancellationToken.Register(static s => ((ManagedWebSocket)s!).Abort(), this))
 33758607            {
 608                try
 33758609                {
 33758610                    await lockTask.ConfigureAwait(false);
 33758611                }
 0612                catch (Exception exc)
 0613                {
 0614                    if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 0615                    throw;
 616                }
 617
 33758618                if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_sendMutex);
 619
 620                try
 33758621                {
 33758622                    int sendBytes = WriteFrameToSendBuffer(opcode, endOfMessage, disableCompression, payloadBuffer.Span)
 33758623                    await _stream.WriteAsync(new ReadOnlyMemory<byte>(_sendBuffer, 0, sendBytes), cancellationToken).Con
 33758624                    await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
 33758625                }
 0626                catch (Exception exc)
 0627                {
 0628                    if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 629
 0630                    if (exc is OperationCanceledException)
 0631                    {
 0632                        throw;
 633                    }
 634
 0635                    throw _state == WebSocketState.Aborted ?
 0636                        CreateOperationCanceledException(exc, cancellationToken) :
 0637                        new WebSocketException(WebSocketError.ConnectionClosedPrematurely, exc);
 638                }
 639                finally
 33758640                {
 33758641                    ReleaseSendBuffer();
 33758642                    _sendMutex.Exit();
 643
 33758644                    if (NetEventSource.Log.IsEnabled())
 0645                    {
 0646                        NetEventSource.MutexExited(_sendMutex);
 0647                        NetEventSource.SendFrameAsyncCompleted(this);
 0648                    }
 33758649                }
 33758650            }
 33758651        }
 652
 653        /// <summary>Writes a frame into the send buffer, which can then be sent over the network.</summary>
 654        private int WriteFrameToSendBuffer(MessageOpcode opcode, bool endOfMessage, bool disableCompression, ReadOnlySpa
 37439655        {
 37439656            ThrowIfDisposed();
 657
 37439658            if (_deflater is not null && !disableCompression)
 37659            {
 37660                payloadBuffer = _deflater.Deflate(payloadBuffer, endOfMessage);
 37661            }
 37439662            int payloadLength = payloadBuffer.Length;
 663
 664            // Ensure we have a _sendBuffer
 37439665            AllocateSendBuffer(payloadLength + MaxMessageHeaderLength);
 37439666            Debug.Assert(_sendBuffer != null);
 667
 668            // Write the message header data to the buffer.
 669            int headerLength;
 37439670            int? maskOffset = null;
 37439671            if (_isServer)
 31604672            {
 673                // The server doesn't send a mask, so the mask offset returned by WriteHeader
 674                // is actually the end of the header.
 31604675                headerLength = WriteHeader(opcode, _sendBuffer, payloadBuffer, endOfMessage, useMask: false, compressed:
 31604676            }
 677            else
 5835678            {
 679                // We need to know where the mask starts so that we can use the mask to manipulate the payload data,
 680                // and we need to know the total length for sending it on the wire.
 5835681                maskOffset = WriteHeader(opcode, _sendBuffer, payloadBuffer, endOfMessage, useMask: true, compressed: _d
 5835682                headerLength = maskOffset.GetValueOrDefault() + MaskLength;
 5835683            }
 684
 685            // Write the payload
 37439686            if (payloadBuffer.Length > 0)
 36997687            {
 36997688                payloadBuffer.CopyTo(new Span<byte>(_sendBuffer, headerLength, payloadLength));
 689
 690                // Release the deflater buffer if any, we're not going to need the payloadBuffer anymore.
 36997691                _deflater?.ReleaseBuffer();
 692
 693                // If we added a mask to the header, XOR the payload with the mask.  We do the manipulation in the send 
 694                // changing the data in the caller-supplied payload buffer.
 36997695                if (maskOffset.HasValue)
 5393696                {
 5393697                    ApplyMask(new Span<byte>(_sendBuffer, headerLength, payloadLength), _sendBuffer, maskOffset.Value, 0
 5393698                }
 36997699            }
 700
 701            // Return the number of bytes in the send buffer
 37439702            return headerLength + payloadLength;
 37439703        }
 704
 705        private static int WriteHeader(MessageOpcode opcode, byte[] sendBuffer, ReadOnlySpan<byte> payload, bool endOfMe
 37439706        {
 707            // Client header format:
 708            // 1 bit - FIN - 1 if this is the final fragment in the message (it could be the only fragment), otherwise 0
 709            // 1 bit - RSV1 - Reserved - 0
 710            // 1 bit - RSV2 - Reserved - 0
 711            // 1 bit - RSV3 - Reserved - 0
 712            // 4 bits - Opcode - How to interpret the payload
 713            //     - 0x0 - continuation
 714            //     - 0x1 - text
 715            //     - 0x2 - binary
 716            //     - 0x8 - connection close
 717            //     - 0x9 - ping
 718            //     - 0xA - pong
 719            //     - (0x3 to 0x7, 0xB-0xF - reserved)
 720            // 1 bit - Masked - 1 if the payload is masked, 0 if it's not.  Must be 1 for the client
 721            // 7 bits, 7+16 bits, or 7+64 bits - Payload length
 722            //     - For length 0 through 125, 7 bits storing the length
 723            //     - For lengths 126 through 2^16, 7 bits storing the value 126, followed by 16 bits storing the length
 724            //     - For lengths 2^16+1 through 2^64, 7 bits storing the value 127, followed by 64 bytes storing the len
 725            // 0 or 4 bytes - Mask, if Masked is 1 - random value XOR'd with each 4 bytes of the payload, round-robin
 726            // Length bytes - Payload data
 727
 37439728            Debug.Assert(sendBuffer.Length >= MaxMessageHeaderLength, $"Expected {nameof(sendBuffer)} to be at least {Ma
 729
 37439730            sendBuffer[0] = (byte)opcode; // 4 bits for the opcode
 37439731            if (endOfMessage)
 37255732            {
 37255733                sendBuffer[0] |= 0x80; // 1 bit for FIN
 37255734            }
 37439735            if (compressed && opcode != MessageOpcode.Continuation)
 37736            {
 737                // Per-Message Deflate flag needs to be set only in the first frame
 37738                sendBuffer[0] |= 0b_0100_0000;
 37739            }
 740
 741            // Store the payload length.
 742            int maskOffset;
 37439743            if (payload.Length <= 125)
 37414744            {
 37414745                sendBuffer[1] = (byte)payload.Length;
 37414746                maskOffset = 2; // no additional payload length
 37414747            }
 25748            else if (payload.Length <= ushort.MaxValue)
 25749            {
 25750                sendBuffer[1] = 126;
 25751                BinaryPrimitives.WriteUInt16BigEndian(sendBuffer.AsSpan(2), (ushort)payload.Length);
 25752                maskOffset = 2 + sizeof(ushort); // additional 2 bytes for 16-bit length
 25753            }
 754            else
 0755            {
 0756                sendBuffer[1] = 127;
 0757                BinaryPrimitives.WriteUInt64BigEndian(sendBuffer.AsSpan(2), (ulong)payload.Length);
 0758                maskOffset = 2 + sizeof(ulong); // additional 8 bytes for 64-bit length
 0759            }
 760
 37439761            if (useMask)
 5835762            {
 763                // Generate the mask.
 5835764                sendBuffer[1] |= 0x80;
 5835765                WriteRandomMask(sendBuffer, maskOffset);
 5835766            }
 767
 768            // Return the position of the mask.
 37439769            return maskOffset;
 37439770        }
 771
 772        /// <summary>Writes a 4-byte random mask to the specified buffer at the specified offset.</summary>
 773        /// <param name="buffer">The buffer to which to write the mask.</param>
 774        /// <param name="offset">The offset into the buffer at which to write the mask.</param>
 775        private static void WriteRandomMask(byte[] buffer, int offset) =>
 5835776            RandomNumberGenerator.Fill(buffer.AsSpan(offset, MaskLength));
 777
 778        /// <summary>
 779        /// Receive the next text, binary, continuation, or close message, returning information about it and
 780        /// writing its payload into the supplied buffer.  Other control messages may be consumed and processed
 781        /// as part of this operation, but data about them will not be returned.
 782        /// </summary>
 783        /// <param name="payloadBuffer">The buffer into which payload data should be written.</param>
 784        /// <param name="cancellationToken">The CancellationToken used to cancel the websocket.</param>
 785        /// <returns>Information about the received message.</returns>
 786        [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
 787        [RuntimeAsyncMethodGeneration(false)]
 788        private async ValueTask<TResult> ReceiveAsyncPrivate<TResult>(Memory<byte> payloadBuffer, CancellationToken canc
 344880789        {
 790            // This is a long method.  While splitting it up into pieces would arguably help with readability, doing so 
 791            // also result in more allocations, as each async method that yields ends up with multiple allocations.  The
 792            // of those allocations is amortized across all of the awaits in the method, and since we generally expect a
 793            // operation to require at most a single yield (while waiting for data to arrive), it's more efficient to ha
 794            // everything in the one method.  We do separate out pieces for handling close and ping/pong messages, as we
 795            // those to be much less frequent (e.g. we should only get one close per websocket), and thus we can afford 
 796            // a bit more for readability and maintainability.
 797
 344880798            if (NetEventSource.Log.IsEnabled()) NetEventSource.ReceiveAsyncPrivateStarted(this, payloadBuffer.Length);
 799
 344880800            CancellationTokenRegistration registration = default;
 801            try
 344880802            {
 344880803                registration = cancellationToken.Register(static s => ((ManagedWebSocket)s!).Abort(), this);
 804
 344880805                await _receiveMutex.EnterAsync(cancellationToken).ConfigureAwait(false);
 344880806                if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_receiveMutex);
 807
 808                try
 344880809                {
 344880810                    ThrowIfDisposed();
 811
 375067812                    while (true) // in case we get control frames that should be ignored from the user's perspective
 375067813                    {
 814                        // Get the last received header.  If its payload length is non-zero, that means we previously
 815                        // received the header but were only able to read a part of the fragment, so we should skip
 816                        // reading another header and just proceed to use that same header and read more data associated
 817                        // with it.  If instead its payload length is zero, then we've completed the processing of
 818                        // that message, and we should read the next header.
 375067819                        MessageHeader header = _lastReceiveHeader;
 375067820                        if (header.Processed)
 366294821                        {
 366294822                            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Reading the next frame heade
 823
 366294824                            if (_receiveBufferCount < (_isServer ? MaxMessageHeaderLength : (MaxMessageHeaderLength - Ma
 67478825                            {
 826                                // Make sure we have the first two bytes, which includes the start of the payload length
 67478827                                if (_receiveBufferCount < 2)
 25776828                                {
 25776829                                    if (payloadBuffer.IsEmpty)
 6290830                                    {
 831                                        // The caller has issued a zero-byte read.  The only meaningful reason to do tha
 832                                        // wait for data to be available without actually consuming any of it. If we jus
 833                                        // our internal buffer, the underlying stream might end up renting and/or pinnin
 834                                        // for the duration of the operation, which isn't necessary when we don't actual
 835                                        // consume anything. Instead, we issue a zero-byte read against the underlying s
 836                                        // given that the receive buffer currently stores fewer than the minimum number 
 837                                        // necessary for a header, it's safe to issue a read (if there were at least the
 838                                        // number of bytes available, we could end up issuing a read that would erroneou
 839                                        // for data that would never arrive). Once that read completes, we can proceed w
 840                                        // other reads necessary, and they'll have a reduced chance of pinning the recei
 6290841                                        await _stream.ReadAsync(Memory<byte>.Empty, cancellationToken).ConfigureAwait(fa
 6290842                                    }
 843
 25776844                                    await EnsureBufferContainsAsync(2, cancellationToken).ConfigureAwait(false);
 24846845                                }
 846
 847                                // Then make sure we have the full header based on the payload length.
 848                                // If this is the server, we also need room for the received mask.
 66548849                                long payloadLength = _receiveBuffer.Span[_receiveBufferOffset + 1] & 0x7F;
 66548850                                if (_isServer || payloadLength > 125)
 49815851                                {
 49815852                                    int minNeeded =
 49815853                                        2 +
 49815854                                        (_isServer ? MaskLength : 0) +
 49815855                                        (payloadLength <= 125 ? 0 : payloadLength == 126 ? sizeof(ushort) : sizeof(ulong
 49815856                                    await EnsureBufferContainsAsync(minNeeded, cancellationToken).ConfigureAwait(false);
 49783857                                }
 66516858                            }
 859
 365332860                            string? headerErrorMessage = TryParseMessageHeaderFromReceiveBuffer(out header);
 365332861                            if (headerErrorMessage != null)
 4491862                            {
 4491863                                await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketEr
 0864                            }
 360841865                            _receivedMaskOffsetOffset = 0;
 866
 360841867                            if (NetEventSource.Log.IsEnabled())
 0868                            {
 0869                                NetEventSource.Trace(this, $"Next frame opcode={header.Opcode}, fin={header.Fin}, compre
 0870                            }
 871
 360841872                            if (header.PayloadLength == 0 && header.Compressed)
 672873                            {
 874                                // In the rare case where we receive a compressed message with no payload
 875                                // we need to tell the inflater about it, because the receive code bellow will
 876                                // not try to do anything when PayloadLength == 0.
 672877                                _inflater!.AddBytes(0, endOfMessage: header.Fin);
 672878                            }
 360841879                        }
 880
 881                        // If the header represents a ping or a pong, it's a control message meant
 882                        // to be transparent to the user, so handle it and then loop around to read again.
 883                        // Alternatively, if it's a close message, handle it and exit.
 369614884                        if (header.Opcode == MessageOpcode.Ping || header.Opcode == MessageOpcode.Pong)
 30208885                        {
 30208886                            await HandleReceivedPingPongAsync(header, cancellationToken).ConfigureAwait(false);
 30187887                            continue;
 888                        }
 339406889                        else if (header.Opcode == MessageOpcode.Close)
 1301890                        {
 1301891                            await HandleReceivedCloseAsync(header, cancellationToken).ConfigureAwait(false);
 45892                            return GetReceiveResult<TResult>(0, WebSocketMessageType.Close, true);
 893                        }
 894
 895                        // If this is a continuation, replace the opcode with the one of the message it's continuing
 338105896                        if (header.Opcode == MessageOpcode.Continuation)
 76756897                        {
 76756898                            header.Opcode = _lastReceiveHeader.Opcode;
 76756899                            header.Compressed = _lastReceiveHeader.Compressed;
 76756900                        }
 901
 902                        // The message should now be a binary or text message.  Handle it by reading the payload and ret
 338105903                        Debug.Assert(header.Opcode == MessageOpcode.Binary || header.Opcode == MessageOpcode.Text, $"Une
 904
 905                        // If there's no data to read, return an appropriate result.
 338105906                        if (header.Processed || payloadBuffer.Length == 0)
 79354907                        {
 79354908                            _lastReceiveHeader = header;
 79354909                            return GetReceiveResult<TResult>(
 79354910                                count: 0,
 79354911                                messageType: header.Opcode == MessageOpcode.Text ? WebSocketMessageType.Text : WebSocket
 79354912                                endOfMessage: header.EndOfMessage);
 913                        }
 914
 915                        // Otherwise, read as much of the payload as we can efficiently, and update the header to reflec
 916                        // remains for future reads.  We first need to copy any data that may be lingering in the receiv
 917                        // into the destination; then to minimize ReceiveAsync calls, we want to read as much as we can,
 918                        // only when we've either read the whole message or when we've filled the payload buffer.
 919
 920                        // First copy any data lingering in the receive buffer.
 258751921                        int totalBytesReceived = 0;
 922
 923                        // Only start a new receive if we haven't received the entire frame.
 258751924                        if (header.PayloadLength > 0)
 258205925                        {
 258205926                            if (header.Compressed)
 165927                            {
 165928                                Debug.Assert(_inflater is not null);
 165929                                _inflater.Prepare(header.PayloadLength, payloadBuffer.Length);
 165930                            }
 931
 932                            // Read directly into the appropriate buffer until we've hit a limit.
 258205933                            int limit = (int)Math.Min(header.Compressed ? _inflater!.Span.Length : payloadBuffer.Length,
 934
 258205935                            if (_receiveBufferCount > 0)
 246024936                            {
 246024937                                int receiveBufferBytesToCopy = Math.Min(limit, _receiveBufferCount);
 938
 246024939                                _receiveBuffer.Span.Slice(_receiveBufferOffset, receiveBufferBytesToCopy).CopyTo(
 246024940                                    header.Compressed ? _inflater!.Span : payloadBuffer.Span);
 246024941                                ConsumeFromBuffer(receiveBufferBytesToCopy);
 246024942                                totalBytesReceived += receiveBufferBytesToCopy;
 246024943                            }
 944
 258205945                            if (totalBytesReceived < limit)
 12941946                            {
 12941947                                int bytesToRead = limit - totalBytesReceived;
 12941948                                Memory<byte> readBuffer = header.Compressed ?
 12941949                                    _inflater!.Memory.Slice(totalBytesReceived, bytesToRead) :
 12941950                                    payloadBuffer.Slice(totalBytesReceived, bytesToRead);
 951
 12941952                                int numBytesRead = await _stream.ReadAtLeastAsync(
 12941953                                    readBuffer, bytesToRead, throwOnEndOfStream: false, cancellationToken).ConfigureAwai
 954
 12941955                                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"bytesRead={numBytesRead
 956
 12941957                                if (numBytesRead < bytesToRead)
 561958                                {
 561959                                    ThrowEOFUnexpected();
 0960                                }
 12380961                                _keepAlivePingState?.OnDataReceived();
 12380962                                totalBytesReceived += numBytesRead;
 12380963                            }
 964
 257644965                            if (_isServer)
 249853966                            {
 249853967                                _receivedMaskOffsetOffset = ApplyMask(header.Compressed ?
 249853968                                    _inflater!.Span.Slice(0, totalBytesReceived) :
 249853969                                    payloadBuffer.Span.Slice(0, totalBytesReceived), header.Mask, _receivedMaskOffsetOff
 249853970                            }
 971
 257644972                            header.PayloadLength -= totalBytesReceived;
 973
 257644974                            if (header.Compressed)
 136975                            {
 136976                                _inflater!.AddBytes(totalBytesReceived, endOfMessage: header.Fin && header.PayloadLength
 136977                            }
 257644978                        }
 979
 258190980                        if (header.Compressed)
 682981                        {
 982                            // In case of compression totalBytesReceived should actually represent how much we've
 983                            // inflated, rather than how much we've read from the stream.
 682984                            header.Processed = _inflater!.Inflate(payloadBuffer.Span, out totalBytesReceived) && header.
 653985                        }
 986                        else
 257508987                        {
 988                            // Without compression the frame is processed as soon as we've received everything
 257508989                            header.Processed = header.PayloadLength == 0;
 257508990                        }
 991
 992                        // If this a text message, validate that it contains valid UTF8.
 258161993                        if (header.Opcode == MessageOpcode.Text &&
 258161994                            !TryValidateUtf8(payloadBuffer.Span.Slice(0, totalBytesReceived), header.EndOfMessage, _utf8
 1223995                        {
 1223996                            await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.InvalidPayloadData, WebSocketE
 0997                        }
 998
 256938999                        if (header.Processed)
 2516561000                        {
 2516561001                            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Data frame fully processed")
 2516561002                        }
 1003
 2569381004                        _lastReceiveHeader = header;
 2569381005                        return GetReceiveResult<TResult>(
 2569381006                            totalBytesReceived,
 2569381007                            header.Opcode == MessageOpcode.Text ? WebSocketMessageType.Text : WebSocketMessageType.Binar
 2569381008                            header.EndOfMessage);
 1009                    }
 1010                }
 1011                finally
 3448801012                {
 3448801013                    _receiveMutex.Exit();
 3448801014                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(_receiveMutex);
 3448801015                }
 1016            }
 85431017            catch (Exception exc)
 85431018            {
 85431019                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 1020
 85431021                if (exc is OperationCanceledException)
 01022                {
 01023                    throw;
 1024                }
 1025
 85431026                if (_state == WebSocketState.Aborted)
 01027                {
 01028                    Exception inner = exc;
 01029                    if (_keepAlivePingState?.Exception is not null)
 01030                    {
 1031                        // exception was most likely caused by us aborting the connection due to
 1032                        // keep-alive timeout; but let's surface both just in case
 01033                        inner = ExceptionDispatchInfo.SetCurrentStackTrace(
 01034                            new AggregateException(_keepAlivePingState.Exception, exc));
 01035                    }
 1036
 01037                    throw new OperationCanceledException(nameof(WebSocketState.Aborted), inner);
 1038                }
 85431039                OnAborted();
 1040
 85431041                if (exc is WebSocketException)
 85431042                {
 85431043                    throw;
 1044                }
 1045
 01046                throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, exc);
 1047            }
 1048            finally
 3448801049            {
 3448801050                registration.Dispose();
 3448801051                if (NetEventSource.Log.IsEnabled()) NetEventSource.ReceiveAsyncPrivateCompleted(this);
 3448801052            }
 3363371053        }
 1054
 1055        /// <summary>
 1056        /// Returns either <see cref="ValueWebSocketReceiveResult"/> or <see cref="WebSocketReceiveResult"/>.
 1057        /// </summary>
 1058        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1059        private TResult GetReceiveResult<TResult>(int count, WebSocketMessageType messageType, bool endOfMessage)
 3363371060        {
 3363371061            if (typeof(TResult) == typeof(ValueWebSocketReceiveResult))
 3363371062            {
 1063                // Although it might seem that this will incur boxing of the struct,
 1064                // the JIT is smart enough to figure out it is unncessessary and will emit
 1065                // bytecode that returns the ValueWebSocketReceiveResult directly.
 3363371066                return (TResult)(object)new ValueWebSocketReceiveResult(count, messageType, endOfMessage);
 1067            }
 1068
 01069            return (TResult)(object)new WebSocketReceiveResult(count, messageType, endOfMessage, _closeStatus, _closeSta
 3363371070        }
 1071
 1072        /// <summary>Processes a received close message.</summary>
 1073        /// <param name="header">The message header.</param>
 1074        /// <param name="cancellationToken">The CancellationToken used to cancel the websocket operation.</param>
 1075        /// <returns>The received result message.</returns>
 1076        private async ValueTask HandleReceivedCloseAsync(MessageHeader header, CancellationToken cancellationToken)
 13011077        {
 13011078            lock (StateUpdateLock)
 13011079            {
 13011080                _receivedCloseFrame = true;
 13011081                WebSocketState state = _state;
 13011082                if (_sentCloseFrame && state < WebSocketState.Closed)
 471083                {
 471084                    _state = WebSocketState.Closed;
 471085                }
 12541086                else if (state < WebSocketState.CloseReceived)
 12541087                {
 12541088                    _state = WebSocketState.CloseReceived;
 12541089                }
 1090
 13011091                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_stat
 13011092            }
 1093
 13011094            WebSocketCloseStatus closeStatus = WebSocketCloseStatus.NormalClosure;
 13011095            string closeStatusDescription = string.Empty;
 1096
 1097            // Handle any payload by parsing it into the close status and description.
 13011098            if (header.PayloadLength == 1)
 291099            {
 1100                // The close payload length can be 0 or >= 2, but not 1.
 291101                await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faulted, SR.
 01102            }
 12721103            else if (header.PayloadLength >= 2)
 12301104            {
 12301105                if (_receiveBufferCount < header.PayloadLength)
 11671106                {
 11671107                    await EnsureBufferContainsAsync((int)header.PayloadLength, cancellationToken).ConfigureAwait(false);
 11531108                }
 1109
 12161110                if (_isServer)
 271111                {
 271112                    ApplyMask(_receiveBuffer.Span.Slice(_receiveBufferOffset, (int)header.PayloadLength), header.Mask, 0
 271113                }
 1114
 12161115                closeStatus = (WebSocketCloseStatus)BinaryPrimitives.ReadUInt16BigEndian(_receiveBuffer.Span.Slice(_rece
 12161116                if (!IsValidCloseStatus(closeStatus))
 12011117                {
 12011118                    await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faulted,
 01119                }
 1120
 151121                if (header.PayloadLength > 2)
 141122                {
 1123                    try
 141124                    {
 141125                        closeStatusDescription = s_textEncoding.GetString(_receiveBuffer.Span.Slice(_receiveBufferOffset
 21126                    }
 121127                    catch (DecoderFallbackException exc)
 121128                    {
 121129                        await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faul
 01130                    }
 21131                }
 31132                ConsumeFromBuffer((int)header.PayloadLength);
 31133            }
 1134
 1135            // Store the close status and description onto the instance.
 451136            _closeStatus = closeStatus;
 451137            _closeStatusDescription = closeStatusDescription;
 1138
 451139            if (!_isServer && _sentCloseFrame)
 181140            {
 181141                await WaitForServerToCloseConnectionAsync(enterMutex: false, cancellationToken).ConfigureAwait(false);
 181142            }
 451143        }
 1144
 1145        /// <summary>Issues a read on the stream to wait for EOF, optionally acquiring <see cref="_receiveMutex"/> first
 1146        private async ValueTask WaitForServerToCloseConnectionAsync(bool enterMutex, CancellationToken cancellationToken
 12461147        {
 12461148            bool mutexEntered = false;
 12461149            Task? task = null;
 1150            try
 12461151            {
 12461152                if (enterMutex)
 361153                {
 361154                    await _receiveMutex.EnterAsync(cancellationToken).ConfigureAwait(false);
 361155                    mutexEntered = true;
 361156                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_receiveMutex);
 361157                }
 1158
 12461159                Debug.Assert(_receiveMutex.IsHeld, $"Expected {nameof(_receiveMutex)} to be held");
 1160
 12461161                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 1162
 1163                // Per RFC 6455 7.1.1, try to let the server close the connection.  We give it up to a second.
 1164                // We simply issue a read and don't care what we get back; we could validate that we don't get
 1165                // additional data, but at this point we're about to close the connection and we're just stalling
 1166                // to try to get the server to close first.
 12461167                ValueTask<int> finalReadTask = _stream.ReadAsync(_receiveBuffer, cancellationToken);
 1168
 1169                const int WaitForCloseTimeoutMs = 1_000; // arbitrary amount of time to give the server
 12461170                task = finalReadTask.AsTask();
 1171
 1172#pragma warning disable CA2016 // Token was already provided to the ReadAsync
 12461173                await task.WaitAsync(TimeSpan.FromMilliseconds(WaitForCloseTimeoutMs)).ConfigureAwait(false);
 1174#pragma warning restore CA2016
 12461175            }
 01176            catch
 01177            {
 01178                if (task is not null)
 01179                {
 01180                    LogExceptions(task);
 01181                }
 01182                Abort();
 01183            }
 1184            finally
 12461185            {
 12461186                if (mutexEntered)
 361187                {
 361188                    _receiveMutex.Exit();
 361189                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(_receiveMutex);
 361190                }
 12461191            }
 12461192        }
 1193
 1194        /// <summary>Processes a received ping or pong message.</summary>
 1195        /// <param name="header">The message header.</param>
 1196        /// <param name="cancellationToken">The CancellationToken used to cancel the websocket operation.</param>
 1197        private async ValueTask HandleReceivedPingPongAsync(MessageHeader header, CancellationToken cancellationToken)
 302081198        {
 302081199            Debug.Assert(_receiveMutex.IsHeld, $"Caller should hold the {nameof(_receiveMutex)}");
 1200
 302081201            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 1202
 1203            // Consume any (optional) payload associated with the ping/pong.
 302081204            if (header.PayloadLength > 0 && _receiveBufferCount < header.PayloadLength)
 20451205            {
 20451206                await EnsureBufferContainsAsync((int)header.PayloadLength, cancellationToken).ConfigureAwait(false);
 20241207            }
 1208
 301871209            bool processPing = header.Opcode == MessageOpcode.Ping;
 1210
 301871211            bool processPong = header.Opcode == MessageOpcode.Pong && _keepAlivePingState is not null
 301871212                && header.PayloadLength == KeepAlivePingState.PingPayloadSize;
 1213
 301871214            if ((processPing || processPong) && _isServer)
 285341215            {
 285341216                ApplyMask(_receiveBuffer.Span.Slice(_receiveBufferOffset, (int)header.PayloadLength), header.Mask, 0);
 285341217            }
 1218
 1219            // If this was a ping, send back a pong response.
 301871220            if (processPing)
 289711221            {
 289711222                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Processing incoming Ping");
 1223
 289711224                await SendFrameAsync(
 289711225                    MessageOpcode.Pong,
 289711226                    endOfMessage: true,
 289711227                    disableCompression: true,
 289711228                    _receiveBuffer.Slice(_receiveBufferOffset, (int)header.PayloadLength),
 289711229                    cancellationToken).ConfigureAwait(false);
 289711230            }
 12161231            else if (processPong)
 01232            {
 01233                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Processing incoming Pong");
 1234
 01235                long pongPayload = BinaryPrimitives.ReadInt64BigEndian(_receiveBuffer.Span.Slice(_receiveBufferOffset, (
 01236                lock (StateUpdateLock)
 01237                {
 01238                    _keepAlivePingState!.OnPongResponseReceived(pongPayload);
 01239                }
 01240            }
 1241            else
 12161242            {
 12161243                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Received Unsolicited Pong. Skipping.");
 12161244            }
 1245
 1246            // Regardless of whether it was a ping or pong, we no longer need the payload.
 301871247            if (header.PayloadLength > 0)
 296621248            {
 296621249                ConsumeFromBuffer((int)header.PayloadLength);
 296621250            }
 301871251        }
 1252
 1253        /// <summary>Check whether a close status is valid according to the RFC.</summary>
 1254        /// <param name="closeStatus">The status to validate.</param>
 1255        /// <returns>true if the status if valid; otherwise, false.</returns>
 1256        private static bool IsValidCloseStatus(WebSocketCloseStatus closeStatus)
 12161257        {
 1258            // 0-999: "not used"
 1259            // 1000-2999: reserved for the protocol; we need to check individual codes manually
 1260            // 3000-3999: reserved for use by higher-level code
 1261            // 4000-4999: reserved for private use
 1262            // 5000-: not mentioned in RFC
 1263
 12161264            if (closeStatus < (WebSocketCloseStatus)1000 || closeStatus >= (WebSocketCloseStatus)5000)
 10281265            {
 10281266                return false;
 1267            }
 1268
 1881269            if (closeStatus >= (WebSocketCloseStatus)3000)
 151270            {
 151271                return true;
 1272            }
 1273
 1731274            switch (closeStatus) // check for the 1000-2999 range known codes
 1275            {
 1276                case WebSocketCloseStatus.EndpointUnavailable:
 1277                case WebSocketCloseStatus.InternalServerError:
 1278                case WebSocketCloseStatus.InvalidMessageType:
 1279                case WebSocketCloseStatus.InvalidPayloadData:
 1280                case WebSocketCloseStatus.MandatoryExtension:
 1281                case WebSocketCloseStatus.MessageTooBig:
 1282                case WebSocketCloseStatus.NormalClosure:
 1283                case WebSocketCloseStatus.PolicyViolation:
 1284                case WebSocketCloseStatus.ProtocolError:
 1285                case (WebSocketCloseStatus)1012: // ServiceRestart
 1286                case (WebSocketCloseStatus)1013: // TryAgainLater
 1287                case (WebSocketCloseStatus)1014: // BadGateway
 01288                    return true;
 1289
 1290                default:
 1731291                    return false;
 1292            }
 12161293        }
 1294
 1295        /// <summary>Send a close message to the server and throw an exception, in response to getting bad data from the
 1296        /// <param name="closeStatus">The close status code to use.</param>
 1297        /// <param name="error">The error reason.</param>
 1298        /// <param name="errorMessage">An optional error message to include in the thrown exception.</param>
 1299        /// <param name="innerException">An optional inner exception to include in the thrown exception.</param>
 1300        private async ValueTask CloseWithReceiveErrorAndThrowAsync(
 1301            WebSocketCloseStatus closeStatus, WebSocketError error, string? errorMessage = null, Exception? innerExcepti
 69561302        {
 69561303            Debug.Assert(_receiveMutex.IsHeld, $"Caller should hold the {nameof(_receiveMutex)}");
 1304
 69561305            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, errorMessage);
 1306
 1307            // Caller holds _receiveMutex; don't re-enter it for the EOF wait.
 69561308            if (!_sentCloseFrame)
 25921309            {
 25921310                await CloseOutputAsyncCore(closeStatus, string.Empty, enterReceiveMutex: false, cancellationToken: defau
 25921311            }
 1312
 1313            // Dump our receive buffer; we're in a bad state to do any further processing
 69561314            _receiveBufferCount = 0;
 1315
 1316            // Let the caller know we've failed
 69561317            throw errorMessage != null ?
 69561318                new WebSocketException(error, errorMessage, innerException) :
 69561319                new WebSocketException(error, innerException);
 1320        }
 1321
 1322        /// <summary>Parses a message header from the buffer.  This assumes the header is in the buffer.</summary>
 1323        /// <param name="resultHeader">The read header.</param>
 1324        /// <returns>null if a valid header was read; non-null containing the string error message to use if the header 
 1325        private string? TryParseMessageHeaderFromReceiveBuffer(out MessageHeader resultHeader)
 3653321326        {
 3653321327            Debug.Assert(_receiveBufferCount >= 2, "Expected to at least have the first two bytes of the header.");
 1328
 3653321329            MessageHeader header = default;
 3653321330            Span<byte> receiveBufferSpan = _receiveBuffer.Span;
 1331
 3653321332            header.Fin = (receiveBufferSpan[_receiveBufferOffset] & 0x80) != 0;
 3653321333            bool reservedSet = (receiveBufferSpan[_receiveBufferOffset] & 0b_0011_0000) != 0;
 3653321334            header.Opcode = (MessageOpcode)(receiveBufferSpan[_receiveBufferOffset] & 0xF);
 3653321335            header.Compressed = (receiveBufferSpan[_receiveBufferOffset] & 0b_0100_0000) != 0;
 1336
 3653321337            bool masked = (receiveBufferSpan[_receiveBufferOffset + 1] & 0x80) != 0;
 3653321338            header.PayloadLength = receiveBufferSpan[_receiveBufferOffset + 1] & 0x7F;
 1339
 3653321340            ConsumeFromBuffer(2);
 1341
 1342            // Read the remainder of the payload length, if necessary
 3653321343            if (header.PayloadLength == 126)
 2561344            {
 2561345                Debug.Assert(_receiveBufferCount >= 2, "Expected to have two bytes for the payload length.");
 2561346                header.PayloadLength = BinaryPrimitives.ReadUInt16BigEndian(receiveBufferSpan.Slice(_receiveBufferOffset
 2561347                ConsumeFromBuffer(2);
 2561348            }
 3650761349            else if (header.PayloadLength == 127)
 4311350            {
 4311351                Debug.Assert(_receiveBufferCount >= 8, "Expected to have eight bytes for the payload length.");
 4311352                header.PayloadLength = BinaryPrimitives.ReadInt64BigEndian(receiveBufferSpan.Slice(_receiveBufferOffset)
 4311353                ConsumeFromBuffer(8);
 4311354            }
 1355
 3653321356            if (reservedSet)
 17321357            {
 17321358                resultHeader = default;
 17321359                return SR.net_Websockets_ReservedBitsSet;
 1360            }
 1361
 3636001362            if (header.PayloadLength < 0)
 1861363            {
 1364                // as per RFC, if payload length is a 64-bit integer, the most significant bit MUST be 0
 1365                // frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF; 64 bits in length
 1861366                resultHeader = default;
 1861367                return SR.net_Websockets_InvalidPayloadLength;
 1368            }
 1369
 3634141370            if (header.Compressed && _inflater is null)
 1661371            {
 1661372                resultHeader = default;
 1661373                return SR.net_Websockets_PerMessageCompressedFlagWhenNotEnabled;
 1374            }
 1375
 3632481376            if (masked)
 2786811377            {
 2786811378                if (!_isServer)
 3631379                {
 3631380                    resultHeader = default;
 3631381                    return SR.net_Websockets_ClientReceivedMaskedFrame;
 1382                }
 2783181383                header.Mask = CombineMaskBytes(receiveBufferSpan, _receiveBufferOffset);
 1384
 1385                // Consume the mask bytes
 2783181386                ConsumeFromBuffer(4);
 2783181387            }
 845671388            else if (_isServer)
 7211389            {
 7211390                resultHeader = default;
 7211391                return SR.net_Websockets_ServerReceivedUnmaskedFrame;
 1392            }
 1393
 1394            // Do basic validation of the header
 3621641395            switch (header.Opcode)
 1396            {
 1397                case MessageOpcode.Continuation:
 769391398                    if (_lastReceiveHeader.Fin)
 1781399                    {
 1400                        // Can't continue from a final message
 1781401                        resultHeader = default;
 1781402                        return SR.net_Websockets_ContinuationFromFinalFrame;
 1403                    }
 767611404                    if (header.Compressed)
 51405                    {
 1406                        // Must not mark continuations as compressed
 51407                        resultHeader = default;
 51408                        return SR.net_Websockets_PerMessageCompressedFlagInContinuation;
 1409                    }
 1410
 1411                    // Set the compressed flag from the previous header so the receive procedure can use it
 1412                    // directly without needing to check the previous header in case of continuations.
 767561413                    header.Compressed = _lastReceiveHeader.Compressed;
 767561414                    break;
 1415
 1416                case MessageOpcode.Binary:
 1417                case MessageOpcode.Text:
 2531971418                    if (!_lastReceiveHeader.Fin)
 6211419                    {
 1420                        // Must continue from a non-final message
 6211421                        resultHeader = default;
 6211422                        return SR.net_Websockets_NonContinuationAfterNonFinalFrame;
 1423                    }
 2525761424                    break;
 1425
 1426                case MessageOpcode.Close:
 1427                case MessageOpcode.Ping:
 1428                case MessageOpcode.Pong:
 316691429                    if (header.PayloadLength > MaxControlPayloadLength || !header.Fin)
 1601430                    {
 1431                        // Invalid control messgae
 1601432                        resultHeader = default;
 1601433                        return SR.net_Websockets_InvalidControlMessage;
 1434                    }
 315091435                    break;
 1436
 1437                default:
 1438                    // Unknown opcode
 3591439                    resultHeader = default;
 3591440                    return SR.Format(SR.net_Websockets_UnknownOpcode, header.Opcode);
 1441            }
 1442
 1443            // Return the read header
 3608411444            header.Processed = header.PayloadLength == 0 && !header.Compressed;
 3608411445            resultHeader = header;
 3608411446            return null;
 3653321447        }
 1448
 1449        /// <summary>Send a close message, then receive until we get a close response message.</summary>
 1450        /// <param name="closeStatus">The close status to send.</param>
 1451        /// <param name="statusDescription">The close status description to send.</param>
 1452        /// <param name="cancellationToken">The CancellationToken to use to cancel the websocket.</param>
 1453        private async Task CloseAsyncPrivate(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationTo
 56181454        {
 56181455            if (NetEventSource.Log.IsEnabled()) NetEventSource.CloseAsyncPrivateStarted(this);
 1456            try
 56181457            {
 1458                // Send the close message.  Skip sending a close frame if we're currently in a CloseSent state,
 1459                // for example having just done a CloseOutputAsync.
 56181460                if (!_sentCloseFrame)
 55651461                {
 55651462                    await SendCloseFrameAsync(closeStatus, statusDescription, cancellationToken).ConfigureAwait(false);
 55651463                }
 1464
 1465                // We should now either be in a CloseSent case (because we just sent one), or in a Closed state, in case
 1466                // there was a concurrent receive that ended up handling an immediate close frame response from the serv
 1467                // Of course it could also be Aborted if something happened concurrently to cause things to blow up.
 56181468                Debug.Assert(
 56181469                    State == WebSocketState.CloseSent ||
 56181470                    State == WebSocketState.Closed ||
 56181471                    State == WebSocketState.Aborted,
 56181472                    $"Unexpected state {State}.");
 1473
 1474                // We only need to wait for a received close frame if we are in the CloseSent State. If we are in the Cl
 1475                // State then it means we already received a close frame. If we are in the Aborted State, then we should
 1476                // wait for a close frame as per RFC 6455 Section 7.1.7 "Fail the WebSocket Connection".
 56181477                if (State == WebSocketState.CloseSent)
 56011478                {
 56011479                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Waiting for a close frame");
 1480
 1481                    // Wait until we've received a close response
 56011482                    byte[] closeBuffer = ArrayPool<byte>.Shared.Rent(MaxMessageHeaderLength + MaxControlPayloadLength);
 1483                    try
 56011484                    {
 1485                        // Loop until we've received a close frame.
 3220051486                        while (!_receivedCloseFrame)
 3219871487                        {
 1488                            // Enter the receive lock in order to get a consistent view of whether we've received a clos
 1489                            // frame.  If we haven't, issue a receive.  Since that receive will try to take the same
 1490                            // non-entrant receive lock, we then exit the lock before waiting for the receive to complet
 1491                            // as it will always complete asynchronously and only after we've exited the lock.
 3219871492                            ValueTask<ValueWebSocketReceiveResult> receiveTask = default;
 1493                            try
 3219871494                            {
 3219871495                                await _receiveMutex.EnterAsync(cancellationToken).ConfigureAwait(false);
 3219871496                                if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_receiveMutex);
 1497
 1498                                try
 3219871499                                {
 3219871500                                    if (!_receivedCloseFrame)
 3219871501                                    {
 3219871502                                        receiveTask = ReceiveAsyncPrivate<ValueWebSocketReceiveResult>(closeBuffer, canc
 3219871503                                    }
 3219871504                                }
 1505                                finally
 3219871506                                {
 3219871507                                    _receiveMutex.Exit();
 3219871508                                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(_receiveMutex);
 3219871509                                }
 3219871510                            }
 01511                            catch (OperationCanceledException)
 01512                            {
 1513                                // If waiting on the receive lock was canceled, abort the connection, as we would do
 1514                                // as part of the receive itself.
 01515                                Abort();
 01516                                throw;
 1517                            }
 1518
 1519                            // Wait for the receive to complete if we issued one.
 3219871520                            await receiveTask.ConfigureAwait(false);
 3164041521                        }
 181522                    }
 1523                    finally
 56011524                    {
 56011525                        ArrayPool<byte>.Shared.Return(closeBuffer);
 56011526                    }
 181527                }
 1528
 1529                // Polite EOF wait under the receive mutex; avoids racing the receive loop on the stream.
 351530                if (!_isServer && _receivedCloseFrame)
 351531                {
 351532                    await WaitForServerToCloseConnectionAsync(enterMutex: true, cancellationToken).ConfigureAwait(false)
 351533                }
 1534
 1535                // We're closed.  Close the connection and update the status.
 351536                lock (StateUpdateLock)
 351537                {
 351538                    DisposeCore();
 351539                }
 351540            }
 55831541            catch (Exception exc)
 55831542            {
 55831543                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 55831544                throw;
 1545            }
 1546            finally
 56181547            {
 56181548                if (NetEventSource.Log.IsEnabled()) NetEventSource.CloseAsyncPrivateCompleted(this);
 56181549            }
 351550        }
 1551
 1552
 1553        /// <summary>Sends a close message to the server.</summary>
 1554        /// <param name="closeStatus">The close status to send.</param>
 1555        /// <param name="closeStatusDescription">The close status description to send.</param>
 1556        /// <param name="cancellationToken">The CancellationToken to use to cancel the websocket.</param>
 1557        private async ValueTask SendCloseFrameAsync(WebSocketCloseStatus closeStatus, string? closeStatusDescription, Ca
 82491558        {
 1559            // Close payload is two bytes containing the close status followed by a UTF8-encoding of the status descript
 1560
 82491561            byte[]? buffer = null;
 1562            try
 82491563            {
 82491564                int count = 2;
 82491565                if (string.IsNullOrEmpty(closeStatusDescription))
 52661566                {
 52661567                    buffer = ArrayPool<byte>.Shared.Rent(count);
 52661568                }
 1569                else
 29831570                {
 29831571                    count += s_textEncoding.GetByteCount(closeStatusDescription);
 29831572                    buffer = ArrayPool<byte>.Shared.Rent(count);
 29831573                    int encodedLength = s_textEncoding.GetBytes(closeStatusDescription, 0, closeStatusDescription.Length
 29831574                    Debug.Assert(count - 2 == encodedLength, $"{nameof(s_textEncoding.GetByteCount)} and {nameof(s_textE
 29831575                }
 1576
 82491577                BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)closeStatus);
 1578
 82491579                await SendFrameAsync(MessageOpcode.Close, endOfMessage: true, disableCompression: true, new Memory<byte>
 82491580            }
 1581            finally
 82491582            {
 82491583                if (buffer != null)
 82491584                {
 82491585                    ArrayPool<byte>.Shared.Return(buffer);
 82491586                }
 82491587            }
 1588
 82491589            lock (StateUpdateLock)
 82491590            {
 82491591                _sentCloseFrame = true;
 82491592                WebSocketState state = _state;
 82491593                if (_receivedCloseFrame && state < WebSocketState.Closed)
 12351594                {
 12351595                    _state = WebSocketState.Closed;
 12351596                }
 70141597                else if (state < WebSocketState.CloseSent)
 70141598                {
 70141599                    _state = WebSocketState.CloseSent;
 70141600                }
 1601
 82491602                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_stat
 82491603            }
 82491604        }
 1605
 1606        private void ConsumeFromBuffer(int count)
 9200261607        {
 9200261608            Debug.Assert(count >= 0, $"Expected non-negative {nameof(count)}, got {count}");
 9200261609            Debug.Assert(count <= _receiveBufferCount, $"Trying to consume {count}, which is more than exists {_receiveB
 9200261610            _receiveBufferCount -= count;
 9200261611            _receiveBufferOffset += count;
 9200261612        }
 1613
 1614        [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))]
 1615        [RuntimeAsyncMethodGeneration(false)]
 1616        private async ValueTask EnsureBufferContainsAsync(int minimumRequiredBytes, CancellationToken cancellationToken)
 788031617        {
 788031618            Debug.Assert(minimumRequiredBytes <= _receiveBuffer.Length, $"Requested number of bytes {minimumRequiredByte
 1619
 1620            // If we don't have enough data in the buffer to satisfy the minimum required, read some more.
 788031621            if (_receiveBufferCount < minimumRequiredBytes)
 423601622            {
 1623                // If there's any data in the buffer, shift it down.
 423601624                if (_receiveBufferCount > 0)
 199941625                {
 199941626                    _receiveBuffer.Span.Slice(_receiveBufferOffset, _receiveBufferCount).CopyTo(_receiveBuffer.Span);
 199941627                }
 423601628                _receiveBufferOffset = 0;
 1629
 423601630                int bytesToRead = minimumRequiredBytes - _receiveBufferCount;
 423601631                int numRead = await _stream.ReadAtLeastAsync(
 423601632                    _receiveBuffer.Slice(_receiveBufferCount), bytesToRead, throwOnEndOfStream: false, cancellationToken
 423601633                _receiveBufferCount += numRead;
 1634
 423601635                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"bytesRead={numRead}");
 1636
 423601637                if (numRead < bytesToRead)
 9971638                {
 9971639                    ThrowEOFUnexpected();
 01640                }
 413631641                _keepAlivePingState?.OnDataReceived();
 413631642            }
 778061643        }
 1644
 1645        private void ThrowEOFUnexpected()
 15581646        {
 1647            // The connection closed before we were able to read everything we needed.
 1648            // If it was due to us being disposed, fail with the correct exception.
 1649            // Otherwise, it was due to the connection being closed and it wasn't expected.
 15581650            ThrowIfDisposed();
 15581651            throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely);
 1652        }
 1653
 1654        /// <summary>Gets a send buffer from the pool.</summary>
 1655        private void AllocateSendBuffer(int minLength)
 374391656        {
 374391657            Debug.Assert(_sendBuffer == null); // would only fail if had some catastrophic error previously that prevent
 374391658            _sendBuffer = ArrayPool<byte>.Shared.Rent(minLength);
 374391659        }
 1660
 1661        /// <summary>Releases the send buffer to the pool.</summary>
 1662        private void ReleaseSendBuffer()
 374391663        {
 374391664            Debug.Assert(_sendMutex.IsHeld, $"Caller should hold the {nameof(_sendMutex)}");
 1665
 374391666            if (_sendBuffer is byte[] toReturn)
 374391667            {
 374391668                _sendBuffer = null;
 374391669                ArrayPool<byte>.Shared.Return(toReturn);
 374391670            }
 374391671        }
 1672
 1673        private static int CombineMaskBytes(ReadOnlySpan<byte> buffer, int maskOffset) =>
 2837111674            BitConverter.ToInt32(buffer.Slice(maskOffset));
 1675
 1676        /// <summary>Applies a mask to a portion of a byte array.</summary>
 1677        /// <param name="toMask">The buffer to which the mask should be applied.</param>
 1678        /// <param name="mask">The array containing the mask to apply.</param>
 1679        /// <param name="maskOffset">The offset into <paramref name="mask"/> of the mask to apply of length <see cref="M
 1680        /// <param name="maskOffsetIndex">The next position offset from <paramref name="maskOffset"/> of which by to app
 1681        /// <returns>The updated maskOffsetOffset value.</returns>
 1682        private static int ApplyMask(Span<byte> toMask, byte[] mask, int maskOffset, int maskOffsetIndex)
 53931683        {
 53931684            Debug.Assert(maskOffsetIndex < MaskLength, $"Unexpected {nameof(maskOffsetIndex)}: {maskOffsetIndex}");
 53931685            Debug.Assert(mask.Length >= MaskLength + maskOffset, $"Unexpected inputs: {mask.Length}, {maskOffset}");
 53931686            return ApplyMask(toMask, CombineMaskBytes(mask, maskOffset), maskOffsetIndex);
 53931687        }
 1688
 1689        /// <summary>Applies a mask to a portion of a byte array.</summary>
 1690        /// <param name="toMask">The buffer to which the mask should be applied.</param>
 1691        /// <param name="mask">The four-byte mask, stored as an Int32.</param>
 1692        /// <param name="maskIndex">The index into the mask.</param>
 1693        /// <returns>The next index into the mask to be used for future applications of the mask.</returns>
 1694        private static int ApplyMask(Span<byte> toMask, int mask, int maskIndex)
 2838071695        {
 2838071696            Debug.Assert(maskIndex < sizeof(int));
 1697
 2838071698            if (toMask.Length >= sizeof(int))
 230601699            {
 230601700                int rolledMask = BitConverter.IsLittleEndian ?
 230601701                    (int)BitOperations.RotateRight((uint)mask, maskIndex * 8) :
 230601702                    (int)BitOperations.RotateLeft((uint)mask, maskIndex * 8);
 1703
 1704                // Process Vector<byte>.Count bytes at a time.
 230601705                if (Vector.IsHardwareAccelerated && toMask.Length >= Vector<byte>.Count)
 2891706                {
 2891707                    Vector<byte> maskVector = Vector.AsVectorByte(new Vector<int>(rolledMask));
 1708                    do
 8951709                    {
 8951710                        (new Vector<byte>(toMask) ^ maskVector).CopyTo(toMask);
 8951711                        toMask = toMask.Slice(Vector<byte>.Count);
 8951712                    }
 8951713                    while (toMask.Length >= Vector<byte>.Count);
 2891714                }
 1715
 1716                // Process 4 bytes at a time.
 705231717                while (toMask.Length >= sizeof(int))
 474631718                {
 474631719                    BitConverter.TryWriteBytes(toMask, BitConverter.ToInt32(toMask) ^ rolledMask);
 474631720                    toMask = toMask.Slice(sizeof(int));
 474631721                }
 230601722            }
 1723
 1724            // Process 1 byte at a time, using the mask byte at native memory offset maskIndex.
 11620361725            for (int i = 0; i < toMask.Length; i++)
 2972111726            {
 2972111727                toMask[i] ^= (byte)(mask >> ((BitConverter.IsLittleEndian ? maskIndex : 3 - maskIndex) * 8));
 2972111728                maskIndex = (maskIndex + 1) & 3;
 2972111729            }
 1730
 2838071731            return maskIndex;
 2838071732        }
 1733
 1734        /// <summary>Aborts the websocket and throws an exception if an existing operation is in progress.</summary>
 1735        private void ThrowIfOperationInProgress(bool operationCompleted, [CallerMemberName] string? methodName = null)
 1736        {
 1737            if (!operationCompleted)
 1738            {
 1739                Abort();
 1740                ThrowOperationInProgress(methodName);
 1741            }
 1742        }
 1743
 1744        private static void ThrowOperationInProgress(string? methodName) => throw new InvalidOperationException(SR.Forma
 1745
 1746        /// <summary>Creates an OperationCanceledException instance, using a default message and the specified inner exc
 1747        private static OperationCanceledException CreateOperationCanceledException(Exception innerException, Cancellatio
 01748        {
 01749            return (OperationCanceledException)ExceptionDispatchInfo.SetCurrentStackTrace(new OperationCanceledException
 01750                new OperationCanceledException().Message,
 01751                innerException,
 01752                cancellationToken));
 01753        }
 1754
 3838771755        private void ThrowIfDisposed() => ThrowIfInvalidState(validStates: ManagedWebSocketStates.All);
 1756
 1757        private void ThrowIfInvalidState(ManagedWebSocketStates validStates)
 4153901758        {
 4153901759            bool disposed = _disposed;
 4153901760            WebSocketState state = _state;
 4153901761            Exception? keepAliveException = null;
 1762
 4153901763            if (_keepAlivePingState is not null)
 01764            {
 1765                // we need to take a lock to maintain consistency
 01766                lock (StateUpdateLock)
 01767                {
 01768                    disposed = _disposed;
 01769                    state = _state;
 01770                    keepAliveException = _keepAlivePingState.Exception;
 01771                }
 01772            }
 1773
 4153901774            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"_state={state}, _disposed={disposed}, _keep
 1775
 4153901776            WebSocketStateHelper.ThrowIfInvalidState(state, disposed, keepAliveException, validStates);
 4152911777        }
 1778
 1779        // From https://github.com/aspnet/WebSockets/blob/aa63e27fce2e9202698053620679a9a1059b501e/src/Microsoft.AspNetC
 1780        // Performs a stateful validation of UTF-8 bytes.
 1781        // It checks for valid formatting, overlong encodings, surrogates, and value ranges.
 1782        private static bool TryValidateUtf8(ReadOnlySpan<byte> span, bool endOfMessage, Utf8MessageState state)
 2557711783        {
 1784            // If no prior segment spilled over and this one is the last, we can validate it efficiently as a complete m
 2557711785            if (endOfMessage && !state.SequenceInProgress)
 2483511786            {
 2483511787                return Utf8.IsValid(span);
 1788            }
 1789
 151051790            for (int i = 0; i < span.Length;)
 68361791            {
 1792                // Have we started a character sequence yet?
 68361793                if (!state.SequenceInProgress)
 67061794                {
 1795                    // Skip past ASCII bytes.
 67061796                    int firstNonAscii = span.Slice(i).IndexOfAnyExceptInRange((byte)0, (byte)127);
 67061797                    if (firstNonAscii < 0)
 61881798                    {
 61881799                        break;
 1800                    }
 5181801                    i += firstNonAscii;
 1802
 1803                    // The first byte tells us how many bytes are in the sequence.
 5181804                    state.SequenceInProgress = true;
 5181805                    byte b = span[i];
 5181806                    i++;
 5181807                    Debug.Assert((b & 0x80) != 0, "Should have already skipped past ASCII");
 5181808                    if ((b & 0xC0) == 0x80)
 1331809                    {
 1810                        // Misplaced 10bbbbbb continuation byte. This cannot be the first byte.
 1331811                        return false;
 1812                    }
 3851813                    else if ((b & 0xE0) == 0xC0) // 110bbbbb 10bbbbbb
 1671814                    {
 1671815                        state.AdditionalBytesExpected = 1;
 1671816                        state.CurrentDecodeBits = b & 0x1F;
 1671817                        state.ExpectedValueMin = 0x80;
 1671818                    }
 2181819                    else if ((b & 0xF0) == 0xE0) // 1110bbbb 10bbbbbb 10bbbbbb
 341820                    {
 341821                        state.AdditionalBytesExpected = 2;
 341822                        state.CurrentDecodeBits = b & 0xF;
 341823                        state.ExpectedValueMin = 0x800;
 341824                    }
 1841825                    else if ((b & 0xF8) == 0xF0) // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
 611826                    {
 611827                        state.AdditionalBytesExpected = 3;
 611828                        state.CurrentDecodeBits = b & 0x7;
 611829                        state.ExpectedValueMin = 0x10000;
 611830                    }
 1831                    else // 111110bb & 1111110b & 11111110 && 11111111 are not valid
 1231832                    {
 1231833                        return false;
 1834                    }
 2621835                }
 1836
 5271837                while (state.AdditionalBytesExpected > 0 && i < span.Length)
 2551838                {
 2551839                    byte b = span[i];
 2551840                    if ((b & 0xC0) != 0x80)
 1121841                    {
 1121842                        return false;
 1843                    }
 1844
 1431845                    i++;
 1431846                    state.AdditionalBytesExpected--;
 1847
 1848                    // Each continuation byte carries 6 bits of data 0x10bbbbbb.
 1431849                    state.CurrentDecodeBits = (state.CurrentDecodeBits << 6) | (b & 0x3F);
 1850
 1431851                    if (state.AdditionalBytesExpected == 1 && state.CurrentDecodeBits >= 0x360 && state.CurrentDecodeBit
 11852                    {
 1853                        // This is going to end up in the range of 0xD800-0xDFFF UTF-16 surrogates that are not allowed 
 11854                        return false;
 1855                    }
 1856
 1421857                    if (state.AdditionalBytesExpected == 2 && state.CurrentDecodeBits >= 0x110)
 71858                    {
 1859                        // This is going to be out of the upper Unicode bound 0x10FFFF.
 71860                        return false;
 1861                    }
 1351862                }
 1863
 2721864                if (state.AdditionalBytesExpected == 0)
 1191865                {
 1191866                    state.SequenceInProgress = false;
 1191867                    if (state.CurrentDecodeBits < state.ExpectedValueMin)
 71868                    {
 1869                        // Overlong encoding (e.g. using 2 bytes to encode something that only needed 1).
 71870                        return false;
 1871                    }
 1121872                }
 2651873            }
 1874
 70371875            return !endOfMessage || !state.SequenceInProgress;
 2557711876        }
 1877
 1878        // "Observe" either a ValueTask result, or any exception, logging and ignoring it
 1879        // to prevent the unobserved exception event from being raised.
 1880        private void LogExceptions(ValueTask t)
 01881        {
 01882            if (t.IsCompletedSuccessfully)
 01883            {
 01884                t.GetAwaiter().GetResult();
 01885            }
 1886            else
 01887            {
 01888                LogExceptions(t.AsTask());
 01889            }
 01890        }
 1891
 1892        // "Observe" and log any exception, ignoring it to prevent the unobserved task
 1893        // exception event from being raised.
 1894        private void LogExceptions(Task t)
 01895        {
 01896            if (t.IsCompleted)
 01897            {
 01898                if (t.IsFaulted)
 01899                {
 01900                    LogFaulted(t, this);
 01901                }
 01902            }
 1903            else
 01904            {
 01905                t.ContinueWith(
 01906                    LogFaulted,
 01907                    this,
 01908                    CancellationToken.None,
 01909                    TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
 01910                    TaskScheduler.Default);
 01911            }
 1912
 1913            static void LogFaulted(Task task, object? thisObj)
 01914            {
 01915                Debug.Assert(task.IsFaulted);
 1916
 1917                // accessing exception to observe it regardless of whether the tracing is enabled
 01918                Exception e = task.Exception!.InnerException!;
 1919
 01920                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(thisObj, e);
 01921            }
 01922        }
 1923
 1924        internal static void ThrowIfInvalidMessageType(WebSocketMessageType messageType, [CallerArgumentExpression(nameo
 2361925        {
 2361926            if (messageType is not (WebSocketMessageType.Text or WebSocketMessageType.Binary))
 01927            {
 01928                ThrowInvalidMessageType(messageType, paramName);
 01929            }
 1930
 1931            static void ThrowInvalidMessageType(WebSocketMessageType messageType, string? paramName) =>
 01932                throw new ArgumentException(SR.Format(
 01933                    SR.net_WebSockets_Argument_InvalidMessageType,
 01934                    messageType, nameof(SendAsync), nameof(WebSocketMessageType.Binary), nameof(WebSocketMessageType.Tex
 01935                    paramName);
 2361936        }
 1937
 1938        private sealed class Utf8MessageState
 1939        {
 1940            internal bool SequenceInProgress;
 1941            internal int AdditionalBytesExpected;
 1942            internal int ExpectedValueMin;
 1943            internal int CurrentDecodeBits;
 1944        }
 1945
 1946        private enum MessageOpcode : byte
 1947        {
 1948            Continuation = 0x0,
 1949            Text = 0x1,
 1950            Binary = 0x2,
 1951            Close = 0x8,
 1952            Ping = 0x9,
 1953            Pong = 0xA
 1954        }
 1955
 1956        [StructLayout(LayoutKind.Auto)]
 1957        private struct MessageHeader
 1958        {
 1959            internal MessageOpcode Opcode;
 1960            internal bool Fin;
 1961            internal long PayloadLength;
 1962            internal bool Compressed;
 1963            internal int Mask;
 1964
 1965            /// <summary>
 1966            /// Returns if frame has been received and processed.
 1967            /// </summary>
 20972301968            internal bool Processed { get; set; }
 1969
 1970            /// <summary>
 1971            /// Returns if message has been received and processed.
 1972            /// </summary>
 5920631973            internal bool EndOfMessage => Fin && Processed && PayloadLength == 0;
 1974        }
 1975    }
 1976}
 1977

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.KeepAlive.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.Binary;
 5using System.Diagnostics;
 6using System.Runtime.ExceptionServices;
 7using System.Threading;
 8using System.Threading.Tasks;
 9
 10namespace System.Net.WebSockets
 11{
 12    internal sealed partial class ManagedWebSocket : WebSocket
 13    {
 014        private bool IsUnsolicitedPongKeepAlive => _keepAlivePingState is null;
 15
 16        private void HeartBeat()
 017        {
 018            if (IsUnsolicitedPongKeepAlive)
 019            {
 020                UnsolicitedPongHeartBeat();
 021            }
 22            else
 023            {
 024                KeepAlivePingHeartBeat();
 025            }
 026        }
 27
 28        private void UnsolicitedPongHeartBeat()
 029        {
 030            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 31
 032            LogExceptions(
 033                TrySendKeepAliveFrameAsync(MessageOpcode.Pong));
 034        }
 35
 36        private ValueTask TrySendKeepAliveFrameAsync(MessageOpcode opcode, ReadOnlyMemory<byte> payload = default)
 037        {
 038            Debug.Assert((opcode is MessageOpcode.Pong) || (!IsUnsolicitedPongKeepAlive && opcode is MessageOpcode.Ping)
 39
 040            if (!WebSocketStateHelper.IsValidSendState(_state))
 041            {
 042                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"Cannot send keep-alive frame in {nameof
 43
 44                // we can't send any frames, but no need to throw as we are not observing errors anyway
 045                return ValueTask.CompletedTask;
 46            }
 47
 048            return SendFrameAsync(opcode, endOfMessage: true, disableCompression: true, payload, CancellationToken.None)
 049        }
 50
 51        private void KeepAlivePingHeartBeat()
 052        {
 053            Debug.Assert(_keepAlivePingState != null);
 54
 055            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 56
 057            bool shouldSendPing = false;
 058            long pingPayload = -1;
 59
 60            try
 061            {
 062                lock (StateUpdateLock)
 063                {
 064                    if (_keepAlivePingState.Exception is not null)
 065                    {
 066                        if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"KeepAlive already faulted, skip
 067                        return;
 68                    }
 69
 070                    long now = Environment.TickCount64;
 71
 072                    if (_keepAlivePingState.PingSent)
 073                    {
 074                        if (now > _keepAlivePingState.PingTimeoutTimestamp)
 075                        {
 076                            if (NetEventSource.Log.IsEnabled())
 077                            {
 078                                NetEventSource.Trace(this, $"Keep-alive ping timed out after {_keepAlivePingState.Timeou
 079                            }
 80
 081                            Exception exc = ExceptionDispatchInfo.SetCurrentStackTrace(
 082                                new WebSocketException(WebSocketError.Faulted, SR.net_Websockets_KeepAlivePingTimeout));
 83
 084                            _keepAlivePingState.OnKeepAliveFaultedCore(exc); // we are holding the lock
 085                            return;
 86                        }
 087                    }
 88                    else
 089                    {
 090                        if (now > _keepAlivePingState.NextPingRequestTimestamp)
 091                        {
 092                            _keepAlivePingState.OnNextPingRequestCore(); // we are holding the lock
 093                            shouldSendPing = true;
 094                            pingPayload = _keepAlivePingState.PingPayload;
 095                        }
 096                    }
 097                }
 98
 099                if (shouldSendPing)
 0100                {
 0101                    LogExceptions(
 0102                        SendPingAsync(pingPayload));
 0103                }
 0104            }
 0105            catch (Exception e)
 0106            {
 0107                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, e);
 108
 0109                _keepAlivePingState.OnKeepAliveFaulted(e);
 0110            }
 0111        }
 112
 113        private async ValueTask SendPingAsync(long pingPayload)
 0114        {
 0115            Debug.Assert(_keepAlivePingState != null);
 116
 0117            byte[] pingPayloadBuffer = new byte[sizeof(long)];
 0118            BinaryPrimitives.WriteInt64BigEndian(pingPayloadBuffer, pingPayload);
 119
 0120            await TrySendKeepAliveFrameAsync(MessageOpcode.Ping, pingPayloadBuffer).ConfigureAwait(false);
 121
 0122            if (NetEventSource.Log.IsEnabled()) NetEventSource.KeepAlivePingSent(this, pingPayload);
 0123        }
 124
 125        private sealed class KeepAlivePingState
 126        {
 127            internal const int PingPayloadSize = sizeof(long);
 128            private const int MinIntervalMs = 1;
 129
 130            private readonly ManagedWebSocket _parent;
 0131            private object StateUpdateLock => _parent.StateUpdateLock;
 132
 0133            internal int DelayMs { get; }
 0134            internal int TimeoutMs { get; }
 0135            internal int HeartBeatIntervalMs => Math.Max(Math.Min(DelayMs, TimeoutMs) / 4, MinIntervalMs);
 136
 0137            internal long PingPayload { get; private set; }
 0138            internal bool PingSent { get; private set; }
 0139            internal long PingTimeoutTimestamp { get; private set; }
 0140            internal long NextPingRequestTimestamp { get; private set; }
 0141            internal Exception? Exception { get; private set; }
 142
 0143            public KeepAlivePingState(TimeSpan keepAliveInterval, TimeSpan keepAliveTimeout, ManagedWebSocket parent)
 0144            {
 0145                DelayMs = TimeSpanToMs(keepAliveInterval);
 0146                TimeoutMs = TimeSpanToMs(keepAliveTimeout);
 0147                NextPingRequestTimestamp = Environment.TickCount64 + DelayMs;
 0148                PingTimeoutTimestamp = Timeout.Infinite;
 0149                _parent = parent;
 150
 0151                static int TimeSpanToMs(TimeSpan value) => (int)Math.Clamp((long)value.TotalMilliseconds, MinIntervalMs,
 0152            }
 153
 154            internal void OnDataReceived()
 0155            {
 0156                lock (StateUpdateLock)
 0157                {
 0158                    NextPingRequestTimestamp = Environment.TickCount64 + DelayMs;
 0159                }
 0160            }
 161
 162            internal void OnPongResponseReceived(long pongPayload)
 0163            {
 0164                lock (StateUpdateLock)
 0165                {
 0166                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"pongPayload={pongPayload}");
 167
 0168                    if (!PingSent)
 0169                    {
 0170                        if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"Not waiting for Pong. Skipping.
 0171                        return;
 172                    }
 173
 0174                    if (pongPayload == PingPayload)
 0175                    {
 0176                        if (NetEventSource.Log.IsEnabled()) NetEventSource.PongResponseReceived(this, pongPayload);
 177
 0178                        PingTimeoutTimestamp = long.MaxValue;
 0179                        PingSent = false;
 0180                    }
 181                    else
 0182                    {
 0183                        if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"Expected payload {PingPayload}.
 0184                    }
 0185                }
 0186            }
 187
 188            internal void OnNextPingRequestCore()
 0189            {
 0190                Debug.Assert(Monitor.IsEntered(StateUpdateLock));
 191
 0192                PingSent = true;
 0193                PingTimeoutTimestamp = Environment.TickCount64 + TimeoutMs;
 0194                ++PingPayload;
 0195            }
 196
 197            internal void OnKeepAliveFaulted(Exception exc)
 0198            {
 0199                lock (StateUpdateLock)
 0200                {
 0201                    OnKeepAliveFaultedCore(exc);
 0202                }
 0203            }
 204
 205            internal void OnKeepAliveFaultedCore(Exception exc)
 0206            {
 0207                Debug.Assert(Monitor.IsEntered(StateUpdateLock));
 208
 0209                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceErrorMsg(this, exc);
 210
 0211                if (_parent._disposed)
 0212                {
 0213                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"WebSocket already disposed, skippin
 0214                    return;
 215                }
 216
 0217                if (_parent.State is WebSocketState.Closed)
 0218                {
 0219                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"WebSocket is already closed, skippi
 220                    // We've transferred into the Closed state, but didn't dispose yet
 221                    // This can happen in e.g. HandleReceivedCloseAsync where we first change the state
 222                    // but then still do some operations with the stream.
 223                    // No need to do anything as we've already completed the Closing Handshake
 0224                    return;
 225                }
 226
 0227                if (_parent.State is WebSocketState.Aborted)
 0228                {
 0229                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"WebSocket is already aborted, skipp
 230                    // Something else already aborted the websocket, but didn't dispose it (yet?)?
 231                    // This can happen either
 232                    //  (1) in the Abort() method, e.g. on cancellation, if we interjected between the state
 233                    //      change and the Dispose() call; or
 234                    //  (2) in the catch block of ReceiveAsyncPrivate (which doesn't do the dispose after??).
 235                    //      This most possibly happens if we've hit a premature EOF from the server.
 236                    // Websocket is not usable in the Aborted state anyway, so let's free the resources while we're at i
 0237                    _parent.Dispose();
 0238                    return;
 239                }
 240
 241                // we were the ones who triggered the abort, let's save the exception
 0242                Exception = exc;
 243
 0244                _parent.OnAbortedCore();
 0245                _parent.DisposeCore();
 0246            }
 247        }
 248    }
 249}
 250

Methods/Properties

.cctor()
.ctor(System.IO.Stream,System.Boolean,System.String,System.TimeSpan,System.TimeSpan)
StateUpdateLock()
.ctor(System.IO.Stream,System.Net.WebSockets.WebSocketCreationOptions)
Dispose()
DisposeCore()
DisposeSafe(System.IDisposable,System.Threading.AsyncMutex)
CloseStatus()
CloseStatusDescription()
State()
SubProtocol()
SendAsync(System.ArraySegment`1<System.Byte>,System.Net.WebSockets.WebSocketMessageType,System.Boolean,System.Threading.CancellationToken)
SendAsync(System.ReadOnlyMemory`1<System.Byte>,System.Net.WebSockets.WebSocketMessageType,System.Boolean,System.Threading.CancellationToken)
SendAsync(System.ReadOnlyMemory`1<System.Byte>,System.Net.WebSockets.WebSocketMessageType,System.Net.WebSockets.WebSocketMessageFlags,System.Threading.CancellationToken)
ReceiveAsync(System.ArraySegment`1<System.Byte>,System.Threading.CancellationToken)
ReceiveAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
CloseAsync(System.Net.WebSockets.WebSocketCloseStatus,System.String,System.Threading.CancellationToken)
CloseOutputAsync(System.Net.WebSockets.WebSocketCloseStatus,System.String,System.Threading.CancellationToken)
CloseOutputAsyncCore(System.Net.WebSockets.WebSocketCloseStatus,System.String,System.Boolean,System.Threading.CancellationToken)
Abort()
OnAborted()
OnAbortedCore()
SendFrameAsync(System.Net.WebSockets.ManagedWebSocket/MessageOpcode,System.Boolean,System.Boolean,System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
SendFrameLockAcquiredNonCancelableAsync(System.Net.WebSockets.ManagedWebSocket/MessageOpcode,System.Boolean,System.Boolean,System.ReadOnlyMemory`1<System.Byte>)
WaitForWriteTaskAsync(System.Threading.Tasks.ValueTask,System.Boolean)
SendFrameFallbackAsync(System.Net.WebSockets.ManagedWebSocket/MessageOpcode,System.Boolean,System.Boolean,System.ReadOnlyMemory`1<System.Byte>,System.Threading.Tasks.Task,System.Threading.CancellationToken)
WriteFrameToSendBuffer(System.Net.WebSockets.ManagedWebSocket/MessageOpcode,System.Boolean,System.Boolean,System.ReadOnlySpan`1<System.Byte>)
WriteHeader(System.Net.WebSockets.ManagedWebSocket/MessageOpcode,System.Byte[],System.ReadOnlySpan`1<System.Byte>,System.Boolean,System.Boolean,System.Boolean)
WriteRandomMask(System.Byte[],System.Int32)
ReceiveAsyncPrivate()
GetReceiveResult(System.Int32,System.Net.WebSockets.WebSocketMessageType,System.Boolean)
HandleReceivedCloseAsync(System.Net.WebSockets.ManagedWebSocket/MessageHeader,System.Threading.CancellationToken)
WaitForServerToCloseConnectionAsync(System.Boolean,System.Threading.CancellationToken)
HandleReceivedPingPongAsync(System.Net.WebSockets.ManagedWebSocket/MessageHeader,System.Threading.CancellationToken)
IsValidCloseStatus(System.Net.WebSockets.WebSocketCloseStatus)
CloseWithReceiveErrorAndThrowAsync(System.Net.WebSockets.WebSocketCloseStatus,System.Net.WebSockets.WebSocketError,System.String,System.Exception)
TryParseMessageHeaderFromReceiveBuffer(System.Net.WebSockets.ManagedWebSocket/MessageHeader&)
CloseAsyncPrivate(System.Net.WebSockets.WebSocketCloseStatus,System.String,System.Threading.CancellationToken)
SendCloseFrameAsync(System.Net.WebSockets.WebSocketCloseStatus,System.String,System.Threading.CancellationToken)
ConsumeFromBuffer(System.Int32)
EnsureBufferContainsAsync()
ThrowEOFUnexpected()
AllocateSendBuffer(System.Int32)
ReleaseSendBuffer()
CombineMaskBytes(System.ReadOnlySpan`1<System.Byte>,System.Int32)
ApplyMask(System.Span`1<System.Byte>,System.Byte[],System.Int32,System.Int32)
ApplyMask(System.Span`1<System.Byte>,System.Int32,System.Int32)
CreateOperationCanceledException(System.Exception,System.Threading.CancellationToken)
ThrowIfDisposed()
ThrowIfInvalidState(System.Net.WebSockets.ManagedWebSocketStates)
TryValidateUtf8(System.ReadOnlySpan`1<System.Byte>,System.Boolean,System.Net.WebSockets.ManagedWebSocket/Utf8MessageState)
LogExceptions(System.Threading.Tasks.ValueTask)
LogExceptions(System.Threading.Tasks.Task)
LogFaulted(System.Threading.Tasks.Task,System.Object)
ThrowIfInvalidMessageType(System.Net.WebSockets.WebSocketMessageType,System.String)
ThrowInvalidMessageType(System.Net.WebSockets.WebSocketMessageType,System.String)
Processed()
EndOfMessage()
IsUnsolicitedPongKeepAlive()
HeartBeat()
UnsolicitedPongHeartBeat()
TrySendKeepAliveFrameAsync(System.Net.WebSockets.ManagedWebSocket/MessageOpcode,System.ReadOnlyMemory`1<System.Byte>)
KeepAlivePingHeartBeat()
SendPingAsync(System.Int64)
StateUpdateLock()
DelayMs()
TimeoutMs()
HeartBeatIntervalMs()
PingPayload()
PingSent()
PingTimeoutTimestamp()
NextPingRequestTimestamp()
Exception()
.ctor(System.TimeSpan,System.TimeSpan,System.Net.WebSockets.ManagedWebSocket)
TimeSpanToMs(System.TimeSpan)
OnDataReceived()
OnPongResponseReceived(System.Int64)
OnNextPingRequestCore()
OnKeepAliveFaulted(System.Exception)
OnKeepAliveFaultedCore(System.Exception)