/// <summary>
        /// Receive PreLogin response from the server.
        /// </summary>
        public void ReceivePreLoginResponse()
        {
            LoggingUtilities.AddEmptyLine();
            LoggingUtilities.WriteLog($" Waiting for PreLogin response.");

            if (this.TdsCommunicator.ReceiveTDSMessage() is TDSPreLoginPacketData response)
            {
                if (response.Options.Exists(opt => opt.Type == TDSPreLoginOptionTokenType.Encryption) && response.Encryption == TDSEncryptionOption.EncryptReq)
                {
                    LoggingUtilities.WriteLog($"  Server requires encryption, enabling encryption.");
                    this.TdsCommunicator.EnableEncryption(this.Server, this.EncryptionProtocol);
                    LoggingUtilities.WriteLog($"  Encryption enabled.");
                }

                if (response.Options.Exists(opt => opt.Type == TDSPreLoginOptionTokenType.FedAuthRequired) && response.FedAuthRequired == true)
                {
                    throw new NotSupportedException("FedAuth is being requested but the client doesn't support FedAuth.");
                }
            }
            else
            {
                throw new InvalidOperationException();
            }

            LoggingUtilities.WriteLog($" PreLogin response received.");
        }
        /// <summary>
        /// Receive Login7 response from the server.
        /// </summary>
        public void ReceiveLogin7Response()
        {
            LoggingUtilities.AddEmptyLine();
            LoggingUtilities.WriteLog($" Waiting for Login7 response.");

            if (this.TdsCommunicator.ReceiveTDSMessage() is TDSTokenStreamPacketData response)
            {
                foreach (var token in response.Tokens)
                {
                    if (token is TDSEnvChangeToken)
                    {
                        var envChangeToken = token as TDSEnvChangeToken;
                        if (envChangeToken.Type == Tokens.EnvChange.TDSEnvChangeType.Routing)
                        {
                            LoggingUtilities.WriteLog($" Client received EnvChange routing token, client is being routed.");
                            this.Server    = envChangeToken.Values["AlternateServer"];
                            this.Port      = int.Parse(envChangeToken.Values["ProtocolProperty"]);
                            this.reconnect = true;
                            LoggingUtilities.WriteLog($" Redirect to {this.Server}:{this.Port}", writeToSummaryLog: true, writeToVerboseLog: false);
                        }
                    }
                    else if (token is TDSErrorToken)
                    {
                        var errorToken = token as TDSErrorToken;
                        LoggingUtilities.WriteLog($" Client received Error token, Number: {errorToken.Number}, State: {errorToken.State}", writeToSummaryLog: true);;
                        LoggingUtilities.WriteLog($"  MsgText: {errorToken.MsgText}");
                        LoggingUtilities.WriteLog($"  Class: {errorToken.Class}");
                        LoggingUtilities.WriteLog($"  ServerName: {errorToken.ServerName}");
                        LoggingUtilities.WriteLog($"  ProcName: {errorToken.ProcName}");
                        LoggingUtilities.WriteLog($"  LineNumber: {errorToken.LineNumber}");

                        if (errorToken.Number == 18456)
                        {
                            throw new Exception("Login failure.");
                        }
                    }
                    else if (token is TDSInfoToken)
                    {
                        var infoToken = token as TDSInfoToken;
                        LoggingUtilities.WriteLog($"  Client received Info token:");

                        LoggingUtilities.WriteLog($"     Number: {infoToken.Number}");
                        LoggingUtilities.WriteLog($"     State: {infoToken.State}");
                        LoggingUtilities.WriteLog($"     Class: {infoToken.Class}");
                        LoggingUtilities.WriteLog($"     MsgText: {infoToken.MsgText}");
                        LoggingUtilities.WriteLog($"     ServerName: {infoToken.ServerName}");
                        LoggingUtilities.WriteLog($"     ProcName: {infoToken.ProcName}");
                        LoggingUtilities.WriteLog($"     LineNumber: {infoToken.LineNumber}");
                    }
                }
            }
            else
            {
                throw new InvalidOperationException();
            }

            LoggingUtilities.WriteLog($" Login7 response received.");
        }
        /// <summary>
        /// Sends PreLogin message to the server.
        /// </summary>
        public void SendPreLogin()
        {
            LoggingUtilities.AddEmptyLine();
            LoggingUtilities.WriteLog($" Building PreLogin message.");
            var tdsMessageBody = new TDSPreLoginPacketData(this.Version);

            tdsMessageBody.AddOption(TDSPreLoginOptionTokenType.Encryption, TDSEncryptionOption.EncryptOff);
            tdsMessageBody.AddOption(TDSPreLoginOptionTokenType.TraceID, new TDSClientTraceID(Guid.NewGuid().ToByteArray(), Guid.NewGuid().ToByteArray(), 0));
            tdsMessageBody.Terminate();

            this.TdsCommunicator.SendTDSMessage(tdsMessageBody);
            LoggingUtilities.WriteLog($" PreLogin message sent.");
        }
        /// <summary>
        /// Sends Login7 message to the server
        /// </summary>
        public void SendLogin7()
        {
            LoggingUtilities.AddEmptyLine();
            LoggingUtilities.WriteLog($" Building Login7 message.");

            var tdsMessageBody = new TDSLogin7PacketData();

            tdsMessageBody.AddOption("HostName", Environment.MachineName);
            tdsMessageBody.AddOption("UserName", this.UserID);
            tdsMessageBody.AddOption("ServerName", this.Server);
            tdsMessageBody.AddOption("Password", this.Password);
            tdsMessageBody.AddOption("Database", this.Database);
            tdsMessageBody.AddOption("CltIntName", "TDSSQLTestClient");

            tdsMessageBody.OptionFlags1.Char      = TDSLogin7OptionFlags1Char.CharsetASCII;
            tdsMessageBody.OptionFlags1.Database  = TDSLogin7OptionFlags1Database.InitDBFatal;
            tdsMessageBody.OptionFlags1.DumpLoad  = TDSLogin7OptionFlags1DumpLoad.DumploadOn;
            tdsMessageBody.OptionFlags1.Float     = TDSLogin7OptionFlags1Float.FloatIEEE754;
            tdsMessageBody.OptionFlags1.SetLang   = TDSLogin7OptionFlags1SetLang.SetLangOn;
            tdsMessageBody.OptionFlags1.ByteOrder = TDSLogin7OptionFlags1ByteOrder.OrderX86;
            tdsMessageBody.OptionFlags1.UseDB     = TDSLogin7OptionFlags1UseDB.UseDBOff;

            tdsMessageBody.OptionFlags2.Language = TDSLogin7OptionFlags2Language.InitLangFatal;
            tdsMessageBody.OptionFlags2.ODBC     = TDSLogin7OptionFlags2ODBC.OdbcOn;
            tdsMessageBody.OptionFlags2.UserType = TDSLogin7OptionFlags2UserType.UserNormal;

            tdsMessageBody.OptionFlags3.ChangePassword           = TDSLogin7OptionFlags3ChangePassword.NoChangeRequest;
            tdsMessageBody.OptionFlags3.UserInstanceProcess      = TDSLogin7OptionFlags3UserInstanceProcess.DontRequestSeparateProcess;
            tdsMessageBody.OptionFlags3.UnknownCollationHandling = TDSLogin7OptionFlags3UnknownCollationHandling.On;
            tdsMessageBody.OptionFlags3.Extension = TDSLogin7OptionFlags3Extension.DoesntExist;

            tdsMessageBody.TypeFlags.OLEDB          = TDSLogin7TypeFlagsOLEDB.On;
            tdsMessageBody.TypeFlags.SQLType        = TDSLogin7TypeFlagsSQLType.DFLT;
            tdsMessageBody.TypeFlags.ReadOnlyIntent = TDSLogin7TypeFlagsReadOnlyIntent.On;

            this.TdsCommunicator.SendTDSMessage(tdsMessageBody);

            LoggingUtilities.WriteLog($" Login7 message sent.");
        }
        /// <summary>
        /// Connect to the server.
        /// </summary>
        public void Connect()
        {
            DateTime connectStartTime   = DateTime.UtcNow;
            bool     preLoginDone       = false;
            var      originalServerName = this.Server;
            var      originalPort       = this.Port;

            LoggingUtilities.WriteLog($"Connect initiated (attempt # {++connectionAttempt}).", writeToSummaryLog: true);

            try
            {
                do
                {
                    preLoginDone   = false;
                    this.reconnect = false;

                    MeasureDNSResolutionTime();
                    this.Client          = new TcpClient(this.Server, this.Port);
                    this.TdsCommunicator = new TDSCommunicator(this.Client.GetStream(), 4096);
                    LoggingUtilities.WriteLog($"  TCP connection open between local {this.Client.Client.LocalEndPoint} and remote {this.Client.Client.RemoteEndPoint}", writeToVerboseLog: false, writeToSummaryLog: true);
                    LoggingUtilities.WriteLog($"  TCP connection open");
                    LoggingUtilities.WriteLog($"   Local endpoint is {this.Client.Client.LocalEndPoint}");
                    LoggingUtilities.WriteLog($"   Remote endpoint is {this.Client.Client.RemoteEndPoint}");
                    connectStartTime = DateTime.UtcNow;
                    this.SendPreLogin();
                    this.ReceivePreLoginResponse();
                    preLoginDone = true;
                    LoggingUtilities.WriteLog($" PreLogin phase took {(int)(DateTime.UtcNow - connectStartTime).TotalMilliseconds} milliseconds.");
                    this.SendLogin7();
                    this.ReceiveLogin7Response();

                    if (this.reconnect)
                    {
                        this.Disconnect();
                        LoggingUtilities.AddEmptyLine();
                        LoggingUtilities.WriteLog($" Routing to: {this.Server}:{this.Port}.");
                    }
                }while (this.reconnect);

                LoggingUtilities.WriteLog($" Connect done.", writeToSummaryLog: true);
            }
            catch (SocketException socketException)
            {
                LoggingUtilities.WriteLog($" Networking error {socketException.NativeErrorCode} while trying to connect to {this.Server}:{this.Port}.", writeToSummaryLog: true);
            }
            catch (Exception ex)
            {
                if (!preLoginDone && DateTime.UtcNow >= connectStartTime.AddSeconds(5))
                {
                    LoggingUtilities.WriteLog($" SNI timeout detected, PreLogin phase was not complete after {(int)(DateTime.UtcNow - connectStartTime).TotalMilliseconds} milliseconds.", writeToSummaryLog: true);
                }
                LoggingUtilities.WriteLog($"Exception:");
                LoggingUtilities.WriteLog($"{ex.Message}");
                if (ex.InnerException != null)
                {
                    LoggingUtilities.WriteLog($"InnerException: {ex.InnerException.Message}");
                }
                //throw ex;
            }
            finally
            {
                this.Server = originalServerName;
                this.Port   = originalPort;
            }
        }