/// <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)
            {
                // this is in response to a close handshake initiated by the remote instance
                ArraySegment <byte> closePayload = new ArraySegment <byte>(buffer.Array, buffer.Offset, frame.Count);
                _state = WebSocketState.CloseReceived;
                Events.Log.CloseHandshakeRespond(_guid, frame.CloseStatus, frame.CloseStatusDescription);

                using (MemoryStream 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));
        }
        /// NOTE: pong payload must be 125 bytes or less
        /// Pong should contain the same payload as the ping
        private async Task SendPongAsync(ArraySegment <byte> payload, CancellationToken cancellationToken)
        {
            // as per websocket spec
            if (payload.Count > MAX_PING_PONG_PAYLOAD_LEN)
            {
                Exception ex = new InvalidOperationException($"Max ping message size {MAX_PING_PONG_PAYLOAD_LEN} exceeded: {payload.Count}");
                await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.ProtocolError, ex.Message, ex);

                throw ex;
            }

            try
            {
                if (_state == WebSocketState.Open)
                {
                    using (MemoryStream stream = _recycledStreamFactory())
                    {
                        WebSocketFrameWriter.Write(WebSocketOpCode.Pong, payload, stream, true, _isClient);
                        Events.Log.SendingFrame(_guid, WebSocketOpCode.Pong, true, payload.Count, false);
                        await WriteStreamToNetwork(stream, cancellationToken);
                    }
                }
            }
            catch (Exception ex)
            {
                await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.EndpointUnavailable, "Unable to send Pong response", ex);

                throw;
            }
        }
        /// <summary>
        /// Send data to the web socket
        /// </summary>
        /// <param name="buffer">the buffer containing data to send</param>
        /// <param name="messageType">The message type. Can be Text or Binary</param>
        /// <param name="endOfMessage">True if this message is a standalone message (this is the norm)
        /// If it is a multi-part message then false (and true for the last message)</param>
        /// <param name="cancellationToken">the cancellation token</param>
        public override async Task SendAsync(ArraySegment <byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
        {
            using (MemoryStream stream = _recycledStreamFactory())
            {
                WebSocketOpCode opCode = GetOppCode(messageType);

                if (_usePerMessageDeflate)
                {
                    // NOTE: Compression is currently work in progress and should NOT be used in this library.
                    // The code below is very inefficient for small messages. Ideally we would like to have some sort of moving window
                    // of data to get the best compression. And we don't want to create new buffers which is bad for GC.
                    using (MemoryStream temp = new MemoryStream())
                    {
                        DeflateStream deflateStream = new DeflateStream(temp, CompressionMode.Compress);
                        deflateStream.Write(buffer.Array, buffer.Offset, buffer.Count);
                        deflateStream.Flush();
                        var compressedBuffer = new ArraySegment <byte>(temp.ToArray());
                        WebSocketFrameWriter.Write(opCode, compressedBuffer, stream, endOfMessage, _isClient);
                        Events.Log.SendingFrame(_guid, opCode, endOfMessage, compressedBuffer.Count, true);
                    }
                }
                else
                {
                    WebSocketFrameWriter.Write(opCode, buffer, stream, endOfMessage, _isClient);
                    Events.Log.SendingFrame(_guid, opCode, endOfMessage, buffer.Count, false);
                }

                await WriteStreamToNetwork(stream, cancellationToken);

                _isContinuationFrame = !endOfMessage;
            }
        }
        /// <summary>
        /// Call this automatically from server side each keepAliveInterval period
        /// NOTE: ping payload must be 125 bytes or less
        /// </summary>
        public async Task SendPingAsync(ArraySegment <byte> payload, CancellationToken cancellationToken)
        {
            if (payload.Count > MAX_PING_PONG_PAYLOAD_LEN)
            {
                throw new InvalidOperationException($"Cannot send Ping: Max ping message size {MAX_PING_PONG_PAYLOAD_LEN} exceeded: {payload.Count}");
            }

            if (_state == WebSocketState.Open)
            {
                using (MemoryStream stream = _recycledStreamFactory())
                {
                    WebSocketFrameWriter.Write(WebSocketOpCode.Ping, payload, stream, true, _isClient);
                    Events.Log.SendingFrame(_guid, WebSocketOpCode.Ping, true, payload.Count, false);
                    await WriteStreamToNetwork(stream, cancellationToken);
                }
            }
        }
        /// <summary>
        /// Polite close (use the close handshake)
        /// </summary>
        public override async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
        {
            if (_state == WebSocketState.Open)
            {
                using (MemoryStream stream = _recycledStreamFactory())
                {
                    ArraySegment <byte> buffer = BuildClosePayload(closeStatus, statusDescription);
                    WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, buffer, stream, true, _isClient);
                    Events.Log.CloseHandshakeStarted(_guid, closeStatus, statusDescription);
                    Events.Log.SendingFrame(_guid, WebSocketOpCode.ConnectionClose, true, buffer.Count, true);
                    await WriteStreamToNetwork(stream, cancellationToken);

                    _state = WebSocketState.CloseSent;
                }
            }
            else
            {
                Events.Log.InvalidStateBeforeClose(_guid, _state);
            }
        }
        /// <summary>
        /// Fire and forget close
        /// </summary>
        public override async Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
        {
            if (_state == WebSocketState.Open)
            {
                _state = WebSocketState.Closed; // set this before we write to the network because the write may fail

                using (MemoryStream stream = _recycledStreamFactory())
                {
                    ArraySegment <byte> buffer = BuildClosePayload(closeStatus, statusDescription);
                    WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, buffer, stream, true, _isClient);
                    Events.Log.CloseOutputNoHandshake(_guid, closeStatus, statusDescription);
                    Events.Log.SendingFrame(_guid, WebSocketOpCode.ConnectionClose, true, buffer.Count, true);
                    await WriteStreamToNetwork(stream, cancellationToken);
                }
            }
            else
            {
                Events.Log.InvalidStateBeforeCloseOutput(_guid, _state);
            }

            // cancel pending reads
            _internalReadCts.Cancel();
        }