diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 926ebf3580..0847907f3b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -765,6 +765,9 @@ Microsoft\Data\SqlClient\TdsParser.cs + + Microsoft\Data\SqlClient\TdsParser.SSPI.cs + Microsoft\Data\SqlClient\TdsParserHelperClasses.cs @@ -826,8 +829,6 @@ - - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.RegisterEncoding.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.RegisterEncoding.cs deleted file mode 100644 index 433e5007c7..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.RegisterEncoding.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Text; - -namespace Microsoft.Data.SqlClient -{ - internal sealed partial class TdsParser - { - static TdsParser() - { - // For CoreCLR, we need to register the ANSI Code Page encoding provider before attempting to get an Encoding from a CodePage - // For a default installation of SqlServer the encoding exchanged during Login is 1252. This encoding is not loaded by default - // See Remarks at https://msdn.microsoft.com/en-us/library/system.text.encodingprovider(v=vs.110).aspx - // SqlClient needs to register the encoding providers to make sure that even basic scenarios work with Sql Server. - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs deleted file mode 100644 index 5809f304fe..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ /dev/null @@ -1,13774 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Buffers; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Data; -using System.Data.SqlTypes; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Security.Authentication; -#if NETFRAMEWORK -using System.Runtime.CompilerServices; -#endif -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using Interop.Common.Sni; -#if NETFRAMEWORK -using Interop.Windows.Sni; -#endif -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; -using Microsoft.Data.Sql; -using Microsoft.Data.SqlClient.DataClassification; -using Microsoft.Data.SqlClient.LocalDb; -using Microsoft.Data.SqlClient.Server; -#if NETFRAMEWORK -using Microsoft.Data.SqlTypes; -#endif -using Microsoft.SqlServer.Server; - -namespace Microsoft.Data.SqlClient -{ - // The TdsParser Object controls reading/writing to the netlib, parsing the tds, - // and surfacing objects to the user. - internal sealed partial class TdsParser - { - private static int _objectTypeCount; // EventSource counter - private readonly SqlClientLogger _logger = new SqlClientLogger(); - - private SspiContextProvider _authenticationProvider; - - internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount); - internal int ObjectID => _objectID; - - /// - /// Verify client encryption possibility. - /// - private bool ClientOSEncryptionSupport => TdsParserStateObjectFactory.Singleton.ClientOSEncryptionSupport; - - // Default state object for parser - internal TdsParserStateObject _physicalStateObj = null; // Default stateObj and connection for Dbnetlib and non-MARS SNI. - - // Also, default logical stateObj and connection for MARS over SNI. - internal TdsParserStateObject _pMarsPhysicalConObj = null; // With MARS enabled, cached physical stateObj and connection. - - // Must keep this around - especially for callbacks on pre-MARS - // ReadAsync which will return if physical connection broken! - // - // Per Instance TDS Parser variables - // - - // Constants - private const int constBinBufferSize = 4096; // Size of the buffer used to read input parameter of type Stream - private const int constTextBufferSize = 4096; // Size of the buffer (in chars) user to read input parameter of type TextReader - - // State variables - internal TdsParserState _state = TdsParserState.Closed; // status flag for connection - - private string _server = ""; // name of server that the parser connects to - - internal volatile bool _fResetConnection = false; // flag to denote whether we are needing to call sp_reset - internal volatile bool _fPreserveTransaction = false; // flag to denote whether we need to preserve the transaction when reseting - - private SqlCollation _defaultCollation; // default collation from the server - - private int _defaultCodePage; - - private int _defaultLCID; - - internal Encoding _defaultEncoding = null; // for sql character data - - private static EncryptionOptions s_sniSupportedEncryptionOption = TdsParserStateObjectFactory.Singleton.EncryptionOptions; - - private EncryptionOptions _encryptionOption = s_sniSupportedEncryptionOption; - - private SqlInternalTransaction _currentTransaction; - private SqlInternalTransaction _pendingTransaction; // pending transaction for 2005 and beyond. - // SQLHOT 483 - // need to hold on to the transaction id if distributed transaction merely rolls back without defecting. - private long _retainedTransactionId = SqlInternalTransaction.NullTransactionId; - - // This counter is used for the entire connection to track the open result count for all - // operations not under a transaction. - private int _nonTransactedOpenResultCount = 0; - - // Connection reference - private SqlInternalConnectionTds _connHandler; - - // Async/Mars variables - private bool _fMARS = false; - - internal bool _loginWithFailover = false; // set to true while connect in failover mode so parser state object can adjust its logic - - internal AutoResetEvent _resetConnectionEvent = null; // Used to serialize executes and call reset on first execute only. - - internal TdsParserSessionPool _sessionPool = null; // initialized only when we're a MARS parser. - - // Version variables - - private bool _is2008 = false; - - private bool _is2012 = false; - - private bool _is2022 = false; - - // SqlStatistics - private SqlStatistics _statistics = null; - - private bool _statisticsIsInTransaction = false; - - // - // STATIC TDS Parser variables - // - - // NIC address caching - private static byte[] s_nicAddress; // cache the NIC address from the registry - - // textptr sequence - private static readonly byte[] s_longDataHeader = { 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; - - // XML metadata substitute sequence - private static readonly byte[] s_xmlMetadataSubstituteSequence = { 0xe7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 }; - - // JSON metadata substitute sequence - private static readonly byte[] s_jsonMetadataSubstituteSequence = { 0xa7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 }; - - // size of Guid (e.g. _clientConnectionId, ActivityId.Id) - private const int GUID_SIZE = 16; - - // now data length is 1 byte - // First bit is 1 indicating client support failover partner with readonly intent - private static readonly byte[] s_featureExtDataAzureSQLSupportFeatureRequest = { 0x01 }; - - // NOTE: You must take the internal connection's _parserLock before modifying this - internal bool _asyncWrite = false; - - /// - /// Get or set if column encryption is supported by the server. - /// - internal bool IsColumnEncryptionSupported { get; set; } = false; - - /// - /// TCE version supported by the server - /// - internal byte TceVersionSupported { get; set; } - - /// - /// Server supports retrying when the enclave CEKs sent by the client do not match what is needed for the query to run. - /// - internal bool AreEnclaveRetriesSupported { get; set; } - - /// - /// Type of enclave being used by the server - /// - internal string EnclaveType { get; set; } - - internal bool isTcpProtocol { get; set; } - internal string FQDNforDNSCache { get; set; } - - /// - /// Get if data classification is enabled by the server. - /// - internal bool IsDataClassificationEnabled => - (DataClassificationVersion != TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED); - - /// - /// Get or set data classification version. A value of 0 means that sensitivity classification is not enabled. - /// - internal int DataClassificationVersion { get; set; } - - private SqlCollation _cachedCollation; - - internal TdsParser(bool MARS, bool fAsynchronous) - { - _fMARS = MARS; // may change during Connect to pre 2005 servers - - _physicalStateObj = TdsParserStateObjectFactory.Singleton.CreateTdsParserStateObject(this); - DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED; - } - - internal SqlInternalConnectionTds Connection - { - get - { - return _connHandler; - } - } - - internal SqlInternalTransaction CurrentTransaction - { - get - { - return _currentTransaction; - } - set - { - Debug.Assert(value == _currentTransaction - || _currentTransaction == null - || value == null - || (_currentTransaction != null && !_currentTransaction.IsLocal), "attempting to change current transaction?"); - - // If there is currently a transaction active, we don't want to - // change it; this can occur when there is a delegated transaction - // and the user attempts to do an API begin transaction; in these - // cases, it's safe to ignore the set. - if ((_currentTransaction == null && value != null) - || (_currentTransaction != null && value == null)) - { - _currentTransaction = value; - } - } - } - - internal int DefaultLCID - { - get - { - return _defaultLCID; - } - } - - internal EncryptionOptions EncryptionOptions - { - get - { - return _encryptionOption; - } - set - { - _encryptionOption = value; - } - } - - internal bool Is2008OrNewer - { - get - { - return _is2008; - } - } - - internal bool MARSOn - { - get - { - return _fMARS; - } - } - - internal SqlInternalTransaction PendingTransaction - { - get - { - return _pendingTransaction; - } - set - { - Debug.Assert(value != null, "setting a non-null PendingTransaction?"); - _pendingTransaction = value; - } - } - - internal string Server - { - get - { - return _server; - } - } - - internal TdsParserState State - { - get - { - return _state; - } - set - { - _state = value; - } - } - - internal SqlStatistics Statistics - { - get - { - return _statistics; - } - set - { - _statistics = value; - } - } - - private bool IncludeTraceHeader - { - get - { - return (_is2012 && SqlClientEventSource.Log.IsEnabled()); - } - } - - internal int IncrementNonTransactedOpenResultCount() - { - // IMPORTANT - this increments the connection wide open result count for all - // operations not under a transaction! Do not call if you intend to modify the - // count for a transaction! - Debug.Assert(_nonTransactedOpenResultCount >= 0, "Unexpected result count state"); - int result = Interlocked.Increment(ref _nonTransactedOpenResultCount); - return result; - } - - internal void DecrementNonTransactedOpenResultCount() - { - // IMPORTANT - this decrements the connection wide open result count for all - // operations not under a transaction! Do not call if you intend to modify the - // count for a transaction! - Interlocked.Decrement(ref _nonTransactedOpenResultCount); - Debug.Assert(_nonTransactedOpenResultCount >= 0, "Unexpected result count state"); - } - - internal void ProcessPendingAck(TdsParserStateObject stateObj) - { - if (stateObj._attentionSent) - { - SqlClientEventSource.Log.TryTraceEvent("TdsParser.ProcessPendingAck | INFO | Connection Object Id {0}, State Obj Id {1}, Processing Attention.", _connHandler.ObjectID, stateObj.ObjectID); - ProcessAttention(stateObj); - } - } - - internal void Connect(ServerInfo serverInfo, - SqlInternalConnectionTds connHandler, - TimeoutTimer timeout, - SqlConnectionString connectionOptions, - bool withFailover) - { - SqlConnectionEncryptOption encrypt = connectionOptions.Encrypt; - bool isTlsFirst = (encrypt == SqlConnectionEncryptOption.Strict); - bool trustServerCert = connectionOptions.TrustServerCertificate; - bool integratedSecurity = connectionOptions.IntegratedSecurity; - SqlAuthenticationMethod authType = connectionOptions.Authentication; - string hostNameInCertificate = connectionOptions.HostNameInCertificate; - string serverCertificateFilename = connectionOptions.ServerCertificate; - - if (_state != TdsParserState.Closed) - { - Debug.Fail("TdsParser.Connect called on non-closed connection!"); - return; - } - - _connHandler = connHandler; - _loginWithFailover = withFailover; - - // Clean up IsSQLDNSCachingSupported flag from previous status - _connHandler.IsSQLDNSCachingSupported = false; - - uint sniStatus = TdsParserStateObjectFactory.Singleton.SNIStatus; - - if (sniStatus != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - Debug.Fail("SNI returned status != success, but no error thrown?"); - } - else - { - SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | Connection Object Id {0}, Authentication Mode: {1}", _connHandler.ObjectID, - authType == SqlAuthenticationMethod.NotSpecified ? SqlAuthenticationMethod.SqlPassword.ToString() : authType.ToString()); - } - - // Encryption is not supported on SQL Local DB - disable it if they have only specified Mandatory - if (connHandler.ConnectionOptions.LocalDBInstance != null && encrypt == SqlConnectionEncryptOption.Mandatory) - { - encrypt = SqlConnectionEncryptOption.Optional; - SqlClientEventSource.Log.TryTraceEvent(" Encryption will be disabled as target server is a SQL Local DB instance."); - } - - _authenticationProvider = null; - - // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server - if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) - { - _authenticationProvider = Connection._sspiContextProvider ?? _physicalStateObj.CreateSspiContextProvider(); - SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | SSPI or Active Directory Authentication Library loaded for SQL Server based integrated authentication"); - } - - // if Strict encryption (i.e. isTlsFirst) is chosen trust server certificate should always be false. - if (isTlsFirst) - { - trustServerCert = false; - } - - byte[] instanceName = null; - - Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point."); - _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin); - _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.InitializeConnection); - - bool fParallel = _connHandler.ConnectionOptions.MultiSubnetFailover; - - FQDNforDNSCache = serverInfo.ResolvedServerName; - - int commaPos = FQDNforDNSCache.IndexOf(",", StringComparison.Ordinal); - if (commaPos != -1) - { - FQDNforDNSCache = FQDNforDNSCache.Substring(0, commaPos); - } - - _connHandler.pendingSQLDNSObject = null; - - // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server - _physicalStateObj.CreatePhysicalSNIHandle( - serverInfo.ExtendedServerName, - timeout, - out instanceName, - out var resolvedServerSpn, - false, - true, - fParallel, - TransparentNetworkResolutionState.DisabledMode, - -1, - _connHandler.ConnectionOptions.IPAddressPreference, - FQDNforDNSCache, - ref _connHandler.pendingSQLDNSObject, - serverInfo.ServerSPN, - integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated, - isTlsFirst, - hostNameInCertificate, - serverCertificateFilename); - - if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - - // Since connect failed, free the unmanaged connection memory. - // HOWEVER - only free this after the netlib error was processed - if you - // don't, the memory for the connection object might not be accurate and thus - // a bad error could be returned (as it was when it was freed to early for me). - _physicalStateObj.Dispose(); - SqlClientEventSource.Log.TryTraceEvent(" Login failure"); - ThrowExceptionAndWarning(_physicalStateObj); - Debug.Fail("SNI returned status != success, but no error thrown?"); - } - - _server = serverInfo.ResolvedServerName; - - if (connHandler.PoolGroupProviderInfo != null) - { - // If we are pooling, check to see if we were processing an - // alias which has changed, which means we need to clean out - // the pool. See Webdata 104293. - // This should not apply to routing, as it is not an alias change, routed connection - // should still use VNN of AlwaysOn cluster as server for pooling purposes. - connHandler.PoolGroupProviderInfo.AliasCheck(serverInfo.PreRoutingServerName == null ? - serverInfo.ResolvedServerName : serverInfo.PreRoutingServerName); - } - _state = TdsParserState.OpenNotLoggedIn; - _physicalStateObj.SniContext = SniContext.Snix_PreLoginBeforeSuccessfulWrite; - _physicalStateObj.TimeoutTime = timeout.LegacyTimerExpire; - - bool marsCapable = false; - - _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.InitializeConnection); - _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake); - - uint result = _physicalStateObj.SniGetConnectionId(ref _connHandler._clientConnectionId); - Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); - - if (_connHandler.pendingSQLDNSObject == null) - { - // for DNS Caching phase 1 - _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); - } - - if (!ClientOSEncryptionSupport) - { - //If encryption is required, an error will be thrown. - if (encrypt != SqlConnectionEncryptOption.Optional) - { - _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - _encryptionOption = EncryptionOptions.NOT_SUP; - } - - // UNDONE - send "" for instance now, need to fix later - SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); - SendPreLoginHandshake(instanceName, encrypt, integratedSecurity, serverCertificateFilename); - - _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake); - _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); - - _physicalStateObj.SniContext = SniContext.Snix_PreLogin; - SqlClientEventSource.Log.TryTraceEvent(" Consuming prelogin handshake"); - PreLoginHandshakeStatus status = ConsumePreLoginHandshake( - encrypt, - trustServerCert, - integratedSecurity, - out marsCapable, - out _connHandler._fedAuthRequired, - isTlsFirst, - serverCertificateFilename); - - if (status == PreLoginHandshakeStatus.InstanceFailure) - { - SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake unsuccessful. Reattempting prelogin handshake"); - _physicalStateObj.Dispose(); // Close previous connection - - // On Instance failure re-connect and flush SNI named instance cache. - _physicalStateObj.SniContext = SniContext.Snix_Connect; - _physicalStateObj.CreatePhysicalSNIHandle( - serverInfo.ExtendedServerName, - timeout, - out instanceName, - out resolvedServerSpn, - true, - true, - fParallel, - TransparentNetworkResolutionState.DisabledMode, - -1, - _connHandler.ConnectionOptions.IPAddressPreference, - FQDNforDNSCache, - ref _connHandler.pendingSQLDNSObject, - serverInfo.ServerSPN, - integratedSecurity, - isTlsFirst, - hostNameInCertificate, - serverCertificateFilename); - - if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - SqlClientEventSource.Log.TryTraceEvent(" Login failure"); - ThrowExceptionAndWarning(_physicalStateObj); - } - - uint retCode = _physicalStateObj.SniGetConnectionId(ref _connHandler._clientConnectionId); - - Debug.Assert(retCode == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); - SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); - - if (_connHandler.pendingSQLDNSObject == null) - { - // for DNS Caching phase 1 - _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); - } - - SendPreLoginHandshake(instanceName, encrypt, integratedSecurity, serverCertificateFilename); - status = ConsumePreLoginHandshake( - encrypt, - trustServerCert, - integratedSecurity, - out marsCapable, - out _connHandler._fedAuthRequired, - isTlsFirst, - serverCertificateFilename); - - // Don't need to check for 7.0 failure, since we've already consumed - // one pre-login packet and know we are connecting to 2000. - if (status == PreLoginHandshakeStatus.InstanceFailure) - { - SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake unsuccessful. Login failure"); - throw SQL.InstanceFailure(); - } - } - SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake successful"); - - if (_authenticationProvider is { }) - { - _authenticationProvider.Initialize(serverInfo, _physicalStateObj, this, resolvedServerSpn.Primary, resolvedServerSpn.Secondary); - } - - if (_fMARS && marsCapable) - { - // if user explicitly disables mars or mars not supported, don't create the session pool - _sessionPool = new TdsParserSessionPool(this); - } - else - { - _fMARS = false; - } - return; - } - - internal void RemoveEncryption() - { - Debug.Assert(_encryptionOption == EncryptionOptions.LOGIN, "Invalid encryption option state"); - - uint error = _physicalStateObj.DisableSsl(); - - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - // create a new packet encryption changes the internal packet size Bug# 228403 - _physicalStateObj.ClearAllWritePackets(); - } - - internal void EnableMars() - { - if (_fMARS) - { - // Cache physical stateObj and connection. - _pMarsPhysicalConObj = _physicalStateObj; - - if (LocalAppContextSwitches.UseManagedNetworking) - _pMarsPhysicalConObj.IncrementPendingCallbacks(); - - uint info = 0; - uint error = _pMarsPhysicalConObj.EnableMars(ref info); - - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - error = _pMarsPhysicalConObj.PostReadAsyncForMars(_physicalStateObj); - if (error != TdsEnums.SNI_SUCCESS_IO_PENDING) - { - Debug.Assert(error != TdsEnums.SNI_SUCCESS, "Unexpected successful read async on physical connection before enabling MARS!"); - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - _physicalStateObj = CreateSession(); // Create and open default MARS stateObj and connection. - } - } - - internal TdsParserStateObject CreateSession() - { - TdsParserStateObject session = TdsParserStateObjectFactory.Singleton.CreateSessionObject(this, _pMarsPhysicalConObj, true); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} created session {1}", ObjectID, session.ObjectID); - return session; - } - - internal TdsParserStateObject GetSession(object owner) - { - TdsParserStateObject session = null; - - // TODO: Ideally, we would not care what we do here -- the session pooler would know whether it should have either one (non-MARS) or many (MARS) sessions. - - if (MARSOn) - { - session = _sessionPool.GetSession(owner); - - Debug.Assert(!session.HasPendingData, "pending data on a pooled MARS session"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} getting session {1} from pool", ObjectID, session.ObjectID); - } - else - { - session = _physicalStateObj; - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} getting physical session {1}", ObjectID, session.ObjectID); - } - Debug.Assert(session._outputPacketNumber == 1, "The packet number is expected to be 1"); - return session; - } - - internal void PutSession(TdsParserStateObject session) - { - session.AssertStateIsClean(); - - if (MARSOn) - { - // This will take care of disposing if the parser is closed - _sessionPool.PutSession(session); - } - else if ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken)) - { - // Parser is closed\broken - dispose the stateObj - Debug.Assert(session == _physicalStateObj, "MARS is off, but session to close is not the _physicalStateObj"); - _physicalStateObj.SniContext = SniContext.Snix_Close; -#if DEBUG - _physicalStateObj.InvalidateDebugOnlyCopyOfSniContext(); -#endif - _physicalStateObj.Dispose(); - } - else - { - // Non-MARS, and session is ok - remove its owner - _physicalStateObj.Owner = null; - } - } - - private void SendPreLoginHandshake( - byte[] instanceName, - SqlConnectionEncryptOption encrypt, - bool integratedSecurity, - string serverCertificateFilename) - { - if (encrypt == SqlConnectionEncryptOption.Strict) - { - //Always validate the certificate when in strict encryption mode - uint info = TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE | TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE | TdsEnums.SNI_SSL_SEND_ALPN_EXTENSION; - - EnableSsl(info, encrypt, integratedSecurity, serverCertificateFilename); - - // Since encryption has already been negotiated, we need to set encryption not supported in - // prelogin so that we don't try to negotiate encryption again during ConsumePreLoginHandshake. - _encryptionOption = EncryptionOptions.NOT_SUP; - } - - // PreLoginHandshake buffer consists of: - // 1) Standard header, with type = MT_PRELOGIN - // 2) Consecutive 5 bytes for each option, (1 byte length, 2 byte offset, 2 byte payload length) - // 3) Consecutive data blocks for each option - - // NOTE: packet data needs to be big endian - not the standard little endian used by - // the rest of the parser. - - _physicalStateObj._outputMessageType = TdsEnums.MT_PRELOGIN; - - // Initialize option offset into payload buffer - // 5 bytes for each option (1 byte length, 2 byte offset, 2 byte payload length) - int offset = (int)PreLoginOptions.NUMOPT * 5 + 1; - - byte[] payload = new byte[(int)PreLoginOptions.NUMOPT * 5 + TdsEnums.MAX_PRELOGIN_PAYLOAD_LENGTH]; - int payloadLength = 0; - - // UNDONE - need to do some length verification to ensure packet does not - // get too big!!! Not beyond it's max length! - - for (int option = (int)PreLoginOptions.VERSION; option < (int)PreLoginOptions.NUMOPT; option++) - { - int optionDataSize = 0; - - // Fill in the option - _physicalStateObj.WriteByte((byte)option); - - // Fill in the offset of the option data - _physicalStateObj.WriteByte((byte)((offset & 0xff00) >> 8)); // send upper order byte - _physicalStateObj.WriteByte((byte)(offset & 0x00ff)); // send lower order byte - - switch (option) - { - case (int)PreLoginOptions.VERSION: - Version systemDataVersion = ADP.GetAssemblyVersion(); - - // Major and minor - payload[payloadLength++] = (byte)(systemDataVersion.Major & 0xff); - payload[payloadLength++] = (byte)(systemDataVersion.Minor & 0xff); - - // Build (Big Endian) - payload[payloadLength++] = (byte)((systemDataVersion.Build & 0xff00) >> 8); - payload[payloadLength++] = (byte)(systemDataVersion.Build & 0xff); - - // Sub-build (Little Endian) - payload[payloadLength++] = (byte)(systemDataVersion.Revision & 0xff); - payload[payloadLength++] = (byte)((systemDataVersion.Revision & 0xff00) >> 8); - offset += 6; - optionDataSize = 6; - break; - - case (int)PreLoginOptions.ENCRYPT: - if (_encryptionOption == EncryptionOptions.NOT_SUP) - { - //If OS doesn't support encryption and encryption is not required, inform server "not supported" by client. - payload[payloadLength] = (byte)EncryptionOptions.NOT_SUP; - } - else - { - // Else, inform server of user request. - if (encrypt == SqlConnectionEncryptOption.Mandatory) - { - payload[payloadLength] = (byte)EncryptionOptions.ON; - _encryptionOption = EncryptionOptions.ON; - } - else - { - payload[payloadLength] = (byte)EncryptionOptions.OFF; - _encryptionOption = EncryptionOptions.OFF; - } - } - - payloadLength += 1; - offset += 1; - optionDataSize = 1; - break; - - case (int)PreLoginOptions.INSTANCE: - int i = 0; - - while (instanceName[i] != 0) - { - payload[payloadLength] = instanceName[i]; - payloadLength++; - i++; - } - - payload[payloadLength] = 0; // null terminate - payloadLength++; - i++; - - offset += i; - optionDataSize = i; - break; - - case (int)PreLoginOptions.THREADID: - int threadID = TdsParserStaticMethods.GetCurrentThreadIdForTdsLoginOnly(); - - payload[payloadLength++] = (byte)((0xff000000 & threadID) >> 24); - payload[payloadLength++] = (byte)((0x00ff0000 & threadID) >> 16); - payload[payloadLength++] = (byte)((0x0000ff00 & threadID) >> 8); - payload[payloadLength++] = (byte)(0x000000ff & threadID); - offset += 4; - optionDataSize = 4; - break; - - case (int)PreLoginOptions.MARS: - payload[payloadLength++] = (byte)(_fMARS ? 1 : 0); - offset += 1; - optionDataSize += 1; - break; - - case (int)PreLoginOptions.TRACEID: - SerializeGuid(_connHandler._clientConnectionId, payload.AsSpan(payloadLength, GUID_SIZE)); - payloadLength += GUID_SIZE; - offset += GUID_SIZE; - optionDataSize = GUID_SIZE; - - ActivityCorrelator.ActivityId actId = ActivityCorrelator.Next(); - SerializeGuid(actId.Id, payload.AsSpan(payloadLength, GUID_SIZE)); - payloadLength += GUID_SIZE; - payload[payloadLength++] = (byte)(0x000000ff & actId.Sequence); - payload[payloadLength++] = (byte)((0x0000ff00 & actId.Sequence) >> 8); - payload[payloadLength++] = (byte)((0x00ff0000 & actId.Sequence) >> 16); - payload[payloadLength++] = (byte)((0xff000000 & actId.Sequence) >> 24); - int actIdSize = GUID_SIZE + sizeof(uint); - offset += actIdSize; - optionDataSize += actIdSize; - SqlClientEventSource.Log.TryTraceEvent(" ClientConnectionID {0}, ActivityID {1}", _connHandler?._clientConnectionId, actId); - break; - - case (int)PreLoginOptions.FEDAUTHREQUIRED: - payload[payloadLength++] = 0x01; - offset += 1; - optionDataSize += 1; - break; - - default: - Debug.Fail("UNKNOWN option in SendPreLoginHandshake"); - break; - } - - // Write data length - _physicalStateObj.WriteByte((byte)((optionDataSize & 0xff00) >> 8)); - _physicalStateObj.WriteByte((byte)(optionDataSize & 0x00ff)); - } - - // Write out last option - to let server know the second part of packet completed - _physicalStateObj.WriteByte((byte)PreLoginOptions.LASTOPT); - - // Write out payload - _physicalStateObj.WriteByteArray(payload, payloadLength, 0); - - // Flush packet - _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); - } - - private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integratedSecurity, string serverCertificateFilename) - { - uint error = 0; - - if (encrypt && !integratedSecurity) - { - // optimization: in case of SQL Authentication and encryption in TDS, set SNI_SSL_IGNORE_CHANNEL_BINDINGS - // to let SNI know that it does not need to allocate/retrieve the Channel Bindings from the SSL context. - // This applies to Native SNI - info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS; - } - - error = _physicalStateObj.EnableSsl(ref info, encrypt == SqlConnectionEncryptOption.Strict, serverCertificateFilename); - - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - SslProtocols protocol = 0; - - // in the case where an async connection is made, encryption is used and Windows Authentication is used, - // wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its - // Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete - // before calling SNISecGenClientContext). -#if NET - if (OperatingSystem.IsWindows()) -#endif - { - error = _physicalStateObj.WaitForSSLHandShakeToComplete(out protocol); - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - } - - string warningMessage = protocol.GetProtocolWarning(); - if (!string.IsNullOrEmpty(warningMessage)) - { - if (!encrypt && LocalAppContextSwitches.SuppressInsecureTlsWarning) - { - // Skip console warning - SqlClientEventSource.Log.TryTraceEvent("{3}", nameof(TdsParser), nameof(EnableSsl), SqlClientLogger.LogLevel.Warning, warningMessage); - } - else - { - // This logs console warning of insecure protocol in use. - _logger.LogWarning(nameof(TdsParser), nameof(EnableSsl), warningMessage); - } - } - - // create a new packet encryption changes the internal packet size Bug# 228403 - _physicalStateObj.ClearAllWritePackets(); - } - - private PreLoginHandshakeStatus ConsumePreLoginHandshake( - SqlConnectionEncryptOption encrypt, - bool trustServerCert, - bool integratedSecurity, - out bool marsCapable, - out bool fedAuthRequired, - bool tlsFirst, - string serverCertificateFilename) - { - // Assign default values - marsCapable = _fMARS; - fedAuthRequired = false; - Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = _physicalStateObj.TryReadNetworkPacket(); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - if (_physicalStateObj._inBytesRead == 0) - { - // If the server did not respond then something has gone wrong and we need to close the connection - _physicalStateObj.AddError(new SqlError(0, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.PreloginError(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - - if (_physicalStateObj.TryProcessHeader() != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - if (_physicalStateObj._inBytesPacket > TdsEnums.MAX_PACKET_SIZE || _physicalStateObj._inBytesPacket <= 0) - { - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); - } - byte[] payload = new byte[_physicalStateObj._inBytesPacket]; - - Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - result = _physicalStateObj.TryReadByteArray(payload, payload.Length); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - if (payload[0] == 0xaa) - { - // If the first byte is 0xAA, we are connecting to a 6.5 or earlier server, which - // is not supported. SQL BU DT 296425 - throw SQL.InvalidSQLServerVersionUnknown(); - } - - int offset = 0; - int payloadOffset = 0; - int payloadLength = 0; - int option = payload[offset++]; - bool serverSupportsEncryption = false; - - while (option != (byte)PreLoginOptions.LASTOPT) - { - switch (option) - { - case (int)PreLoginOptions.VERSION: - payloadOffset = payload[offset++] << 8 | payload[offset++]; - payloadLength = payload[offset++] << 8 | payload[offset++]; - - byte majorVersion = payload[payloadOffset]; - byte minorVersion = payload[payloadOffset + 1]; - int level = (payload[payloadOffset + 2] << 8) | - payload[payloadOffset + 3]; - break; - - case (int)PreLoginOptions.ENCRYPT: - if (tlsFirst) - { - // Can skip/ignore this option if we are doing TDS 8. - offset += 4; - break; - } - - payloadOffset = payload[offset++] << 8 | payload[offset++]; - payloadLength = payload[offset++] << 8 | payload[offset++]; - - EncryptionOptions serverOption = ((EncryptionOptions)payload[payloadOffset]) & EncryptionOptions.OPTIONS_MASK; - - /* internal enum EncryptionOptions { - OFF, - ON, - NOT_SUP, - REQ, - LOGIN, - OPTIONS_MASK = 0x3f - } */ - - // Any response other than NOT_SUP means the server supports encryption. - serverSupportsEncryption = serverOption != EncryptionOptions.NOT_SUP; - - switch (_encryptionOption) - { - case (EncryptionOptions.OFF): - if (serverOption == EncryptionOptions.OFF) - { - // Only encrypt login. - _encryptionOption = EncryptionOptions.LOGIN; - } - else if (serverOption == EncryptionOptions.REQ) - { - // Encrypt all. - _encryptionOption = EncryptionOptions.ON; - } - // NOT_SUP: No encryption. - break; - - case (EncryptionOptions.NOT_SUP): - if (serverOption == EncryptionOptions.REQ) - { - // Server requires encryption, but client does not support it. - _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - - break; - default: - // Any other client option needs encryption - if (serverOption == EncryptionOptions.NOT_SUP) - { - _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - break; - } - - break; - - case (int)PreLoginOptions.INSTANCE: - payloadOffset = payload[offset++] << 8 | payload[offset++]; - payloadLength = payload[offset++] << 8 | payload[offset++]; - - byte ERROR_INST = 0x1; - byte instanceResult = payload[payloadOffset]; - - if (instanceResult == ERROR_INST) - { - // Check if server says ERROR_INST. That either means the cached info - // we used to connect is not valid or we connected to a named instance - // listening on default params. - return PreLoginHandshakeStatus.InstanceFailure; - } - - break; - - case (int)PreLoginOptions.THREADID: - // DO NOTHING FOR THREADID - offset += 4; - break; - - case (int)PreLoginOptions.MARS: - payloadOffset = payload[offset++] << 8 | payload[offset++]; - payloadLength = payload[offset++] << 8 | payload[offset++]; - - marsCapable = (payload[payloadOffset] == 0 ? false : true); - - Debug.Assert(payload[payloadOffset] == 0 || payload[payloadOffset] == 1, "Value for Mars PreLoginHandshake option not equal to 1 or 0!"); - break; - - case (int)PreLoginOptions.TRACEID: - // DO NOTHING FOR TRACEID - offset += 4; - break; - - case (int)PreLoginOptions.FEDAUTHREQUIRED: - payloadOffset = payload[offset++] << 8 | payload[offset++]; - payloadLength = payload[offset++] << 8 | payload[offset++]; - - // Only 0x00 and 0x01 are accepted values from the server. - if (payload[payloadOffset] != 0x00 && payload[payloadOffset] != 0x01) - { - SqlClientEventSource.Log.TryTraceEvent(" {0}, " + - "Server sent an unexpected value for FedAuthRequired PreLogin Option. Value was {1}.", ObjectID, (int)payload[payloadOffset]); - throw SQL.ParsingErrorValue(ParsingErrorState.FedAuthRequiredPreLoginResponseInvalidValue, (int)payload[payloadOffset]); - } - - // We must NOT use the response for the FEDAUTHREQUIRED PreLogin option, if the connection string option - // was not using the new Authentication keyword or in other words, if Authentication=NotSpecified - // Or AccessToken is not null, mean token based authentication is used. - if ((_connHandler.ConnectionOptions != null - && _connHandler.ConnectionOptions.Authentication != SqlAuthenticationMethod.NotSpecified) - || _connHandler._accessTokenInBytes != null || _connHandler._accessTokenCallback != null) - { - fedAuthRequired = payload[payloadOffset] == 0x01 ? true : false; - } - break; - - default: - Debug.Fail("UNKNOWN option in ConsumePreLoginHandshake, option:" + option); - - // DO NOTHING FOR THESE UNKNOWN OPTIONS - offset += 4; - - break; - } - - if (offset < payload.Length) - { - option = payload[offset++]; - } - else - { - break; - } - } - - if (_encryptionOption == EncryptionOptions.ON || - _encryptionOption == EncryptionOptions.LOGIN) - { - if (!serverSupportsEncryption) - { - _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - - // Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server. - bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || - ((_connHandler._accessTokenInBytes != null || _connHandler._accessTokenCallback != null) && !trustServerCert); - - uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) - | TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE; - - EnableSsl(info, encrypt, integratedSecurity, serverCertificateFilename); - } - - return PreLoginHandshakeStatus.Successful; - } - - internal void Deactivate(bool connectionIsDoomed) - { - // Called when the connection that owns us is deactivated. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} deactivating", ObjectID); - if (SqlClientEventSource.Log.IsStateDumpEnabled()) - { - SqlClientEventSource.Log.StateDumpEvent(" {0} {1}", ObjectID, TraceString()); - } - - if (MARSOn) - { - _sessionPool.Deactivate(); - } - - Debug.Assert(connectionIsDoomed || _pendingTransaction == null, "pending transaction at disconnect?"); - - if (!connectionIsDoomed && _physicalStateObj != null) - { - if (_physicalStateObj.HasPendingData) - { - DrainData(_physicalStateObj); - } - - if (_physicalStateObj.HasOpenResult) - { // SQL BU DT 383773 - need to decrement openResultCount for all pending operations. - _physicalStateObj.DecrementOpenResultCount(); - } - } - - // Any active, non-distributed transaction must be rolled back. We - // need to wait for distributed transactions to be completed by the - // transaction manager -- we don't want to automatically roll them - // back. - // - // Note that when there is a transaction delegated to this connection, - // we will defer the deactivation of this connection until the - // transaction manager completes the transaction. - SqlInternalTransaction currentTransaction = CurrentTransaction; - - if (currentTransaction != null && currentTransaction.HasParentTransaction) - { - currentTransaction.CloseFromConnection(); - Debug.Assert(CurrentTransaction == null, "rollback didn't clear current transaction?"); - } - - Statistics = null; // must come after CleanWire or we won't count the stuff that happens there... - } - - // Used to close the connection and then free the memory allocated for the netlib connection. - internal void Disconnect() - { - if (_sessionPool != null) - { - // MARSOn may be true, but _sessionPool not yet created - _sessionPool.Dispose(); - } - - // Can close the connection if its open or broken - if (_state != TdsParserState.Closed) - { - //benign assert - the user could close the connection before consuming all the data - //Debug.Assert(_physicalStateObj._inBytesUsed == _physicalStateObj._inBytesRead && _physicalStateObj._outBytesUsed == _physicalStateObj._inputHeaderLen, "TDSParser closed with data not fully sent or consumed."); - - _state = TdsParserState.Closed; - - try - { - // If the _physicalStateObj has an owner, we will delay the disposal until the owner is finished with it - if (!_physicalStateObj.HasOwner) - { - _physicalStateObj.SniContext = SniContext.Snix_Close; -#if DEBUG - _physicalStateObj.InvalidateDebugOnlyCopyOfSniContext(); -#endif - _physicalStateObj.Dispose(); - } - else - { - // Remove the "initial" callback (this will allow the stateObj to be GC collected if need be) - _physicalStateObj.DecrementPendingCallbacks(false); - } - - // Not allocated until MARS is actually enabled in SNI. - if (_pMarsPhysicalConObj != null) - { - _pMarsPhysicalConObj.Dispose(); - } - } - finally - { - _pMarsPhysicalConObj = null; - } - } - - _resetConnectionEvent?.Dispose(); - _resetConnectionEvent = null; - - _defaultEncoding = null; - _defaultCollation = null; - } - - // Fires a single InfoMessageEvent - private void FireInfoMessageEvent(SqlConnection connection, SqlCommand command, TdsParserStateObject stateObj, SqlError error) - { - string serverVersion = null; - - Debug.Assert(connection != null && _connHandler.Connection == connection); - - if (_state == TdsParserState.OpenLoggedIn) - { - serverVersion = _connHandler.ServerVersion; - } - - SqlErrorCollection sqlErs = new SqlErrorCollection(); - - sqlErs.Add(error); - - SqlException exc = SqlException.CreateException(sqlErs, serverVersion, _connHandler, innerException: null, batchCommand: command?.GetCurrentBatchCommand()); - - bool notified; - connection.OnInfoMessage(new SqlInfoMessageEventArgs(exc), out notified); - if (notified) - { - // observable side-effects, no retry - stateObj._syncOverAsync = true; - } - return; - } - - internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) - { - Debug.Assert(_currentTransaction != null && _currentTransaction == internalTransaction, "disconnecting different transaction"); - - if (_currentTransaction != null && _currentTransaction == internalTransaction) - { - _currentTransaction = null; - } - } - - internal void RollbackOrphanedAPITransactions() - { - // Any active, non-distributed transaction must be rolled back. - SqlInternalTransaction currentTransaction = CurrentTransaction; - - if (currentTransaction != null && currentTransaction.HasParentTransaction && currentTransaction.IsOrphaned) - { - currentTransaction.CloseFromConnection(); - Debug.Assert(CurrentTransaction == null, "rollback didn't clear current transaction?"); - } - } - - internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command = null, bool callerHasConnectionLock = false, bool asyncClose = false) - { - Debug.Assert(!callerHasConnectionLock || _connHandler._parserLock.ThreadMayHaveLock(), "Caller claims to have lock, but connection lock is not taken"); - - SqlException exception = null; - bool breakConnection; - - // This function should only be called when there was an error or warning. If there aren't any - // errors, the handler will be called for the warning(s). If there was an error, the warning(s) will - // be copied to the end of the error collection so that the user may see all the errors and also the - // warnings that occurred. - // can be deleted) - //_errorAndWarningsLock lock is implemented inside GetFullErrorAndWarningCollection - SqlErrorCollection temp = stateObj.GetFullErrorAndWarningCollection(out breakConnection); - - if (temp.Count == 0) - { - SqlClientEventSource.Log.TryTraceEvent(" Potential multi-threaded misuse of connection, unexpectedly empty warnings/errors under lock {0}", ObjectID); - } - Debug.Assert(temp != null, "TdsParser::ThrowExceptionAndWarning: null errors collection!"); - Debug.Assert(temp.Count > 0, "TdsParser::ThrowExceptionAndWarning called with no exceptions or warnings!"); - Debug.Assert(_connHandler != null, "TdsParser::ThrowExceptionAndWarning called with null connectionHandler!"); - - // Don't break the connection if it is already closed - breakConnection &= (TdsParserState.Closed != _state); - if (breakConnection) - { - if ((_state == TdsParserState.OpenNotLoggedIn) && (_connHandler.ConnectionOptions.MultiSubnetFailover || _loginWithFailover) && (temp.Count == 1) && ((temp[0].Number == TdsEnums.TIMEOUT_EXPIRED) || (temp[0].Number == TdsEnums.SNI_WAIT_TIMEOUT))) - { - // DevDiv2 Bug 459546: With "MultiSubnetFailover=yes" in the Connection String, SQLClient incorrectly throws a Timeout using shorter time slice (3-4 seconds), not honoring the actual 'Connect Timeout' - // http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/459546 - // For Multisubnet Failover we slice the timeout to make reconnecting faster (with the assumption that the server will not failover instantaneously) - // However, when timeout occurs we need to not doom the internal connection and also to mark the TdsParser as closed such that the login will be will retried - breakConnection = false; - Disconnect(); - } - else - { - _state = TdsParserState.Broken; - } - } - - if (temp != null && temp.Count > 0) - { - // Construct the exception now that we've collected all the errors - string serverVersion = null; - if (_state == TdsParserState.OpenLoggedIn) - { - serverVersion = _connHandler.ServerVersion; - } - - if (temp.Count == 1 && temp[0].Exception != null) - { - exception = SqlException.CreateException(temp, serverVersion, _connHandler, temp[0].Exception, command?.GetBatchCommand(temp[0].BatchIndex)); - } - else - { - SqlBatchCommand batchCommand = null; - if (temp[0]?.BatchIndex is var index and >= 0 && command is not null) - { - batchCommand = command.GetBatchCommand(index.Value); - } - exception = SqlException.CreateException(temp, serverVersion, _connHandler, innerException: null, batchCommand: batchCommand); - } - } - - if (exception != null) - { - if (breakConnection) - { - // report exception to pending async operation - // before OnConnectionClosed overrides the exception - // due to connection close notification through references - TaskCompletionSource taskSource = stateObj._networkPacketTaskSource; - if (taskSource != null) - { - taskSource.TrySetException(ADP.ExceptionWithStackTrace(exception)); - } - } - - if (asyncClose) - { - // Wait until we have the parser lock, then try to close - SqlInternalConnectionTds connHandler = _connHandler; - Action wrapCloseAction = closeAction => - { - Task.Factory.StartNew(() => - { - connHandler._parserLock.Wait(canReleaseFromAnyThread: false); - connHandler.ThreadHasParserLockForClose = true; - try - { - closeAction(); - } - finally - { - connHandler.ThreadHasParserLockForClose = false; - connHandler._parserLock.Release(); - } - }); - }; - - _connHandler.OnError(exception, breakConnection, wrapCloseAction); - } - else - { - // Let close know that we already have the _parserLock - bool threadAlreadyHadParserLockForClose = _connHandler.ThreadHasParserLockForClose; - if (callerHasConnectionLock) - { - _connHandler.ThreadHasParserLockForClose = true; - } - try - { - // the following handler will throw an exception or generate a warning event - _connHandler.OnError(exception, breakConnection); - } - finally - { - if (callerHasConnectionLock) - { - _connHandler.ThreadHasParserLockForClose = threadAlreadyHadParserLockForClose; - } - } - } - } - } - - internal SqlError ProcessSNIError(TdsParserStateObject stateObj) - { - using (TryEventScope.Create(nameof(TdsParser))) - { -#if DEBUG - // There is an exception here for MARS as its possible that another thread has closed the connection just as we see an error - Debug.Assert(SniContext.Undefined != stateObj.DebugOnlyCopyOfSniContext || ((_fMARS) && ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken))), "SniContext must not be None"); - SqlClientEventSource.Log.TryTraceEvent(" SNIContext must not be None = {0}, _fMARS = {1}, TDS Parser State = {2}", stateObj.DebugOnlyCopyOfSniContext, _fMARS, _state); - -#endif - TdsParserStateObject.SniErrorDetails details = stateObj.GetErrorDetails(); - - if (details.SniErrorNumber != 0) - { - // handle special SNI error codes that are converted into exception which is not a SqlException. - switch (details.SniErrorNumber) - { - case SniErrors.MultiSubnetFailoverWithMoreThan64IPs: - // Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported."); - throw SQL.MultiSubnetFailoverWithMoreThan64IPs(); - - case SniErrors.MultiSubnetFailoverWithInstanceSpecified: - // Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported."); - throw SQL.MultiSubnetFailoverWithInstanceSpecified(); - - case SniErrors.MultiSubnetFailoverWithNonTcpProtocol: - // Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol."); - throw SQL.MultiSubnetFailoverWithNonTcpProtocol(); - // continue building SqlError instance - } - } - // PInvoke code automatically sets the length of the string for us - // So no need to look for \0 - string errorMessage = details.ErrorMessage; - SqlClientEventSource.Log.TryAdvancedTraceEvent("< sc.TdsParser.ProcessSNIError |ERR|ADV > Error message Detail: {0}", details.ErrorMessage); - - /* Format SNI errors and add Context Information - * - * General syntax is: - * - * (provider:, error: - ) - * - * errorMessage | sniError | - * ------------------------------------------- - * ==null | x | must never happen - * !=null | != 0 | retrieve corresponding errorMessage from resources - * !=null | == 0 | replace text left of errorMessage - */ - - if (LocalAppContextSwitches.UseManagedNetworking) - { - Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage) || details.SniErrorNumber != 0, "Empty error message received from SNI"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.ErrorMessage, details.SniErrorNumber); - } - else - { - Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage), "Empty error message received from SNI"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}", details.ErrorMessage); - } - - string sqlContextInfo = StringsHelper.GetResourceString(stateObj.SniContext.ToString()); - string providerRid = string.Format("SNI_PN{0}", details.Provider); - string providerName = StringsHelper.GetResourceString(providerRid); - Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); - int win32ErrorCode = details.NativeError; - - SqlClientEventSource.Log.TryAdvancedTraceEvent(" SNI Native Error Code = {0}", win32ErrorCode); - if (details.SniErrorNumber == 0) - { - // Provider error. The message from provider is preceded with non-localizable info from SNI - // strip provider info from SNI - // - int iColon = errorMessage.IndexOf(':'); - Debug.Assert(0 <= iColon, "':' character missing in sni errorMessage"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" ':' character missing in sni errorMessage. Error Message index of ':' = {0}", iColon); - Debug.Assert(errorMessage.Length > iColon + 1 && errorMessage[iColon + 1] == ' ', "Expecting a space after the ':' character"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Expecting a space after the ':' character. Error Message Length = {0}", errorMessage.Length); - // extract the message excluding the colon and trailing cr/lf chars - if (0 <= iColon) - { - int len = errorMessage.Length; - len -= Environment.NewLine.Length; // exclude newline sequence - iColon += 2; // skip over ": " sequence - len -= iColon; - /* - The error message should come back in the following format: "TCP Provider: MESSAGE TEXT" - Fix Bug 370686, if the message is received on a Win9x OS, the error message will not contain MESSAGE TEXT - per Bug: 269574. If we get a errormessage with no message text, just return the entire message otherwise - return just the message text. - */ - if (len > 0) - { - errorMessage = errorMessage.Substring(iColon, len); - } - } - } - else - { - if (LocalAppContextSwitches.UseManagedNetworking) - { - // SNI error. Append additional error message info if available and hasn't been included. - string sniLookupMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); - errorMessage = (string.IsNullOrEmpty(errorMessage) || errorMessage.Contains(sniLookupMessage)) - ? sniLookupMessage - : (sniLookupMessage + ": " + errorMessage); - } - else - { - // SNI error. Replace the entire message. - errorMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); - - // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code - if (details.SniErrorNumber == SniErrors.LocalDBErrorCode) - { - errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError); - win32ErrorCode = 0; - } - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage); - } - } - errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})", - sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage); - - SqlClientEventSource.Log.TryAdvancedTraceErrorEvent(" SNI Error Message. Native Error = {0}, Line Number ={1}, Function ={2}, Exception ={3}, Server = {4}", - details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server); - - return new SqlError(infoNumber: details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, - errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: details.NativeError, details.Exception); - } - } - - internal void CheckResetConnection(TdsParserStateObject stateObj) - { - if (_fResetConnection && !stateObj._fResetConnectionSent) - { - Debug.Assert(stateObj._outputPacketNumber == 1 || stateObj._outputPacketNumber == 2, "In ResetConnection logic unexpectedly!"); - try - { - if (_fMARS && !stateObj._fResetEventOwned) - { - // If using Async & MARS and we do not own ResetEvent - grab it. We need to not grab lock here - // for case where multiple packets are sent to server from one execute. - stateObj._fResetEventOwned = _resetConnectionEvent.WaitOne(stateObj.GetTimeoutRemaining()); - - if (stateObj._fResetEventOwned) - { - if (stateObj.TimeoutHasExpired) - { - // We didn't timeout on the WaitOne, but we timed out by the time we decremented stateObj._timeRemaining. - stateObj._fResetEventOwned = !_resetConnectionEvent.Set(); - stateObj.TimeoutTime = 0; - } - } - - if (!stateObj._fResetEventOwned) - { - // We timed out waiting for ResetEvent. Throw timeout exception and reset - // the buffer. Nothing else to do since we did not actually send anything - // to the server. - stateObj.ResetBuffer(); - Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point."); - stateObj.AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, (byte)0x00, TdsEnums.MIN_ERROR_CLASS, _server, _connHandler.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); - Debug.Assert(_connHandler._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock"); - ThrowExceptionAndWarning(stateObj, callerHasConnectionLock: true); - } - } - - if (_fResetConnection) - { - // Check again to see if we need to send reset. - - Debug.Assert(!stateObj._fResetConnectionSent, "Unexpected state for sending reset connection"); - - if (_fPreserveTransaction) - { - // if we are reseting, set bit in header by or'ing with other value - stateObj._outBuff[1] = (byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION_PRESERVE_TRANSACTION); - } - else - { - // if we are reseting, set bit in header by or'ing with other value - stateObj._outBuff[1] = (byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION); - } - - if (!_fMARS) - { - _fResetConnection = false; // If not MARS, can turn off flag now. - _fPreserveTransaction = false; - } - else - { - stateObj._fResetConnectionSent = true; // Otherwise set flag so we don't resend on multiple packet execute. - } - } - else if (_fMARS && stateObj._fResetEventOwned) - { - Debug.Assert(!stateObj._fResetConnectionSent, "Unexpected state on WritePacket ResetConnection"); - - // Otherwise if 2005 and we grabbed the event, free it. Another execute grabbed the event and - // took care of sending the reset. - stateObj._fResetEventOwned = !_resetConnectionEvent.Set(); - Debug.Assert(!stateObj._fResetEventOwned, "Invalid AutoResetEvent state!"); - } - } - catch (Exception) - { - if (_fMARS && stateObj._fResetEventOwned) - { - // If exception thrown, and we are on 2005 and own the event, release it! - stateObj._fResetConnectionSent = false; - stateObj._fResetEventOwned = !_resetConnectionEvent.Set(); - Debug.Assert(!stateObj._fResetEventOwned, "Invalid AutoResetEvent state!"); - } - - throw; - } - } -#if DEBUG - else - { - Debug.Assert(!_fResetConnection || - (_fResetConnection && stateObj._fResetConnectionSent && stateObj._fResetEventOwned), - "Unexpected state on else ResetConnection block in WritePacket"); - } -#endif - } - - /// - /// Serializes a 16 bit short to the returned buffer. - /// - /// The value to serialize. - /// containing the cached buffer bytes. - /// The serialized 16 bit short. - internal byte[] SerializeShort(int v, TdsParserStateObject stateObj) - { - if (stateObj._bShortBytes == null) - { - stateObj._bShortBytes = new byte[2]; - } - else - { - Debug.Assert(2 == stateObj._bShortBytes.Length); - } - - byte[] bytes = stateObj._bShortBytes; - int current = 0; - bytes[current++] = (byte)(v & 0xff); - bytes[current++] = (byte)((v >> 8) & 0xff); - return bytes; - } - - /// - /// Writes a 16 bit short to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteShort(int v, TdsParserStateObject stateObj) - { - if ((stateObj._outBytesUsed + 2) > stateObj._outBuff.Length) - { - // if all of the short doesn't fit into the buffer - stateObj.WriteByte((byte)(v & 0xff)); - stateObj.WriteByte((byte)((v >> 8) & 0xff)); - } - else - { - // all of the short fits into the buffer - stateObj._outBuff[stateObj._outBytesUsed] = (byte)(v & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 1] = (byte)((v >> 8) & 0xff); - stateObj._outBytesUsed += 2; - } - } - - /// - /// Writes a 16 bit unsigned short to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteUnsignedShort(ushort us, TdsParserStateObject stateObj) - { - WriteShort((short)us, stateObj); - } - - /// - /// Serializes a Guid to the specified buffer. - /// - /// The value to serialize. - /// The buffer to serialize to. The size of this buffer must be 16 bytes or larger. - private static void SerializeGuid(in Guid v, Span buffer) - { - Debug.Assert(buffer.Length >= GUID_SIZE); -#if NET - v.TryWriteBytes(buffer, bigEndian: false, out _); -#else - byte[] guidBytes = v.ToByteArray(); - guidBytes.AsSpan().CopyTo(buffer); -#endif - } - - /// - /// Writes a SqlGuid to the wire. - /// - /// The value to write. - /// containing the wire buffer. - private static void WriteGuid(in SqlGuid v, TdsParserStateObject stateObj) - { - Guid innerValue = v.IsNull ? Guid.Empty : v.Value; - - WriteGuid(in innerValue, stateObj); - } - - /// - /// Writes a Guid to the wire. - /// - /// The value to write. - /// containing the wire buffer. - private static void WriteGuid(in Guid v, TdsParserStateObject stateObj) - { - if ((stateObj._outBytesUsed + GUID_SIZE) > stateObj._outBuff.Length) - { - Span buffer = stackalloc byte[GUID_SIZE]; - - SerializeGuid(in v, buffer); - // if all of the guid doesn't fit into the buffer - for (int index = 0; index < buffer.Length; index++) - { - stateObj.WriteByte(buffer[index]); - } - } - else - { - // all of the guid fits into the buffer - SerializeGuid(in v, stateObj._outBuff.AsSpan(stateObj._outBytesUsed, GUID_SIZE)); - stateObj._outBytesUsed += GUID_SIZE; - } - } - - /// - /// Serializes an unsigned 32 bit integer to the returned buffer. - /// - /// The value to serialize. - /// containing the cached buffer bytes. - /// The serialized unsigned 32 bit integer. - internal byte[] SerializeUnsignedInt(uint i, TdsParserStateObject stateObj) - { - return SerializeInt((int)i, stateObj); - } - - /// - /// Writes an unsigned 32 bit integer to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteUnsignedInt(uint i, TdsParserStateObject stateObj) - { - WriteInt((int)i, stateObj); - } - - /// - /// Serializes a signed 32 bit integer to the returned buffer. - /// - /// The value to serialize. - /// containing the cached buffer bytes. - /// The serialized signed 32 bit integer. - internal byte[] SerializeInt(int v, TdsParserStateObject stateObj) - { - if (stateObj._bIntBytes == null) - { - stateObj._bIntBytes = new byte[sizeof(int)]; - } - else - { - Debug.Assert(sizeof(int) == stateObj._bIntBytes.Length); - } - - BinaryPrimitives.WriteInt32LittleEndian(stateObj._bIntBytes, v); - return stateObj._bIntBytes; - } - - /// - /// Writes a signed 32 bit integer to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteInt(int v, TdsParserStateObject stateObj) - { - if ((stateObj._outBytesUsed + 4) > stateObj._outBuff.Length) - { - Span buffer = stackalloc byte[sizeof(int)]; - - BinaryPrimitives.WriteInt32LittleEndian(buffer, v); - // if all of the int doesn't fit into the buffer - for (int index = 0; index < sizeof(int); index++) - { - stateObj.WriteByte(buffer[index]); - } - } - else - { - // all of the int fits into the buffer - BinaryPrimitives.WriteInt32LittleEndian(stateObj._outBuff.AsSpan(stateObj._outBytesUsed, sizeof(int)), v); - stateObj._outBytesUsed += 4; - } - } - - /// - /// Serializes a float to the returned buffer. - /// - /// The value to serialize. - /// The serialized float. - internal byte[] SerializeFloat(float v) - { - if (Single.IsInfinity(v) || Single.IsNaN(v)) - { - throw ADP.ParameterValueOutOfRange(v.ToString()); - } - - var bytes = new byte[4]; - BinaryPrimitives.WriteInt32LittleEndian(bytes, BitConverterCompatible.SingleToInt32Bits(v)); - return bytes; - } - - /// - /// Writes a float to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteFloat(float v, TdsParserStateObject stateObj) - { - Span bytes = stackalloc byte[sizeof(float)]; - BinaryPrimitives.WriteInt32LittleEndian(bytes, BitConverterCompatible.SingleToInt32Bits(v)); - stateObj.WriteByteSpan(bytes); - } - - /// - /// Serializes a signed 64 bit long to the returned buffer. - /// - /// The value to serialize. - /// containing the cached buffer bytes. - /// The serialized signed 64 bit long. - internal byte[] SerializeLong(long v, TdsParserStateObject stateObj) - { - int current = 0; - if (stateObj._bLongBytes == null) - { - stateObj._bLongBytes = new byte[8]; - } - - byte[] bytes = stateObj._bLongBytes; - Debug.Assert(8 == bytes.Length, "Cached buffer has wrong size"); - - bytes[current++] = (byte)(v & 0xff); - bytes[current++] = (byte)((v >> 8) & 0xff); - bytes[current++] = (byte)((v >> 16) & 0xff); - bytes[current++] = (byte)((v >> 24) & 0xff); - bytes[current++] = (byte)((v >> 32) & 0xff); - bytes[current++] = (byte)((v >> 40) & 0xff); - bytes[current++] = (byte)((v >> 48) & 0xff); - bytes[current++] = (byte)((v >> 56) & 0xff); - - return bytes; - } - - /// - /// Writes a signed 64 bit long to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteLong(long v, TdsParserStateObject stateObj) - { - if ((stateObj._outBytesUsed + 8) > stateObj._outBuff.Length) - { - // if all of the long doesn't fit into the buffer - for (int shiftValue = 0; shiftValue < sizeof(long) * 8; shiftValue += 8) - { - stateObj.WriteByte((byte)((v >> shiftValue) & 0xff)); - } - } - else - { - // all of the long fits into the buffer - // NOTE: We don't use a loop here for performance - stateObj._outBuff[stateObj._outBytesUsed] = (byte)(v & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 1] = (byte)((v >> 8) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 2] = (byte)((v >> 16) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 3] = (byte)((v >> 24) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 4] = (byte)((v >> 32) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 5] = (byte)((v >> 40) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 6] = (byte)((v >> 48) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 7] = (byte)((v >> 56) & 0xff); - stateObj._outBytesUsed += 8; - } - } - - /// - /// Serializes the first bytes of a signed 64 bit long to the returned buffer. - /// - /// The value to serialize. - /// The number of bytes to serialize. - /// The serialized signed 64 bit long. - internal byte[] SerializePartialLong(long v, int length) - { - Debug.Assert(length <= 8, "Length specified is longer than the size of a long"); - Debug.Assert(length >= 0, "Length should not be negative"); - - byte[] bytes = new byte[length]; - - // all of the long fits into the buffer - for (int index = 0; index < length; index++) - { - bytes[index] = (byte)((v >> (index * 8)) & 0xff); - } - - return bytes; - } - - /// - /// Writes the first bytes of a signed 64 bit long to the wire. - /// - /// The value to write. - /// The number of bytes to serialize. - /// containing the wire buffer. - internal void WritePartialLong(long v, int length, TdsParserStateObject stateObj) - { - Debug.Assert(length <= 8, "Length specified is longer than the size of a long"); - Debug.Assert(length >= 0, "Length should not be negative"); - - if ((stateObj._outBytesUsed + length) > stateObj._outBuff.Length) - { - // if all of the long doesn't fit into the buffer - for (int shiftValue = 0; shiftValue < length * 8; shiftValue += 8) - { - stateObj.WriteByte((byte)((v >> shiftValue) & 0xff)); - } - } - else - { - // all of the long fits into the buffer - for (int index = 0; index < length; index++) - { - stateObj._outBuff[stateObj._outBytesUsed + index] = (byte)((v >> (index * 8)) & 0xff); - } - stateObj._outBytesUsed += length; - } - } - - /// - /// Writes an unsigned 64 bit long to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteUnsignedLong(ulong uv, TdsParserStateObject stateObj) - { - WriteLong((long)uv, stateObj); - } - - /// - /// Serializes a double to the returned buffer. - /// - /// The value to serialize. - /// The serialized double. - internal byte[] SerializeDouble(double v) - { - if (double.IsInfinity(v) || double.IsNaN(v)) - { - throw ADP.ParameterValueOutOfRange(v.ToString()); - } - - var bytes = new byte[8]; - BinaryPrimitives.WriteInt64LittleEndian(bytes, BitConverter.DoubleToInt64Bits(v)); - return bytes; - } - - /// - /// Writes a double to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteDouble(double v, TdsParserStateObject stateObj) - { - Span bytes = stackalloc byte[sizeof(double)]; - BinaryPrimitives.WriteInt64LittleEndian(bytes, BitConverter.DoubleToInt64Bits(v)); - stateObj.WriteByteSpan(bytes); - } - - internal void PrepareResetConnection(bool preserveTransaction) - { - // Set flag to reset connection upon next use - only for use on 2000! - _fResetConnection = true; - _fPreserveTransaction = preserveTransaction; - } - - internal bool Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) - { - bool syncOverAsync = stateObj._syncOverAsync; - try - { - stateObj._syncOverAsync = true; - - bool dataReady; - TdsOperationStatus result = TryRun(runBehavior, cmdHandler, dataStream, bulkCopyHandler, stateObj, out dataReady); - Debug.Assert(result == TdsOperationStatus.Done, "Should always return Done when _syncOverAsync is set"); - return dataReady; - } - finally - { - stateObj._syncOverAsync = syncOverAsync; - } - } - - /// - /// Checks if the given token is a valid TDS token - /// - /// Token to check - /// True if the token is a valid TDS token, otherwise false - internal static bool IsValidTdsToken(byte token) - { - return ( - token == TdsEnums.SQLERROR || - token == TdsEnums.SQLINFO || - token == TdsEnums.SQLLOGINACK || - token == TdsEnums.SQLENVCHANGE || - token == TdsEnums.SQLRETURNVALUE || - token == TdsEnums.SQLRETURNSTATUS || - token == TdsEnums.SQLCOLNAME || - token == TdsEnums.SQLCOLFMT || - token == TdsEnums.SQLRESCOLSRCS || - token == TdsEnums.SQLDATACLASSIFICATION || - token == TdsEnums.SQLCOLMETADATA || - token == TdsEnums.SQLALTMETADATA || - token == TdsEnums.SQLTABNAME || - token == TdsEnums.SQLCOLINFO || - token == TdsEnums.SQLORDER || - token == TdsEnums.SQLALTROW || - token == TdsEnums.SQLROW || - token == TdsEnums.SQLNBCROW || - token == TdsEnums.SQLDONE || - token == TdsEnums.SQLDONEPROC || - token == TdsEnums.SQLDONEINPROC || - token == TdsEnums.SQLROWCRC || - token == TdsEnums.SQLSECLEVEL || - token == TdsEnums.SQLPROCID || - token == TdsEnums.SQLOFFSET || - token == TdsEnums.SQLSSPI || - token == TdsEnums.SQLFEATUREEXTACK || - token == TdsEnums.SQLSESSIONSTATE || - token == TdsEnums.SQLFEDAUTHINFO); - } - - // Main parse loop for the top-level tds tokens, calls back into the I*Handler interfaces - internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, out bool dataReady) - { - Debug.Assert((SniContext.Undefined != stateObj.SniContext) && // SniContext must not be Undefined - ((stateObj._attentionSent) || ((SniContext.Snix_Execute != stateObj.SniContext) && (SniContext.Snix_SendRows != stateObj.SniContext))), // SniContext should not be Execute or SendRows unless attention was sent (and, therefore, we are looking for an ACK) - $"Unexpected SniContext on call to TryRun; SniContext={stateObj.SniContext}"); - - if (TdsParserState.Broken == State || TdsParserState.Closed == State) - { - dataReady = true; - return TdsOperationStatus.Done; // Just in case this is called in a loop, expecting data to be returned. - } - - TdsOperationStatus result; - dataReady = false; - - do - { - // If there is data ready, but we didn't exit the loop, then something is wrong - Debug.Assert(!dataReady, "dataReady not expected - did we forget to skip the row?"); - - if (stateObj.IsTimeoutStateExpired) - { - runBehavior = RunBehavior.Attention; - } - - if (TdsParserState.Broken == State || TdsParserState.Closed == State) - { - break; // jump out of the loop if the state is already broken or closed. - } - - if (!stateObj._accumulateInfoEvents && (stateObj._pendingInfoEvents != null)) - { - if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) - { - SqlConnection connection = null; - if (_connHandler != null) - connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref - // We are omitting checks for error.Class in the code below (see processing of INFO) since we know (and assert) that error class - // error.Class < TdsEnums.MIN_ERROR_CLASS for info message. - // Also we know that TdsEnums.MIN_ERROR_CLASS Potential multi-threaded misuse of connection, unexpected TDS token found {0}", ObjectID); -#if DEBUG - throw new InvalidOperationException(message); -#else - throw SQL.ParsingErrorToken(ParsingErrorState.InvalidTdsTokenReceived, token); // MDAC 82443 -#endif - - } - - int tokenLength; - result = TryGetTokenLength(token, stateObj, out tokenLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - - switch (token) - { - case TdsEnums.SQLERROR: - case TdsEnums.SQLINFO: - { - if (token == TdsEnums.SQLERROR) - { - stateObj.HasReceivedError = true; // Keep track of the fact error token was received - for Done processing. - } - - SqlError error; - result = TryProcessError(token, stateObj, cmdHandler, out error); - if (result != TdsOperationStatus.Done) - { - return result; - } - - if (token == TdsEnums.SQLINFO && stateObj._accumulateInfoEvents) - { - Debug.Assert(error.Class < TdsEnums.MIN_ERROR_CLASS, "INFO with class > TdsEnums.MIN_ERROR_CLASS"); - - if (stateObj._pendingInfoEvents == null) - stateObj._pendingInfoEvents = new List(); - stateObj._pendingInfoEvents.Add(error); - stateObj._syncOverAsync = true; - break; - } - - if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) - { - // If FireInfoMessageEventOnUserErrors is true, we have to fire event without waiting. - // Otherwise we can go ahead and add it to errors/warnings collection. - SqlConnection connection = null; - if (_connHandler != null) - connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref - - if ((connection != null) && - (connection.FireInfoMessageEventOnUserErrors == true) && - (error.Class <= TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS)) - { - // Fire SqlInfoMessage here - FireInfoMessageEvent(connection, cmdHandler, stateObj, error); - } - else - { - // insert error/info into the appropriate exception - warning if info, exception if error - if (error.Class < TdsEnums.MIN_ERROR_CLASS) - { - stateObj.AddWarning(error); - } - else if (error.Class < TdsEnums.FATAL_ERROR_CLASS) - { - // VSTFDEVDIV 479643: continue results processing for all non-fatal errors (<20) - - stateObj.AddError(error); - - // Add it to collection - but do NOT change run behavior UNLESS - // we are in an ExecuteReader call - at which time we will be throwing - // anyways so we need to consume all errors. This is not the case - // if we have already given out a reader. If we have already given out - // a reader we need to throw the error but not halt further processing. We used to - // halt processing. - - if (dataStream != null) - { // Webdata 104560 - if (!dataStream.IsInitialized) - { - runBehavior = RunBehavior.UntilDone; - } - } - } - else - { - stateObj.AddError(error); - - // Else we have a fatal error and we need to change the behavior - // since we want the complete error information in the exception. - // Besides - no further results will be received. - runBehavior = RunBehavior.UntilDone; - } - } - } - else if (error.Class >= TdsEnums.FATAL_ERROR_CLASS) - { - stateObj.AddError(error); - } - break; - } - - case TdsEnums.SQLCOLINFO: - { - if (dataStream != null) - { - _SqlMetaDataSet metaDataSet; - result = TryProcessColInfo(dataStream.MetaData, dataStream, stateObj, out metaDataSet); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = dataStream.TrySetMetaData(metaDataSet, false); - if (result != TdsOperationStatus.Done) - { - return result; - } - dataStream.BrowseModeInfoConsumed = true; - } - else - { - // no dataStream - result = stateObj.TrySkipBytes(tokenLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - break; - } - - case TdsEnums.SQLDONE: - case TdsEnums.SQLDONEPROC: - case TdsEnums.SQLDONEINPROC: - { - // RunBehavior can be modified - see SQL BU DT 269516 & 290090 - result = TryProcessDone(cmdHandler, dataStream, ref runBehavior, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - if ((token == TdsEnums.SQLDONEPROC) && (cmdHandler != null)) - { - // If the current parse/read is for the results of describe parameter encryption RPC requests, - // call a different handler which will update the describe parameter encryption RPC structures - // with the results, instead of the actual user RPC requests. - if (cmdHandler.IsDescribeParameterEncryptionRPCCurrentlyInProgress) - { - cmdHandler.OnDoneDescribeParameterEncryptionProc(stateObj); - } - else - { - cmdHandler.OnDoneProc(stateObj); - } - } - - break; - } - - case TdsEnums.SQLORDER: - { - // don't do anything with the order token so read off the pipe - result = stateObj.TrySkipBytes(tokenLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - } - - case TdsEnums.SQLENVCHANGE: - { - // ENVCHANGE must be processed synchronously (since it can modify the state of many objects) - stateObj._syncOverAsync = true; - - SqlEnvChange env; - result = TryProcessEnvChange(tokenLength, stateObj, out env); - if (result != TdsOperationStatus.Done) - { - return result; - } - - while (env != null) - { - if (!Connection.IgnoreEnvChange) - { - switch (env._type) - { - case TdsEnums.ENV_BEGINTRAN: - case TdsEnums.ENV_ENLISTDTC: - // When we get notification from the server of a new - // transaction, we move any pending transaction over to - // the current transaction, then we store the token in it. - // if there isn't a pending transaction, then it's either - // a TSQL transaction or a distributed transaction. - Debug.Assert(_currentTransaction == null, "non-null current transaction with an ENV Change"); - _currentTransaction = _pendingTransaction; - _pendingTransaction = null; - - if (_currentTransaction != null) - { - _currentTransaction.TransactionId = env._newLongValue; // this is defined as a ULongLong in the server and in the TDS Spec. - } - else - { - TransactionType transactionType = (TdsEnums.ENV_BEGINTRAN == env._type) ? TransactionType.LocalFromTSQL : TransactionType.Distributed; - _currentTransaction = new SqlInternalTransaction(_connHandler, transactionType, null, env._newLongValue); - } - if (_statistics != null && !_statisticsIsInTransaction) - { - _statistics.SafeIncrement(ref _statistics._transactions); - } - _statisticsIsInTransaction = true; - _retainedTransactionId = SqlInternalTransaction.NullTransactionId; - break; - case TdsEnums.ENV_DEFECTDTC: - case TdsEnums.ENV_TRANSACTIONENDED: - case TdsEnums.ENV_COMMITTRAN: - // SQLHOT 483 - // Must clear the retain id if the server-side transaction ends by anything other - // than rollback. - _retainedTransactionId = SqlInternalTransaction.NullTransactionId; - goto case TdsEnums.ENV_ROLLBACKTRAN; - case TdsEnums.ENV_ROLLBACKTRAN: - // When we get notification of a completed transaction - // we null out the current transaction. - if (_currentTransaction != null) - { -#if DEBUG - // Check null for case where Begin and Rollback obtained in the same message. - if (SqlInternalTransaction.NullTransactionId != _currentTransaction.TransactionId) - { - Debug.Assert(_currentTransaction.TransactionId != env._newLongValue, "transaction id's are not equal!"); - } -#endif - - if (TdsEnums.ENV_COMMITTRAN == env._type) - { - _currentTransaction.Completed(TransactionState.Committed); - } - else if (TdsEnums.ENV_ROLLBACKTRAN == env._type) - { - // Hold onto transaction id if distributed tran is rolled back. This must - // be sent to the server on subsequent executions even though the transaction - // is considered to be rolled back. - if (_currentTransaction.IsDistributed && _currentTransaction.IsActive) - { - _retainedTransactionId = env._oldLongValue; - } - _currentTransaction.Completed(TransactionState.Aborted); - } - else - { - // TODO: While not techically necessary at this point, we are not certain whether TransactionEnded indicates Committed, Aborted or Unknown - this is fine for now, but we should investigate. - _currentTransaction.Completed(TransactionState.Unknown); - } - _currentTransaction = null; - } - _statisticsIsInTransaction = false; - break; - - default: - _connHandler.OnEnvChange(env); - break; - } - } - SqlEnvChange head = env; - env = env._next; - head.Clear(); - head = null; - } - break; - } - case TdsEnums.SQLLOGINACK: - { - SqlClientEventSource.Log.TryTraceEvent(" Received login acknowledgement token"); - SqlLoginAck ack; - result = TryProcessLoginAck(stateObj, out ack); - if (result != TdsOperationStatus.Done) - { - return result; - } - - _connHandler.OnLoginAck(ack); - break; - } - case TdsEnums.SQLFEATUREEXTACK: - { - result = TryProcessFeatureExtAck(stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - } - case TdsEnums.SQLFEDAUTHINFO: - { - _connHandler._federatedAuthenticationInfoReceived = true; - SqlFedAuthInfo info; - - result = TryProcessFedAuthInfo(stateObj, tokenLength, out info); - if (result != TdsOperationStatus.Done) - { - return result; - } - _connHandler.OnFedAuthInfo(info); - break; - } - case TdsEnums.SQLSESSIONSTATE: - { - result = TryProcessSessionState(stateObj, tokenLength, _connHandler._currentSessionData); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - } - case TdsEnums.SQLCOLMETADATA: - { - if (tokenLength != TdsEnums.VARNULL) - { - _SqlMetaDataSet metadata; - result = TryProcessMetaData(tokenLength, stateObj, out metadata, - cmdHandler?.ColumnEncryptionSetting ?? SqlCommandColumnEncryptionSetting.UseConnectionSetting); - if (result != TdsOperationStatus.Done) - { - return result; - } - stateObj._cleanupMetaData = metadata; - } - else - { - if (cmdHandler != null) - { - stateObj._cleanupMetaData = cmdHandler.MetaData; - } - } - - byte peekedToken; - result = stateObj.TryPeekByte(out peekedToken); - if (result != TdsOperationStatus.Done) - { - return result; - } - - if (TdsEnums.SQLDATACLASSIFICATION == peekedToken) - { - byte dataClassificationToken; - result = stateObj.TryReadByte(out dataClassificationToken); - if (result != TdsOperationStatus.Done) - { - return result; - } - Debug.Assert(TdsEnums.SQLDATACLASSIFICATION == dataClassificationToken); - - SensitivityClassification sensitivityClassification; - result = TryProcessDataClassification(stateObj, out sensitivityClassification); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (dataStream != null) - { - result = dataStream.TrySetSensitivityClassification(sensitivityClassification); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - // update peekedToken - result = stateObj.TryPeekByte(out peekedToken); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - if (dataStream != null) - { - result = dataStream.TrySetMetaData(stateObj._cleanupMetaData, (TdsEnums.SQLTABNAME == peekedToken || TdsEnums.SQLCOLINFO == peekedToken)); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else if (bulkCopyHandler != null) - { - bulkCopyHandler.SetMetaData(stateObj._cleanupMetaData); - } - break; - } - case TdsEnums.SQLROW: - case TdsEnums.SQLNBCROW: - { - Debug.Assert(stateObj._cleanupMetaData != null, "Reading a row, but the metadata is null"); - - if (token == TdsEnums.SQLNBCROW) - { - result = stateObj.TryStartNewRow(isNullCompressed: true, nullBitmapColumnsCount: stateObj._cleanupMetaData.Length); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else - { - result = stateObj.TryStartNewRow(isNullCompressed: false); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - if (bulkCopyHandler != null) - { - // TODO: Consider improving Bulk Copy performance by avoiding boxing. - result = TryProcessRow(stateObj._cleanupMetaData, bulkCopyHandler.CreateRowBuffer(), bulkCopyHandler.CreateIndexMap(), stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) - { - result = TrySkipRow(stateObj._cleanupMetaData, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else - { - dataReady = true; - } - - if (_statistics != null) - { - _statistics.WaitForDoneAfterRow = true; - } - break; - } - case TdsEnums.SQLRETURNSTATUS: - int status; - result = stateObj.TryReadInt32(out status); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (cmdHandler != null) - { - cmdHandler.OnReturnStatus(status); - } - break; - case TdsEnums.SQLRETURNVALUE: - { - SqlReturnValue returnValue; - result = TryProcessReturnValue(tokenLength, stateObj, out returnValue, - cmdHandler?.ColumnEncryptionSetting ?? SqlCommandColumnEncryptionSetting.UseConnectionSetting); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (cmdHandler != null) - { - cmdHandler.OnReturnValue(returnValue, stateObj); - } - break; - } - case TdsEnums.SQLSSPI: - { - // token length is length of SSPI data - call ProcessSSPI with it - - Debug.Assert(stateObj._syncOverAsync, "ProcessSSPI does not support retry, do not attempt asynchronously"); - stateObj._syncOverAsync = true; - - ProcessSSPI(tokenLength); - break; - } - case TdsEnums.SQLTABNAME: - { - if (dataStream != null) - { - MultiPartTableName[] tableNames; - result = TryProcessTableName(tokenLength, stateObj, out tableNames); - if (result != TdsOperationStatus.Done) - { - return result; - } - dataStream.TableNames = tableNames; - } - else - { - result = stateObj.TrySkipBytes(tokenLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - break; - } - case TdsEnums.SQLRESCOLSRCS: - { - result = TryProcessResColSrcs(stateObj, tokenLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - } - - // deprecated - case TdsEnums.SQLALTMETADATA: - { - stateObj.CloneCleanupAltMetaDataSetArray(); - - if (stateObj._cleanupAltMetaDataSetArray == null) - { - // create object on demand (lazy creation) - stateObj._cleanupAltMetaDataSetArray = new _SqlMetaDataSetCollection(); - } - - _SqlMetaDataSet cleanupAltMetaDataSet; - result = TryProcessAltMetaData(tokenLength, stateObj, out cleanupAltMetaDataSet); - if (result != TdsOperationStatus.Done) - { - return result; - } - - stateObj._cleanupAltMetaDataSetArray.SetAltMetaData(cleanupAltMetaDataSet); - if (dataStream != null) - { - byte metadataConsumedByte; - result = stateObj.TryPeekByte(out metadataConsumedByte); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = dataStream.TrySetAltMetaDataSet(cleanupAltMetaDataSet, (TdsEnums.SQLALTMETADATA != metadataConsumedByte)); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - break; - } - case TdsEnums.SQLALTROW: - { - result = stateObj.TryStartNewRow(isNullCompressed: false); - if (result != TdsOperationStatus.Done) - { - // altrows are not currently null compressed - return result; - } - - // read will call run until dataReady. Must not read any data if return immediately set - if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) - { - ushort altRowId; - result = stateObj.TryReadUInt16(out altRowId); - if (result != TdsOperationStatus.Done) - { - // get altRowId - return result; - } - - result = TrySkipRow(stateObj._cleanupAltMetaDataSetArray.GetAltMetaData(altRowId), stateObj); - if (result != TdsOperationStatus.Done) - { - // skip altRow - return result; - } - } - else - { - dataReady = true; - } - - break; - } - - default: - Debug.Fail("Unhandled token: " + token.ToString(CultureInfo.InvariantCulture)); - break; - } - - Debug.Assert(stateObj.HasPendingData || !dataReady, "dataReady is set, but there is no pending data"); - } - - // Loop while data pending & runbehavior not return immediately, OR - // if in attention case, loop while no more pending data & attention has not yet been - // received. - while ((stateObj.HasPendingData && - (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior))) || - (!stateObj.HasPendingData && stateObj._attentionSent && !stateObj.HasReceivedAttention)); - -#if DEBUG - if ((stateObj.HasPendingData) && (!dataReady)) - { - byte token; - result = stateObj.TryPeekByte(out token); - if (result != TdsOperationStatus.Done) - { - return result; - } - Debug.Assert(IsValidTdsToken(token), $"DataReady is false, but next token is not valid: {token,-2:X2}"); - } -#endif - - if (!stateObj.HasPendingData) - { - if (CurrentTransaction != null) - { - CurrentTransaction.Activate(); - } - } - - // if we received an attention (but this thread didn't send it) then - // we throw an Operation Cancelled error - if (stateObj.HasReceivedAttention) - { - // Dev11 #344723: SqlClient stress test suspends System_Data!Tcp::ReadSync via a call to SqlDataReader::Close - // Spin until SendAttention has cleared _attentionSending, this prevents a race condition between receiving the attention ACK and setting _attentionSent - TryRunSetupSpinWaitContinuation(stateObj); - - Debug.Assert(stateObj._attentionSent, "Attention ACK has been received without attention sent"); - if (stateObj._attentionSent) - { - // Reset attention state. - stateObj._attentionSent = false; - stateObj.HasReceivedAttention = false; - - if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior) && !stateObj.IsTimeoutStateExpired) - { - // Add attention error to collection - if not RunBehavior.Clean! - stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.OperationCancelled(), "", 0, exception: null, batchIndex: cmdHandler?.GetCurrentBatchIndex() ?? -1)); - } - } - } - - if (stateObj.HasErrorOrWarning) - { - ThrowExceptionAndWarning(stateObj, cmdHandler); - } - return TdsOperationStatus.Done; - } - - // This is in its own method to avoid always allocating the lambda in TryRun - private static void TryRunSetupSpinWaitContinuation(TdsParserStateObject stateObj) => SpinWait.SpinUntil(() => !stateObj._attentionSending); - - private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, out SqlEnvChange sqlEnvChange) - { - // There could be multiple environment change messages following this token. - byte byteLength; - int processedLength = 0; - SqlEnvChange head = null; - SqlEnvChange tail = null; - - sqlEnvChange = null; - - while (tokenLength > processedLength) - { - SqlEnvChange env = new SqlEnvChange(); - TdsOperationStatus result = stateObj.TryReadByte(out env._type); - if (result != TdsOperationStatus.Done) - { - return result; - } - - if (head is null) - { - head = env; - tail = env; - } - else - { - tail._next = env; - tail = env; - } - - switch (env._type) - { - case TdsEnums.ENV_DATABASE: - case TdsEnums.ENV_LANG: - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - - // TdsEnums.ENV_CHARSET (3) is only supported in TDS <= 7 which is no longer supported - - case TdsEnums.ENV_PACKETSIZE: - // take care of packet size right here - Debug.Assert(stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - if (TryReadTwoStringFields(env, stateObj) != TdsOperationStatus.Done) - { - // Changing packet size does not support retry, should not pend" - throw SQL.SynchronousCallMayNotPend(); - } - - // Only set on physical state object - this should only occur on LoginAck prior - // to MARS initialization! - int packetSize = int.Parse(env._newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); - - SqlClientEventSource.Log.TryTraceEvent("{0}.{1} | Info | Server sent env packet size change of {2}, ClientConnectionID {3}", - nameof(TdsParser), nameof(TryProcessEnvChange), packetSize, _connHandler._clientConnectionId); - - if (_physicalStateObj.SetPacketSize(packetSize)) - { - // If packet size changed, we need to release our SNIPackets since - // those are tied to packet size of connection. - _physicalStateObj.ClearAllWritePackets(); - - // Update SNI ConsumerInfo value to be resulting packet size - uint unsignedPacketSize = (uint)packetSize; - uint bufferSizeResult = _physicalStateObj.SetConnectionBufferSize(ref unsignedPacketSize); - - Debug.Assert(bufferSizeResult == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SNISetInfo"); - } - - break; - - // TdsEnums.ENV_LOCALE (5) is only supported in TDS <= 7 which is no longer supported - - // TdsEnums.ENV_COMPFLAGS (6) is only supported in TDS <= 7 which is no longer supported - - case TdsEnums.ENV_COLLATION: - Debug.Assert(env._newLength == 5 || env._newLength == 0, "Improper length in new collation!"); - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._newLength = byteLength; - if (env._newLength == 5) - { - result = TryProcessCollation(stateObj, out env._newCollation); - if (result != TdsOperationStatus.Done) - { - return result; - } - - // Give the parser the new collation values in case parameters don't specify one - _defaultCollation = env._newCollation; - - // UTF8 collation - if (env._newCollation.IsUTF8) - { - _defaultEncoding = s_utf8EncodingWithoutBom; - } - else - { - int newCodePage = GetCodePage(env._newCollation, stateObj); - if (newCodePage != _defaultCodePage) - { - _defaultCodePage = newCodePage; - _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); - } - } - _defaultLCID = env._newCollation.LCID; - } - - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._oldLength = byteLength; - Debug.Assert(env._oldLength == 5 || env._oldLength == 0, "Improper length in old collation!"); - if (env._oldLength == 5) - { - result = TryProcessCollation(stateObj, out env._oldCollation); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - env._length = 3 + env._newLength + env._oldLength; - break; - - case TdsEnums.ENV_BEGINTRAN: - case TdsEnums.ENV_COMMITTRAN: - case TdsEnums.ENV_ROLLBACKTRAN: - case TdsEnums.ENV_ENLISTDTC: - case TdsEnums.ENV_DEFECTDTC: - case TdsEnums.ENV_TRANSACTIONENDED: - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._newLength = byteLength; - Debug.Assert(env._newLength == 0 || env._newLength == 8, "Improper length for new transaction id!"); - - if (env._newLength > 0) - { - result = stateObj.TryReadInt64(out env._newLongValue); - if (result != TdsOperationStatus.Done) - { - return result; - } - Debug.Assert(env._newLongValue != SqlInternalTransaction.NullTransactionId, "New transaction id is null?"); // the server guarantees that zero is an invalid transaction id. - } - else - { - env._newLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. - } - - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._oldLength = byteLength; - Debug.Assert(env._oldLength == 0 || env._oldLength == 8, "Improper length for old transaction id!"); - - if (env._oldLength > 0) - { - result = stateObj.TryReadInt64(out env._oldLongValue); - if (result != TdsOperationStatus.Done) - { - return result; - } - Debug.Assert(env._oldLongValue != SqlInternalTransaction.NullTransactionId, "Old transaction id is null?"); // the server guarantees that zero is an invalid transaction id. - } - else - { - env._oldLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. - } - - // env.length includes 1 byte type token - env._length = 3 + env._newLength + env._oldLength; - break; - - case TdsEnums.ENV_LOGSHIPNODE: - // env.newBinValue is secondary node, env.oldBinValue is witness node - // comes before LoginAck so we can't assert this - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - - case TdsEnums.ENV_PROMOTETRANSACTION: - result = stateObj.TryReadInt32(out env._newLength); - if (result != TdsOperationStatus.Done) - { - // new value has 4 byte length - return result; - } - // read new value with 4 byte length - env._newBinValue = new byte[env._newLength]; - result = stateObj.TryReadByteArray(env._newBinValue, env._newLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._oldLength = byteLength; - Debug.Assert(0 == env._oldLength, "old length should be zero"); - - // env.length includes 1 byte for type token - env._length = 5 + env._newLength; - break; - - case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS: - case TdsEnums.ENV_SPRESETCONNECTIONACK: - result = TryReadTwoBinaryFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - - case TdsEnums.ENV_USERINSTANCE: - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - - case TdsEnums.ENV_ROUTING: - ushort newLength; - result = stateObj.TryReadUInt16(out newLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._newLength = newLength; - byte protocol; - result = stateObj.TryReadByte(out protocol); - if (result != TdsOperationStatus.Done) - { - return result; - } - ushort port; - result = stateObj.TryReadUInt16(out port); - if (result != TdsOperationStatus.Done) - { - return result; - } - ushort serverLen; - result = stateObj.TryReadUInt16(out serverLen); - if (result != TdsOperationStatus.Done) - { - return result; - } - string serverName; - result = stateObj.TryReadString(serverLen, out serverName); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._newRoutingInfo = new RoutingInfo(protocol, port, serverName); - ushort oldLength; - result = stateObj.TryReadUInt16(out oldLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TrySkipBytes(oldLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._length = env._newLength + oldLength + 5; // 5=2*sizeof(UInt16)+sizeof(byte) [token+newLength+oldLength] - break; - - default: - Debug.Fail("Unknown environment change token: " + env._type); - break; - } - processedLength += env._length; - } - - sqlEnvChange = head; - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryReadTwoBinaryFields(SqlEnvChange env, TdsParserStateObject stateObj) - { - // Used by ProcessEnvChangeToken - byte byteLength; - TdsOperationStatus result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._newLength = byteLength; - env._newBinValue = ArrayPool.Shared.Rent(env._newLength); - env._newBinRented = true; - result = stateObj.TryReadByteArray(env._newBinValue, env._newLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._oldLength = byteLength; - env._oldBinValue = ArrayPool.Shared.Rent(env._oldLength); - env._oldBinRented = true; - result = stateObj.TryReadByteArray(env._oldBinValue, env._oldLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - - // env.length includes 1 byte type token - env._length = 3 + env._newLength + env._oldLength; - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryReadTwoStringFields(SqlEnvChange env, TdsParserStateObject stateObj) - { - // Used by ProcessEnvChangeToken - byte newLength, oldLength; - string newValue, oldValue; - TdsOperationStatus result = stateObj.TryReadByte(out newLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TryReadString(newLength, out newValue); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TryReadByte(out oldLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TryReadString(oldLength, out oldValue); - if (result != TdsOperationStatus.Done) - { - return result; - } - - env._newLength = newLength; - env._newValue = newValue; - env._oldLength = oldLength; - env._oldValue = oldValue; - - // env.length includes 1 byte type token - env._length = 3 + env._newLength * 2 + env._oldLength * 2; - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavior run, TdsParserStateObject stateObj) - { - ushort curCmd; - ushort status; - int count; - - if (LocalAppContextSwitches.MakeReadAsyncBlocking) - { - // Don't retry TryProcessDone - stateObj._syncOverAsync = true; - } - - // status - // command - // rowcount (valid only if DONE_COUNT bit is set) - - TdsOperationStatus result = stateObj.TryReadUInt16(out status); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TryReadUInt16(out curCmd); - if (result != TdsOperationStatus.Done) - { - return result; - } - - long longCount; - result = stateObj.TryReadInt64(out longCount); - if (result != TdsOperationStatus.Done) - { - return result; - } - count = (int)longCount; - - // We get a done token with the attention bit set - if (TdsEnums.DONE_ATTN == (status & TdsEnums.DONE_ATTN)) - { - Debug.Assert(TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE), "Not expecting DONE_MORE when receiving DONE_ATTN"); - Debug.Assert(stateObj._attentionSent, "Received attention done without sending one!"); - stateObj.HasReceivedAttention = true; - Debug.Assert(stateObj._inBytesUsed == stateObj._inBytesRead && stateObj._inBytesPacket == 0, "DONE_ATTN received with more data left on wire"); - } - if (cmd != null && (TdsEnums.DONE_COUNT == (status & TdsEnums.DONE_COUNT))) - { - if (curCmd != TdsEnums.SELECT) - { - if (cmd.IsDescribeParameterEncryptionRPCCurrentlyInProgress) - { - // The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. - cmd.RowsAffectedByDescribeParameterEncryption = count; - } - else - { - cmd.InternalRecordsAffected = count; - } - } - // Skip the bogus DONE counts sent by the server - if (stateObj.HasReceivedColumnMetadata || (curCmd != TdsEnums.SELECT)) - { - cmd.OnStatementCompleted(count); - } - } - - stateObj.HasReceivedColumnMetadata = false; - - // Surface exception for DONE_ERROR in the case we did not receive an error token - // in the stream, but an error occurred. In these cases, we throw a general server error. The - // situations where this can occur are: an invalid buffer received from client, login error - // and the server refused our connection, and the case where we are trying to log in but - // the server has reached its max connection limit. Bottom line, we need to throw general - // error in the cases where we did not receive an error token along with the DONE_ERROR. - if ((TdsEnums.DONE_ERROR == (TdsEnums.DONE_ERROR & status)) && stateObj.ErrorCount == 0 && - stateObj.HasReceivedError == false && (RunBehavior.Clean != (RunBehavior.Clean & run))) - { - stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.SevereError(), "", 0, exception: null, batchIndex: cmd?.GetCurrentBatchIndex() ?? -1)); - - if (reader != null) - { // SQL BU DT 269516 - if (!reader.IsInitialized) - { - run = RunBehavior.UntilDone; - } - } - } - - // Similar to above, only with a more severe error. In this case, if we received - // the done_srverror, this exception will be added to the collection regardless. - // MDAC #93896. Also, per Ashwin, the server will always break the connection in this case. - if ((TdsEnums.DONE_SRVERROR == (TdsEnums.DONE_SRVERROR & status)) && (RunBehavior.Clean != (RunBehavior.Clean & run))) - { - stateObj.AddError(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.SevereError(), "", 0, exception: null, batchIndex: cmd?.GetCurrentBatchIndex() ?? -1)); - - if (reader != null) - { // SQL BU DT 269516 - if (!reader.IsInitialized) - { - run = RunBehavior.UntilDone; - } - } - } - - ProcessSqlStatistics(curCmd, status, count); - - // stop if the DONE_MORE bit isn't set (see above for attention handling) - if (TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE)) - { - stateObj.HasReceivedError = false; - if (stateObj._inBytesUsed >= stateObj._inBytesRead) - { - stateObj.HasPendingData = false; - } - } - - // _pendingData set by e.g. 'TdsExecuteSQLBatch' - // _hasOpenResult always set to true by 'WriteMarsHeader' - // - if (!stateObj.HasPendingData && stateObj.HasOpenResult) - { - /* - Debug.Assert(!((sqlTransaction != null && _distributedTransaction != null) || - (_userStartedLocalTransaction != null && _distributedTransaction != null)) - , "ProcessDone - have both distributed and local transactions not null!"); - */ - // WebData 112722 - - stateObj.DecrementOpenResultCount(); - } - - return TdsOperationStatus.Done; - } - - private void ProcessSqlStatistics(ushort curCmd, ushort status, int count) - { - // SqlStatistics bookkeeping stuff - // - if (_statistics != null) - { - // any done after row(s) counts as a resultset - if (_statistics.WaitForDoneAfterRow) - { - _statistics.SafeIncrement(ref _statistics._sumResultSets); - _statistics.WaitForDoneAfterRow = false; - } - - // clear row count DONE_COUNT flag is not set - if (!(TdsEnums.DONE_COUNT == (status & TdsEnums.DONE_COUNT))) - { - count = 0; - } - - switch (curCmd) - { - case TdsEnums.INSERT: - case TdsEnums.DELETE: - case TdsEnums.UPDATE: - case TdsEnums.MERGE: - _statistics.SafeIncrement(ref _statistics._iduCount); - _statistics.SafeAdd(ref _statistics._iduRows, count); - if (!_statisticsIsInTransaction) - { - _statistics.SafeIncrement(ref _statistics._transactions); - } - - break; - - case TdsEnums.SELECT: - _statistics.SafeIncrement(ref _statistics._selectCount); - _statistics.SafeAdd(ref _statistics._selectRows, count); - break; - - case TdsEnums.BEGINXACT: - if (!_statisticsIsInTransaction) - { - _statistics.SafeIncrement(ref _statistics._transactions); - } - _statisticsIsInTransaction = true; - break; - - case TdsEnums.OPENCURSOR: - _statistics.SafeIncrement(ref _statistics._cursorOpens); - break; - - case TdsEnums.ABORT: - _statisticsIsInTransaction = false; - break; - - case TdsEnums.ENDXACT: - _statisticsIsInTransaction = false; - break; - } // switch - } - else - { - switch (curCmd) - { - case TdsEnums.BEGINXACT: - _statisticsIsInTransaction = true; - break; - - case TdsEnums.ABORT: - case TdsEnums.ENDXACT: - _statisticsIsInTransaction = false; - break; - } - } - } - - private TdsOperationStatus TryProcessFeatureExtAck(TdsParserStateObject stateObj) - { - // read feature ID - byte featureId; - do - { - TdsOperationStatus result = stateObj.TryReadByte(out featureId); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (featureId != TdsEnums.FEATUREEXT_TERMINATOR) - { - uint dataLen; - result = stateObj.TryReadUInt32(out dataLen); - if (result != TdsOperationStatus.Done) - { - return result; - } - byte[] data = new byte[dataLen]; - if (dataLen > 0) - { - result = stateObj.TryReadByteArray(data, checked((int)dataLen)); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - _connHandler.OnFeatureExtAck(featureId, data); - } - } while (featureId != TdsEnums.FEATUREEXT_TERMINATOR); - - // Write to DNS Cache or clean up DNS Cache for TCP protocol - bool ret = false; - if (_connHandler._cleanSQLDNSCaching) - { - ret = SQLFallbackDNSCache.Instance.DeleteDNSInfo(FQDNforDNSCache); - } - - if (_connHandler.IsSQLDNSCachingSupported && _connHandler.pendingSQLDNSObject != null - && !SQLFallbackDNSCache.Instance.IsDuplicate(_connHandler.pendingSQLDNSObject)) - { - ret = SQLFallbackDNSCache.Instance.AddDNSInfo(_connHandler.pendingSQLDNSObject); - _connHandler.pendingSQLDNSObject = null; - } - - // Check if column encryption was on and feature wasn't acknowledged and we aren't going to be routed to another server. - if (Connection.RoutingInfo == null - && _connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled - && !IsColumnEncryptionSupported) - { - throw SQL.TceNotSupported(); - } - - // Check if server does not support Enclave Computations and we aren't going to be routed to another server. - if (Connection.RoutingInfo == null) - { - SqlConnectionAttestationProtocol attestationProtocol = _connHandler.ConnectionOptions.AttestationProtocol; - - if (TceVersionSupported < TdsEnums.MIN_TCE_VERSION_WITH_ENCLAVE_SUPPORT) - { - // Check if enclave attestation url was specified and server does not support enclave computations and we aren't going to be routed to another server. - if (!string.IsNullOrWhiteSpace(_connHandler.ConnectionOptions.EnclaveAttestationUrl) && attestationProtocol != SqlConnectionAttestationProtocol.NotSpecified) - { - throw SQL.EnclaveComputationsNotSupported(); - } - else if (!string.IsNullOrWhiteSpace(_connHandler.ConnectionOptions.EnclaveAttestationUrl)) - { - throw SQL.AttestationURLNotSupported(); - } - else if (_connHandler.ConnectionOptions.AttestationProtocol != SqlConnectionAttestationProtocol.NotSpecified) - { - throw SQL.AttestationProtocolNotSupported(); - } - } - - // Check if enclave attestation url was specified and server does not return an enclave type and we aren't going to be routed to another server. - if (!string.IsNullOrWhiteSpace(_connHandler.ConnectionOptions.EnclaveAttestationUrl) || attestationProtocol == SqlConnectionAttestationProtocol.None) - { - if (string.IsNullOrWhiteSpace(EnclaveType)) - { - throw SQL.EnclaveTypeNotReturned(); - } - else - { - // Check if the attestation protocol is specified and supports the enclave type. - if (SqlConnectionAttestationProtocol.NotSpecified != attestationProtocol && !IsValidAttestationProtocol(attestationProtocol, EnclaveType)) - { - throw SQL.AttestationProtocolNotSupportEnclaveType(attestationProtocol.ToString(), EnclaveType); - } - } - } - } - - return TdsOperationStatus.Done; - } - - private bool IsValidAttestationProtocol(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType) - { - switch (enclaveType.ToUpper()) - { - case TdsEnums.ENCLAVE_TYPE_VBS: - if (attestationProtocol != SqlConnectionAttestationProtocol.AAS - && attestationProtocol != SqlConnectionAttestationProtocol.HGS - && attestationProtocol != SqlConnectionAttestationProtocol.None) - { - return false; - } - break; - - case TdsEnums.ENCLAVE_TYPE_SGX: -#if ENCLAVE_SIMULATOR - if (attestationProtocol != SqlConnectionAttestationProtocol.AAS - && attestationProtocol != SqlConnectionAttestationProtocol.None) -#else - if (attestationProtocol != SqlConnectionAttestationProtocol.AAS) -#endif - { - return false; - } - break; - -#if ENCLAVE_SIMULATOR - case TdsEnums.ENCLAVE_TYPE_SIMULATOR: - if (attestationProtocol != SqlConnectionAttestationProtocol.None) - { - return false; - } - break; -#endif - default: - // if we reach here, the enclave type is not supported - throw SQL.EnclaveTypeNotSupported(enclaveType); - } - - return true; - } - - private TdsOperationStatus TryReadByteString(TdsParserStateObject stateObj, out string value) - { - value = string.Empty; - - byte byteLen; - TdsOperationStatus result = stateObj.TryReadByte(out byteLen); - if (result != TdsOperationStatus.Done) - { - return result; - } - - result = stateObj.TryReadString(byteLen, out value); - if (result != TdsOperationStatus.Done) - { - return result; - } - - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryReadSensitivityLabel(TdsParserStateObject stateObj, out string label, out string id) - { - label = string.Empty; - id = string.Empty; - - TdsOperationStatus result = TryReadByteString(stateObj, out label); - if (result != TdsOperationStatus.Done) - { - return result; - } - - result = TryReadByteString(stateObj, out id); - if (result != TdsOperationStatus.Done) - { - return result; - } - - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryReadSensitivityInformationType(TdsParserStateObject stateObj, out string informationType, out string id) - { - informationType = string.Empty; - id = string.Empty; - - TdsOperationStatus result = TryReadByteString(stateObj, out informationType); - if (result != TdsOperationStatus.Done) - { - return result; - } - - result = TryReadByteString(stateObj, out id); - if (result != TdsOperationStatus.Done) - { - return result; - } - - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryProcessDataClassification(TdsParserStateObject stateObj, out SensitivityClassification sensitivityClassification) - { - if (DataClassificationVersion == 0) - { - throw SQL.ParsingError(ParsingErrorState.DataClassificationNotExpected); - } - - sensitivityClassification = null; - - // get the labels - TdsOperationStatus result = stateObj.TryReadUInt16(out ushort numLabels); - if (result != TdsOperationStatus.Done) - { - return result; - } - List