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();
            }
        }
        // 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 (_unmaskInput != _frameInProgress.Masked)
            {
                throw new InvalidOperationException("Unmasking settings out of sync with data.");
            }

            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;
            }
        }
        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);
            }

            // Handle fragmentation, remember the first frame type
            int opCode = _frameInProgress.OpCode;
            if (opCode == Constants.OpCodes.ContinuationFrame)
            {
                if (!_firstDataOpCode.HasValue)
                {
                    throw new InvalidOperationException("A continuation can't be the first frame");
                }
                opCode = _firstDataOpCode.Value;
            }
            else
            {
                _firstDataOpCode = opCode;
            }

            if (opCode == Constants.OpCodes.CloseFrame)
            {
                return await ProcessCloseFrameAsync(cancellationToken);
            }

            // 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 (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("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();
            }
        }