/// <summary>
        /// Called when a Close frame is received
        /// Send a response close frame if applicable
        /// </summary>
        private async Task <WebSocketReceiveResult> RespondToCloseFrame(WebSocketFrame frame, ArraySegment <byte> buffer,
                                                                        CancellationToken token)
        {
            _closeStatus            = frame.CloseStatus;
            _closeStatusDescription = frame.CloseStatusDescription;

            if (_state == WebSocketState.CloseSent)
            {
                // this is a response to close handshake initiated by this instance
                _state = WebSocketState.Closed;
                Events.Log.CloseHandshakeComplete(_guid);
            }
            else if (_state == WebSocketState.Open)
            {
                // do not echo the close payload back to the client, there is no requirement for it in the spec.
                // However, the same CloseStatus as recieved should be sent back.
                var closePayload = new ArraySegment <byte>(new byte[0], 0, 0);
                _state = WebSocketState.CloseReceived;
                Events.Log.CloseHandshakeRespond(_guid, frame.CloseStatus, frame.CloseStatusDescription);

                using (var stream = _recycledStreamFactory())
                {
                    WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, closePayload, stream, true, _isClient);
                    Events.Log.SendingFrame(_guid, WebSocketOpCode.ConnectionClose, true, closePayload.Count, false);
                    await WriteStreamToNetwork(stream, token);
                }
            }
            else
            {
                Events.Log.CloseFrameReceivedInUnexpectedState(_guid, _state, frame.CloseStatus,
                                                               frame.CloseStatusDescription);
            }

            return(new WebSocketReceiveResult(frame.Count, WebSocketMessageType.Close, frame.IsFinBitSet,
                                              frame.CloseStatus, frame.CloseStatusDescription));
        }
        /// <summary>
        /// Receive web socket result
        /// </summary>
        /// <param name="buffer">The buffer to copy data into</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The web socket result details</returns>
        public override async Task <WebSocketReceiveResult> ReceiveAsync(ArraySegment <byte> buffer, CancellationToken cancellationToken)
        {
            try
            {
                // we may receive control frames so reading needs to happen in an infinite loop
                while (true)
                {
                    // allow this operation to be cancelled from iniside OR outside this instance
                    using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_internalReadCts.Token, cancellationToken))
                    {
                        WebSocketFrame frame = null;
                        try
                        {
                            frame = await WebSocketFrameReader.ReadAsync(_stream, buffer, linkedCts.Token);

                            Events.Log.ReceivedFrame(_guid, frame.OpCode, frame.IsFinBitSet, frame.Count);
                        }
                        catch (SocketException)
                        {
                            // do nothing, the socket has been disconnected
                        }
                        catch (InternalBufferOverflowException ex)
                        {
                            await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.MessageTooBig, "Frame too large to fit in buffer. Use message fragmentation", ex);

                            throw;
                        }
                        catch (ArgumentOutOfRangeException ex)
                        {
                            await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.ProtocolError, "Payload length out of range", ex);

                            throw;
                        }
                        catch (EndOfStreamException ex)
                        {
                            await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InvalidPayloadData, "Unexpected end of stream encountered", ex);

                            throw;
                        }
                        catch (OperationCanceledException ex)
                        {
                            await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.EndpointUnavailable, "Operation cancelled", ex);

                            throw;
                        }
                        catch (Exception ex)
                        {
                            await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InternalServerError, "Error reading WebSocket frame", ex);

                            throw;
                        }

                        switch (frame.OpCode)
                        {
                        case WebSocketOpCode.ConnectionClose:
                            return(await RespondToCloseFrame(frame, buffer, linkedCts.Token));

                        case WebSocketOpCode.Ping:
                            ArraySegment <byte> pingPayload = new ArraySegment <byte>(buffer.Array, buffer.Offset, frame.Count);
                            await SendPongAsync(pingPayload, linkedCts.Token);

                            break;

                        case WebSocketOpCode.Pong:
                            ArraySegment <byte> pongBuffer = new ArraySegment <byte>(buffer.Array, frame.Count, buffer.Offset);
                            Pong?.Invoke(this, new PongEventArgs(pongBuffer));
                            break;

                        case WebSocketOpCode.TextFrame:
                            if (!frame.IsFinBitSet)
                            {
                                // continuation frames will follow, record the message type Text
                                _continuationFrameMessageType = WebSocketMessageType.Text;
                            }
                            return(new WebSocketReceiveResult(frame.Count, WebSocketMessageType.Text, frame.IsFinBitSet));

                        case WebSocketOpCode.BinaryFrame:
                            if (!frame.IsFinBitSet)
                            {
                                // continuation frames will follow, record the message type Binary
                                _continuationFrameMessageType = WebSocketMessageType.Binary;
                            }
                            return(new WebSocketReceiveResult(frame.Count, WebSocketMessageType.Binary, frame.IsFinBitSet));

                        case WebSocketOpCode.ContinuationFrame:
                            return(new WebSocketReceiveResult(frame.Count, _continuationFrameMessageType, frame.IsFinBitSet));

                        default:
                            Exception ex = new NotSupportedException($"Unknown WebSocket opcode {frame.OpCode}");
                            await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.ProtocolError, ex.Message, ex);

                            throw ex;
                        }
                    }
                }
            }
            catch (Exception catchAll)
            {
                // Most exceptions will be caught closer to their source to send an appropriate close message (and set the WebSocketState)
                // However, if an unhandled exception is encountered and a close message not sent then send one here
                if (_state == WebSocketState.Open)
                {
                    await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InternalServerError, "Unexpected error reading from WebSocket", catchAll);
                }

                throw;
            }
        }
        /// <summary>
        /// Read a WebSocket frame from the stream
        /// </summary>
        /// <param name="fromStream">The stream to read from</param>
        /// <param name="intoBuffer">The buffer to read into</param>
        /// <param name="cancellationToken">the cancellation token</param>
        /// <returns>A websocket frame</returns>
        public static async Task <WebSocketReadCursor> ReadAsync(Stream fromStream, ArraySegment <byte> intoBuffer, CancellationToken cancellationToken)
        {
            // allocate a small buffer to read small chunks of data from the stream
            var smallBuffer = new ArraySegment <byte>(new byte[8]);

            await BinaryReaderWriter.ReadExactly(2, fromStream, smallBuffer, cancellationToken);

            byte byte1 = smallBuffer.Array[0];
            byte byte2 = smallBuffer.Array[1];

            // process first byte
            byte            finBitFlag  = 0x80;
            byte            opCodeFlag  = 0x0F;
            bool            isFinBitSet = (byte1 & finBitFlag) == finBitFlag;
            WebSocketOpCode opCode      = (WebSocketOpCode)(byte1 & opCodeFlag);

            // read and process second byte
            byte maskFlag     = 0x80;
            bool isMaskBitSet = (byte2 & maskFlag) == maskFlag;
            uint len          = await ReadLength(byte2, smallBuffer, fromStream, cancellationToken);

            int count    = (int)len;
            var minCount = CalculateNumBytesToRead(count, intoBuffer.Count);
            ArraySegment <byte> maskKey = new ArraySegment <byte>();

            try
            {
                // use the masking key to decode the data if needed
                if (isMaskBitSet)
                {
                    maskKey = new ArraySegment <byte>(smallBuffer.Array, 0, WebSocketFrameCommon.MaskKeyLength);
                    await BinaryReaderWriter.ReadExactly(maskKey.Count, fromStream, maskKey, cancellationToken);

                    await BinaryReaderWriter.ReadExactly(minCount, fromStream, intoBuffer, cancellationToken);

                    ArraySegment <byte> payloadToMask = new ArraySegment <byte>(intoBuffer.Array, intoBuffer.Offset, minCount);
                    WebSocketFrameCommon.ToggleMask(maskKey, payloadToMask);
                }
                else
                {
                    await BinaryReaderWriter.ReadExactly(minCount, fromStream, intoBuffer, cancellationToken);
                }
            }
            catch (InternalBufferOverflowException e)
            {
                throw new InternalBufferOverflowException($"Supplied buffer too small to read {0} bytes from {Enum.GetName(typeof(WebSocketOpCode), opCode)} frame", e);
            }

            WebSocketFrame frame;

            if (opCode == WebSocketOpCode.ConnectionClose)
            {
                frame = DecodeCloseFrame(isFinBitSet, opCode, count, intoBuffer, maskKey);
            }
            else
            {
                // note that by this point the payload will be populated
                frame = new WebSocketFrame(isFinBitSet, opCode, count, maskKey);
            }

            return(new WebSocketReadCursor(frame, minCount, count - minCount));
        }