protected virtual async Task CreateSSLConnectionAsync()
        {
            var client = new TcpClient(_parameters.Uri, _parameters.Port)
            {
                ReceiveTimeout = 60000
            };

            var clientCertificates = new X509Certificate2Collection();

            if (_parameters.ClientCertificates != null)
            {
                for (int i = 0; i < _parameters.ClientCertificates.Length; i++)
                {
                    clientCertificates.Add(_parameters.ClientCertificates[i]);
                }
            }

            var sslStream = new SslStream(client.GetStream());
            await sslStream.AuthenticateAsClientAsync(_parameters.Uri, clientCertificates, _parameters.EnabledSslProtocols, false);;

            if (sslStream.IsAuthenticated && sslStream.IsEncrypted)
            {
                _connection = new ConnectionWS
                {
                    Client = client,
                    Stream = sslStream
                };
            }
            else
            {
                throw new Exception("Could not create connection - SSL cert has validation problem.");
            }
        }
        protected virtual Task CreateConnectionAsync()
        {
            var client = new TcpClient(_parameters.Uri, _parameters.Port)
            {
                ReceiveTimeout = 60000
            };
            var stream = client.GetStream();

            _connection = new ConnectionWS
            {
                Client = client,
                Stream = stream
            };

            return(Task.CompletedTask);
        }
        public virtual async Task <bool> DisconnectAsync()
        {
            try
            {
                if (_connection != null)
                {
                    if (_connection.Websocket != null &&
                        _connection.Websocket.State == WebSocketState.Open)
                    {
                        await _connection.Websocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                    }

                    if (_connection.Client.Connected)
                    {
                        _connection.Client.Close();
                        _connection.Client.Dispose();
                    }

                    await FireEventAsync(this, new WSConnectionClientEventArgs
                    {
                        ConnectionEventType = ConnectionEventType.Disconnect,
                        Connection          = _connection
                    });

                    _connection = null;

                    return(true);
                }
            }
            catch (Exception ex)
            {
                await FireEventAsync(this, new WSErrorClientEventArgs
                {
                    Exception  = ex,
                    Message    = "Error in StopAsync()",
                    Connection = _connection
                });
            }

            return(false);
        }
        protected async Task <(string, string[])> ParseAndValidateConnectResponseAsync(IConnectionWS connection, string expectedSecWebSocketAccept)
        {
            while (connection.Client.Connected)
            {
                if (connection.Client.Available > 0)
                {
                    break;
                }
            }

            if (!connection.Client.Connected)
            {
                await DisconnectAsync();

                return(null, null);
            }

            var readBuffer = new byte[connection.Client.Available];

            await connection.Stream.ReadAsync(readBuffer, 0, readBuffer.Length);

            var message = Encoding.UTF8.GetString(readBuffer);

            var messagesSplit = message.Split("\r\n");

            if (messagesSplit.Length <= 0)
            {
                throw new WebSocketException("Not valid handshake");
            }

            // Depending on the underlying sockets implementation and timing, connecting to a server that then
            // immediately closes the connection may either result in an exception getting thrown from the connect
            // earlier, or it may result in getting to here but reading 0 bytes.  If we read 0 bytes and thus have
            // an empty status line, treat it as a connect failure.
            if (string.IsNullOrEmpty(messagesSplit[0]))
            {
                throw new WebSocketException("Connection failure.");
            }

            const string ExpectedStatusStart        = "HTTP/1.1 ";
            const string ExpectedStatusStatWithCode = "HTTP/1.1 101"; // 101 == SwitchingProtocols

            // If the status line doesn't begin with "HTTP/1.1" or isn't long enough to contain a status code, fail.
            if (!messagesSplit[0].StartsWith(ExpectedStatusStart, StringComparison.Ordinal) || messagesSplit[0].Length < ExpectedStatusStatWithCode.Length)
            {
                throw new WebSocketException(WebSocketError.HeaderError, $"Connection failure (status line = '{messagesSplit[0]}').");
            }

            // If the status line doesn't contain a status code 101, or if it's long enough to have a status description
            // but doesn't contain whitespace after the 101, fail.
            if (!messagesSplit[0].StartsWith(ExpectedStatusStatWithCode, StringComparison.Ordinal) ||
                (messagesSplit[0].Length > ExpectedStatusStatWithCode.Length && !char.IsWhiteSpace(messagesSplit[0][ExpectedStatusStatWithCode.Length])))
            {
                throw new WebSocketException(WebSocketError.HeaderError, $"Connection failure (status line = '{messagesSplit[0]}').");
            }

            // Read each response header. Be liberal in parsing the response header, treating
            // everything to the left of the colon as the key and everything to the right as the value, trimming both.
            // For each header, validate that we got the expected value.
            bool   foundUpgrade = false, foundConnection = false, foundSecWebSocketAccept = false;
            string subprotocol       = null;
            var    remainingMessages = new List <string>();;

            for (int i = 1; i < messagesSplit.Length; i++)
            {
                if (string.IsNullOrEmpty(messagesSplit[i]) && messagesSplit.Length >= i + 1)
                {
                    for (int j = i + 1; j < messagesSplit.Length; j++)
                    {
                        if (!string.IsNullOrWhiteSpace(RemoveBytesFromString(messagesSplit[j])))
                        {
                            remainingMessages.Add(RemoveBytesFromString(messagesSplit[j].Trim()));
                        }
                    }

                    break;
                }

                var colonIndex = messagesSplit[i].IndexOf(':');
                if (colonIndex == -1)
                {
                    throw new WebSocketException(WebSocketError.HeaderError);
                }

                var headerName  = SubstringTrim(messagesSplit[i], 0, colonIndex);
                var headerValue = SubstringTrim(messagesSplit[i], colonIndex + 1);

                // The Connection, Upgrade, and SecWebSocketAccept headers are required and with specific values.
                ValidateAndTrackHeader(HttpKnownHeaderNames.Connection, "Upgrade", headerName, headerValue, ref foundConnection);
                ValidateAndTrackHeader(HttpKnownHeaderNames.Upgrade, "websocket", headerName, headerValue, ref foundUpgrade);
                ValidateAndTrackHeader(HttpKnownHeaderNames.SecWebSocketAccept, expectedSecWebSocketAccept, headerName, headerValue, ref foundSecWebSocketAccept);

                // The SecWebSocketProtocol header is optional.  We should only get it with a non-empty value if we requested subprotocols,
                // and then it must only be one of the ones we requested.  If we got a subprotocol other than one we requested (or if we
                // already got one in a previous header), fail. Otherwise, track which one we got.
                if (string.Equals(HttpKnownHeaderNames.SecWebSocketProtocol, headerName, StringComparison.OrdinalIgnoreCase) &&
                    !string.IsNullOrWhiteSpace(headerValue))
                {
                    if (_parameters.RequestedSubProtocols == null)
                    {
                        throw new WebSocketException("Requested sub protocols cannot be empty if server returns sub protocol");
                    }

                    var newSubprotocol = _parameters.RequestedSubProtocols.ToList().Find(requested => string.Equals(requested, headerValue, StringComparison.OrdinalIgnoreCase));
                    if (newSubprotocol == null || subprotocol != null)
                    {
                        throw new WebSocketException(
                                  string.Format("Unsupported sub-protocol '{0}' (expected one of [{1}]).",
                                                subprotocol,
                                                string.Join(", ", _parameters.RequestedSubProtocols)
                                                )
                                  );
                    }
                    subprotocol = newSubprotocol;
                }
            }
            if (!foundUpgrade || !foundConnection || !foundSecWebSocketAccept)
            {
                throw new WebSocketException("Connection failure.");
            }

            return(subprotocol, remainingMessages.ToArray());
        }