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