// We received a ping, send a pong in reply private async Task SendPongReplyAsync(CancellationToken cancellationToken) { await _writeLock.WaitAsync(cancellationToken); try { if (State != WebSocketState.Open) { // Output closed, discard the pong. return; } ArraySegment <byte> dataSegment = new ArraySegment <byte>(_receiveBuffer, _receiveBufferOffset, (int)_frameBytesRemaining); if (_unmaskInput) { // _frameInProgress.Masked == _unmaskInput already verified Utilities.MaskInPlace(_frameInProgress.MaskKey, dataSegment); } int mask = GetNextMask(); FrameHeader header = new FrameHeader(true, Constants.OpCodes.PongFrame, _maskOutput, mask, _frameBytesRemaining); if (_maskOutput) { Utilities.MaskInPlace(mask, dataSegment); } ArraySegment <byte> headerSegment = header.Buffer; await _stream.WriteAsync(headerSegment.Array, headerSegment.Offset, headerSegment.Count, cancellationToken); await _stream.WriteAsync(dataSegment.Array, dataSegment.Offset, dataSegment.Count, cancellationToken); } finally { _writeLock.Release(); } }
private async Task ReadNextFrameAsync(CancellationToken cancellationToken) { await EnsureDataAvailableOrReadAsync(2, cancellationToken); int frameHeaderSize = FrameHeader.CalculateFrameHeaderSize(_receiveBuffer[_receiveBufferOffset + 1]); await EnsureDataAvailableOrReadAsync(frameHeaderSize, cancellationToken); _frameInProgress = new FrameHeader(new ArraySegment <byte>(_receiveBuffer, _receiveBufferOffset, frameHeaderSize)); _receiveBufferOffset += frameHeaderSize; _receiveBufferBytes -= frameHeaderSize; _frameBytesRemaining = _frameInProgress.DataLength; if (_frameInProgress.AreReservedSet()) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Unexpected reserved bits set", cancellationToken); } if (_unmaskInput != _frameInProgress.Masked) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Incorrect masking", cancellationToken); } if (!ValidateOpCode(_frameInProgress.OpCode)) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid opcode: " + _frameInProgress.OpCode, cancellationToken); } if (_frameInProgress.IsControlFrame) { if (_frameBytesRemaining > 125) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid control frame size", cancellationToken); } if (!_frameInProgress.Fin) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Fragmented control frame", cancellationToken); } if (_frameInProgress.OpCode == Constants.OpCodes.PingFrame || _frameInProgress.OpCode == Constants.OpCodes.PongFrame) { // Drain it, should be less than 125 bytes await EnsureDataAvailableOrReadAsync((int)_frameBytesRemaining, cancellationToken); if (_frameInProgress.OpCode == Constants.OpCodes.PingFrame) { await SendPongReplyAsync(cancellationToken); } _receiveBufferOffset += (int)_frameBytesRemaining; _receiveBufferBytes -= (int)_frameBytesRemaining; _frameBytesRemaining = 0; _frameInProgress = null; } } else if (_firstDataOpCode.HasValue && _frameInProgress.OpCode != Constants.OpCodes.ContinuationFrame) { // A data frame is already in progress, but this new frame is not a continuation frame. await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Expected a continuation frame: " + _frameInProgress.OpCode, cancellationToken); } }
public override async Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) { await _writeLock.WaitAsync(cancellationToken); try { ThrowIfDisposed(); ThrowIfOutputClosed(); if (_keepAliveTimer != null) { _keepAliveTimer.Dispose(); } byte[] descriptionBytes = Encoding.UTF8.GetBytes(statusDescription ?? string.Empty); byte[] fullData = new byte[descriptionBytes.Length + 2]; fullData[0] = (byte)((int)closeStatus >> 8); fullData[1] = (byte)closeStatus; Array.Copy(descriptionBytes, 0, fullData, 2, descriptionBytes.Length); int mask = GetNextMask(); if (_maskOutput) { Utilities.MaskInPlace(mask, new ArraySegment<byte>(fullData)); } FrameHeader frameHeader = new FrameHeader(true, Constants.OpCodes.CloseFrame, _maskOutput, mask, fullData.Length); ArraySegment<byte> segment = frameHeader.Buffer; await _stream.WriteAsync(segment.Array, segment.Offset, segment.Count, cancellationToken); await _stream.WriteAsync(fullData, 0, fullData.Length, cancellationToken); if (State == WebSocketState.Open) { _state = WebSocketState.CloseSent; } else if (State == WebSocketState.CloseReceived) { _state = WebSocketState.Closed; _stream.Dispose(); } } finally { _writeLock.Release(); } }
public async override Task <WebSocketReceiveResult> ReceiveAsync(ArraySegment <byte> buffer, CancellationToken cancellationToken) { ThrowIfDisposed(); ThrowIfInputClosed(); ValidateSegment(buffer); // TODO: InvalidOperationException if any receives are currently in progress. // No active frame. Loop because we may be discarding ping/pong frames. while (_frameInProgress == null) { await ReadNextFrameAsync(cancellationToken); } int opCode = _frameInProgress.OpCode; if (opCode == Constants.OpCodes.CloseFrame) { return(await ProcessCloseFrameAsync(cancellationToken)); } // Handle fragmentation, remember the first frame type if (opCode == Constants.OpCodes.ContinuationFrame) { if (!_firstDataOpCode.HasValue) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid continuation frame", cancellationToken); } opCode = _firstDataOpCode.Value; } else { _firstDataOpCode = opCode; } // Make sure there's at least some data in the buffer int bytesToBuffer = (int)Math.Min((long)_receiveBuffer.Length, _frameBytesRemaining); await EnsureDataAvailableOrReadAsync(bytesToBuffer, cancellationToken); // Copy buffered data to the users buffer int bytesToRead = (int)Math.Min((long)buffer.Count, _frameBytesRemaining); int bytesToCopy = Math.Min(bytesToRead, _receiveBufferBytes); Array.Copy(_receiveBuffer, _receiveBufferOffset, buffer.Array, buffer.Offset, bytesToCopy); if (_unmaskInput) { // _frameInProgress.Masked == _unmaskInput already verified Utilities.MaskInPlace(_frameInProgress.MaskKey, ref _dataUnmaskOffset, new ArraySegment <byte>(buffer.Array, buffer.Offset, bytesToCopy)); } WebSocketReceiveResult result; WebSocketMessageType messageType = Utilities.GetMessageType(opCode); if (messageType == WebSocketMessageType.Text && !Utilities.TryValidateUtf8(new ArraySegment <byte>(buffer.Array, buffer.Offset, bytesToCopy), _frameInProgress.Fin, _incomingUtf8MessageState)) { await SendErrorAbortAndThrow(WebSocketCloseStatus.InvalidPayloadData, "Invalid UTF-8", cancellationToken); } if (bytesToCopy == _frameBytesRemaining) { result = new WebSocketReceiveResult(bytesToCopy, messageType, _frameInProgress.Fin); if (_frameInProgress.Fin) { _firstDataOpCode = null; } _frameInProgress = null; _dataUnmaskOffset = 0; } else { result = new WebSocketReceiveResult(bytesToCopy, messageType, false); } _frameBytesRemaining -= bytesToCopy; _receiveBufferBytes -= bytesToCopy; _receiveBufferOffset += bytesToCopy; return(result); }
// We received a ping, send a pong in reply private async Task SendPongReplyAsync(CancellationToken cancellationToken) { await _writeLock.WaitAsync(cancellationToken); try { if (State != WebSocketState.Open) { // Output closed, discard the pong. return; } ArraySegment<byte> dataSegment = new ArraySegment<byte>(_receiveBuffer, _receiveBufferOffset, (int)_frameBytesRemaining); if (_unmaskInput) { // _frameInProgress.Masked == _unmaskInput already verified Utilities.MaskInPlace(_frameInProgress.MaskKey, dataSegment); } int mask = GetNextMask(); FrameHeader header = new FrameHeader(true, Constants.OpCodes.PongFrame, _maskOutput, mask, _frameBytesRemaining); if (_maskOutput) { Utilities.MaskInPlace(mask, dataSegment); } ArraySegment<byte> headerSegment = header.Buffer; await _stream.WriteAsync(headerSegment.Array, headerSegment.Offset, headerSegment.Count, cancellationToken); await _stream.WriteAsync(dataSegment.Array, dataSegment.Offset, dataSegment.Count, cancellationToken); } finally { _writeLock.Release(); } }
private async Task ReadNextFrameAsync(CancellationToken cancellationToken) { await EnsureDataAvailableOrReadAsync(2, cancellationToken); int frameHeaderSize = FrameHeader.CalculateFrameHeaderSize(_receiveBuffer[_receiveBufferOffset + 1]); await EnsureDataAvailableOrReadAsync(frameHeaderSize, cancellationToken); _frameInProgress = new FrameHeader(new ArraySegment<byte>(_receiveBuffer, _receiveBufferOffset, frameHeaderSize)); _receiveBufferOffset += frameHeaderSize; _receiveBufferBytes -= frameHeaderSize; _frameBytesRemaining = _frameInProgress.DataLength; if (_frameInProgress.AreReservedSet()) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Unexpected reserved bits set", cancellationToken); } if (_unmaskInput != _frameInProgress.Masked) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Incorrect masking", cancellationToken); } if (!ValidateOpCode(_frameInProgress.OpCode)) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid opcode: " + _frameInProgress.OpCode, cancellationToken); } if (_frameInProgress.IsControlFrame) { if (_frameBytesRemaining > 125) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid control frame size", cancellationToken); } if (!_frameInProgress.Fin) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Fragmented control frame", cancellationToken); } if (_frameInProgress.OpCode == Constants.OpCodes.PingFrame || _frameInProgress.OpCode == Constants.OpCodes.PongFrame) { // Drain it, should be less than 125 bytes await EnsureDataAvailableOrReadAsync((int)_frameBytesRemaining, cancellationToken); if (_frameInProgress.OpCode == Constants.OpCodes.PingFrame) { await SendPongReplyAsync(cancellationToken); } _receiveBufferOffset += (int)_frameBytesRemaining; _receiveBufferBytes -= (int)_frameBytesRemaining; _frameBytesRemaining = 0; _frameInProgress = null; } } else if (_firstDataOpCode.HasValue && _frameInProgress.OpCode != Constants.OpCodes.ContinuationFrame) { // A data frame is already in progress, but this new frame is not a continuation frame. await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Expected a continuation frame: " + _frameInProgress.OpCode, cancellationToken); } }
public async override Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken) { ThrowIfDisposed(); ThrowIfInputClosed(); ValidateSegment(buffer); // TODO: InvalidOperationException if any receives are currently in progress. // No active frame. Loop because we may be discarding ping/pong frames. while (_frameInProgress == null) { await ReadNextFrameAsync(cancellationToken); } int opCode = _frameInProgress.OpCode; if (opCode == Constants.OpCodes.CloseFrame) { return await ProcessCloseFrameAsync(cancellationToken); } // Handle fragmentation, remember the first frame type if (opCode == Constants.OpCodes.ContinuationFrame) { if (!_firstDataOpCode.HasValue) { await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid continuation frame", cancellationToken); } opCode = _firstDataOpCode.Value; } else { _firstDataOpCode = opCode; } // Make sure there's at least some data in the buffer int bytesToBuffer = (int)Math.Min((long)_receiveBuffer.Length, _frameBytesRemaining); await EnsureDataAvailableOrReadAsync(bytesToBuffer, cancellationToken); // Copy buffered data to the users buffer int bytesToRead = (int)Math.Min((long)buffer.Count, _frameBytesRemaining); int bytesToCopy = Math.Min(bytesToRead, _receiveBufferBytes); Array.Copy(_receiveBuffer, _receiveBufferOffset, buffer.Array, buffer.Offset, bytesToCopy); if (_unmaskInput) { // _frameInProgress.Masked == _unmaskInput already verified Utilities.MaskInPlace(_frameInProgress.MaskKey, ref _dataUnmaskOffset, new ArraySegment<byte>(buffer.Array, buffer.Offset, bytesToCopy)); } WebSocketReceiveResult result; WebSocketMessageType messageType = Utilities.GetMessageType(opCode); if (messageType == WebSocketMessageType.Text && !Utilities.TryValidateUtf8(new ArraySegment<byte>(buffer.Array, buffer.Offset, bytesToCopy), _frameInProgress.Fin, _incomingUtf8MessageState)) { await SendErrorAbortAndThrow(WebSocketCloseStatus.InvalidPayloadData, "Invalid UTF-8", cancellationToken); } if (bytesToCopy == _frameBytesRemaining) { result = new WebSocketReceiveResult(bytesToCopy, messageType, _frameInProgress.Fin); if (_frameInProgress.Fin) { _firstDataOpCode = null; } _frameInProgress = null; _dataUnmaskOffset = 0; } else { result = new WebSocketReceiveResult(bytesToCopy, messageType, false); } _frameBytesRemaining -= bytesToCopy; _receiveBufferBytes -= bytesToCopy; _receiveBufferOffset += bytesToCopy; return result; }
private async void SendKeepAliveAsync() { // Check concurrent writes, pings & pongs, or closes if (!_writeLock.Wait(0)) { // Sending real data is better than a ping, discard it. return; } try { if (State == WebSocketState.CloseSent || State >= WebSocketState.Closed) { _keepAliveTimer.Dispose(); return; } int mask = GetNextMask(); FrameHeader frameHeader = new FrameHeader(true, Constants.OpCodes.PingFrame, _maskOutput, mask, PingBuffer.Length); ArraySegment<byte> headerSegment = frameHeader.Buffer; // TODO: CancelationToken / timeout? if (_maskOutput && mask != 0) { byte[] maskedFrame = Utilities.MergeAndMask(mask, headerSegment, new ArraySegment<byte>(PingBuffer)); await _stream.WriteAsync(maskedFrame, 0, maskedFrame.Length); } else { await _stream.WriteAsync(headerSegment.Array, headerSegment.Offset, headerSegment.Count); await _stream.WriteAsync(PingBuffer, 0, PingBuffer.Length); } } catch (Exception) { // TODO: Log exception, this is a background thread. // Shut down, we must be in a faulted state; Abort(); } finally { _writeLock.Release(); } }
public override async Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) { ValidateSegment(buffer); if (messageType != WebSocketMessageType.Binary && messageType != WebSocketMessageType.Text) { // Block control frames throw new ArgumentOutOfRangeException(nameof(messageType), messageType, string.Empty); } // Check concurrent writes, pings & pongs, or closes await _writeLock.WaitAsync(cancellationToken); try { ThrowIfDisposed(); ThrowIfOutputClosed(); int mask = GetNextMask(); int opcode = _isOutgoingMessageInProgress ? Constants.OpCodes.ContinuationFrame : Utilities.GetOpCode(messageType); FrameHeader frameHeader = new FrameHeader(endOfMessage, opcode, _maskOutput, mask, buffer.Count); ArraySegment<byte> headerSegment = frameHeader.Buffer; if (_maskOutput && mask != 0) { // TODO: For larger messages consider using a limited size buffer and masking & sending in segments. byte[] maskedFrame = Utilities.MergeAndMask(mask, headerSegment, buffer); await _stream.WriteAsync(maskedFrame, 0, maskedFrame.Length, cancellationToken); } else { await _stream.WriteAsync(headerSegment.Array, headerSegment.Offset, headerSegment.Count, cancellationToken); await _stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken); } _isOutgoingMessageInProgress = !endOfMessage; } finally { _writeLock.Release(); } }