< Summary

Line coverage
71%
Covered lines: 908
Uncovered lines: 361
Coverable lines: 1269
Total lines: 2225
Line coverage: 71.5%
Branch coverage
66%
Covered branches: 405
Total branches: 608
Branch coverage: 66.6%
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(...)70%101092.59%
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.92%11211289.69%
File 1: GetReceiveResult(...)50%2283.33%
File 1: HandleReceivedCloseAsync(...)96.15%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(...)97.5%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)

D:\runner\runtime\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>
 732160        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>
 732165        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>
 732170        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>
 732178        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>
 732198        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>
 47445127        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>
 7321140        internal ManagedWebSocket(Stream stream, bool isServer, string? subprotocol, TimeSpan keepAliveInterval, TimeSpa
 7321141        {
 7321142            Debug.Assert(StateUpdateLock != null, $"Expected {nameof(StateUpdateLock)} to be non-null");
 7321143            Debug.Assert(stream != null, $"Expected non-null {nameof(stream)}");
 7321144            Debug.Assert(stream.CanRead, $"Expected readable {nameof(stream)}");
 7321145            Debug.Assert(stream.CanWrite, $"Expected writeable {nameof(stream)}");
 7321146            Debug.Assert(keepAliveInterval == Timeout.InfiniteTimeSpan || keepAliveInterval >= TimeSpan.Zero, $"Invalid 
 7321147            Debug.Assert(keepAliveTimeout == Timeout.InfiniteTimeSpan || keepAliveTimeout >= TimeSpan.Zero, $"Invalid {n
 148
 7321149            _stream = stream;
 7321150            _isServer = isServer;
 7321151            _subprotocol = subprotocol;
 152
 7321153            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;
 7321164            _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.
 7321169            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            }
 7321199        }
 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)
 7321205            : this(stream, options.IsServer, options.SubProtocol, options.KeepAliveInterval, options.KeepAliveTimeout)
 7321206        {
 7321207            var deflateOptions = options.DangerousDeflateOptions;
 208
 7321209            if (deflateOptions is not null)
 6020210            {
 6020211                if (options.IsServer)
 298212                {
 298213                    _inflater = new WebSocketInflater(deflateOptions.ClientMaxWindowBits, deflateOptions.ClientContextTa
 298214                    _deflater = new WebSocketDeflater(deflateOptions.ServerMaxWindowBits, deflateOptions.ServerContextTa
 298215                }
 216                else
 5722217                {
 5722218                    _inflater = new WebSocketInflater(deflateOptions.ServerMaxWindowBits, deflateOptions.ServerContextTa
 5722219                    _deflater = new WebSocketDeflater(deflateOptions.ClientMaxWindowBits, deflateOptions.ClientContextTa
 5722220                }
 6020221            }
 7321222        }
 223
 224        public override void Dispose()
 7345225        {
 7345226            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 227
 7345228            lock (StateUpdateLock)
 7345229            {
 7345230                DisposeCore();
 7345231            }
 7345232        }
 233
 234        private void DisposeCore()
 8019235        {
 8019236            Debug.Assert(Monitor.IsEntered(StateUpdateLock), $"Expected {nameof(StateUpdateLock)} to be held");
 237
 8019238            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"{nameof(_disposed)}={_disposed}");
 239
 8019240            if (!_disposed)
 7321241            {
 7321242                _disposed = true;
 7321243                _keepAliveTimer?.Dispose();
 7321244                _stream.Dispose();
 245
 7321246                WebSocketState state = _state;
 7321247                if (state < WebSocketState.Aborted)
 756248                {
 756249                    _state = WebSocketState.Closed;
 756250                }
 251
 7321252                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_stat
 253
 7321254                DisposeSafe(_inflater, _receiveMutex);
 7321255                DisposeSafe(_deflater, _sendMutex);
 7321256            }
 8019257        }
 258
 259        private static void DisposeSafe(IDisposable? resource, AsyncMutex mutex)
 14642260        {
 14642261            if (resource is not null)
 12040262            {
 12040263                Task lockTask = mutex.EnterAsync(CancellationToken.None);
 264
 12040265                if (lockTask.IsCompleted)
 11440266                {
 11440267                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(mutex);
 268
 11440269                    resource.Dispose();
 11440270                    mutex.Exit();
 271
 11440272                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(mutex);
 11440273                }
 274                else
 600275                {
 600276                    lockTask.GetAwaiter().UnsafeOnCompleted(() =>
 600277                    {
 600278                        if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(mutex);
 600279
 600280                        resource.Dispose();
 600281                        mutex.Exit();
 600282
 600283                        if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(mutex);
 1200284                    });
 600285                }
 12040286            }
 14642287        }
 288
 0289        public override WebSocketCloseStatus? CloseStatus => _closeStatus;
 290
 0291        public override string? CloseStatusDescription => _closeStatusDescription;
 292
 7576293        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
 1776309            SendAsync(buffer, messageType, endOfMessage ? WebSocketMessageFlags.EndOfMessage : default, cancellationToke
 310
 311        public override ValueTask SendAsync(ReadOnlyMemory<byte> buffer, WebSocketMessageType messageType, WebSocketMess
 1776312        {
 1776313            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 314
 1776315            ThrowIfInvalidMessageType(messageType);
 316
 317            try
 1776318            {
 1776319                ThrowIfInvalidState(WebSocketStateHelper.ValidSendStates);
 1752320            }
 24321            catch (Exception exc)
 24322            {
 24323                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 24324                return ValueTask.FromException(exc);
 325            }
 326
 1752327            bool endOfMessage = (messageFlags & WebSocketMessageFlags.EndOfMessage) != 0;
 1752328            bool disableCompression = (messageFlags & WebSocketMessageFlags.DisableCompression) != 0;
 329            MessageOpcode opcode;
 330
 1752331            if (_lastSendWasFragment)
 135332            {
 135333                if (_lastSendHadDisableCompression != disableCompression)
 0334                {
 0335                    throw new ArgumentException(SR.net_WebSockets_Argument_MessageFlagsHasDifferentCompressionOptions, n
 336                }
 135337                opcode = MessageOpcode.Continuation;
 135338            }
 339            else
 1617340            {
 1617341                opcode = messageType == WebSocketMessageType.Binary ? MessageOpcode.Binary : MessageOpcode.Text;
 1617342            }
 343
 1752344            ValueTask t = SendFrameAsync(opcode, endOfMessage, disableCompression, buffer, cancellationToken);
 1752345            _lastSendWasFragment = !endOfMessage;
 1752346            _lastSendHadDisableCompression = disableCompression;
 347
 1752348            return t;
 1776349        }
 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
 41452371        {
 41452372            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 373
 374            try
 41452375            {
 41452376                ThrowIfInvalidState(WebSocketStateHelper.ValidReceiveStates);
 377
 41377378                return ReceiveAsyncPrivate<ValueWebSocketReceiveResult>(buffer, cancellationToken);
 379            }
 75380            catch (Exception exc)
 75381            {
 75382                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 75383                return ValueTask.FromException<ValueWebSocketReceiveResult>(exc);
 384            }
 41452385        }
 386
 387        public override Task CloseAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken c
 3774388        {
 3774389            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 390
 3774391            WebSocketValidate.ValidateCloseStatus(closeStatus, statusDescription);
 392
 393            try
 3774394            {
 3774395                ThrowIfInvalidState(WebSocketStateHelper.ValidCloseStates);
 3774396            }
 0397            catch (Exception exc)
 0398            {
 0399                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 0400                return Task.FromException(exc);
 401            }
 402
 3774403            return CloseAsyncPrivate(closeStatus, statusDescription, cancellationToken);
 3774404        }
 405
 406        public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationT
 85407        {
 85408            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 409
 85410            WebSocketValidate.ValidateCloseStatus(closeStatus, statusDescription);
 85411            return CloseOutputAsyncCore(closeStatus, statusDescription, enterReceiveMutex: true, cancellationToken: canc
 85412        }
 413
 414        private async Task CloseOutputAsyncCore(WebSocketCloseStatus closeStatus, string? statusDescription, bool enterR
 2938415        {
 2938416            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 417
 2938418            ThrowIfInvalidState(WebSocketStateHelper.ValidCloseOutputStates);
 419
 2922420            await SendCloseFrameAsync(closeStatus, statusDescription, cancellationToken).ConfigureAwait(false);
 421
 422            // Polite EOF wait under the receive mutex; avoids racing the receive loop on the stream.
 2922423            if (!_isServer && _receivedCloseFrame)
 541424            {
 541425                await WaitForServerToCloseConnectionAsync(enterReceiveMutex, cancellationToken).ConfigureAwait(false);
 541426            }
 427
 428            // If we already received a close frame, since we've now also sent one, we're now closed.
 2922429            lock (StateUpdateLock)
 2922430            {
 2922431                Debug.Assert(_sentCloseFrame);
 2922432                if (_receivedCloseFrame)
 643433                {
 643434                    DisposeCore();
 643435                }
 2922436            }
 2922437        }
 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()
 7206448        {
 7206449            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 450
 7206451            lock (StateUpdateLock)
 7206452            {
 7206453                OnAbortedCore();
 7206454            }
 7206455        }
 456
 457        private void OnAbortedCore()
 7206458        {
 7206459            Debug.Assert(Monitor.IsEntered(StateUpdateLock), $"Expected {nameof(StateUpdateLock)} to be held");
 460
 7206461            WebSocketState state = _state;
 7206462            if (state is not WebSocketState.Closed and not WebSocketState.Aborted)
 6565463            {
 6565464                _state = state is not WebSocketState.None and not WebSocketState.Connecting ?
 6565465                    WebSocketState.Aborted :
 6565466                    WebSocketState.Closed;
 6565467            }
 468
 7206469            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_state}")
 7206470        }
 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
 12481479        {
 12481480            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.
 12481486            Task lockTask = _sendMutex.EnterAsync(cancellationToken);
 12481487            return cancellationToken.CanBeCanceled || !lockTask.IsCompletedSuccessfully ?
 12481488                SendFrameFallbackAsync(opcode, endOfMessage, disableCompression, payloadBuffer, lockTask, cancellationTo
 12481489                SendFrameLockAcquiredNonCancelableAsync(opcode, endOfMessage, disableCompression, payloadBuffer);
 12481490        }
 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
 8227498        {
 8227499            Debug.Assert(_sendMutex.IsHeld, $"Caller should hold the {nameof(_sendMutex)}");
 500
 8227501            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.
 8227505            ValueTask writeTask = default;
 8227506            bool releaseSendBufferAndSemaphore = true;
 507            try
 8227508            {
 509                // Write the payload synchronously to the buffer, then write that buffer out to the network.
 8227510                int sendBytes = WriteFrameToSendBuffer(opcode, endOfMessage, disableCompression, payloadBuffer.Span);
 8227511                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.
 8227516                if (writeTask.IsCompleted)
 8227517                {
 8227518                    writeTask.GetAwaiter().GetResult();
 8227519                    Task flushTask = _stream.FlushAsync();
 8227520                    if (flushTask.IsCompleted)
 893521                    {
 893522                        flushTask.GetAwaiter().GetResult();
 893523                        return ValueTask.CompletedTask;
 524                    }
 525                    else
 7334526                    {
 7334527                        releaseSendBufferAndSemaphore = false;
 7334528                        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
 8227547            {
 8227548                if (releaseSendBufferAndSemaphore)
 893549                {
 893550                    ReleaseSendBuffer();
 893551                    _sendMutex.Exit();
 552
 893553                    if (NetEventSource.Log.IsEnabled())
 0554                    {
 0555                        NetEventSource.MutexExited(_sendMutex);
 0556                        NetEventSource.SendFrameAsyncCompleted(this);
 0557                    }
 893558                }
 8227559            }
 560
 0561            return WaitForWriteTaskAsync(writeTask, shouldFlush: true);
 8227562        }
 563
 564        private async ValueTask WaitForWriteTaskAsync(ValueTask writeTask, bool shouldFlush)
 7334565        {
 566            try
 7334567            {
 7334568                await writeTask.ConfigureAwait(false);
 7334569                if (shouldFlush)
 0570                {
 0571                    await _stream.FlushAsync().ConfigureAwait(false);
 0572                }
 7334573            }
 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
 7334588            {
 7334589                ReleaseSendBuffer();
 7334590                _sendMutex.Exit();
 591
 7334592                if (NetEventSource.Log.IsEnabled())
 0593                {
 0594                    NetEventSource.MutexExited(_sendMutex);
 0595                    NetEventSource.SendFrameAsyncCompleted(this);
 0596                }
 7334597            }
 7334598        }
 599
 600        private async ValueTask SendFrameFallbackAsync(MessageOpcode opcode, bool endOfMessage, bool disableCompression,
 4254601        {
 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.
 4254606            using (cancellationToken.Register(static s => ((ManagedWebSocket)s!).Abort(), this))
 4254607            {
 608                try
 4254609                {
 4254610                    await lockTask.ConfigureAwait(false);
 4254611                }
 0612                catch (Exception exc)
 0613                {
 0614                    if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 0615                    throw;
 616                }
 617
 4254618                if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_sendMutex);
 619
 620                try
 4254621                {
 4254622                    int sendBytes = WriteFrameToSendBuffer(opcode, endOfMessage, disableCompression, payloadBuffer.Span)
 4254623                    await _stream.WriteAsync(new ReadOnlyMemory<byte>(_sendBuffer, 0, sendBytes), cancellationToken).Con
 4254624                    await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
 4254625                }
 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
 4254640                {
 4254641                    ReleaseSendBuffer();
 4254642                    _sendMutex.Exit();
 643
 4254644                    if (NetEventSource.Log.IsEnabled())
 0645                    {
 0646                        NetEventSource.MutexExited(_sendMutex);
 0647                        NetEventSource.SendFrameAsyncCompleted(this);
 0648                    }
 4254649                }
 4254650            }
 4254651        }
 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
 12481655        {
 12481656            ThrowIfDisposed();
 657
 12481658            if (_deflater is not null && !disableCompression)
 1483659            {
 1483660                payloadBuffer = _deflater.Deflate(payloadBuffer, endOfMessage);
 1483661            }
 12481662            int payloadLength = payloadBuffer.Length;
 663
 664            // Ensure we have a _sendBuffer
 12481665            AllocateSendBuffer(payloadLength + MaxMessageHeaderLength);
 12481666            Debug.Assert(_sendBuffer != null);
 667
 668            // Write the message header data to the buffer.
 669            int headerLength;
 12481670            int? maskOffset = null;
 12481671            if (_isServer)
 362672            {
 673                // The server doesn't send a mask, so the mask offset returned by WriteHeader
 674                // is actually the end of the header.
 362675                headerLength = WriteHeader(opcode, _sendBuffer, payloadBuffer, endOfMessage, useMask: false, compressed:
 362676            }
 677            else
 12119678            {
 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.
 12119681                maskOffset = WriteHeader(opcode, _sendBuffer, payloadBuffer, endOfMessage, useMask: true, compressed: _d
 12119682                headerLength = maskOffset.GetValueOrDefault() + MaskLength;
 12119683            }
 684
 685            // Write the payload
 12481686            if (payloadBuffer.Length > 0)
 8523687            {
 8523688                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.
 8523691                _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.
 8523695                if (maskOffset.HasValue)
 8169696                {
 8169697                    ApplyMask(new Span<byte>(_sendBuffer, headerLength, payloadLength), _sendBuffer, maskOffset.Value, 0
 8169698                }
 8523699            }
 700
 701            // Return the number of bytes in the send buffer
 12481702            return headerLength + payloadLength;
 12481703        }
 704
 705        private static int WriteHeader(MessageOpcode opcode, byte[] sendBuffer, ReadOnlySpan<byte> payload, bool endOfMe
 12481706        {
 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
 12481728            Debug.Assert(sendBuffer.Length >= MaxMessageHeaderLength, $"Expected {nameof(sendBuffer)} to be at least {Ma
 729
 12481730            sendBuffer[0] = (byte)opcode; // 4 bits for the opcode
 12481731            if (endOfMessage)
 12117732            {
 12117733                sendBuffer[0] |= 0x80; // 1 bit for FIN
 12117734            }
 12481735            if (compressed && opcode != MessageOpcode.Continuation)
 1445736            {
 737                // Per-Message Deflate flag needs to be set only in the first frame
 1445738                sendBuffer[0] |= 0b_0100_0000;
 1445739            }
 740
 741            // Store the payload length.
 742            int maskOffset;
 12481743            if (payload.Length <= 125)
 12474744            {
 12474745                sendBuffer[1] = (byte)payload.Length;
 12474746                maskOffset = 2; // no additional payload length
 12474747            }
 7748            else if (payload.Length <= ushort.MaxValue)
 7749            {
 7750                sendBuffer[1] = 126;
 7751                BinaryPrimitives.WriteUInt16BigEndian(sendBuffer.AsSpan(2), (ushort)payload.Length);
 7752                maskOffset = 2 + sizeof(ushort); // additional 2 bytes for 16-bit length
 7753            }
 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
 12481761            if (useMask)
 12119762            {
 763                // Generate the mask.
 12119764                sendBuffer[1] |= 0x80;
 12119765                WriteRandomMask(sendBuffer, maskOffset);
 12119766            }
 767
 768            // Return the position of the mask.
 12481769            return maskOffset;
 12481770        }
 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) =>
 12119776            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
 117103789        {
 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
 117103798            if (NetEventSource.Log.IsEnabled()) NetEventSource.ReceiveAsyncPrivateStarted(this, payloadBuffer.Length);
 799
 117103800            CancellationTokenRegistration registration = default;
 801            try
 117103802            {
 117103803                registration = cancellationToken.Register(static s => ((ManagedWebSocket)s!).Abort(), this);
 804
 117103805                await _receiveMutex.EnterAsync(cancellationToken).ConfigureAwait(false);
 117103806                if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_receiveMutex);
 807
 808                try
 117103809                {
 117103810                    ThrowIfDisposed();
 811
 121471812                    while (true) // in case we get control frames that should be ignored from the user's perspective
 121471813                    {
 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.
 121471819                        MessageHeader header = _lastReceiveHeader;
 121471820                        if (header.Processed)
 107681821                        {
 107681822                            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Reading the next frame heade
 823
 107681824                            if (_receiveBufferCount < (_isServer ? MaxMessageHeaderLength : (MaxMessageHeaderLength - Ma
 27063825                            {
 826                                // Make sure we have the first two bytes, which includes the start of the payload length
 27063827                                if (_receiveBufferCount < 2)
 10599828                                {
 10599829                                    if (payloadBuffer.IsEmpty)
 5792830                                    {
 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
 5792841                                        await _stream.ReadAsync(Memory<byte>.Empty, cancellationToken).ConfigureAwait(fa
 5792842                                    }
 843
 10599844                                    await EnsureBufferContainsAsync(2, cancellationToken).ConfigureAwait(false);
 9039845                                }
 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.
 25503849                                long payloadLength = _receiveBuffer.Span[_receiveBufferOffset + 1] & 0x7F;
 25503850                                if (_isServer || payloadLength > 125)
 684851                                {
 684852                                    int minNeeded =
 684853                                        2 +
 684854                                        (_isServer ? MaskLength : 0) +
 684855                                        (payloadLength <= 125 ? 0 : payloadLength == 126 ? sizeof(ushort) : sizeof(ulong
 684856                                    await EnsureBufferContainsAsync(minNeeded, cancellationToken).ConfigureAwait(false);
 633857                                }
 25452858                            }
 859
 106070860                            string? headerErrorMessage = TryParseMessageHeaderFromReceiveBuffer(out header);
 106070861                            if (headerErrorMessage != null)
 3325862                            {
 3325863                                await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketEr
 0864                            }
 102745865                            _receivedMaskOffsetOffset = 0;
 866
 102745867                            if (NetEventSource.Log.IsEnabled())
 0868                            {
 0869                                NetEventSource.Trace(this, $"Next frame opcode={header.Opcode}, fin={header.Fin}, compre
 0870                            }
 871
 102745872                            if (header.PayloadLength == 0 && header.Compressed)
 45786873                            {
 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.
 45786877                                _inflater!.AddBytes(0, endOfMessage: header.Fin);
 45786878                            }
 102745879                        }
 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.
 116535884                        if (header.Opcode == MessageOpcode.Ping || header.Opcode == MessageOpcode.Pong)
 4385885                        {
 4385886                            await HandleReceivedPingPongAsync(header, cancellationToken).ConfigureAwait(false);
 4368887                            continue;
 888                        }
 112150889                        else if (header.Opcode == MessageOpcode.Close)
 724890                        {
 724891                            await HandleReceivedCloseAsync(header, cancellationToken).ConfigureAwait(false);
 78892                            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
 111426896                        if (header.Opcode == MessageOpcode.Continuation)
 91428897                        {
 91428898                            header.Opcode = _lastReceiveHeader.Opcode;
 91428899                            header.Compressed = _lastReceiveHeader.Compressed;
 91428900                        }
 901
 902                        // The message should now be a binary or text message.  Handle it by reading the payload and ret
 111426903                        Debug.Assert(header.Opcode == MessageOpcode.Binary || header.Opcode == MessageOpcode.Text, $"Une
 904
 905                        // If there's no data to read, return an appropriate result.
 111426906                        if (header.Processed || payloadBuffer.Length == 0)
 51485907                        {
 51485908                            _lastReceiveHeader = header;
 51485909                            return GetReceiveResult<TResult>(
 51485910                                count: 0,
 51485911                                messageType: header.Opcode == MessageOpcode.Text ? WebSocketMessageType.Text : WebSocket
 51485912                                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.
 59941921                        int totalBytesReceived = 0;
 922
 923                        // Only start a new receive if we haven't received the entire frame.
 59941924                        if (header.PayloadLength > 0)
 17590925                        {
 17590926                            if (header.Compressed)
 5082927                            {
 5082928                                Debug.Assert(_inflater is not null);
 5082929                                _inflater.Prepare(header.PayloadLength, payloadBuffer.Length);
 5082930                            }
 931
 932                            // Read directly into the appropriate buffer until we've hit a limit.
 17590933                            int limit = (int)Math.Min(header.Compressed ? _inflater!.Span.Length : payloadBuffer.Length,
 934
 17590935                            if (_receiveBufferCount > 0)
 11066936                            {
 11066937                                int receiveBufferBytesToCopy = Math.Min(limit, _receiveBufferCount);
 938
 11066939                                _receiveBuffer.Span.Slice(_receiveBufferOffset, receiveBufferBytesToCopy).CopyTo(
 11066940                                    header.Compressed ? _inflater!.Span : payloadBuffer.Span);
 11066941                                ConsumeFromBuffer(receiveBufferBytesToCopy);
 11066942                                totalBytesReceived += receiveBufferBytesToCopy;
 11066943                            }
 944
 17590945                            if (totalBytesReceived < limit)
 7226946                            {
 7226947                                int bytesToRead = limit - totalBytesReceived;
 7226948                                Memory<byte> readBuffer = header.Compressed ?
 7226949                                    _inflater!.Memory.Slice(totalBytesReceived, bytesToRead) :
 7226950                                    payloadBuffer.Slice(totalBytesReceived, bytesToRead);
 951
 7226952                                int numBytesRead = await _stream.ReadAtLeastAsync(
 7226953                                    readBuffer, bytesToRead, throwOnEndOfStream: false, cancellationToken).ConfigureAwai
 954
 7226955                                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"bytesRead={numBytesRead
 956
 7226957                                if (numBytesRead < bytesToRead)
 707958                                {
 707959                                    ThrowEOFUnexpected();
 0960                                }
 6519961                                _keepAlivePingState?.OnDataReceived();
 6519962                                totalBytesReceived += numBytesRead;
 6519963                            }
 964
 16883965                            if (_isServer)
 649966                            {
 649967                                _receivedMaskOffsetOffset = ApplyMask(header.Compressed ?
 649968                                    _inflater!.Span.Slice(0, totalBytesReceived) :
 649969                                    payloadBuffer.Span.Slice(0, totalBytesReceived), header.Mask, _receivedMaskOffsetOff
 649970                            }
 971
 16883972                            header.PayloadLength -= totalBytesReceived;
 973
 16883974                            if (header.Compressed)
 4903975                            {
 4903976                                _inflater!.AddBytes(totalBytesReceived, endOfMessage: header.Fin && header.PayloadLength
 4903977                            }
 16883978                        }
 979
 59234980                        if (header.Compressed)
 47254981                        {
 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.
 47254984                            header.Processed = _inflater!.Inflate(payloadBuffer.Span, out totalBytesReceived) && header.
 46991985                        }
 986                        else
 11980987                        {
 988                            // Without compression the frame is processed as soon as we've received everything
 11980989                            header.Processed = header.PayloadLength == 0;
 11980990                        }
 991
 992                        // If this a text message, validate that it contains valid UTF8.
 58971993                        if (header.Opcode == MessageOpcode.Text &&
 58971994                            !TryValidateUtf8(payloadBuffer.Span.Slice(0, totalBytesReceived), header.EndOfMessage, _utf8
 637995                        {
 637996                            await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.InvalidPayloadData, WebSocketE
 0997                        }
 998
 58334999                        if (header.Processed)
 474351000                        {
 474351001                            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Data frame fully processed")
 474351002                        }
 1003
 583341004                        _lastReceiveHeader = header;
 583341005                        return GetReceiveResult<TResult>(
 583341006                            totalBytesReceived,
 583341007                            header.Opcode == MessageOpcode.Text ? WebSocketMessageType.Text : WebSocketMessageType.Binar
 583341008                            header.EndOfMessage);
 1009                    }
 1010                }
 1011                finally
 1171031012                {
 1171031013                    _receiveMutex.Exit();
 1171031014                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(_receiveMutex);
 1171031015                }
 1016            }
 72061017            catch (Exception exc)
 72061018            {
 72061019                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 1020
 72061021                if (exc is OperationCanceledException)
 01022                {
 01023                    throw;
 1024                }
 1025
 72061026                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                }
 72061039                OnAborted();
 1040
 72061041                if (exc is WebSocketException)
 72061042                {
 72061043                    throw;
 1044                }
 1045
 01046                throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, exc);
 1047            }
 1048            finally
 1171031049            {
 1171031050                registration.Dispose();
 1171031051                if (NetEventSource.Log.IsEnabled()) NetEventSource.ReceiveAsyncPrivateCompleted(this);
 1171031052            }
 1098971053        }
 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)
 1098971060        {
 1098971061            if (typeof(TResult) == typeof(ValueWebSocketReceiveResult))
 1098971062            {
 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.
 1098971066                return (TResult)(object)new ValueWebSocketReceiveResult(count, messageType, endOfMessage);
 1067            }
 1068
 01069            return (TResult)(object)new WebSocketReceiveResult(count, messageType, endOfMessage, _closeStatus, _closeSta
 1098971070        }
 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)
 7241077        {
 7241078            lock (StateUpdateLock)
 7241079            {
 7241080                _receivedCloseFrame = true;
 7241081                WebSocketState state = _state;
 7241082                if (_sentCloseFrame && state < WebSocketState.Closed)
 41083                {
 41084                    _state = WebSocketState.Closed;
 41085                }
 7201086                else if (state < WebSocketState.CloseReceived)
 7201087                {
 7201088                    _state = WebSocketState.CloseReceived;
 7201089                }
 1090
 7241091                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_stat
 7241092            }
 1093
 7241094            WebSocketCloseStatus closeStatus = WebSocketCloseStatus.NormalClosure;
 7241095            string closeStatusDescription = string.Empty;
 1096
 1097            // Handle any payload by parsing it into the close status and description.
 7241098            if (header.PayloadLength == 1)
 511099            {
 1100                // The close payload length can be 0 or >= 2, but not 1.
 511101                await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faulted, SR.
 01102            }
 6731103            else if (header.PayloadLength >= 2)
 6051104            {
 6051105                if (_receiveBufferCount < header.PayloadLength)
 5621106                {
 5621107                    await EnsureBufferContainsAsync((int)header.PayloadLength, cancellationToken).ConfigureAwait(false);
 5571108                }
 1109
 6001110                if (_isServer)
 661111                {
 661112                    ApplyMask(_receiveBuffer.Span.Slice(_receiveBufferOffset, (int)header.PayloadLength), header.Mask, 0
 661113                }
 1114
 6001115                closeStatus = (WebSocketCloseStatus)BinaryPrimitives.ReadUInt16BigEndian(_receiveBuffer.Span.Slice(_rece
 6001116                if (!IsValidCloseStatus(closeStatus))
 5861117                {
 5861118                    await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faulted,
 01119                }
 1120
 141121                if (header.PayloadLength > 2)
 61122                {
 1123                    try
 61124                    {
 61125                        closeStatusDescription = s_textEncoding.GetString(_receiveBuffer.Span.Slice(_receiveBufferOffset
 21126                    }
 41127                    catch (DecoderFallbackException exc)
 41128                    {
 41129                        await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faul
 01130                    }
 21131                }
 101132                ConsumeFromBuffer((int)header.PayloadLength);
 101133            }
 1134
 1135            // Store the close status and description onto the instance.
 781136            _closeStatus = closeStatus;
 781137            _closeStatusDescription = closeStatusDescription;
 1138
 781139            if (!_isServer && _sentCloseFrame)
 31140            {
 31141                await WaitForServerToCloseConnectionAsync(enterMutex: false, cancellationToken).ConfigureAwait(false);
 31142            }
 781143        }
 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
 5751147        {
 5751148            bool mutexEntered = false;
 5751149            Task? task = null;
 1150            try
 5751151            {
 5751152                if (enterMutex)
 341153                {
 341154                    await _receiveMutex.EnterAsync(cancellationToken).ConfigureAwait(false);
 341155                    mutexEntered = true;
 341156                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_receiveMutex);
 341157                }
 1158
 5751159                Debug.Assert(_receiveMutex.IsHeld, $"Expected {nameof(_receiveMutex)} to be held");
 1160
 5751161                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.
 5751167                ValueTask<int> finalReadTask = _stream.ReadAsync(_receiveBuffer, cancellationToken);
 1168
 1169                const int WaitForCloseTimeoutMs = 1_000; // arbitrary amount of time to give the server
 5751170                task = finalReadTask.AsTask();
 1171
 1172#pragma warning disable CA2016 // Token was already provided to the ReadAsync
 5751173                await task.WaitAsync(TimeSpan.FromMilliseconds(WaitForCloseTimeoutMs)).ConfigureAwait(false);
 1174#pragma warning restore CA2016
 5751175            }
 01176            catch
 01177            {
 01178                if (task is not null)
 01179                {
 01180                    LogExceptions(task);
 01181                }
 01182                Abort();
 01183            }
 1184            finally
 5751185            {
 5751186                if (mutexEntered)
 341187                {
 341188                    _receiveMutex.Exit();
 341189                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(_receiveMutex);
 341190                }
 5751191            }
 5751192        }
 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)
 43851198        {
 43851199            Debug.Assert(_receiveMutex.IsHeld, $"Caller should hold the {nameof(_receiveMutex)}");
 1200
 43851201            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 1202
 1203            // Consume any (optional) payload associated with the ping/pong.
 43851204            if (header.PayloadLength > 0 && _receiveBufferCount < header.PayloadLength)
 1991205            {
 1991206                await EnsureBufferContainsAsync((int)header.PayloadLength, cancellationToken).ConfigureAwait(false);
 1821207            }
 1208
 43681209            bool processPing = header.Opcode == MessageOpcode.Ping;
 1210
 43681211            bool processPong = header.Opcode == MessageOpcode.Pong && _keepAlivePingState is not null
 43681212                && header.PayloadLength == KeepAlivePingState.PingPayloadSize;
 1213
 43681214            if ((processPing || processPong) && _isServer)
 81215            {
 81216                ApplyMask(_receiveBuffer.Span.Slice(_receiveBufferOffset, (int)header.PayloadLength), header.Mask, 0);
 81217            }
 1218
 1219            // If this was a ping, send back a pong response.
 43681220            if (processPing)
 40581221            {
 40581222                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Processing incoming Ping");
 1223
 40581224                await SendFrameAsync(
 40581225                    MessageOpcode.Pong,
 40581226                    endOfMessage: true,
 40581227                    disableCompression: true,
 40581228                    _receiveBuffer.Slice(_receiveBufferOffset, (int)header.PayloadLength),
 40581229                    cancellationToken).ConfigureAwait(false);
 40581230            }
 3101231            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
 3101242            {
 3101243                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Received Unsolicited Pong. Skipping.");
 3101244            }
 1245
 1246            // Regardless of whether it was a ping or pong, we no longer need the payload.
 43681247            if (header.PayloadLength > 0)
 3471248            {
 3471249                ConsumeFromBuffer((int)header.PayloadLength);
 3471250            }
 43681251        }
 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)
 6001257        {
 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
 6001264            if (closeStatus < (WebSocketCloseStatus)1000 || closeStatus >= (WebSocketCloseStatus)5000)
 5781265            {
 5781266                return false;
 1267            }
 1268
 221269            if (closeStatus >= (WebSocketCloseStatus)3000)
 141270            {
 141271                return true;
 1272            }
 1273
 81274            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:
 81291                    return false;
 1292            }
 6001293        }
 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
 46031302        {
 46031303            Debug.Assert(_receiveMutex.IsHeld, $"Caller should hold the {nameof(_receiveMutex)}");
 1304
 46031305            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, errorMessage);
 1306
 1307            // Caller holds _receiveMutex; don't re-enter it for the EOF wait.
 46031308            if (!_sentCloseFrame)
 28531309            {
 28531310                await CloseOutputAsyncCore(closeStatus, string.Empty, enterReceiveMutex: false, cancellationToken: defau
 28531311            }
 1312
 1313            // Dump our receive buffer; we're in a bad state to do any further processing
 46031314            _receiveBufferCount = 0;
 1315
 1316            // Let the caller know we've failed
 46031317            throw errorMessage != null ?
 46031318                new WebSocketException(error, errorMessage, innerException) :
 46031319                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)
 1060701326        {
 1060701327            Debug.Assert(_receiveBufferCount >= 2, "Expected to at least have the first two bytes of the header.");
 1328
 1060701329            MessageHeader header = default;
 1060701330            Span<byte> receiveBufferSpan = _receiveBuffer.Span;
 1331
 1060701332            header.Fin = (receiveBufferSpan[_receiveBufferOffset] & 0x80) != 0;
 1060701333            bool reservedSet = (receiveBufferSpan[_receiveBufferOffset] & 0b_0011_0000) != 0;
 1060701334            header.Opcode = (MessageOpcode)(receiveBufferSpan[_receiveBufferOffset] & 0xF);
 1060701335            header.Compressed = (receiveBufferSpan[_receiveBufferOffset] & 0b_0100_0000) != 0;
 1336
 1060701337            bool masked = (receiveBufferSpan[_receiveBufferOffset + 1] & 0x80) != 0;
 1060701338            header.PayloadLength = receiveBufferSpan[_receiveBufferOffset + 1] & 0x7F;
 1339
 1060701340            ConsumeFromBuffer(2);
 1341
 1342            // Read the remainder of the payload length, if necessary
 1060701343            if (header.PayloadLength == 126)
 481344            {
 481345                Debug.Assert(_receiveBufferCount >= 2, "Expected to have two bytes for the payload length.");
 481346                header.PayloadLength = BinaryPrimitives.ReadUInt16BigEndian(receiveBufferSpan.Slice(_receiveBufferOffset
 481347                ConsumeFromBuffer(2);
 481348            }
 1060221349            else if (header.PayloadLength == 127)
 2171350            {
 2171351                Debug.Assert(_receiveBufferCount >= 8, "Expected to have eight bytes for the payload length.");
 2171352                header.PayloadLength = BinaryPrimitives.ReadInt64BigEndian(receiveBufferSpan.Slice(_receiveBufferOffset)
 2171353                ConsumeFromBuffer(8);
 2171354            }
 1355
 1060701356            if (reservedSet)
 10371357            {
 10371358                resultHeader = default;
 10371359                return SR.net_Websockets_ReservedBitsSet;
 1360            }
 1361
 1050331362            if (header.PayloadLength < 0)
 861363            {
 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
 861366                resultHeader = default;
 861367                return SR.net_Websockets_InvalidPayloadLength;
 1368            }
 1369
 1049471370            if (header.Compressed && _inflater is null)
 231371            {
 231372                resultHeader = default;
 231373                return SR.net_Websockets_PerMessageCompressedFlagWhenNotEnabled;
 1374            }
 1375
 1049241376            if (masked)
 9161377            {
 9161378                if (!_isServer)
 3811379                {
 3811380                    resultHeader = default;
 3811381                    return SR.net_Websockets_ClientReceivedMaskedFrame;
 1382                }
 5351383                header.Mask = CombineMaskBytes(receiveBufferSpan, _receiveBufferOffset);
 1384
 1385                // Consume the mask bytes
 5351386                ConsumeFromBuffer(4);
 5351387            }
 1040081388            else if (_isServer)
 881389            {
 881390                resultHeader = default;
 881391                return SR.net_Websockets_ServerReceivedUnmaskedFrame;
 1392            }
 1393
 1394            // Do basic validation of the header
 1044551395            switch (header.Opcode)
 1396            {
 1397                case MessageOpcode.Continuation:
 917921398                    if (_lastReceiveHeader.Fin)
 2361399                    {
 1400                        // Can't continue from a final message
 2361401                        resultHeader = default;
 2361402                        return SR.net_Websockets_ContinuationFromFinalFrame;
 1403                    }
 915561404                    if (header.Compressed)
 1281405                    {
 1406                        // Must not mark continuations as compressed
 1281407                        resultHeader = default;
 1281408                        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.
 914281413                    header.Compressed = _lastReceiveHeader.Compressed;
 914281414                    break;
 1415
 1416                case MessageOpcode.Binary:
 1417                case MessageOpcode.Text:
 65661418                    if (!_lastReceiveHeader.Fin)
 3581419                    {
 1420                        // Must continue from a non-final message
 3581421                        resultHeader = default;
 3581422                        return SR.net_Websockets_NonContinuationAfterNonFinalFrame;
 1423                    }
 62081424                    break;
 1425
 1426                case MessageOpcode.Close:
 1427                case MessageOpcode.Ping:
 1428                case MessageOpcode.Pong:
 52891429                    if (header.PayloadLength > MaxControlPayloadLength || !header.Fin)
 1801430                    {
 1431                        // Invalid control messgae
 1801432                        resultHeader = default;
 1801433                        return SR.net_Websockets_InvalidControlMessage;
 1434                    }
 51091435                    break;
 1436
 1437                default:
 1438                    // Unknown opcode
 8081439                    resultHeader = default;
 8081440                    return SR.Format(SR.net_Websockets_UnknownOpcode, header.Opcode);
 1441            }
 1442
 1443            // Return the read header
 1027451444            header.Processed = header.PayloadLength == 0 && !header.Compressed;
 1027451445            resultHeader = header;
 1027451446            return null;
 1060701447        }
 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
 37741454        {
 37741455            if (NetEventSource.Log.IsEnabled()) NetEventSource.CloseAsyncPrivateStarted(this);
 1456            try
 37741457            {
 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.
 37741460                if (!_sentCloseFrame)
 37491461                {
 37491462                    await SendCloseFrameAsync(closeStatus, statusDescription, cancellationToken).ConfigureAwait(false);
 37491463                }
 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.
 37741468                Debug.Assert(
 37741469                    State == WebSocketState.CloseSent ||
 37741470                    State == WebSocketState.Closed ||
 37741471                    State == WebSocketState.Aborted,
 37741472                    $"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".
 37741477                if (State == WebSocketState.CloseSent)
 37461478                {
 37461479                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Waiting for a close frame");
 1480
 1481                    // Wait until we've received a close response
 37461482                    byte[] closeBuffer = ArrayPool<byte>.Shared.Rent(MaxMessageHeaderLength + MaxControlPayloadLength);
 1483                    try
 37461484                    {
 1485                        // Loop until we've received a close frame.
 757291486                        while (!_receivedCloseFrame)
 757261487                        {
 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.
 757261492                            ValueTask<ValueWebSocketReceiveResult> receiveTask = default;
 1493                            try
 757261494                            {
 757261495                                await _receiveMutex.EnterAsync(cancellationToken).ConfigureAwait(false);
 757261496                                if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_receiveMutex);
 1497
 1498                                try
 757261499                                {
 757261500                                    if (!_receivedCloseFrame)
 757261501                                    {
 757261502                                        receiveTask = ReceiveAsyncPrivate<ValueWebSocketReceiveResult>(closeBuffer, canc
 757261503                                    }
 757261504                                }
 1505                                finally
 757261506                                {
 757261507                                    _receiveMutex.Exit();
 757261508                                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(_receiveMutex);
 757261509                                }
 757261510                            }
 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.
 757261520                            await receiveTask.ConfigureAwait(false);
 719831521                        }
 31522                    }
 1523                    finally
 37461524                    {
 37461525                        ArrayPool<byte>.Shared.Return(closeBuffer);
 37461526                    }
 31527                }
 1528
 1529                // Polite EOF wait under the receive mutex; avoids racing the receive loop on the stream.
 311530                if (!_isServer && _receivedCloseFrame)
 311531                {
 311532                    await WaitForServerToCloseConnectionAsync(enterMutex: true, cancellationToken).ConfigureAwait(false)
 311533                }
 1534
 1535                // We're closed.  Close the connection and update the status.
 311536                lock (StateUpdateLock)
 311537                {
 311538                    DisposeCore();
 311539                }
 311540            }
 37431541            catch (Exception exc)
 37431542            {
 37431543                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 37431544                throw;
 1545            }
 1546            finally
 37741547            {
 37741548                if (NetEventSource.Log.IsEnabled()) NetEventSource.CloseAsyncPrivateCompleted(this);
 37741549            }
 311550        }
 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
 66711558        {
 1559            // Close payload is two bytes containing the close status followed by a UTF8-encoding of the status descript
 1560
 66711561            byte[]? buffer = null;
 1562            try
 66711563            {
 66711564                int count = 2;
 66711565                if (string.IsNullOrEmpty(closeStatusDescription))
 31941566                {
 31941567                    buffer = ArrayPool<byte>.Shared.Rent(count);
 31941568                }
 1569                else
 34771570                {
 34771571                    count += s_textEncoding.GetByteCount(closeStatusDescription);
 34771572                    buffer = ArrayPool<byte>.Shared.Rent(count);
 34771573                    int encodedLength = s_textEncoding.GetBytes(closeStatusDescription, 0, closeStatusDescription.Length
 34771574                    Debug.Assert(count - 2 == encodedLength, $"{nameof(s_textEncoding.GetByteCount)} and {nameof(s_textE
 34771575                }
 1576
 66711577                BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)closeStatus);
 1578
 66711579                await SendFrameAsync(MessageOpcode.Close, endOfMessage: true, disableCompression: true, new Memory<byte>
 66711580            }
 1581            finally
 66711582            {
 66711583                if (buffer != null)
 66711584                {
 66711585                    ArrayPool<byte>.Shared.Return(buffer);
 66711586                }
 66711587            }
 1588
 66711589            lock (StateUpdateLock)
 66711590            {
 66711591                _sentCloseFrame = true;
 66711592                WebSocketState state = _state;
 66711593                if (_receivedCloseFrame && state < WebSocketState.Closed)
 6711594                {
 6711595                    _state = WebSocketState.Closed;
 6711596                }
 60001597                else if (state < WebSocketState.CloseSent)
 60001598                {
 60001599                    _state = WebSocketState.CloseSent;
 60001600                }
 1601
 66711602                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_stat
 66711603            }
 66711604        }
 1605
 1606        private void ConsumeFromBuffer(int count)
 1182931607        {
 1182931608            Debug.Assert(count >= 0, $"Expected non-negative {nameof(count)}, got {count}");
 1182931609            Debug.Assert(count <= _receiveBufferCount, $"Trying to consume {count}, which is more than exists {_receiveB
 1182931610            _receiveBufferCount -= count;
 1182931611            _receiveBufferOffset += count;
 1182931612        }
 1613
 1614        [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))]
 1615        [RuntimeAsyncMethodGeneration(false)]
 1616        private async ValueTask EnsureBufferContainsAsync(int minimumRequiredBytes, CancellationToken cancellationToken)
 120441617        {
 120441618            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.
 120441621            if (_receiveBufferCount < minimumRequiredBytes)
 115291622            {
 1623                // If there's any data in the buffer, shift it down.
 115291624                if (_receiveBufferCount > 0)
 17741625                {
 17741626                    _receiveBuffer.Span.Slice(_receiveBufferOffset, _receiveBufferCount).CopyTo(_receiveBuffer.Span);
 17741627                }
 115291628                _receiveBufferOffset = 0;
 1629
 115291630                int bytesToRead = minimumRequiredBytes - _receiveBufferCount;
 115291631                int numRead = await _stream.ReadAtLeastAsync(
 115291632                    _receiveBuffer.Slice(_receiveBufferCount), bytesToRead, throwOnEndOfStream: false, cancellationToken
 115291633                _receiveBufferCount += numRead;
 1634
 115291635                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"bytesRead={numRead}");
 1636
 115291637                if (numRead < bytesToRead)
 16331638                {
 16331639                    ThrowEOFUnexpected();
 01640                }
 98961641                _keepAlivePingState?.OnDataReceived();
 98961642            }
 104111643        }
 1644
 1645        private void ThrowEOFUnexpected()
 23401646        {
 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.
 23401650            ThrowIfDisposed();
 23401651            throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely);
 1652        }
 1653
 1654        /// <summary>Gets a send buffer from the pool.</summary>
 1655        private void AllocateSendBuffer(int minLength)
 124811656        {
 124811657            Debug.Assert(_sendBuffer == null); // would only fail if had some catastrophic error previously that prevent
 124811658            _sendBuffer = ArrayPool<byte>.Shared.Rent(minLength);
 124811659        }
 1660
 1661        /// <summary>Releases the send buffer to the pool.</summary>
 1662        private void ReleaseSendBuffer()
 124811663        {
 124811664            Debug.Assert(_sendMutex.IsHeld, $"Caller should hold the {nameof(_sendMutex)}");
 1665
 124811666            if (_sendBuffer is byte[] toReturn)
 124811667            {
 124811668                _sendBuffer = null;
 124811669                ArrayPool<byte>.Shared.Return(toReturn);
 124811670            }
 124811671        }
 1672
 1673        private static int CombineMaskBytes(ReadOnlySpan<byte> buffer, int maskOffset) =>
 87041674            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)
 81691683        {
 81691684            Debug.Assert(maskOffsetIndex < MaskLength, $"Unexpected {nameof(maskOffsetIndex)}: {maskOffsetIndex}");
 81691685            Debug.Assert(mask.Length >= MaskLength + maskOffset, $"Unexpected inputs: {mask.Length}, {maskOffset}");
 81691686            return ApplyMask(toMask, CombineMaskBytes(mask, maskOffset), maskOffsetIndex);
 81691687        }
 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)
 88921695        {
 88921696            Debug.Assert(maskIndex < sizeof(int));
 1697
 88921698            if (toMask.Length >= sizeof(int))
 43581699            {
 43581700                int rolledMask = BitConverter.IsLittleEndian ?
 43581701                    (int)BitOperations.RotateRight((uint)mask, maskIndex * 8) :
 43581702                    (int)BitOperations.RotateLeft((uint)mask, maskIndex * 8);
 1703
 1704                // Process Vector<byte>.Count bytes at a time.
 43581705                if (Vector.IsHardwareAccelerated && toMask.Length >= Vector<byte>.Count)
 1011706                {
 1011707                    Vector<byte> maskVector = Vector.AsVectorByte(new Vector<int>(rolledMask));
 1708                    do
 1371709                    {
 1371710                        (new Vector<byte>(toMask) ^ maskVector).CopyTo(toMask);
 1371711                        toMask = toMask.Slice(Vector<byte>.Count);
 1371712                    }
 1371713                    while (toMask.Length >= Vector<byte>.Count);
 1011714                }
 1715
 1716                // Process 4 bytes at a time.
 204041717                while (toMask.Length >= sizeof(int))
 160461718                {
 160461719                    BitConverter.TryWriteBytes(toMask, BitConverter.ToInt32(toMask) ^ rolledMask);
 160461720                    toMask = toMask.Slice(sizeof(int));
 160461721                }
 43581722            }
 1723
 1724            // Process 1 byte at a time, using the mask byte at native memory offset maskIndex.
 564981725            for (int i = 0; i < toMask.Length; i++)
 193571726            {
 193571727                toMask[i] ^= (byte)(mask >> ((BitConverter.IsLittleEndian ? maskIndex : 3 - maskIndex) * 8));
 193571728                maskIndex = (maskIndex + 1) & 3;
 193571729            }
 1730
 88921731            return maskIndex;
 88921732        }
 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
 1319241755        private void ThrowIfDisposed() => ThrowIfInvalidState(validStates: ManagedWebSocketStates.All);
 1756
 1757        private void ThrowIfInvalidState(ManagedWebSocketStates validStates)
 1818641758        {
 1818641759            bool disposed = _disposed;
 1818641760            WebSocketState state = _state;
 1818641761            Exception? keepAliveException = null;
 1762
 1818641763            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
 1818641774            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"_state={state}, _disposed={disposed}, _keep
 1775
 1818641776            WebSocketStateHelper.ThrowIfInvalidState(state, disposed, keepAliveException, validStates);
 1817491777        }
 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)
 516801783        {
 1784            // If no prior segment spilled over and this one is the last, we can validate it efficiently as a complete m
 516801785            if (endOfMessage && !state.SequenceInProgress)
 1171786            {
 1171787                return Utf8.IsValid(span);
 1788            }
 1789
 1033841790            for (int i = 0; i < span.Length;)
 69461791            {
 1792                // Have we started a character sequence yet?
 69461793                if (!state.SequenceInProgress)
 68551794                {
 1795                    // Skip past ASCII bytes.
 68551796                    int firstNonAscii = span.Slice(i).IndexOfAnyExceptInRange((byte)0, (byte)127);
 68551797                    if (firstNonAscii < 0)
 60571798                    {
 60571799                        break;
 1800                    }
 7981801                    i += firstNonAscii;
 1802
 1803                    // The first byte tells us how many bytes are in the sequence.
 7981804                    state.SequenceInProgress = true;
 7981805                    byte b = span[i];
 7981806                    i++;
 7981807                    Debug.Assert((b & 0x80) != 0, "Should have already skipped past ASCII");
 7981808                    if ((b & 0xC0) == 0x80)
 1441809                    {
 1810                        // Misplaced 10bbbbbb continuation byte. This cannot be the first byte.
 1441811                        return false;
 1812                    }
 6541813                    else if ((b & 0xE0) == 0xC0) // 110bbbbb 10bbbbbb
 2071814                    {
 2071815                        state.AdditionalBytesExpected = 1;
 2071816                        state.CurrentDecodeBits = b & 0x1F;
 2071817                        state.ExpectedValueMin = 0x80;
 2071818                    }
 4471819                    else if ((b & 0xF0) == 0xE0) // 1110bbbb 10bbbbbb 10bbbbbb
 181820                    {
 181821                        state.AdditionalBytesExpected = 2;
 181822                        state.CurrentDecodeBits = b & 0xF;
 181823                        state.ExpectedValueMin = 0x800;
 181824                    }
 4291825                    else if ((b & 0xF8) == 0xF0) // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
 581826                    {
 581827                        state.AdditionalBytesExpected = 3;
 581828                        state.CurrentDecodeBits = b & 0x7;
 581829                        state.ExpectedValueMin = 0x10000;
 581830                    }
 1831                    else // 111110bb & 1111110b & 11111110 && 11111111 are not valid
 3711832                    {
 3711833                        return false;
 1834                    }
 2831835                }
 1836
 5511837                while (state.AdditionalBytesExpected > 0 && i < span.Length)
 2851838                {
 2851839                    byte b = span[i];
 2851840                    if ((b & 0xC0) != 0x80)
 1061841                    {
 1061842                        return false;
 1843                    }
 1844
 1791845                    i++;
 1791846                    state.AdditionalBytesExpected--;
 1847
 1848                    // Each continuation byte carries 6 bits of data 0x10bbbbbb.
 1791849                    state.CurrentDecodeBits = (state.CurrentDecodeBits << 6) | (b & 0x3F);
 1850
 1791851                    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
 1781857                    if (state.AdditionalBytesExpected == 2 && state.CurrentDecodeBits >= 0x110)
 11858                    {
 1859                        // This is going to be out of the upper Unicode bound 0x10FFFF.
 11860                        return false;
 1861                    }
 1771862                }
 1863
 2661864                if (state.AdditionalBytesExpected == 0)
 1661865                {
 1661866                    state.SequenceInProgress = false;
 1661867                    if (state.CurrentDecodeBits < state.ExpectedValueMin)
 81868                    {
 1869                        // Overlong encoding (e.g. using 2 bytes to encode something that only needed 1).
 81870                        return false;
 1871                    }
 1581872                }
 2581873            }
 1874
 509321875            return !endOfMessage || !state.SequenceInProgress;
 516801876        }
 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
 17761925        {
 17761926            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);
 17761936        }
 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>
 4649801968            internal bool Processed { get; set; }
 1969
 1970            /// <summary>
 1971            /// Returns if message has been received and processed.
 1972            /// </summary>
 1614991973            internal bool EndOfMessage => Fin && Processed && PayloadLength == 0;
 1974        }
 1975    }
 1976}

D:\runner\runtime\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}

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)