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 c7983fcc15..c2e88310da 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -272,6 +272,9 @@ Microsoft\Data\SqlClient\Connection\CachedContexts.cs + + Microsoft\Data\SqlClient\Connection\ConnectionCapabilities.cs + Microsoft\Data\SqlClient\Connection\ServerInfo.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index ea6b7de6ce..afe4b6b02c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -342,6 +342,9 @@ Microsoft\Data\SqlClient\Connection\CachedContexts.cs + + Microsoft\Data\SqlClient\Connection\ConnectionCapabilities.cs + Microsoft\Data\SqlClient\Connection\ServerInfo.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ConnectionCapabilities.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ConnectionCapabilities.cs new file mode 100644 index 0000000000..df9da18e2e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ConnectionCapabilities.cs @@ -0,0 +1,528 @@ +// 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.Text; + +#nullable enable + +namespace Microsoft.Data.SqlClient; + +/// +/// Describes the capabilities and related information (such as the +/// reported server version and TDS version) of the connection. +/// +internal sealed class ConnectionCapabilities +{ + /// + /// This TDS version is used by SQL Server 2005. + /// + private const uint SqlServer2005TdsVersion = 0x72_09_0002; + + /// + /// This TDS version is used by SQL Server 2008 R2. + /// + private const uint SqlServer2008R2TdsVersion = 0x73_0B_0003; + + /// + /// This TDS version is used by SQL Server 2012 and onwards. + /// In SQL Server 2022 and SQL Server 2025, this is used when + /// the SQL Server instance is responding with the TDS 7.x + /// protocol. + /// + private const uint SqlServer2012TdsVersion = TdsEnums.TDS7X_VERSION; + + /// + /// This TDS version is used by SQL Server 2022 and onwards, + /// when responding with the TDS 8.x protocol. + /// + private const uint SqlServer2022TdsVersion = TdsEnums.TDS80_VERSION; + + private readonly int _objectId; + + /// + /// The TDS version reported by the LoginAck response + /// from the server. + /// + public uint TdsVersion { get; private set; } + + /// + /// The SQL Server major version reported by the LoginAck + /// response from the server. + /// + public byte ServerMajorVersion { get; private set; } + + /// + /// The SQL Server minor version reported by the LoginAck + /// response from the server. + /// + public byte ServerMinorVersion { get; private set; } + + /// + /// The SQL Server build number reported by the LoginAck + /// response from the server. + /// + public ushort ServerBuildNumber { get; private set; } + + /// + /// The user-friendly SQL Server version reported by the + /// LoginAck response from the server. + /// + public string ServerVersion => + $"{ServerMajorVersion:00}.{ServerMinorVersion:00}.{ServerBuildNumber:0000}"; + + /// + /// If true (as determined by the value of ) + /// then the connection is to SQL Server 2008 R2 or newer. + /// + public bool Is2008R2OrNewer => + Is2012OrNewer || TdsVersion == SqlServer2008R2TdsVersion; + + /// + /// If true (as determined by the value of ) + /// then the connection is to SQL Server 2012 or newer. + /// + public bool Is2012OrNewer => + Is2022OrNewer || TdsVersion == SqlServer2012TdsVersion; + + /// + /// If true (as determined by the value of ) + /// then the connection is to SQL Server 2022 or newer. + /// + public bool Is2022OrNewer => + TdsVersion == SqlServer2022TdsVersion; + + /// + /// If true, this connection is to an Azure SQL instance. This is determined + /// by the receipt of a FEATUREEXTACK token of value 0x08. + /// + public bool IsAzureSql { get; private set; } + + /// + /// Indicates support for user-defined CLR types (up to a length of 8000 + /// bytes.) This was introduced in SQL Server 2005. + /// + public bool UserDefinedTypes => true; + + /// + /// Indicates support for the xml data type. This was introduced + /// in SQL Server 2005. + /// + public bool XmlDataType => true; + + /// + /// Indicates support for the date, time, datetime2 + /// and datetimeoffset data types. These were introduced in SQL + /// Server 2008. + /// + public bool ExpandedDateTimeDataTypes => Is2008R2OrNewer; + + /// + /// Indicates support for user-defined CLR types of any length. This + /// was introduced in SQL Server 2008. + /// + public bool LargeUserDefinedTypes => Is2008R2OrNewer; + + /// + /// Indicates support for the client to include a TDS trace header, + /// which is surfaced in XEvents traces to correlate events between + /// the client and the server. This was introduced in SQL Server 2012. + /// + public bool TraceHeader => Is2012OrNewer; + + /// + /// Indicates support for UTF8 collations. This was introduced in SQL + /// Server 2019, and is only available if a FEATUREEXTACK token of value + /// 0x0A is received. + /// + public bool Utf8 { get; private set; } + + /// + /// Indicates support for the client to cache DNS resolution responses for + /// the server. This is only supported by Azure SQL, and is only available + /// if a FEATUREEXTACK token of value 0x0B is received. + /// + public bool DnsCaching { get; private set; } + + /// + /// Indicates support for Data Classification and specifies the version of + /// Data Classification which is supported. This was introduced in SQL + /// Server 2019, and is only available if a FEATUREEXTACK token of value + /// 0x09 is received. + /// + /// + /// This should only be 1 or 2. + /// + public byte DataClassificationVersion { get; private set; } + + /// + /// Indicates that Global Transactions are available (even if not currently enabled.) + /// Global Transactions are only supported by Azure SQL, and are only available if a + /// FEATUREEXTACK token of value 0x05 is received. + /// + public bool GlobalTransactionsAvailable { get; private set; } + + /// + /// Indicates support for Global Transactions. This is only supported by + /// Azure SQL, and is only available if a FEATUREEXTACK token of value + /// 0x05 is received. + /// + public bool GlobalTransactionsSupported { get; private set; } + + /// + /// Indicates support for Enhanced Routing. This is only supported by + /// Azure SQL, and is only available if a FEATUREEXTACK token of value + /// 0x0F is received. + /// + public bool EnhancedRouting { get; private set; } + + /// + /// Indicates support for connecting to the current connection's failover + /// partner with an Application Intent of ReadOnly. This is only supported + /// by Azure SQL, and is only available if a FEATUREEXTACK token of value + /// 0x08 is received, and if bit zero of this token's data is set. + /// + public bool ReadOnlyFailoverPartnerConnection { get; private set; } + + /// + /// Indicates support for the vector data type, with a backing type + /// of float32. This was introduced in SQL Server 2022, and is only + /// available if a FEATUREEXTACK token of value 0x0E is received, and + /// if the version in this token's data is greater than or equal to 1. + /// + public bool Float32VectorType { get; private set; } + + /// + /// Indicates support for the json data type. This was introduced in + /// SQL Server 2022, and is only available if a FEATUREEXTACK token of value + /// 0x0D is received, and if the version in this token's data is + /// greater than or equal to 1. + /// + public bool JsonType { get; private set; } + + /// + /// Indicates support for column encryption and specifies the version of column + /// encryption which is supported. This was introduced in SQL Server 2016, and is + /// only available if a FEATUREEXTACK token of value 0x04 is received. + /// + /// + /// This should only be 1, 2 or 3. v1 is supported from SQL + /// Server 2016 upwards, v2 is supported from SQL Server 2019 upwards, v3 is supported + /// from SQL Server 2022 upwards. + /// + public byte ColumnEncryptionVersion { get; private set; } + + /// + /// If column encryption is enabled, the type of enclave reported by the server. This + /// was introduced in SQL Server 2019, and is only available if a FEATUREEXTACK token + /// of value 0x04 is received, and the resultant + /// is 2 or 3. + /// + public string? ColumnEncryptionEnclaveType { get; private set; } + + public ConnectionCapabilities(int parentObjectId) + { + _objectId = parentObjectId; + } + + /// + /// Returns the capability records to unset values. + /// + public void Reset() + { + TdsVersion = 0; + ServerMajorVersion = 0; + ServerMinorVersion = 0; + ServerBuildNumber = 0; + + IsAzureSql = false; + Utf8 = false; + DnsCaching = false; + DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED; + GlobalTransactionsAvailable = false; + GlobalTransactionsSupported = false; + EnhancedRouting = false; + ReadOnlyFailoverPartnerConnection = false; + Float32VectorType = false; + JsonType = false; + ColumnEncryptionVersion = TdsEnums.TCE_NOT_ENABLED; + ColumnEncryptionEnclaveType = null; + } + + /// + /// Updates the connection capability record based upon the LOGINACK + /// token stream sent by the server. + /// + /// The LOGINACK token stream sent by the server + public void ProcessLoginAck(SqlLoginAck loginAck) + { + if (loginAck.TdsVersion is not SqlServer2005TdsVersion + and not SqlServer2008R2TdsVersion + and not SqlServer2012TdsVersion + and not SqlServer2022TdsVersion) + { + throw SQL.InvalidTDSVersion(); + } + + TdsVersion = loginAck.TdsVersion; + ServerMajorVersion = loginAck.MajorVersion; + ServerMinorVersion = loginAck.MinorVersion; + ServerBuildNumber = loginAck.BuildNumber; + } + + public void ProcessFeatureExtAck(byte featureId, ReadOnlySpan featureData) + { + switch (featureId) + { + case TdsEnums.FEATUREEXT_UTF8SUPPORT: + SqlClientEventSource.Log.TryAdvancedTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ADV | " + + $"Object ID {_objectId}, " + + $"Received feature extension acknowledgement for UTF8 support"); + + if (featureData.Length < 1) + { + SqlClientEventSource.Log.TryTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Unknown value for UTF8 support"); + + throw SQL.ParsingError(); + } + + // The server can send and receive UTF8-encoded data if bit 0 of the + // feature data is set. + Utf8 = (featureData[0] & 0x01) == 0x01; + break; + + case TdsEnums.FEATUREEXT_SQLDNSCACHING: + SqlClientEventSource.Log.TryAdvancedTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ADV | " + + $"Object ID {_objectId}, " + + $"Received feature extension acknowledgement for SQLDNSCACHING"); + + if (featureData.Length < 1) + { + SqlClientEventSource.Log.TryTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Unknown token for SQLDNSCACHING"); + + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + + // The client may cache DNS resolution responses if bit 0 of the feature + // data is set. + DnsCaching = (featureData[0] & 0x01) == 0x01; + break; + + case TdsEnums.FEATUREEXT_DATACLASSIFICATION: + SqlClientEventSource.Log.TryAdvancedTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ADV | " + + $"Object ID {_objectId}, " + + $"Received feature extension acknowledgement for DATACLASSIFICATION"); + + if (featureData.Length != 2) + { + SqlClientEventSource.Log.TryTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Unknown token for DATACLASSIFICATION"); + + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + + byte dcVersion = featureData[0]; + + if (dcVersion is TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED + or > TdsEnums.DATA_CLASSIFICATION_VERSION_MAX_SUPPORTED) + { + SqlClientEventSource.Log.TryTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Invalid version number for DATACLASSIFICATION"); + + throw SQL.ParsingErrorValue( + ParsingErrorState.DataClassificationInvalidVersion, + dcVersion); + } + + // The feature data is comprised of a single byte containing the version, + // followed by another byte indicating whether or not data classification is + // enabled. + DataClassificationVersion = featureData[1] == 0x00 + ? TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED + : dcVersion; + break; + + case TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS: + SqlClientEventSource.Log.TryAdvancedTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ADV | " + + $"Object ID {_objectId}, " + + $"Received feature extension acknowledgement for GlobalTransactions"); + + if (featureData.Length < 1) + { + SqlClientEventSource.Log.TryTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Unknown version number for GlobalTransactions"); + + throw SQL.ParsingError(); + } + + // Feature data is comprised of a single byte which indicates whether + // global transactions are available. + GlobalTransactionsAvailable = true; + + GlobalTransactionsSupported = featureData[0] == 0x01; + break; + + case TdsEnums.FEATUREEXT_AZURESQLSUPPORT: + SqlClientEventSource.Log.TryAdvancedTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ADV | " + + $"Object ID {_objectId}, " + + $"Received feature extension acknowledgement for AzureSQLSupport"); + + if (featureData.Length < 1) + { + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + + IsAzureSql = true; + // Clients can connect to the failover partner with a read-only AppIntent if bit 0 + // of the only byte in the feature data is set. + ReadOnlyFailoverPartnerConnection = (featureData[0] & 0x01) == 0x01; + + if (ReadOnlyFailoverPartnerConnection && SqlClientEventSource.Log.IsTraceEnabled()) + { + SqlClientEventSource.Log.TryAdvancedTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ADV | " + + $"Object ID {_objectId}, " + + $"FailoverPartner enabled with Readonly intent for AzureSQL DB"); + } + break; + + case TdsEnums.FEATUREEXT_VECTORSUPPORT: + SqlClientEventSource.Log.TryAdvancedTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ADV | " + + $"Object ID {_objectId}, " + + $"Received feature extension acknowledgement for VECTORSUPPORT"); + + if (featureData.Length != 1) + { + SqlClientEventSource.Log.TryTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Unknown token for VECTORSUPPORT"); + + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + + // Feature data is comprised of a single byte which specifies the version of the vector + // type which is available. + if (featureData[0] is 0x00 + or > TdsEnums.MAX_SUPPORTED_VECTOR_VERSION) + { + SqlClientEventSource.Log.TryTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Invalid version number {featureData[0]} for VECTORSUPPORT, " + + $"Max supported version is {TdsEnums.MAX_SUPPORTED_VECTOR_VERSION}"); + + throw SQL.ParsingError(); + } + + Float32VectorType = true; + break; + + case TdsEnums.FEATUREEXT_JSONSUPPORT: + SqlClientEventSource.Log.TryAdvancedTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ADV | " + + $"Object ID {_objectId}, " + + $"Received feature extension acknowledgement for JSONSUPPORT"); + + if (featureData.Length != 1) + { + SqlClientEventSource.Log.TryTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Unknown token for JSONSUPPORT"); + + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + + // Feature data is comprised of a single byte which specifies the version of the JSON + // type which is available. + if (featureData[0] is 0x00 + or > TdsEnums.MAX_SUPPORTED_JSON_VERSION) + { + SqlClientEventSource.Log.TryTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Invalid version number for JSONSUPPORT"); + + throw SQL.ParsingError(); + } + + JsonType = true; + break; + + case TdsEnums.FEATUREEXT_TCE: + SqlClientEventSource.Log.TryAdvancedTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Received feature extension acknowledgement for TCE"); + + if (featureData.Length < 1) + { + SqlClientEventSource.Log.TryTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Unknown version number for TCE"); + + throw SQL.ParsingError(ParsingErrorState.TceUnknownVersion); + } + + if (featureData[0] is TdsEnums.TCE_NOT_ENABLED + or > TdsEnums.MAX_SUPPORTED_TCE_VERSION) + { + SqlClientEventSource.Log.TryTraceEvent( + $"{nameof(ConnectionCapabilities)}.{nameof(ProcessFeatureExtAck)} | ERR | " + + $"Object ID {_objectId}, " + + $"Invalid version number for TCE"); + + throw SQL.ParsingErrorValue(ParsingErrorState.TceInvalidVersion, featureData[0]); + } + + // Feature data begins with one byte containing the column encryption version. If + // this version is 2 or 3, the version is followed by a B_NVARCHAR (i.e., a one-byte + // string length followed by a Unicode-encoded string containing the enclave type.) + // NB 1: the MS-TDS specification requires that a client must throw an exception if + // the column encryption version is 2 and no enclave type is specified. We do not do + // this. + // NB 2: although the length is specified, we assume that everything from position 2 + // of the packet and forwards is a Unicode-encoded string. + ColumnEncryptionVersion = featureData[0]; + + if (featureData.Length > 1) + { + ReadOnlySpan enclaveTypeSpan = featureData.Slice(2); +#if NET + ColumnEncryptionEnclaveType = Encoding.Unicode.GetString(enclaveTypeSpan); +#else + unsafe + { + fixed (byte* fDataPtr = enclaveTypeSpan) + { + ColumnEncryptionEnclaveType = Encoding.Unicode.GetString(fDataPtr, enclaveTypeSpan.Length); + } + } +#endif + } + + break; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index 5567dac6d5..9600d3486c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -165,10 +165,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable // @TODO: Probably a good idea to introduce a delegate type internal readonly Func> _accessTokenCallback; - // @TODO: Should be private and accessed via internal property - // @TODO: Rename to match naming conventions - internal bool _cleanSQLDNSCaching = false; - internal Guid _clientConnectionId = Guid.Empty; /// @@ -199,18 +195,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable // @TODO: Should be private and accessed via internal property internal bool _federatedAuthenticationRequested; - /// - /// Flag indicating whether JSON objects are supported by the server. - /// - // @TODO: Should be private and accessed via internal property - internal bool IsJsonSupportEnabled = false; - - /// - /// Flag indicating whether vector objects are supported by the server. - /// - // @TODO: Should be private and accessed via internal property - internal bool IsVectorSupportEnabled = false; - // @TODO: This should be private internal readonly SyncAsyncLock _parserLock = new SyncAsyncLock(); @@ -220,12 +204,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable // @TODO: Should be private and accessed via internal property internal readonly SspiContextProvider _sspiContextProvider; - /// - /// TCE flags supported by the server. - /// - // @TODO: Should be private and accessed via internal property - internal byte _tceVersionSupported; - private readonly ActiveDirectoryAuthenticationTimeoutRetryHelper _activeDirectoryAuthTimeoutRetryHelper; /// @@ -266,8 +244,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable private string _instanceName = string.Empty; - private SqlLoginAck _loginAck; - /// /// This is used to preserve the authentication context object if we decide to cache it for /// subsequent connections in the same pool. This will finally end up in @@ -302,9 +278,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable // @TODO: Rename to match naming conventions private bool _SQLDNSRetryEnabled = false; - // @TODO: Rename to match naming conventions - private bool _serverSupportsDNSCaching = false; - private bool _sessionRecoveryRequested; private int _threadIdOwningParserLock = -1; @@ -500,10 +473,8 @@ internal SqlConnectionInternal( #region Properties // @TODO: Make internal - public override string ServerVersion - { - get => $"{_loginAck.majorVersion:00}.{(short)_loginAck.minorVersion:00}.{_loginAck.buildNum:0000}"; - } + public override string ServerVersion => + _parser.Capabilities.ServerVersion; /// /// Gets the collection of async call contexts that belong to this connection. @@ -639,16 +610,15 @@ internal bool IsDNSCachingBeforeRedirectSupported internal bool IsEnlistedInTransaction { get; private set; } /// - /// Whether this is a Global Transaction (Non-MSDTC, Azure SQL DB Transaction) - /// TODO: overlaps with IsGlobalTransactionsEnabledForServer, need to consolidate to avoid bugs + /// Whether the server is capable of supporting a Global Transaction (Non-MSDTC, Azure SQL DB Transaction) /// - internal bool IsGlobalTransaction { get; private set; } + internal bool IsGlobalTransaction => _parser.Capabilities.GlobalTransactionsAvailable; /// /// Whether Global Transactions are enabled. Only supported by Azure SQL. False if disabled /// or connected to on-prem SQL Server. /// - internal bool IsGlobalTransactionsEnabledForServer { get; private set; } + internal bool IsGlobalTransactionsEnabledForServer => _parser.Capabilities.GlobalTransactionsSupported; /// /// Whether this connection is locked for bulk copy operations. @@ -662,11 +632,7 @@ internal bool IsLockedForBulkCopy /// Get or set if SQLDNSCaching is supported by the server. /// // @TODO: Make auto-property - internal bool IsSQLDNSCachingSupported - { - get => _serverSupportsDNSCaching; - set => _serverSupportsDNSCaching = value; - } + internal bool IsSQLDNSCachingSupported => _parser.Capabilities.DnsCaching; /// /// Get or set if we need retrying with IP received from FeatureExtAck. @@ -814,12 +780,6 @@ private SqlInternalTransaction AvailableInternalTransaction get => _parser._fResetConnection ? null : CurrentTransaction; } - /// - /// Whether this connection is to an Azure SQL Database. - /// - // @TODO: Make private field. - private bool IsAzureSqlConnection { get; set; } - #endregion #region Public and Internal Methods @@ -1025,8 +985,6 @@ public override void Dispose() finally { // Close will always close, even if exception is thrown. - // Remember to null out any object references. - _loginAck = null; // Mark internal connection as closed _fConnectionOpen = false; @@ -1315,16 +1273,12 @@ internal void OnError(SqlException exception, bool breakConnection, Action + RoutingInfo is null || featureId is TdsEnums.FEATUREEXT_SQLDNSCACHING; + // @TODO: This class should not do low-level parsing of data from the server. internal void OnFeatureExtAck(int featureId, byte[] data) { - if (RoutingInfo != null && featureId != TdsEnums.FEATUREEXT_SQLDNSCACHING) - { - return; - } - switch (featureId) { case TdsEnums.FEATUREEXT_SRECOVERY: @@ -1385,32 +1339,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) break; } - case TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS: - { - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " + - $"Object ID {ObjectID}, " + - $"Received feature extension acknowledgement for GlobalTransactions"); - - if (data.Length < 1) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Unknown version number for GlobalTransactions"); - - throw SQL.ParsingError(); - } - - IsGlobalTransaction = true; - if (data[0] == 0x01) - { - IsGlobalTransactionsEnabledForServer = true; - } - - break; - } - case TdsEnums.FEATUREEXT_FEDAUTH: { SqlClientEventSource.Log.TryAdvancedTraceEvent( @@ -1507,245 +1435,13 @@ internal void OnFeatureExtAck(int featureId, byte[] data) break; } - case TdsEnums.FEATUREEXT_TCE: - { - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " + - $"Object ID {ObjectID}, " + - $"Received feature extension acknowledgement for TCE"); - - if (data.Length < 1) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Unknown version number for TCE"); - - throw SQL.ParsingError(ParsingErrorState.TceUnknownVersion); - } - - byte supportedTceVersion = data[0]; - if (supportedTceVersion == 0 || supportedTceVersion > TdsEnums.MAX_SUPPORTED_TCE_VERSION) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Invalid version number for TCE"); - - throw SQL.ParsingErrorValue(ParsingErrorState.TceInvalidVersion, supportedTceVersion); - } - - _tceVersionSupported = supportedTceVersion; - - Debug.Assert(_tceVersionSupported <= TdsEnums.MAX_SUPPORTED_TCE_VERSION, - "Client support TCE version 2"); - - _parser.IsColumnEncryptionSupported = true; - _parser.TceVersionSupported = _tceVersionSupported; - _parser.AreEnclaveRetriesSupported = _tceVersionSupported == 3; - - if (data.Length > 1) - { - // Extract the type of enclave being used by the server. - _parser.EnclaveType = Encoding.Unicode.GetString(data, 2, data.Length - 2); - } - break; - } - case TdsEnums.FEATUREEXT_AZURESQLSUPPORT: - { - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " + - $"Object ID {ObjectID}, " + - $"Received feature extension acknowledgement for AzureSQLSupport"); - - if (data.Length < 1) - { - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); - } - - IsAzureSqlConnection = true; - - // Bit 0 for RO/FP support - // @TODO: Add a constant somewhere for that - if ((data[0] & 1) == 1 && SqlClientEventSource.Log.IsTraceEnabled()) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " + - $"Object ID {ObjectID}, " + - $"FailoverPartner enabled with Readonly intent for AzureSQL DB"); - } - - break; - } - case TdsEnums.FEATUREEXT_DATACLASSIFICATION: - { - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " + - $"Object ID {ObjectID}, " + - $"Received feature extension acknowledgement for DATACLASSIFICATION"); - - if (data.Length < 1) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Unknown token for DATACLASSIFICATION"); - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); - } - - byte supportedDataClassificationVersion = data[0]; - if (supportedDataClassificationVersion == 0 || - supportedDataClassificationVersion > TdsEnums.DATA_CLASSIFICATION_VERSION_MAX_SUPPORTED) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Invalid version number for DATACLASSIFICATION"); - - throw SQL.ParsingErrorValue( - ParsingErrorState.DataClassificationInvalidVersion, - supportedDataClassificationVersion); - } - - if (data.Length != 2) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Unknown token for DATACLASSIFICATION"); - - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); - } - - byte enabled = data[1]; - _parser.DataClassificationVersion = enabled == 0 - ? TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED - : supportedDataClassificationVersion; - - break; - } - - case TdsEnums.FEATUREEXT_UTF8SUPPORT: - { - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " + - $"Object ID {ObjectID}, " + - $"Received feature extension acknowledgement for UTF8 support"); - - if (data.Length < 1) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Unknown value for UTF8 support", ObjectID); - - throw SQL.ParsingError(); - } - break; - } case TdsEnums.FEATUREEXT_SQLDNSCACHING: { - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " + - $"Object ID {ObjectID}, " + - $"Received feature extension acknowledgement for SQLDNSCACHING"); - - if (data.Length < 1) + if (IsSQLDNSCachingSupported && RoutingInfo != null) { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Unknown token for SQLDNSCACHING"); - - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); - } - - if (data[0] == 1) - { - IsSQLDNSCachingSupported = true; - _cleanSQLDNSCaching = false; - - if (RoutingInfo != null) - { - IsDNSCachingBeforeRedirectSupported = true; - } + IsDNSCachingBeforeRedirectSupported = true; } - else - { - // we receive the IsSupported whose value is 0 - IsSQLDNSCachingSupported = false; - _cleanSQLDNSCaching = true; - } - - // TODO: need to add more steps for phase 2 - // get IPv4 + IPv6 + Port number - // not put them in the DNS cache at this point but need to store them somewhere - // generate pendingSQLDNSObject and turn on IsSQLDNSRetryEnabled flag - break; - } - case TdsEnums.FEATUREEXT_JSONSUPPORT: - { - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " + - $"Object ID {ObjectID}, " + - $"Received feature extension acknowledgement for JSONSUPPORT"); - - if (data.Length != 1) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Unknown token for JSONSUPPORT"); - - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); - } - - byte jsonSupportVersion = data[0]; - if (jsonSupportVersion == 0 || jsonSupportVersion > TdsEnums.MAX_SUPPORTED_JSON_VERSION) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Invalid version number for JSONSUPPORT"); - - throw SQL.ParsingError(); - } - - IsJsonSupportEnabled = true; - break; - } - case TdsEnums.FEATUREEXT_VECTORSUPPORT: - { - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " + - $"Object ID {ObjectID}, " + - $"Received feature extension acknowledgement for VECTORSUPPORT"); - - if (data.Length != 1) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Unknown token for VECTORSUPPORT"); - - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); - } - - byte vectorSupportVersion = data[0]; - if (vectorSupportVersion == 0 || vectorSupportVersion > TdsEnums.MAX_SUPPORTED_VECTOR_VERSION) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + - $"Object ID {ObjectID}, " + - $"Invalid version number {vectorSupportVersion} for VECTORSUPPORT, " + - $"Max supported version is {TdsEnums.MAX_SUPPORTED_VECTOR_VERSION}"); - - throw SQL.ParsingError(); - } - - IsVectorSupportEnabled = true; - break; } case TdsEnums.FEATUREEXT_USERAGENT: @@ -1758,11 +1454,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) break; } - default: - { - // Unknown feature ack - throw SQL.ParsingError(); - } } } @@ -1943,10 +1634,9 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) internal void OnLoginAck(SqlLoginAck rec) { - _loginAck = rec; if (_recoverySessionData != null) { - if (_recoverySessionData._tdsVersion != rec.tdsVersion) + if (_recoverySessionData._tdsVersion != rec.TdsVersion) { throw SQL.CR_TDSVersionNotPreserved(this); } @@ -1954,7 +1644,7 @@ internal void OnLoginAck(SqlLoginAck rec) if (_currentSessionData != null) { - _currentSessionData._tdsVersion = rec.tdsVersion; + _currentSessionData._tdsVersion = rec.TdsVersion; } } @@ -3874,7 +3564,7 @@ private void OpenLoginEnlist( timeout); } - if (!IsAzureSqlConnection) + if (!_parser.Capabilities.IsAzureSql) { // If not a connection to Azure SQL, Readonly with FailoverPartner is not supported if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniPhysicalHandle.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniPhysicalHandle.netcore.cs index 7e7b35655f..1c9d8a2d4a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniPhysicalHandle.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniPhysicalHandle.netcore.cs @@ -16,9 +16,7 @@ internal abstract class SniPhysicalHandle : SniHandle { protected const int DefaultPoolSize = 4; -#if DEBUG private static int s_packetId; -#endif private ObjectPool _pool; protected SniPhysicalHandle(int poolSize = DefaultPoolSize) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 79c1c4a239..b4a3cccaae 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -758,11 +758,12 @@ private static DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(Sql private static SqlMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection) { Debug.Assert(internalConnection is not null, "internalConnection may not be null."); + Debug.Assert(internalConnection as SqlConnectionInternal is not null, "innerConnection must be a SqlConnectionInternal."); Stream xmlStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Data.SqlClient.SqlMetaData.xml"); Debug.Assert(xmlStream is not null, $"{nameof(xmlStream)} may not be null."); - - return new SqlMetaDataFactory(xmlStream, internalConnection.ServerVersion); + + return new SqlMetaDataFactory(xmlStream, ((SqlConnectionInternal)internalConnection).Parser.Capabilities); } private Task CreateReplaceConnectionContinuation( diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs index ca3dec0658..b79437d943 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -38,12 +38,13 @@ internal sealed class SqlMetaDataFactory : IDisposable private readonly DataSet _collectionDataSet; private readonly string _serverVersion; - public SqlMetaDataFactory(Stream xmlStream, string serverVersion) + public SqlMetaDataFactory(Stream xmlStream, ConnectionCapabilities connectionCapabilities) { ADP.CheckArgumentNull(xmlStream, nameof(xmlStream)); - ADP.CheckArgumentNull(serverVersion, nameof(serverVersion)); + ADP.CheckArgumentNull(connectionCapabilities, nameof(connectionCapabilities)); + ADP.CheckArgumentNull(connectionCapabilities.ServerVersion, nameof(connectionCapabilities.ServerVersion)); - _serverVersion = serverVersion; + _serverVersion = connectionCapabilities.ServerVersion; _collectionDataSet = LoadDataSetFromXml(xmlStream); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs index f6df0a82fe..5128b7900a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -314,62 +314,11 @@ public enum ActiveDirectoryWorkflow : byte public const int TEXT_TIME_STAMP_LEN = 8; public const int COLLATION_INFO_LEN = 4; - /* - public const byte INT4_LSB_HI = 0; // lsb is low byte (e.g. 68000) - // public const byte INT4_LSB_LO = 1; // lsb is low byte (e.g. VAX) - public const byte INT2_LSB_HI = 2; // lsb is low byte (e.g. 68000) - // public const byte INT2_LSB_LO = 3; // lsb is low byte (e.g. VAX) - public const byte FLT_IEEE_HI = 4; // lsb is low byte (e.g. 68000) - public const byte CHAR_ASCII = 6; // ASCII character set - public const byte TWO_I4_LSB_HI = 8; // lsb is low byte (e.g. 68000 - // public const byte TWO_I4_LSB_LO = 9; // lsb is low byte (e.g. VAX) - // public const byte FLT_IEEE_LO = 10; // lsb is low byte (e.g. MSDOS) - public const byte FLT4_IEEE_HI = 12; // IEEE 4-byte floating point -lsb is high byte - // public const byte FLT4_IEEE_LO = 13; // IEEE 4-byte floating point -lsb is low byte - public const byte TWO_I2_LSB_HI = 16; // lsb is high byte - // public const byte TWO_I2_LSB_LO = 17; // lsb is low byte - - public const byte LDEFSQL = 0; // server sends its default - public const byte LDEFUSER = 0; // regular old user - public const byte LINTEGRATED = 8; // integrated security login - */ - - /* Versioning scheme table: - - Client sends: - 0x70000000 -> 7.0 - 0x71000000 -> 2000 RTM - 0x71000001 -> 2000 SP1 - 0x72xx0002 -> 2005 RTM - - Server responds: - 0x07000000 -> 7.0 // Notice server response format is different for bwd compat - 0x07010000 -> 2000 RTM // Notice server response format is different for bwd compat - 0x71000001 -> 2000 SP1 - 0x72xx0002 -> 2005 RTM - */ - - // Majors: - // For 2000 SP1 and later the versioning schema changed and - // the high-byte is sufficient to distinguish later versions - public const int SQL2005_MAJOR = 0x72; - public const int SQL2008_MAJOR = 0x73; - public const int SQL2012_MAJOR = 0x74; - public const int TDS8_MAJOR = 0x08; // TDS8 version to be used at login7 + // Versions to be used on login for TDS 7.x and TDS 8.0 + public const uint TDS7X_VERSION = 0x74_00_0004; + public const uint TDS80_VERSION = 0x08_00_0000; public const string TDS8_Protocol = "tds/8.0"; //TDS8 - // Increments: - public const int SQL2005_INCREMENT = 0x09; - public const int SQL2008_INCREMENT = 0x0b; - public const int SQL2012_INCREMENT = 0x00; - public const int TDS8_INCREMENT = 0x00; - - // Minors: - public const int SQL2005_RTM_MINOR = 0x0002; - public const int SQL2008_MINOR = 0x0003; - public const int SQL2012_MINOR = 0x0004; - public const int TDS8_MINOR = 0x00; - public const int ORDER_68000 = 1; public const int USE_DB_ON = 1; public const int INIT_DB_FATAL = 1; @@ -991,8 +940,10 @@ internal enum FedAuthInfoId : byte internal const byte SUPPORTED_USER_AGENT_VERSION = 0x01; // TCE Related constants + internal const byte TCE_NOT_ENABLED = 0x00; internal const byte MAX_SUPPORTED_TCE_VERSION = 0x03; // max version internal const byte MIN_TCE_VERSION_WITH_ENCLAVE_SUPPORT = 0x02; // min version with enclave support + internal const byte MIN_TCE_VERSION_WITH_ENCLAVE_RETRY_SUPPORT = 0x03; internal const ushort MAX_TCE_CIPHERINFO_SIZE = 2048; // max size of cipherinfo blob internal const long MAX_TCE_CIPHERTEXT_SIZE = 2147483648; // max size of encrypted blob- currently 2GB. internal const byte CustomCipherAlgorithmId = 0; // Id used for custom encryption algorithm. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs index 07ab80b96a..fd936351b2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -112,14 +112,6 @@ internal sealed partial class TdsParser 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; @@ -154,25 +146,30 @@ internal sealed partial class TdsParser // NOTE: You must take the internal connection's _parserLock before modifying this internal bool _asyncWrite = false; + // Capability records + internal ConnectionCapabilities Capabilities { get; } + /// /// Get or set if column encryption is supported by the server. /// - internal bool IsColumnEncryptionSupported { get; set; } = false; + internal bool IsColumnEncryptionSupported => + TceVersionSupported != TdsEnums.TCE_NOT_ENABLED; /// /// TCE version supported by the server /// - internal byte TceVersionSupported { get; set; } + internal byte TceVersionSupported => Capabilities.ColumnEncryptionVersion; /// /// 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; } + internal bool AreEnclaveRetriesSupported => + TceVersionSupported >= TdsEnums.MIN_TCE_VERSION_WITH_ENCLAVE_RETRY_SUPPORT; /// /// Type of enclave being used by the server /// - internal string EnclaveType { get; set; } + internal string EnclaveType => Capabilities.ColumnEncryptionEnclaveType; internal bool isTcpProtocol { get; set; } internal string FQDNforDNSCache { get; set; } @@ -186,7 +183,7 @@ internal sealed partial class TdsParser /// /// Get or set data classification version. A value of 0 means that sensitivity classification is not enabled. /// - internal int DataClassificationVersion { get; set; } + internal int DataClassificationVersion => Capabilities.DataClassificationVersion; private SqlCollation _cachedCollation; @@ -205,8 +202,9 @@ internal TdsParser(bool MARS, bool fAsynchronous) { _fMARS = MARS; // may change during Connect to pre 2005 servers + Capabilities = new(ObjectID); + _physicalStateObj = TdsParserStateObjectFactory.Singleton.CreateTdsParserStateObject(this); - DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED; } internal SqlConnectionInternal Connection @@ -262,13 +260,7 @@ internal EncryptionOptions EncryptionOptions } } - internal bool Is2008OrNewer - { - get - { - return _is2008; - } - } + internal bool Is2008OrNewer => Capabilities.Is2008R2OrNewer; internal bool MARSOn { @@ -323,13 +315,7 @@ internal SqlStatistics Statistics } } - private bool IncludeTraceHeader - { - get - { - return (_is2012 && SqlClientEventSource.Log.IsEnabled()); - } - } + private bool IncludeTraceHeader => Capabilities.Is2012OrNewer && SqlClientEventSource.Log.IsEnabled(); internal int IncrementNonTransactedOpenResultCount() { @@ -391,9 +377,6 @@ bool withFailover _connHandler = connHandler; _loginWithFailover = withFailover; - // Clean up IsSQLDNSCachingSupported flag from previous status - _connHandler.IsSQLDNSCachingSupported = false; - uint sniStatus = TdsParserStateObjectFactory.Singleton.SNIStatus; if (sniStatus != TdsEnums.SNI_SUCCESS) @@ -1552,6 +1535,8 @@ internal void Disconnect() _defaultEncoding = null; _defaultCollation = null; + + Capabilities.Reset(); } // Fires a single InfoMessageEvent @@ -2756,8 +2741,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle case TdsEnums.SQLLOGINACK: { SqlClientEventSource.Log.TryTraceEvent(" Received login acknowledgement token"); - SqlLoginAck ack; - result = TryProcessLoginAck(stateObj, out ack); + result = TryProcessLoginAck(stateObj, out SqlLoginAck ack); if (result != TdsOperationStatus.Done) { return result; @@ -3723,6 +3707,12 @@ private TdsOperationStatus TryProcessFeatureExtAck(TdsParserStateObject stateObj } if (featureId != TdsEnums.FEATUREEXT_TERMINATOR) { + // Unknown feature ack + if (!IsFeatureExtSupported(featureId)) + { + throw SQL.ParsingError(); + } + uint dataLen; result = stateObj.TryReadUInt32(out dataLen); if (result != TdsOperationStatus.Done) @@ -3738,18 +3728,23 @@ private TdsOperationStatus TryProcessFeatureExtAck(TdsParserStateObject stateObj return result; } } - _connHandler.OnFeatureExtAck(featureId, data); + + if (_connHandler.ShouldProcessFeatureExtAck(featureId)) + { + Capabilities.ProcessFeatureExtAck(featureId, data); + _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) + if (!Capabilities.DnsCaching) { ret = SQLFallbackDNSCache.Instance.DeleteDNSInfo(FQDNforDNSCache); } - if (_connHandler.IsSQLDNSCachingSupported && _connHandler.pendingSQLDNSObject != null + if (Capabilities.DnsCaching && _connHandler.pendingSQLDNSObject != null && !SQLFallbackDNSCache.Instance.IsDuplicate(_connHandler.pendingSQLDNSObject)) { ret = SQLFallbackDNSCache.Instance.AddDNSInfo(_connHandler.pendingSQLDNSObject); @@ -3805,6 +3800,13 @@ private TdsOperationStatus TryProcessFeatureExtAck(TdsParserStateObject stateObj } return TdsOperationStatus.Done; + + static bool IsFeatureExtSupported(byte fId) => + fId is TdsEnums.FEATUREEXT_SRECOVERY or TdsEnums.FEATUREEXT_FEDAUTH or TdsEnums.FEATUREEXT_TCE + or TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS or TdsEnums.FEATUREEXT_AZURESQLSUPPORT + or TdsEnums.FEATUREEXT_DATACLASSIFICATION or TdsEnums.FEATUREEXT_UTF8SUPPORT + or TdsEnums.FEATUREEXT_SQLDNSCACHING or TdsEnums.FEATUREEXT_JSONSUPPORT + or TdsEnums.FEATUREEXT_VECTORSUPPORT or TdsEnums.FEATUREEXT_USERAGENT; } private bool IsValidAttestationProtocol(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType) @@ -4183,9 +4185,7 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj, private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out SqlLoginAck sqlLoginAck) { - SqlLoginAck a = new SqlLoginAck(); - - sqlLoginAck = null; + sqlLoginAck = default; // read past interface type and version TdsOperationStatus result = stateObj.TrySkipBytes(1); @@ -4200,50 +4200,15 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out { return result; } - a.tdsVersion = (uint)((((((b[0] << 8) | b[1]) << 8) | b[2]) << 8) | b[3]); // bytes are in motorola order (high byte first) - uint majorMinor = a.tdsVersion & 0xff00ffff; - uint increment = (a.tdsVersion >> 16) & 0xff; - - // Server responds: - // 0x07000000 -> 7.0 // Notice server response format is different for bwd compat - // 0x07010000 -> 2000 RTM // Notice server response format is different for bwd compat - // 0x71000001 -> 2000 SP1 - // 0x72xx0002 -> 2005 RTM - // information provided by S. Ashwin - switch (majorMinor) - { - case TdsEnums.SQL2005_MAJOR << 24 | TdsEnums.SQL2005_RTM_MINOR: // 2005 - if (increment != TdsEnums.SQL2005_INCREMENT) - { - throw SQL.InvalidTDSVersion(); - } - break; - case TdsEnums.SQL2008_MAJOR << 24 | TdsEnums.SQL2008_MINOR: - if (increment != TdsEnums.SQL2008_INCREMENT) - { - throw SQL.InvalidTDSVersion(); - } - _is2008 = true; - break; - case TdsEnums.SQL2012_MAJOR << 24 | TdsEnums.SQL2012_MINOR: - if (increment != TdsEnums.SQL2012_INCREMENT) - { - throw SQL.InvalidTDSVersion(); - } - _is2012 = true; - break; - case TdsEnums.TDS8_MAJOR << 24 | TdsEnums.TDS8_MINOR: - if (increment != TdsEnums.TDS8_INCREMENT) - { - throw SQL.InvalidTDSVersion(); - } - _is2022 = true; - break; - default: - throw SQL.InvalidTDSVersion(); - } - _is2012 |= _is2022; - _is2008 |= _is2012; + // When connecting to SQL Server 2000, the TDS version sent + // by the client would be a completely different value to the + // TDS version received by the server. + // From SQL Server 2000 SP1, the TDS version is identical. As + // an artifact of this historical difference, the client sends + // its TDS version to the server in a little-endian layout, and + // receives the server's TDS version in a big-endian layout. + // Reference: MS-TDS, 2.2.7.14, footnote on TDSVersion field. + uint tdsVersion = BinaryPrimitives.ReadUInt32BigEndian(b); stateObj._outBytesUsed = stateObj._outputHeaderLen; byte len; @@ -4258,29 +4223,32 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out { return result; } - result = stateObj.TryReadByte(out a.majorVersion); + result = stateObj.TryReadByte(out byte majorVersion); if (result != TdsOperationStatus.Done) { return result; } - result = stateObj.TryReadByte(out a.minorVersion); + result = stateObj.TryReadByte(out byte minorVersion); if (result != TdsOperationStatus.Done) { return result; } - byte buildNumHi, buildNumLo; - result = stateObj.TryReadByte(out buildNumHi); + result = stateObj.TryReadByte(out byte buildNumHi); if (result != TdsOperationStatus.Done) { return result; } - result = stateObj.TryReadByte(out buildNumLo); + result = stateObj.TryReadByte(out byte buildNumLo); if (result != TdsOperationStatus.Done) { return result; } - a.buildNum = (short)((buildNumHi << 8) + buildNumLo); + ushort buildNumber = (ushort)((buildNumHi << 8) | buildNumLo); + + sqlLoginAck = new SqlLoginAck(majorVersion, minorVersion, buildNumber, tdsVersion); + + Capabilities.ProcessLoginAck(sqlLoginAck); Debug.Assert(_state == TdsParserState.OpenNotLoggedIn, "ProcessLoginAck called with state not TdsParserState.OpenNotLoggedIn"); _state = TdsParserState.OpenLoggedIn; @@ -4300,7 +4268,6 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out ThrowExceptionAndWarning(stateObj); } - sqlLoginAck = a; return TdsOperationStatus.Done; } @@ -9241,11 +9208,11 @@ private void WriteLoginData(SqlLogin rec, { if (encrypt == SqlConnectionEncryptOption.Strict) { - WriteInt((TdsEnums.TDS8_MAJOR << 24) | (TdsEnums.TDS8_INCREMENT << 16) | TdsEnums.TDS8_MINOR, _physicalStateObj); + WriteUnsignedInt(TdsEnums.TDS80_VERSION, _physicalStateObj); } else { - WriteInt((TdsEnums.SQL2012_MAJOR << 24) | (TdsEnums.SQL2012_INCREMENT << 16) | TdsEnums.SQL2012_MINOR, _physicalStateObj); + WriteUnsignedInt(TdsEnums.TDS7X_VERSION, _physicalStateObj); } } else @@ -10199,7 +10166,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout continue; } - if (!_is2008 && !mt.Is90Supported) + if (!Is2008OrNewer && !mt.Is90Supported) { throw ADP.VersionDoesNotSupportDataType(mt.TypeName); } @@ -10953,7 +10920,7 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa ParameterPeekAheadValue peekAhead; SmiParameterMetaData metaData = param.MetaDataForSmi(out peekAhead); - if (!_is2008) + if (!Is2008OrNewer) { MetaType mt = MetaType.GetMetaTypeFromSqlDbType(metaData.SqlDbType, metaData.IsMultiValued); throw ADP.VersionDoesNotSupportDataType(mt.TypeName); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs index 8f0bb915c0..6f2059e73b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs @@ -120,12 +120,20 @@ internal sealed class SqlLogin internal SecureString newSecurePassword; } - internal sealed class SqlLoginAck + internal readonly ref struct SqlLoginAck { - internal byte majorVersion; - internal byte minorVersion; - internal short buildNum; - internal uint tdsVersion; + public readonly byte MajorVersion; + public readonly byte MinorVersion; + public readonly ushort BuildNumber; + public readonly uint TdsVersion; + + public SqlLoginAck(byte majorVersion, byte minorVersion, ushort buildNumber, uint tdsVersion) + { + MajorVersion = majorVersion; + MinorVersion = minorVersion; + BuildNumber = buildNumber; + TdsVersion = tdsVersion; + } } internal sealed class SqlFedAuthInfo