< 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>
 931460        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>
 931465        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>
 931470        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>
 931478        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>
 931498        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>
 61855127        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>
 9314140        internal ManagedWebSocket(Stream stream, bool isServer, string? subprotocol, TimeSpan keepAliveInterval, TimeSpa
 9314141        {
 9314142            Debug.Assert(StateUpdateLock != null, $"Expected {nameof(StateUpdateLock)} to be non-null");
 9314143            Debug.Assert(stream != null, $"Expected non-null {nameof(stream)}");
 9314144            Debug.Assert(stream.CanRead, $"Expected readable {nameof(stream)}");
 9314145            Debug.Assert(stream.CanWrite, $"Expected writeable {nameof(stream)}");
 9314146            Debug.Assert(keepAliveInterval == Timeout.InfiniteTimeSpan || keepAliveInterval >= TimeSpan.Zero, $"Invalid 
 9314147            Debug.Assert(keepAliveTimeout == Timeout.InfiniteTimeSpan || keepAliveTimeout >= TimeSpan.Zero, $"Invalid {n
 148
 9314149            _stream = stream;
 9314150            _isServer = isServer;
 9314151            _subprotocol = subprotocol;
 152
 9314153            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;
 9314164            _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.
 9314169            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            }
 9314199        }
 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)
 9314205            : this(stream, options.IsServer, options.SubProtocol, options.KeepAliveInterval, options.KeepAliveTimeout)
 9314206        {
 9314207            var deflateOptions = options.DangerousDeflateOptions;
 208
 9314209            if (deflateOptions is not null)
 7978210            {
 7978211                if (options.IsServer)
 306212                {
 306213                    _inflater = new WebSocketInflater(deflateOptions.ClientMaxWindowBits, deflateOptions.ClientContextTa
 306214                    _deflater = new WebSocketDeflater(deflateOptions.ServerMaxWindowBits, deflateOptions.ServerContextTa
 306215                }
 216                else
 7672217                {
 7672218                    _inflater = new WebSocketInflater(deflateOptions.ServerMaxWindowBits, deflateOptions.ServerContextTa
 7672219                    _deflater = new WebSocketDeflater(deflateOptions.ClientMaxWindowBits, deflateOptions.ClientContextTa
 7672220                }
 7978221            }
 9314222        }
 223
 224        public override void Dispose()
 9340225        {
 9340226            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 227
 9340228            lock (StateUpdateLock)
 9340229            {
 9340230                DisposeCore();
 9340231            }
 9340232        }
 233
 234        private void DisposeCore()
 10647235        {
 10647236            Debug.Assert(Monitor.IsEntered(StateUpdateLock), $"Expected {nameof(StateUpdateLock)} to be held");
 237
 10647238            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"{nameof(_disposed)}={_disposed}");
 239
 10647240            if (!_disposed)
 9314241            {
 9314242                _disposed = true;
 9314243                _keepAliveTimer?.Dispose();
 9314244                _stream.Dispose();
 245
 9314246                WebSocketState state = _state;
 9314247                if (state < WebSocketState.Aborted)
 1404248                {
 1404249                    _state = WebSocketState.Closed;
 1404250                }
 251
 9314252                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_stat
 253
 9314254                DisposeSafe(_inflater, _receiveMutex);
 9314255                DisposeSafe(_deflater, _sendMutex);
 9314256            }
 10647257        }
 258
 259        private static void DisposeSafe(IDisposable? resource, AsyncMutex mutex)
 18628260        {
 18628261            if (resource is not null)
 15956262            {
 15956263                Task lockTask = mutex.EnterAsync(CancellationToken.None);
 264
 15956265                if (lockTask.IsCompleted)
 14726266                {
 14726267                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(mutex);
 268
 14726269                    resource.Dispose();
 14726270                    mutex.Exit();
 271
 14726272                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(mutex);
 14726273                }
 274                else
 1230275                {
 1230276                    lockTask.GetAwaiter().UnsafeOnCompleted(() =>
 1230277                    {
 1230278                        if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(mutex);
 1230279
 1230280                        resource.Dispose();
 1230281                        mutex.Exit();
 1230282
 1230283                        if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(mutex);
 2460284                    });
 1230285                }
 15956286            }
 18628287        }
 288
 0289        public override WebSocketCloseStatus? CloseStatus => _closeStatus;
 290
 0291        public override string? CloseStatusDescription => _closeStatusDescription;
 292
 9048293        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
 2969309            SendAsync(buffer, messageType, endOfMessage ? WebSocketMessageFlags.EndOfMessage : default, cancellationToke
 310
 311        public override ValueTask SendAsync(ReadOnlyMemory<byte> buffer, WebSocketMessageType messageType, WebSocketMess
 2969312        {
 2969313            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 314
 2969315            ThrowIfInvalidMessageType(messageType);
 316
 317            try
 2969318            {
 2969319                ThrowIfInvalidState(WebSocketStateHelper.ValidSendStates);
 2941320            }
 28321            catch (Exception exc)
 28322            {
 28323                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 28324                return ValueTask.FromException(exc);
 325            }
 326
 2941327            bool endOfMessage = (messageFlags & WebSocketMessageFlags.EndOfMessage) != 0;
 2941328            bool disableCompression = (messageFlags & WebSocketMessageFlags.DisableCompression) != 0;
 329            MessageOpcode opcode;
 330
 2941331            if (_lastSendWasFragment)
 136332            {
 136333                if (_lastSendHadDisableCompression != disableCompression)
 0334                {
 0335                    throw new ArgumentException(SR.net_WebSockets_Argument_MessageFlagsHasDifferentCompressionOptions, n
 336                }
 136337                opcode = MessageOpcode.Continuation;
 136338            }
 339            else
 2805340            {
 2805341                opcode = messageType == WebSocketMessageType.Binary ? MessageOpcode.Binary : MessageOpcode.Text;
 2805342            }
 343
 2941344            ValueTask t = SendFrameAsync(opcode, endOfMessage, disableCompression, buffer, cancellationToken);
 2941345            _lastSendWasFragment = !endOfMessage;
 2941346            _lastSendHadDisableCompression = disableCompression;
 347
 2941348            return t;
 2969349        }
 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
 60578371        {
 60578372            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 373
 374            try
 60578375            {
 60578376                ThrowIfInvalidState(WebSocketStateHelper.ValidReceiveStates);
 377
 60500378                return ReceiveAsyncPrivate<ValueWebSocketReceiveResult>(buffer, cancellationToken);
 379            }
 78380            catch (Exception exc)
 78381            {
 78382                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 78383                return ValueTask.FromException<ValueWebSocketReceiveResult>(exc);
 384            }
 60578385        }
 386
 387        public override Task CloseAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken c
 4510388        {
 4510389            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 390
 4510391            WebSocketValidate.ValidateCloseStatus(closeStatus, statusDescription);
 392
 393            try
 4510394            {
 4510395                ThrowIfInvalidState(WebSocketStateHelper.ValidCloseStates);
 4510396            }
 0397            catch (Exception exc)
 0398            {
 0399                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 0400                return Task.FromException(exc);
 401            }
 402
 4510403            return CloseAsyncPrivate(closeStatus, statusDescription, cancellationToken);
 4510404        }
 405
 406        public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationT
 99407        {
 99408            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 409
 99410            WebSocketValidate.ValidateCloseStatus(closeStatus, statusDescription);
 99411            return CloseOutputAsyncCore(closeStatus, statusDescription, enterReceiveMutex: true, cancellationToken: canc
 99412        }
 413
 414        private async Task CloseOutputAsyncCore(WebSocketCloseStatus closeStatus, string? statusDescription, bool enterR
 4164415        {
 4164416            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 417
 4164418            ThrowIfInvalidState(WebSocketStateHelper.ValidCloseOutputStates);
 419
 4147420            await SendCloseFrameAsync(closeStatus, statusDescription, cancellationToken).ConfigureAwait(false);
 421
 422            // Polite EOF wait under the receive mutex; avoids racing the receive loop on the stream.
 4147423            if (!_isServer && _receivedCloseFrame)
 1169424            {
 1169425                await WaitForServerToCloseConnectionAsync(enterReceiveMutex, cancellationToken).ConfigureAwait(false);
 1169426            }
 427
 428            // If we already received a close frame, since we've now also sent one, we're now closed.
 4147429            lock (StateUpdateLock)
 4147430            {
 4147431                Debug.Assert(_sentCloseFrame);
 4147432                if (_receivedCloseFrame)
 1276433                {
 1276434                    DisposeCore();
 1276435                }
 4147436            }
 4147437        }
 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()
 9191448        {
 9191449            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 450
 9191451            lock (StateUpdateLock)
 9191452            {
 9191453                OnAbortedCore();
 9191454            }
 9191455        }
 456
 457        private void OnAbortedCore()
 9191458        {
 9191459            Debug.Assert(Monitor.IsEntered(StateUpdateLock), $"Expected {nameof(StateUpdateLock)} to be held");
 460
 9191461            WebSocketState state = _state;
 9191462            if (state is not WebSocketState.Closed and not WebSocketState.Aborted)
 7910463            {
 7910464                _state = state is not WebSocketState.None and not WebSocketState.Connecting ?
 7910465                    WebSocketState.Aborted :
 7910466                    WebSocketState.Closed;
 7910467            }
 468
 9191469            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_state}")
 9191470        }
 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
 18277479        {
 18277480            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.
 18277486            Task lockTask = _sendMutex.EnterAsync(cancellationToken);
 18277487            return cancellationToken.CanBeCanceled || !lockTask.IsCompletedSuccessfully ?
 18277488                SendFrameFallbackAsync(opcode, endOfMessage, disableCompression, payloadBuffer, lockTask, cancellationTo
 18277489                SendFrameLockAcquiredNonCancelableAsync(opcode, endOfMessage, disableCompression, payloadBuffer);
 18277490        }
 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
 10411498        {
 10411499            Debug.Assert(_sendMutex.IsHeld, $"Caller should hold the {nameof(_sendMutex)}");
 500
 10411501            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.
 10411505            ValueTask writeTask = default;
 10411506            bool releaseSendBufferAndSemaphore = true;
 507            try
 10411508            {
 509                // Write the payload synchronously to the buffer, then write that buffer out to the network.
 10411510                int sendBytes = WriteFrameToSendBuffer(opcode, endOfMessage, disableCompression, payloadBuffer.Span);
 10411511                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.
 10411516                if (writeTask.IsCompleted)
 10411517                {
 10411518                    writeTask.GetAwaiter().GetResult();
 10411519                    Task flushTask = _stream.FlushAsync();
 10411520                    if (flushTask.IsCompleted)
 1715521                    {
 1715522                        flushTask.GetAwaiter().GetResult();
 1715523                        return ValueTask.CompletedTask;
 524                    }
 525                    else
 8696526                    {
 8696527                        releaseSendBufferAndSemaphore = false;
 8696528                        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
 10411547            {
 10411548                if (releaseSendBufferAndSemaphore)
 1715549                {
 1715550                    ReleaseSendBuffer();
 1715551                    _sendMutex.Exit();
 552
 1715553                    if (NetEventSource.Log.IsEnabled())
 0554                    {
 0555                        NetEventSource.MutexExited(_sendMutex);
 0556                        NetEventSource.SendFrameAsyncCompleted(this);
 0557                    }
 1715558                }
 10411559            }
 560
 0561            return WaitForWriteTaskAsync(writeTask, shouldFlush: true);
 10411562        }
 563
 564        private async ValueTask WaitForWriteTaskAsync(ValueTask writeTask, bool shouldFlush)
 8696565        {
 566            try
 8696567            {
 8696568                await writeTask.ConfigureAwait(false);
 8696569                if (shouldFlush)
 0570                {
 0571                    await _stream.FlushAsync().ConfigureAwait(false);
 0572                }
 8696573            }
 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
 8696588            {
 8696589                ReleaseSendBuffer();
 8696590                _sendMutex.Exit();
 591
 8696592                if (NetEventSource.Log.IsEnabled())
 0593                {
 0594                    NetEventSource.MutexExited(_sendMutex);
 0595                    NetEventSource.SendFrameAsyncCompleted(this);
 0596                }
 8696597            }
 8696598        }
 599
 600        private async ValueTask SendFrameFallbackAsync(MessageOpcode opcode, bool endOfMessage, bool disableCompression,
 7866601        {
 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.
 7866606            using (cancellationToken.Register(static s => ((ManagedWebSocket)s!).Abort(), this))
 7866607            {
 608                try
 7866609                {
 7866610                    await lockTask.ConfigureAwait(false);
 7866611                }
 0612                catch (Exception exc)
 0613                {
 0614                    if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 0615                    throw;
 616                }
 617
 7866618                if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_sendMutex);
 619
 620                try
 7866621                {
 7866622                    int sendBytes = WriteFrameToSendBuffer(opcode, endOfMessage, disableCompression, payloadBuffer.Span)
 7866623                    await _stream.WriteAsync(new ReadOnlyMemory<byte>(_sendBuffer, 0, sendBytes), cancellationToken).Con
 7866624                    await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
 7866625                }
 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
 7866640                {
 7866641                    ReleaseSendBuffer();
 7866642                    _sendMutex.Exit();
 643
 7866644                    if (NetEventSource.Log.IsEnabled())
 0645                    {
 0646                        NetEventSource.MutexExited(_sendMutex);
 0647                        NetEventSource.SendFrameAsyncCompleted(this);
 0648                    }
 7866649                }
 7866650            }
 7866651        }
 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
 18277655        {
 18277656            ThrowIfDisposed();
 657
 18277658            if (_deflater is not null && !disableCompression)
 2668659            {
 2668660                payloadBuffer = _deflater.Deflate(payloadBuffer, endOfMessage);
 2668661            }
 18277662            int payloadLength = payloadBuffer.Length;
 663
 664            // Ensure we have a _sendBuffer
 18277665            AllocateSendBuffer(payloadLength + MaxMessageHeaderLength);
 18277666            Debug.Assert(_sendBuffer != null);
 667
 668            // Write the message header data to the buffer.
 669            int headerLength;
 18277670            int? maskOffset = null;
 18277671            if (_isServer)
 375672            {
 673                // The server doesn't send a mask, so the mask offset returned by WriteHeader
 674                // is actually the end of the header.
 375675                headerLength = WriteHeader(opcode, _sendBuffer, payloadBuffer, endOfMessage, useMask: false, compressed:
 375676            }
 677            else
 17902678            {
 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.
 17902681                maskOffset = WriteHeader(opcode, _sendBuffer, payloadBuffer, endOfMessage, useMask: true, compressed: _d
 17902682                headerLength = maskOffset.GetValueOrDefault() + MaskLength;
 17902683            }
 684
 685            // Write the payload
 18277686            if (payloadBuffer.Length > 0)
 11713687            {
 11713688                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.
 11713691                _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.
 11713695                if (maskOffset.HasValue)
 11348696                {
 11348697                    ApplyMask(new Span<byte>(_sendBuffer, headerLength, payloadLength), _sendBuffer, maskOffset.Value, 0
 11348698                }
 11713699            }
 700
 701            // Return the number of bytes in the send buffer
 18277702            return headerLength + payloadLength;
 18277703        }
 704
 705        private static int WriteHeader(MessageOpcode opcode, byte[] sendBuffer, ReadOnlySpan<byte> payload, bool endOfMe
 18277706        {
 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
 18277728            Debug.Assert(sendBuffer.Length >= MaxMessageHeaderLength, $"Expected {nameof(sendBuffer)} to be at least {Ma
 729
 18277730            sendBuffer[0] = (byte)opcode; // 4 bits for the opcode
 18277731            if (endOfMessage)
 17901732            {
 17901733                sendBuffer[0] |= 0x80; // 1 bit for FIN
 17901734            }
 18277735            if (compressed && opcode != MessageOpcode.Continuation)
 2629736            {
 737                // Per-Message Deflate flag needs to be set only in the first frame
 2629738                sendBuffer[0] |= 0b_0100_0000;
 2629739            }
 740
 741            // Store the payload length.
 742            int maskOffset;
 18277743            if (payload.Length <= 125)
 18270744            {
 18270745                sendBuffer[1] = (byte)payload.Length;
 18270746                maskOffset = 2; // no additional payload length
 18270747            }
 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
 18277761            if (useMask)
 17902762            {
 763                // Generate the mask.
 17902764                sendBuffer[1] |= 0x80;
 17902765                WriteRandomMask(sendBuffer, maskOffset);
 17902766            }
 767
 768            // Return the position of the mask.
 18277769            return maskOffset;
 18277770        }
 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) =>
 17902776            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
 177724789        {
 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
 177724798            if (NetEventSource.Log.IsEnabled()) NetEventSource.ReceiveAsyncPrivateStarted(this, payloadBuffer.Length);
 799
 177724800            CancellationTokenRegistration registration = default;
 801            try
 177724802            {
 177724803                registration = cancellationToken.Register(static s => ((ManagedWebSocket)s!).Abort(), this);
 804
 177724805                await _receiveMutex.EnterAsync(cancellationToken).ConfigureAwait(false);
 177724806                if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_receiveMutex);
 807
 808                try
 177724809                {
 177724810                    ThrowIfDisposed();
 811
 184756812                    while (true) // in case we get control frames that should be ignored from the user's perspective
 184756813                    {
 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.
 184756819                        MessageHeader header = _lastReceiveHeader;
 184756820                        if (header.Processed)
 168278821                        {
 168278822                            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Reading the next frame heade
 823
 168278824                            if (_receiveBufferCount < (_isServer ? MaxMessageHeaderLength : (MaxMessageHeaderLength - Ma
 38267825                            {
 826                                // Make sure we have the first two bytes, which includes the start of the payload length
 38267827                                if (_receiveBufferCount < 2)
 13875828                                {
 13875829                                    if (payloadBuffer.IsEmpty)
 7705830                                    {
 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
 7705841                                        await _stream.ReadAsync(Memory<byte>.Empty, cancellationToken).ConfigureAwait(fa
 7705842                                    }
 843
 13875844                                    await EnsureBufferContainsAsync(2, cancellationToken).ConfigureAwait(false);
 11998845                                }
 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.
 36390849                                long payloadLength = _receiveBuffer.Span[_receiveBufferOffset + 1] & 0x7F;
 36390850                                if (_isServer || payloadLength > 125)
 717851                                {
 717852                                    int minNeeded =
 717853                                        2 +
 717854                                        (_isServer ? MaskLength : 0) +
 717855                                        (payloadLength <= 125 ? 0 : payloadLength == 126 ? sizeof(ushort) : sizeof(ulong
 717856                                    await EnsureBufferContainsAsync(minNeeded, cancellationToken).ConfigureAwait(false);
 665857                                }
 36338858                            }
 859
 166349860                            string? headerErrorMessage = TryParseMessageHeaderFromReceiveBuffer(out header);
 166349861                            if (headerErrorMessage != null)
 4054862                            {
 4054863                                await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketEr
 0864                            }
 162295865                            _receivedMaskOffsetOffset = 0;
 866
 162295867                            if (NetEventSource.Log.IsEnabled())
 0868                            {
 0869                                NetEventSource.Trace(this, $"Next frame opcode={header.Opcode}, fin={header.Fin}, compre
 0870                            }
 871
 162295872                            if (header.PayloadLength == 0 && header.Compressed)
 67120873                            {
 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.
 67120877                                _inflater!.AddBytes(0, endOfMessage: header.Fin);
 67120878                            }
 162295879                        }
 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.
 178773884                        if (header.Opcode == MessageOpcode.Ping || header.Opcode == MessageOpcode.Pong)
 7052885                        {
 7052886                            await HandleReceivedPingPongAsync(header, cancellationToken).ConfigureAwait(false);
 7032887                            continue;
 888                        }
 171721889                        else if (header.Opcode == MessageOpcode.Close)
 1367890                        {
 1367891                            await HandleReceivedCloseAsync(header, cancellationToken).ConfigureAwait(false);
 79892                            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
 170354896                        if (header.Opcode == MessageOpcode.Continuation)
 145724897                        {
 145724898                            header.Opcode = _lastReceiveHeader.Opcode;
 145724899                            header.Compressed = _lastReceiveHeader.Compressed;
 145724900                        }
 901
 902                        // The message should now be a binary or text message.  Handle it by reading the payload and ret
 170354903                        Debug.Assert(header.Opcode == MessageOpcode.Binary || header.Opcode == MessageOpcode.Text, $"Une
 904
 905                        // If there's no data to read, return an appropriate result.
 170354906                        if (header.Processed || payloadBuffer.Length == 0)
 87866907                        {
 87866908                            _lastReceiveHeader = header;
 87866909                            return GetReceiveResult<TResult>(
 87866910                                count: 0,
 87866911                                messageType: header.Opcode == MessageOpcode.Text ? WebSocketMessageType.Text : WebSocket
 87866912                                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.
 82488921                        int totalBytesReceived = 0;
 922
 923                        // Only start a new receive if we haven't received the entire frame.
 82488924                        if (header.PayloadLength > 0)
 21159925                        {
 21159926                            if (header.Compressed)
 7642927                            {
 7642928                                Debug.Assert(_inflater is not null);
 7642929                                _inflater.Prepare(header.PayloadLength, payloadBuffer.Length);
 7642930                            }
 931
 932                            // Read directly into the appropriate buffer until we've hit a limit.
 21159933                            int limit = (int)Math.Min(header.Compressed ? _inflater!.Span.Length : payloadBuffer.Length,
 934
 21159935                            if (_receiveBufferCount > 0)
 14352936                            {
 14352937                                int receiveBufferBytesToCopy = Math.Min(limit, _receiveBufferCount);
 938
 14352939                                _receiveBuffer.Span.Slice(_receiveBufferOffset, receiveBufferBytesToCopy).CopyTo(
 14352940                                    header.Compressed ? _inflater!.Span : payloadBuffer.Span);
 14352941                                ConsumeFromBuffer(receiveBufferBytesToCopy);
 14352942                                totalBytesReceived += receiveBufferBytesToCopy;
 14352943                            }
 944
 21159945                            if (totalBytesReceived < limit)
 7970946                            {
 7970947                                int bytesToRead = limit - totalBytesReceived;
 7970948                                Memory<byte> readBuffer = header.Compressed ?
 7970949                                    _inflater!.Memory.Slice(totalBytesReceived, bytesToRead) :
 7970950                                    payloadBuffer.Slice(totalBytesReceived, bytesToRead);
 951
 7970952                                int numBytesRead = await _stream.ReadAtLeastAsync(
 7970953                                    readBuffer, bytesToRead, throwOnEndOfStream: false, cancellationToken).ConfigureAwai
 954
 7970955                                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"bytesRead={numBytesRead
 956
 7970957                                if (numBytesRead < bytesToRead)
 775958                                {
 775959                                    ThrowEOFUnexpected();
 0960                                }
 7195961                                _keepAlivePingState?.OnDataReceived();
 7195962                                totalBytesReceived += numBytesRead;
 7195963                            }
 964
 20384965                            if (_isServer)
 723966                            {
 723967                                _receivedMaskOffsetOffset = ApplyMask(header.Compressed ?
 723968                                    _inflater!.Span.Slice(0, totalBytesReceived) :
 723969                                    payloadBuffer.Span.Slice(0, totalBytesReceived), header.Mask, _receivedMaskOffsetOff
 723970                            }
 971
 20384972                            header.PayloadLength -= totalBytesReceived;
 973
 20384974                            if (header.Compressed)
 7446975                            {
 7446976                                _inflater!.AddBytes(totalBytesReceived, endOfMessage: header.Fin && header.PayloadLength
 7446977                            }
 20384978                        }
 979
 81713980                        if (header.Compressed)
 68775981                        {
 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.
 68775984                            header.Processed = _inflater!.Inflate(payloadBuffer.Span, out totalBytesReceived) && header.
 68473985                        }
 986                        else
 12938987                        {
 988                            // Without compression the frame is processed as soon as we've received everything
 12938989                            header.Processed = header.PayloadLength == 0;
 12938990                        }
 991
 992                        // If this a text message, validate that it contains valid UTF8.
 81411993                        if (header.Opcode == MessageOpcode.Text &&
 81411994                            !TryValidateUtf8(payloadBuffer.Span.Slice(0, totalBytesReceived), header.EndOfMessage, _utf8
 823995                        {
 823996                            await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.InvalidPayloadData, WebSocketE
 0997                        }
 998
 80588999                        if (header.Processed)
 684011000                        {
 684011001                            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Data frame fully processed")
 684011002                        }
 1003
 805881004                        _lastReceiveHeader = header;
 805881005                        return GetReceiveResult<TResult>(
 805881006                            totalBytesReceived,
 805881007                            header.Opcode == MessageOpcode.Text ? WebSocketMessageType.Text : WebSocketMessageType.Binar
 805881008                            header.EndOfMessage);
 1009                    }
 1010                }
 1011                finally
 1777241012                {
 1777241013                    _receiveMutex.Exit();
 1777241014                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(_receiveMutex);
 1777241015                }
 1016            }
 91911017            catch (Exception exc)
 91911018            {
 91911019                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 1020
 91911021                if (exc is OperationCanceledException)
 01022                {
 01023                    throw;
 1024                }
 1025
 91911026                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                }
 91911039                OnAborted();
 1040
 91911041                if (exc is WebSocketException)
 91911042                {
 91911043                    throw;
 1044                }
 1045
 01046                throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, exc);
 1047            }
 1048            finally
 1777241049            {
 1777241050                registration.Dispose();
 1777241051                if (NetEventSource.Log.IsEnabled()) NetEventSource.ReceiveAsyncPrivateCompleted(this);
 1777241052            }
 1685331053        }
 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)
 1685331060        {
 1685331061            if (typeof(TResult) == typeof(ValueWebSocketReceiveResult))
 1685331062            {
 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.
 1685331066                return (TResult)(object)new ValueWebSocketReceiveResult(count, messageType, endOfMessage);
 1067            }
 1068
 01069            return (TResult)(object)new WebSocketReceiveResult(count, messageType, endOfMessage, _closeStatus, _closeSta
 1685331070        }
 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)
 13671077        {
 13671078            lock (StateUpdateLock)
 13671079            {
 13671080                _receivedCloseFrame = true;
 13671081                WebSocketState state = _state;
 13671082                if (_sentCloseFrame && state < WebSocketState.Closed)
 111083                {
 111084                    _state = WebSocketState.Closed;
 111085                }
 13561086                else if (state < WebSocketState.CloseReceived)
 13561087                {
 13561088                    _state = WebSocketState.CloseReceived;
 13561089                }
 1090
 13671091                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_stat
 13671092            }
 1093
 13671094            WebSocketCloseStatus closeStatus = WebSocketCloseStatus.NormalClosure;
 13671095            string closeStatusDescription = string.Empty;
 1096
 1097            // Handle any payload by parsing it into the close status and description.
 13671098            if (header.PayloadLength == 1)
 541099            {
 1100                // The close payload length can be 0 or >= 2, but not 1.
 541101                await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faulted, SR.
 01102            }
 13131103            else if (header.PayloadLength >= 2)
 12441104            {
 12441105                if (_receiveBufferCount < header.PayloadLength)
 11991106                {
 11991107                    await EnsureBufferContainsAsync((int)header.PayloadLength, cancellationToken).ConfigureAwait(false);
 11921108                }
 1109
 12371110                if (_isServer)
 691111                {
 691112                    ApplyMask(_receiveBuffer.Span.Slice(_receiveBufferOffset, (int)header.PayloadLength), header.Mask, 0
 691113                }
 1114
 12371115                closeStatus = (WebSocketCloseStatus)BinaryPrimitives.ReadUInt16BigEndian(_receiveBuffer.Span.Slice(_rece
 12371116                if (!IsValidCloseStatus(closeStatus))
 12221117                {
 12221118                    await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faulted,
 01119                }
 1120
 151121                if (header.PayloadLength > 2)
 71122                {
 1123                    try
 71124                    {
 71125                        closeStatusDescription = s_textEncoding.GetString(_receiveBuffer.Span.Slice(_receiveBufferOffset
 21126                    }
 51127                    catch (DecoderFallbackException exc)
 51128                    {
 51129                        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.
 791136            _closeStatus = closeStatus;
 791137            _closeStatusDescription = closeStatusDescription;
 1138
 791139            if (!_isServer && _sentCloseFrame)
 31140            {
 31141                await WaitForServerToCloseConnectionAsync(enterMutex: false, cancellationToken).ConfigureAwait(false);
 31142            }
 791143        }
 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
 12031147        {
 12031148            bool mutexEntered = false;
 12031149            Task? task = null;
 1150            try
 12031151            {
 12031152                if (enterMutex)
 341153                {
 341154                    await _receiveMutex.EnterAsync(cancellationToken).ConfigureAwait(false);
 341155                    mutexEntered = true;
 341156                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_receiveMutex);
 341157                }
 1158
 12031159                Debug.Assert(_receiveMutex.IsHeld, $"Expected {nameof(_receiveMutex)} to be held");
 1160
 12031161                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.
 12031167                ValueTask<int> finalReadTask = _stream.ReadAsync(_receiveBuffer, cancellationToken);
 1168
 1169                const int WaitForCloseTimeoutMs = 1_000; // arbitrary amount of time to give the server
 12031170                task = finalReadTask.AsTask();
 1171
 1172#pragma warning disable CA2016 // Token was already provided to the ReadAsync
 12031173                await task.WaitAsync(TimeSpan.FromMilliseconds(WaitForCloseTimeoutMs)).ConfigureAwait(false);
 1174#pragma warning restore CA2016
 12031175            }
 01176            catch
 01177            {
 01178                if (task is not null)
 01179                {
 01180                    LogExceptions(task);
 01181                }
 01182                Abort();
 01183            }
 1184            finally
 12031185            {
 12031186                if (mutexEntered)
 341187                {
 341188                    _receiveMutex.Exit();
 341189                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(_receiveMutex);
 341190                }
 12031191            }
 12031192        }
 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)
 70521198        {
 70521199            Debug.Assert(_receiveMutex.IsHeld, $"Caller should hold the {nameof(_receiveMutex)}");
 1200
 70521201            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this);
 1202
 1203            // Consume any (optional) payload associated with the ping/pong.
 70521204            if (header.PayloadLength > 0 && _receiveBufferCount < header.PayloadLength)
 2441205            {
 2441206                await EnsureBufferContainsAsync((int)header.PayloadLength, cancellationToken).ConfigureAwait(false);
 2241207            }
 1208
 70321209            bool processPing = header.Opcode == MessageOpcode.Ping;
 1210
 70321211            bool processPong = header.Opcode == MessageOpcode.Pong && _keepAlivePingState is not null
 70321212                && header.PayloadLength == KeepAlivePingState.PingPayloadSize;
 1213
 70321214            if ((processPing || processPong) && _isServer)
 101215            {
 101216                ApplyMask(_receiveBuffer.Span.Slice(_receiveBufferOffset, (int)header.PayloadLength), header.Mask, 0);
 101217            }
 1218
 1219            // If this was a ping, send back a pong response.
 70321220            if (processPing)
 67091221            {
 67091222                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Processing incoming Ping");
 1223
 67091224                await SendFrameAsync(
 67091225                    MessageOpcode.Pong,
 67091226                    endOfMessage: true,
 67091227                    disableCompression: true,
 67091228                    _receiveBuffer.Slice(_receiveBufferOffset, (int)header.PayloadLength),
 67091229                    cancellationToken).ConfigureAwait(false);
 67091230            }
 3231231            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
 3231242            {
 3231243                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Received Unsolicited Pong. Skipping.");
 3231244            }
 1245
 1246            // Regardless of whether it was a ping or pong, we no longer need the payload.
 70321247            if (header.PayloadLength > 0)
 4031248            {
 4031249                ConsumeFromBuffer((int)header.PayloadLength);
 4031250            }
 70321251        }
 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)
 12371257        {
 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
 12371264            if (closeStatus < (WebSocketCloseStatus)1000 || closeStatus >= (WebSocketCloseStatus)5000)
 12141265            {
 12141266                return false;
 1267            }
 1268
 231269            if (closeStatus >= (WebSocketCloseStatus)3000)
 151270            {
 151271                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            }
 12371293        }
 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
 61581302        {
 61581303            Debug.Assert(_receiveMutex.IsHeld, $"Caller should hold the {nameof(_receiveMutex)}");
 1304
 61581305            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, errorMessage);
 1306
 1307            // Caller holds _receiveMutex; don't re-enter it for the EOF wait.
 61581308            if (!_sentCloseFrame)
 40651309            {
 40651310                await CloseOutputAsyncCore(closeStatus, string.Empty, enterReceiveMutex: false, cancellationToken: defau
 40651311            }
 1312
 1313            // Dump our receive buffer; we're in a bad state to do any further processing
 61581314            _receiveBufferCount = 0;
 1315
 1316            // Let the caller know we've failed
 61581317            throw errorMessage != null ?
 61581318                new WebSocketException(error, errorMessage, innerException) :
 61581319                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)
 1663491326        {
 1663491327            Debug.Assert(_receiveBufferCount >= 2, "Expected to at least have the first two bytes of the header.");
 1328
 1663491329            MessageHeader header = default;
 1663491330            Span<byte> receiveBufferSpan = _receiveBuffer.Span;
 1331
 1663491332            header.Fin = (receiveBufferSpan[_receiveBufferOffset] & 0x80) != 0;
 1663491333            bool reservedSet = (receiveBufferSpan[_receiveBufferOffset] & 0b_0011_0000) != 0;
 1663491334            header.Opcode = (MessageOpcode)(receiveBufferSpan[_receiveBufferOffset] & 0xF);
 1663491335            header.Compressed = (receiveBufferSpan[_receiveBufferOffset] & 0b_0100_0000) != 0;
 1336
 1663491337            bool masked = (receiveBufferSpan[_receiveBufferOffset + 1] & 0x80) != 0;
 1663491338            header.PayloadLength = receiveBufferSpan[_receiveBufferOffset + 1] & 0x7F;
 1339
 1663491340            ConsumeFromBuffer(2);
 1341
 1342            // Read the remainder of the payload length, if necessary
 1663491343            if (header.PayloadLength == 126)
 601344            {
 601345                Debug.Assert(_receiveBufferCount >= 2, "Expected to have two bytes for the payload length.");
 601346                header.PayloadLength = BinaryPrimitives.ReadUInt16BigEndian(receiveBufferSpan.Slice(_receiveBufferOffset
 601347                ConsumeFromBuffer(2);
 601348            }
 1662891349            else if (header.PayloadLength == 127)
 2921350            {
 2921351                Debug.Assert(_receiveBufferCount >= 8, "Expected to have eight bytes for the payload length.");
 2921352                header.PayloadLength = BinaryPrimitives.ReadInt64BigEndian(receiveBufferSpan.Slice(_receiveBufferOffset)
 2921353                ConsumeFromBuffer(8);
 2921354            }
 1355
 1663491356            if (reservedSet)
 12391357            {
 12391358                resultHeader = default;
 12391359                return SR.net_Websockets_ReservedBitsSet;
 1360            }
 1361
 1651101362            if (header.PayloadLength < 0)
 1081363            {
 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
 1081366                resultHeader = default;
 1081367                return SR.net_Websockets_InvalidPayloadLength;
 1368            }
 1369
 1650021370            if (header.Compressed && _inflater is null)
 231371            {
 231372                resultHeader = default;
 231373                return SR.net_Websockets_PerMessageCompressedFlagWhenNotEnabled;
 1374            }
 1375
 1649791376            if (masked)
 10301377            {
 10301378                if (!_isServer)
 4771379                {
 4771380                    resultHeader = default;
 4771381                    return SR.net_Websockets_ClientReceivedMaskedFrame;
 1382                }
 5531383                header.Mask = CombineMaskBytes(receiveBufferSpan, _receiveBufferOffset);
 1384
 1385                // Consume the mask bytes
 5531386                ConsumeFromBuffer(4);
 5531387            }
 1639491388            else if (_isServer)
 911389            {
 911390                resultHeader = default;
 911391                return SR.net_Websockets_ServerReceivedUnmaskedFrame;
 1392            }
 1393
 1394            // Do basic validation of the header
 1644111395            switch (header.Opcode)
 1396            {
 1397                case MessageOpcode.Continuation:
 1461041398                    if (_lastReceiveHeader.Fin)
 2461399                    {
 1400                        // Can't continue from a final message
 2461401                        resultHeader = default;
 2461402                        return SR.net_Websockets_ContinuationFromFinalFrame;
 1403                    }
 1458581404                    if (header.Compressed)
 1341405                    {
 1406                        // Must not mark continuations as compressed
 1341407                        resultHeader = default;
 1341408                        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.
 1457241413                    header.Compressed = _lastReceiveHeader.Compressed;
 1457241414                    break;
 1415
 1416                case MessageOpcode.Binary:
 1417                case MessageOpcode.Text:
 85581418                    if (!_lastReceiveHeader.Fin)
 4061419                    {
 1420                        // Must continue from a non-final message
 4061421                        resultHeader = default;
 4061422                        return SR.net_Websockets_NonContinuationAfterNonFinalFrame;
 1423                    }
 81521424                    break;
 1425
 1426                case MessageOpcode.Close:
 1427                case MessageOpcode.Ping:
 1428                case MessageOpcode.Pong:
 86171429                    if (header.PayloadLength > MaxControlPayloadLength || !header.Fin)
 1981430                    {
 1431                        // Invalid control messgae
 1981432                        resultHeader = default;
 1981433                        return SR.net_Websockets_InvalidControlMessage;
 1434                    }
 84191435                    break;
 1436
 1437                default:
 1438                    // Unknown opcode
 11321439                    resultHeader = default;
 11321440                    return SR.Format(SR.net_Websockets_UnknownOpcode, header.Opcode);
 1441            }
 1442
 1443            // Return the read header
 1622951444            header.Processed = header.PayloadLength == 0 && !header.Compressed;
 1622951445            resultHeader = header;
 1622951446            return null;
 1663491447        }
 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
 45101454        {
 45101455            if (NetEventSource.Log.IsEnabled()) NetEventSource.CloseAsyncPrivateStarted(this);
 1456            try
 45101457            {
 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.
 45101460                if (!_sentCloseFrame)
 44801461                {
 44801462                    await SendCloseFrameAsync(closeStatus, statusDescription, cancellationToken).ConfigureAwait(false);
 44801463                }
 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.
 45101468                Debug.Assert(
 45101469                    State == WebSocketState.CloseSent ||
 45101470                    State == WebSocketState.Closed ||
 45101471                    State == WebSocketState.Aborted,
 45101472                    $"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".
 45101477                if (State == WebSocketState.CloseSent)
 44821478                {
 44821479                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, "Waiting for a close frame");
 1480
 1481                    // Wait until we've received a close response
 44821482                    byte[] closeBuffer = ArrayPool<byte>.Shared.Rent(MaxMessageHeaderLength + MaxControlPayloadLength);
 1483                    try
 44821484                    {
 1485                        // Loop until we've received a close frame.
 1172271486                        while (!_receivedCloseFrame)
 1172241487                        {
 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.
 1172241492                            ValueTask<ValueWebSocketReceiveResult> receiveTask = default;
 1493                            try
 1172241494                            {
 1172241495                                await _receiveMutex.EnterAsync(cancellationToken).ConfigureAwait(false);
 1172241496                                if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexEntered(_receiveMutex);
 1497
 1498                                try
 1172241499                                {
 1172241500                                    if (!_receivedCloseFrame)
 1172241501                                    {
 1172241502                                        receiveTask = ReceiveAsyncPrivate<ValueWebSocketReceiveResult>(closeBuffer, canc
 1172241503                                    }
 1172241504                                }
 1505                                finally
 1172241506                                {
 1172241507                                    _receiveMutex.Exit();
 1172241508                                    if (NetEventSource.Log.IsEnabled()) NetEventSource.MutexExited(_receiveMutex);
 1172241509                                }
 1172241510                            }
 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.
 1172241520                            await receiveTask.ConfigureAwait(false);
 1127451521                        }
 31522                    }
 1523                    finally
 44821524                    {
 44821525                        ArrayPool<byte>.Shared.Return(closeBuffer);
 44821526                    }
 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            }
 44791541            catch (Exception exc)
 44791542            {
 44791543                if (NetEventSource.Log.IsEnabled()) NetEventSource.TraceException(this, exc);
 44791544                throw;
 1545            }
 1546            finally
 45101547            {
 45101548                if (NetEventSource.Log.IsEnabled()) NetEventSource.CloseAsyncPrivateCompleted(this);
 45101549            }
 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
 86271558        {
 1559            // Close payload is two bytes containing the close status followed by a UTF8-encoding of the status descript
 1560
 86271561            byte[]? buffer = null;
 1562            try
 86271563            {
 86271564                int count = 2;
 86271565                if (string.IsNullOrEmpty(closeStatusDescription))
 44541566                {
 44541567                    buffer = ArrayPool<byte>.Shared.Rent(count);
 44541568                }
 1569                else
 41731570                {
 41731571                    count += s_textEncoding.GetByteCount(closeStatusDescription);
 41731572                    buffer = ArrayPool<byte>.Shared.Rent(count);
 41731573                    int encodedLength = s_textEncoding.GetBytes(closeStatusDescription, 0, closeStatusDescription.Length
 41731574                    Debug.Assert(count - 2 == encodedLength, $"{nameof(s_textEncoding.GetByteCount)} and {nameof(s_textE
 41731575                }
 1576
 86271577                BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)closeStatus);
 1578
 86271579                await SendFrameAsync(MessageOpcode.Close, endOfMessage: true, disableCompression: true, new Memory<byte>
 86271580            }
 1581            finally
 86271582            {
 86271583                if (buffer != null)
 86271584                {
 86271585                    ArrayPool<byte>.Shared.Return(buffer);
 86271586                }
 86271587            }
 1588
 86271589            lock (StateUpdateLock)
 86271590            {
 86271591                _sentCloseFrame = true;
 86271592                WebSocketState state = _state;
 86271593                if (_receivedCloseFrame && state < WebSocketState.Closed)
 13041594                {
 13041595                    _state = WebSocketState.Closed;
 13041596                }
 73231597                else if (state < WebSocketState.CloseSent)
 73231598                {
 73231599                    _state = WebSocketState.CloseSent;
 73231600                }
 1601
 86271602                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"State transition from {state} to {_stat
 86271603            }
 86271604        }
 1605
 1606        private void ConsumeFromBuffer(int count)
 1820191607        {
 1820191608            Debug.Assert(count >= 0, $"Expected non-negative {nameof(count)}, got {count}");
 1820191609            Debug.Assert(count <= _receiveBufferCount, $"Trying to consume {count}, which is more than exists {_receiveB
 1820191610            _receiveBufferCount -= count;
 1820191611            _receiveBufferOffset += count;
 1820191612        }
 1613
 1614        [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))]
 1615        [RuntimeAsyncMethodGeneration(false)]
 1616        private async ValueTask EnsureBufferContainsAsync(int minimumRequiredBytes, CancellationToken cancellationToken)
 160351617        {
 160351618            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.
 160351621            if (_receiveBufferCount < minimumRequiredBytes)
 154921622            {
 1623                // If there's any data in the buffer, shift it down.
 154921624                if (_receiveBufferCount > 0)
 32101625                {
 32101626                    _receiveBuffer.Span.Slice(_receiveBufferOffset, _receiveBufferCount).CopyTo(_receiveBuffer.Span);
 32101627                }
 154921628                _receiveBufferOffset = 0;
 1629
 154921630                int bytesToRead = minimumRequiredBytes - _receiveBufferCount;
 154921631                int numRead = await _stream.ReadAtLeastAsync(
 154921632                    _receiveBuffer.Slice(_receiveBufferCount), bytesToRead, throwOnEndOfStream: false, cancellationToken
 154921633                _receiveBufferCount += numRead;
 1634
 154921635                if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"bytesRead={numRead}");
 1636
 154921637                if (numRead < bytesToRead)
 19561638                {
 19561639                    ThrowEOFUnexpected();
 01640                }
 135361641                _keepAlivePingState?.OnDataReceived();
 135361642            }
 140791643        }
 1644
 1645        private void ThrowEOFUnexpected()
 27311646        {
 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.
 27311650            ThrowIfDisposed();
 27311651            throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely);
 1652        }
 1653
 1654        /// <summary>Gets a send buffer from the pool.</summary>
 1655        private void AllocateSendBuffer(int minLength)
 182771656        {
 182771657            Debug.Assert(_sendBuffer == null); // would only fail if had some catastrophic error previously that prevent
 182771658            _sendBuffer = ArrayPool<byte>.Shared.Rent(minLength);
 182771659        }
 1660
 1661        /// <summary>Releases the send buffer to the pool.</summary>
 1662        private void ReleaseSendBuffer()
 182771663        {
 182771664            Debug.Assert(_sendMutex.IsHeld, $"Caller should hold the {nameof(_sendMutex)}");
 1665
 182771666            if (_sendBuffer is byte[] toReturn)
 182771667            {
 182771668                _sendBuffer = null;
 182771669                ArrayPool<byte>.Shared.Return(toReturn);
 182771670            }
 182771671        }
 1672
 1673        private static int CombineMaskBytes(ReadOnlySpan<byte> buffer, int maskOffset) =>
 119011674            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)
 113481683        {
 113481684            Debug.Assert(maskOffsetIndex < MaskLength, $"Unexpected {nameof(maskOffsetIndex)}: {maskOffsetIndex}");
 113481685            Debug.Assert(mask.Length >= MaskLength + maskOffset, $"Unexpected inputs: {mask.Length}, {maskOffset}");
 113481686            return ApplyMask(toMask, CombineMaskBytes(mask, maskOffset), maskOffsetIndex);
 113481687        }
 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)
 121501695        {
 121501696            Debug.Assert(maskIndex < sizeof(int));
 1697
 121501698            if (toMask.Length >= sizeof(int))
 53791699            {
 53791700                int rolledMask = BitConverter.IsLittleEndian ?
 53791701                    (int)BitOperations.RotateRight((uint)mask, maskIndex * 8) :
 53791702                    (int)BitOperations.RotateLeft((uint)mask, maskIndex * 8);
 1703
 1704                // Process Vector<byte>.Count bytes at a time.
 53791705                if (Vector.IsHardwareAccelerated && toMask.Length >= Vector<byte>.Count)
 1531706                {
 1531707                    Vector<byte> maskVector = Vector.AsVectorByte(new Vector<int>(rolledMask));
 1708                    do
 1931709                    {
 1931710                        (new Vector<byte>(toMask) ^ maskVector).CopyTo(toMask);
 1931711                        toMask = toMask.Slice(Vector<byte>.Count);
 1931712                    }
 1931713                    while (toMask.Length >= Vector<byte>.Count);
 1531714                }
 1715
 1716                // Process 4 bytes at a time.
 251521717                while (toMask.Length >= sizeof(int))
 197731718                {
 197731719                    BitConverter.TryWriteBytes(toMask, BitConverter.ToInt32(toMask) ^ rolledMask);
 197731720                    toMask = toMask.Slice(sizeof(int));
 197731721                }
 53791722            }
 1723
 1724            // Process 1 byte at a time, using the mask byte at native memory offset maskIndex.
 751241725            for (int i = 0; i < toMask.Length; i++)
 254121726            {
 254121727                toMask[i] ^= (byte)(mask >> ((BitConverter.IsLittleEndian ? maskIndex : 3 - maskIndex) * 8));
 254121728                maskIndex = (maskIndex + 1) & 3;
 254121729            }
 1730
 121501731            return maskIndex;
 121501732        }
 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
 1987321755        private void ThrowIfDisposed() => ThrowIfInvalidState(validStates: ManagedWebSocketStates.All);
 1756
 1757        private void ThrowIfInvalidState(ManagedWebSocketStates validStates)
 2709531758        {
 2709531759            bool disposed = _disposed;
 2709531760            WebSocketState state = _state;
 2709531761            Exception? keepAliveException = null;
 1762
 2709531763            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
 2709531774            if (NetEventSource.Log.IsEnabled()) NetEventSource.Trace(this, $"_state={state}, _disposed={disposed}, _keep
 1775
 2709531776            WebSocketStateHelper.ThrowIfInvalidState(state, disposed, keepAliveException, validStates);
 2708301777        }
 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)
 739801783        {
 1784            // If no prior segment spilled over and this one is the last, we can validate it efficiently as a complete m
 739801785            if (endOfMessage && !state.SequenceInProgress)
 1211786            {
 1211787                return Utf8.IsValid(span);
 1788            }
 1789
 1479981790            for (int i = 0; i < span.Length;)
 91091791            {
 1792                // Have we started a character sequence yet?
 91091793                if (!state.SequenceInProgress)
 90091794                {
 1795                    // Skip past ASCII bytes.
 90091796                    int firstNonAscii = span.Slice(i).IndexOfAnyExceptInRange((byte)0, (byte)127);
 90091797                    if (firstNonAscii < 0)
 80151798                    {
 80151799                        break;
 1800                    }
 9941801                    i += firstNonAscii;
 1802
 1803                    // The first byte tells us how many bytes are in the sequence.
 9941804                    state.SequenceInProgress = true;
 9941805                    byte b = span[i];
 9941806                    i++;
 9941807                    Debug.Assert((b & 0x80) != 0, "Should have already skipped past ASCII");
 9941808                    if ((b & 0xC0) == 0x80)
 1811809                    {
 1810                        // Misplaced 10bbbbbb continuation byte. This cannot be the first byte.
 1811811                        return false;
 1812                    }
 8131813                    else if ((b & 0xE0) == 0xC0) // 110bbbbb 10bbbbbb
 2251814                    {
 2251815                        state.AdditionalBytesExpected = 1;
 2251816                        state.CurrentDecodeBits = b & 0x1F;
 2251817                        state.ExpectedValueMin = 0x80;
 2251818                    }
 5881819                    else if ((b & 0xF0) == 0xE0) // 1110bbbb 10bbbbbb 10bbbbbb
 201820                    {
 201821                        state.AdditionalBytesExpected = 2;
 201822                        state.CurrentDecodeBits = b & 0xF;
 201823                        state.ExpectedValueMin = 0x800;
 201824                    }
 5681825                    else if ((b & 0xF8) == 0xF0) // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
 601826                    {
 601827                        state.AdditionalBytesExpected = 3;
 601828                        state.CurrentDecodeBits = b & 0x7;
 601829                        state.ExpectedValueMin = 0x10000;
 601830                    }
 1831                    else // 111110bb & 1111110b & 11111110 && 11111111 are not valid
 5081832                    {
 5081833                        return false;
 1834                    }
 3051835                }
 1836
 5951837                while (state.AdditionalBytesExpected > 0 && i < span.Length)
 3071838                {
 3071839                    byte b = span[i];
 3071840                    if ((b & 0xC0) != 0x80)
 1151841                    {
 1151842                        return false;
 1843                    }
 1844
 1921845                    i++;
 1921846                    state.AdditionalBytesExpected--;
 1847
 1848                    // Each continuation byte carries 6 bits of data 0x10bbbbbb.
 1921849                    state.CurrentDecodeBits = (state.CurrentDecodeBits << 6) | (b & 0x3F);
 1850
 1921851                    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
 1911857                    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                    }
 1901862                }
 1863
 2881864                if (state.AdditionalBytesExpected == 0)
 1791865                {
 1791866                    state.SequenceInProgress = false;
 1791867                    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                    }
 1711872                }
 2801873            }
 1874
 730451875            return !endOfMessage || !state.SequenceInProgress;
 739801876        }
 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
 29691925        {
 29691926            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);
 29691936        }
 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>
 6935601968            internal bool Processed { get; set; }
 1969
 1970            /// <summary>
 1971            /// Returns if message has been received and processed.
 1972            /// </summary>
 2424341973            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)