diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 06736ab693..9b240cae82 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -210,9 +210,6 @@ internal bool IsDNSCachingBeforeRedirectSupported // Json Support Flag internal bool IsJsonSupportEnabled = false; - // User Agent Flag - internal bool IsUserAgentEnabled = true; - // Vector Support Flag internal bool IsVectorSupportEnabled = false; @@ -1414,10 +1411,7 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching; requestedFeatures |= TdsEnums.FeatureExtension.JsonSupport; requestedFeatures |= TdsEnums.FeatureExtension.VectorSupport; - - #if DEBUG requestedFeatures |= TdsEnums.FeatureExtension.UserAgent; - #endif _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData, encrypt); } @@ -3023,6 +3017,12 @@ internal void OnFeatureExtAck(int featureId, byte[] data) IsVectorSupportEnabled = true; break; } + case TdsEnums.FEATUREEXT_USERAGENT: + { + // Unexpected ack from server but we ignore it entirely + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for USERAGENTSUPPORT (ignored)", ObjectID); + break; + } default: { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 9f4a612951..1e5ffbeef4 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -214,9 +214,6 @@ internal bool IsDNSCachingBeforeRedirectSupported // Vector Support Flag internal bool IsVectorSupportEnabled = false; - // User Agent Flag - internal bool IsUserAgentEnabled = true; - // TCE flags internal byte _tceVersionSupported; @@ -3068,7 +3065,18 @@ internal void OnFeatureExtAck(int featureId, byte[] data) IsVectorSupportEnabled = true; break; } + case TdsEnums.FEATUREEXT_USERAGENT: + { + // TODO: Verify that the server sends an acknowledgment (Ack) + // using this log message in the future. + // This Ack from the server is unexpected and is ignored completely. + // According to the TDS specification, an Ack is not defined/expected + // for this scenario. We handle it only for completeness + // and to support testing. + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for USERAGENTSUPPORT (ignored)", ObjectID); + break; + } default: { // Unknown feature ack diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index 31b5e66b98..f2ca2db2cc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -25,6 +25,7 @@ private enum Tristate : byte private const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; private const string TruncateScaledDecimalString = @"Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; private const string IgnoreServerProvidedFailoverPartnerString = @"Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner"; + private const string EnableUserAgentString = @"Switch.Microsoft.Data.SqlClient.EnableUserAgent"; #if NET private const string GlobalizationInvariantModeString = @"System.Globalization.Invariant"; private const string GlobalizationInvariantModeEnvironmentVariable = "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"; @@ -45,6 +46,7 @@ private enum Tristate : byte private static Tristate s_useConnectionPoolV2; private static Tristate s_truncateScaledDecimal; private static Tristate s_ignoreServerProvidedFailoverPartner; + private static Tristate s_enableUserAgent; #if NET private static Tristate s_globalizationInvariantMode; private static Tristate s_useManagedNetworking; @@ -328,7 +330,27 @@ public static bool IgnoreServerProvidedFailoverPartner return s_ignoreServerProvidedFailoverPartner == Tristate.True; } } - + /// + /// When set to true, the user agent feature is enabled and the driver will send the user agent string to the server. + /// + public static bool EnableUserAgent + { + get + { + if (s_enableUserAgent == Tristate.NotInitialized) + { + if (AppContext.TryGetSwitch(EnableUserAgentString, out bool returnedValue) && returnedValue) + { + s_enableUserAgent = Tristate.True; + } + else + { + s_enableUserAgent = Tristate.False; + } + } + return s_enableUserAgent == Tristate.True; + } + } #if NET /// /// .NET Core 2.0 and up supports Globalization Invariant mode, which reduces the size of the required libraries for 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 53aaf8295b..f6df0a82fe 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -241,8 +241,7 @@ public enum EnvChangeType : byte public const byte FEATUREEXT_SQLDNSCACHING = 0x0B; public const byte FEATUREEXT_JSONSUPPORT = 0x0D; public const byte FEATUREEXT_VECTORSUPPORT = 0x0E; - // TODO: re-verify if this byte competes with another feature - public const byte FEATUREEXT_USERAGENT = 0x0F; + public const byte FEATUREEXT_USERAGENT = 0x10; [Flags] public enum FeatureExtension : uint 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 5724771d51..d59ab67b3c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -29,7 +29,10 @@ using Microsoft.Data.SqlClient.DataClassification; using Microsoft.Data.SqlClient.LocalDb; using Microsoft.Data.SqlClient.Server; +using Microsoft.Data.SqlClient.UserAgent; using Microsoft.Data.SqlClient.Utilities; + + #if NETFRAMEWORK using Microsoft.Data.SqlTypes; #endif @@ -1361,7 +1364,14 @@ internal void TdsLogin( int feOffset = length; // calculate and reserve the required bytes for the featureEx - length = ApplyFeatureExData(requestedFeatures, recoverySessionData, fedAuthFeatureExtensionData, useFeatureExt, length); + length = ApplyFeatureExData( + requestedFeatures, + recoverySessionData, + fedAuthFeatureExtensionData, + UserAgentInfo.UserAgentCachedJsonPayload.ToArray(), + useFeatureExt, + length + ); WriteLoginData(rec, requestedFeatures, @@ -9448,7 +9458,15 @@ private void WriteLoginData(SqlLogin rec, } } - ApplyFeatureExData(requestedFeatures, recoverySessionData, fedAuthFeatureExtensionData, useFeatureExt, length, true); + ApplyFeatureExData( + requestedFeatures, + recoverySessionData, + fedAuthFeatureExtensionData, + UserAgentInfo.UserAgentCachedJsonPayload.ToArray(), + useFeatureExt, + length, + true + ); } catch (Exception e) { @@ -9467,6 +9485,7 @@ private void WriteLoginData(SqlLogin rec, private int ApplyFeatureExData(TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData, + byte[] userAgentJsonPayload, bool useFeatureExt, int length, bool write = false) @@ -9475,6 +9494,11 @@ private int ApplyFeatureExData(TdsEnums.FeatureExtension requestedFeatures, { checked { + // NOTE: As part of TDS spec UserAgent feature extension should be the first feature extension in the list. + if (LocalAppContextSwitches.EnableUserAgent && ((requestedFeatures & TdsEnums.FeatureExtension.UserAgent) != 0)) + { + length += WriteUserAgentFeatureRequest(userAgentJsonPayload, write); + } if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0) { length += WriteSessionRecoveryFeatureRequest(recoverySessionData, write); diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 2dea4ce022..0f7b994e5e 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -32,6 +32,7 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable private readonly PropertyInfo _useConnectionPoolV2Property; private readonly PropertyInfo _truncateScaledDecimalProperty; private readonly PropertyInfo _ignoreServerProvidedFailoverPartner; + private readonly PropertyInfo _enableUserAgent; #if NET private readonly PropertyInfo _globalizationInvariantModeProperty; private readonly PropertyInfo _useManagedNetworkingProperty; @@ -60,6 +61,8 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable private readonly Tristate _truncateScaledDecimalOriginal; private readonly FieldInfo _ignoreServerProvidedFailoverPartnerField; private readonly Tristate _ignoreServerProvidedFailoverPartnerOriginal; + private readonly FieldInfo _enableUserAgentField; + private readonly Tristate _enableUserAgentOriginal; #if NET private readonly FieldInfo _globalizationInvariantModeField; private readonly Tristate _globalizationInvariantModeOriginal; @@ -162,7 +165,11 @@ void InitProperty(string name, out PropertyInfo property) "IgnoreServerProvidedFailoverPartner", out _ignoreServerProvidedFailoverPartner); - #if NET + InitProperty( + "EnableUserAgent", + out _enableUserAgent); + +#if NET InitProperty( "GlobalizationInvariantMode", out _globalizationInvariantModeProperty); @@ -240,6 +247,11 @@ void InitField(string name, out FieldInfo field, out Tristate value) "s_ignoreServerProvidedFailoverPartner", out _ignoreServerProvidedFailoverPartnerField, out _ignoreServerProvidedFailoverPartnerOriginal); + + InitField( + "s_enableUserAgent", + out _enableUserAgentField, + out _enableUserAgentOriginal); #if NET InitField( @@ -323,6 +335,10 @@ void RestoreField(FieldInfo field, Tristate value) _ignoreServerProvidedFailoverPartnerField, _ignoreServerProvidedFailoverPartnerOriginal); + RestoreField( + _enableUserAgentField, + _enableUserAgentOriginal); + #if NET RestoreField( _globalizationInvariantModeField, @@ -429,6 +445,11 @@ public bool IgnoreServerProvidedFailoverPartner get => (bool)_ignoreServerProvidedFailoverPartner.GetValue(null); } + public bool EnableUserAgent + { + get => (bool)_enableUserAgent.GetValue(null); + } + #if NET /// /// Access the LocalAppContextSwitches.GlobalizationInvariantMode property. @@ -553,6 +574,12 @@ public Tristate IgnoreServerProvidedFailoverPartnerField set => SetValue(_ignoreServerProvidedFailoverPartnerField, value); } + public Tristate EnableUserAgentField + { + get => GetValue(_enableUserAgentField); + set => SetValue(_enableUserAgentField, value); + } + #if NET /// /// Get or set the LocalAppContextSwitches.GlobalizationInvariantMode switch value. diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index 3b038beb7d..9dad33aa1d 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -12,6 +12,7 @@ using System.Security; using System.Threading; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.Tests.Common; using Microsoft.SqlServer.TDS; using Microsoft.SqlServer.TDS.FeatureExtAck; using Microsoft.SqlServer.TDS.Login7; @@ -828,5 +829,152 @@ public void TestConnWithVectorFeatExtVersionNegotiation(bool expectedConnectionR Assert.Throws(() => connection.Open()); } } + + // Test to verify that the client sends a UserAgent version + // and driver behaves correctly even if server sent an Ack + [Theory] + [InlineData(false)] // We do not force test server to send an Ack + [InlineData(true)] // Server is forced to send an Ack + public void TestConnWithUserAgentFeatureExtension(bool forceAck) + { + // Make sure needed switch is enabled + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.EnableUserAgentField = LocalAppContextSwitchesHelper.Tristate.True; + + using var server = new TdsServer(); + server.Start(); + + // Configure the server to support UserAgent version 0x01 + server.ServerSupportedUserAgentFeatureExtVersion = 0x01; + + // Opt in to forced ACK for UserAgentSupport (no negotiation) + server.EnableUserAgentFeatureExt = forceAck; + + bool loginFound = false; + + // Captured from LOGIN7 as parsed by the test server + byte observedVersion = 0; + byte[] observedJsonBytes = Array.Empty(); + + bool firstFeatureIsUserAgent = false; + bool tokenWasNotNull = false; + bool dataLengthAtLeast2 = false; + + // Inspect what the client sends in the LOGIN7 packet + server.OnLogin7Validated = loginToken => + { + var tdsFeatureExt = loginToken.FeatureExt + .OfType().ToArray(); + var token = tdsFeatureExt?.FirstOrDefault(t => t.FeatureID == TDSFeatureID.UserAgentSupport); + + // Capture conditions instead of asserting here + firstFeatureIsUserAgent = tdsFeatureExt?.Length > 0 && tdsFeatureExt[0].FeatureID == TDSFeatureID.UserAgentSupport; + tokenWasNotNull = token is not null; + + var data = token?.Data ?? Array.Empty(); + dataLengthAtLeast2 = data.Length >= 2; + + if (data.Length >= 1) + { + observedVersion = data[0]; + } + + if (data.Length >= 2) + { + observedJsonBytes = data.AsSpan(1).ToArray(); + } + + loginFound = true; + }; + + // Connect to the test TDS server. + var connStr = new SqlConnectionStringBuilder + { + DataSource = $"localhost,{server.EndPoint.Port}", + Encrypt = SqlConnectionEncryptOption.Optional, + }.ConnectionString; + + // TODO: Confirm the server sent an Ack by reading log message from SqlInternalConnectionTds + using var connection = new SqlConnection(connStr); + connection.Open(); + + // Verify the connection itself succeeded + Assert.Equal(ConnectionState.Open, connection.State); + + // Verify client did offer UserAgent and captured conditions hold + Assert.True(loginFound, "Expected UserAgent extension in LOGIN7"); + Assert.True(firstFeatureIsUserAgent); + Assert.True(tokenWasNotNull); + Assert.True(dataLengthAtLeast2); + Assert.Equal(0x1, observedVersion); + + // Note: Accessing UserAgentInfo via Reflection. + // We cannot use InternalsVisibleTo here because making internals visible to FunctionalTests + // causes the *.TestHarness.cs stubs to clash with the real internal types in SqlClient. + var asm = typeof(SqlConnection).Assembly; + var userAgentInfoType = + asm.GetTypes().FirstOrDefault(t => string.Equals(t.Name, "UserAgentInfo", StringComparison.Ordinal)) ?? + asm.GetTypes().FirstOrDefault(t => t.FullName?.EndsWith(".UserAgentInfo", StringComparison.Ordinal) == true); + + Assert.NotNull(userAgentInfoType); + + // Try to get the property + var prop = userAgentInfoType.GetProperty("UserAgentCachedJsonPayload", + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + Assert.NotNull(prop); + + ReadOnlyMemory cachedPayload = (ReadOnlyMemory)prop.GetValue(null)!; + Assert.Equal(cachedPayload.ToArray(), observedJsonBytes.ToArray()); + } + + /// + /// Test to verify no UserAgent relevant information is sent when EnableUserAgentField switch is disabled. + /// + [Fact] + public void TestConnWithoutUserAgentFeatureExtension() + { + // Disable the client-side UserAgent field entirely + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.EnableUserAgentField = LocalAppContextSwitchesHelper.Tristate.False; + + using var server = new TdsServer(); + server.Start(); + + // Do not advertise or force the UserAgent feature on the server + server.ServerSupportedUserAgentFeatureExtVersion = 0x00; // no support + server.EnableUserAgentFeatureExt = false; // no forced ACK + + bool loginValidated = false; + bool userAgentFeatureSeen = false; + + // Inspect the LOGIN7 packet captured by the test server + server.OnLogin7Validated = loginToken => + { + var featureExtTokens = loginToken.FeatureExt + .OfType() + .ToArray(); + + // Ensure there is no UserAgentSupport token at all + var uaToken = featureExtTokens.FirstOrDefault(t => t.FeatureID == TDSFeatureID.UserAgentSupport); + userAgentFeatureSeen = uaToken is not null; + + loginValidated = true; + }; + + // Connect to the test TDS server with a basic connection string + var connStr = new SqlConnectionStringBuilder + { + DataSource = $"localhost,{server.EndPoint.Port}", + Encrypt = SqlConnectionEncryptOption.Optional, + }.ConnectionString; + + using var connection = new SqlConnection(connStr); + connection.Open(); + + // Verify that the connection succeeded and no UserAgent data was sent + Assert.Equal(ConnectionState.Open, connection.State); + Assert.True(loginValidated, "Expected LOGIN7 to be validated by the test server"); + Assert.False(userAgentFeatureSeen, "Did not expect a UserAgentSupport feature token in LOGIN7"); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs index 9b5b7804b4..bb7b1c9771 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs @@ -93,5 +93,10 @@ public interface ITDSServerSession /// Indicates whether the client supports Vector column type /// bool IsVectorSupportEnabled { get; set; } + + /// + /// Indicates whether the client supports UserAgent + /// + bool IsUserAgentSupportEnabled { get; set; } } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServer.cs index c17726fb8b..633f6edf0c 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServer.cs @@ -50,11 +50,36 @@ public delegate void OnAuthenticationCompletedDelegate( /// public const byte DefaultSupportedVectorFeatureExtVersion = 0x01; + /// + /// Property for setting server version for vector feature extension. + /// + public bool EnableVectorFeatureExt { get; set; } = false; + + /// + /// Property for setting server flag for user agent feature extension. + /// + public bool EnableUserAgentFeatureExt { get; set; } = false; + + /// + /// Property for setting server version for vector feature extension. + /// + public byte ServerSupportedVectorFeatureExtVersion { get; set; } = DefaultSupportedVectorFeatureExtVersion; + + /// + /// Property for setting server version for user agent feature extension. + /// + public byte ServerSupportedUserAgentFeatureExtVersion { get; set; } = DefaultSupportedUserAgentFeatureExtVersion; + /// /// Client version for vector FeatureExtension. /// private byte _clientSupportedVectorFeatureExtVersion = 0; + /// + /// Default feature extension version supported on the server for user agent. + /// + public const byte DefaultSupportedUserAgentFeatureExtVersion = 0x01; + /// /// Session counter /// @@ -107,16 +132,6 @@ public GenericTdsServer(T arguments, QueryEngine queryEngine) /// public int PreLoginCount => _preLoginCount; - /// - /// Property for setting server version for vector feature extension. - /// - public bool EnableVectorFeatureExt { get; set; } = false; - - /// - /// Property for setting server version for vector feature extension. - /// - public byte ServerSupportedVectorFeatureExtVersion { get; set; } = DefaultSupportedVectorFeatureExtVersion; - public OnAuthenticationCompletedDelegate OnAuthenticationResponseCompleted { private get; set; } public OnLogin7ValidatedDelegate OnLogin7Validated { private get; set; } @@ -314,6 +329,15 @@ public virtual TDSMessageCollection OnLogin7Request(ITDSServerSession session, T } break; } + case TDSFeatureID.UserAgentSupport: + { + if (EnableUserAgentFeatureExt) + { + // Enable User Agent Support + session.IsUserAgentSupportEnabled = true; + } + break; + } default: { @@ -616,6 +640,45 @@ protected virtual TDSMessageCollection OnAuthenticationCompleted(ITDSServerSessi // Serialize the login token into the response packet responseMessage.Add(loginResponseToken); + CheckSessionRecovery(session, responseMessage); + CheckJsonSupported(session, responseMessage); + CheckVectorSupport(session, responseMessage); + CheckUserAgentSupport(session, responseMessage); + + if (!string.IsNullOrEmpty(Arguments.FailoverPartner)) + { + envChange = new TDSEnvChangeToken(TDSEnvChangeTokenType.RealTimeLogShipping, Arguments.FailoverPartner); + + // Log response + TDSUtilities.Log(Arguments.Log, "Response", envChange); + + responseMessage.Add(envChange); + } + + // Create DONE token + TDSDoneToken doneToken = new TDSDoneToken(TDSDoneTokenStatusType.Final); + + // Log response + TDSUtilities.Log(Arguments.Log, "Response", doneToken); + + // Serialize DONE token into the response packet + responseMessage.Add(doneToken); + + // Invoke delegate for response validation + OnAuthenticationResponseCompleted?.Invoke(responseMessage); + + // Wrap a single message in a collection + return new TDSMessageCollection(responseMessage); + } + + + /// + /// Check if session recovery is enabled + /// + /// Server session + /// Response message + protected void CheckSessionRecovery(ITDSServerSession session, TDSMessage responseMessage) + { // Check if session recovery is enabled if (session.IsSessionRecoveryEnabled) { @@ -625,10 +688,19 @@ protected virtual TDSMessageCollection OnAuthenticationCompleted(ITDSServerSessi // Log response TDSUtilities.Log(Arguments.Log, "Response", featureExtActToken); - // Serialize feature extnesion token into the response + // Serialize feature extension token into the response responseMessage.Add(featureExtActToken); } + } + + /// + /// Check if Json is supported + /// + /// Server session + /// Response message + protected void CheckJsonSupported(ITDSServerSession session, TDSMessage responseMessage) + { // Check if Json is supported if (session.IsJsonSupportEnabled) { @@ -654,7 +726,15 @@ protected virtual TDSMessageCollection OnAuthenticationCompleted(ITDSServerSessi featureExtAckToken.Options.Add(jsonSupportOption); } } + } + /// + /// Check if Vector is supported + /// + /// Server session + /// Response message + protected void CheckVectorSupport(ITDSServerSession session, TDSMessage responseMessage) + { // Check if Vector is supported if (session.IsVectorSupportEnabled) { @@ -680,31 +760,41 @@ protected virtual TDSMessageCollection OnAuthenticationCompleted(ITDSServerSessi featureExtAckToken.Options.Add(vectorSupportOption); } } + } - if (!string.IsNullOrEmpty(Arguments.FailoverPartner)) + /// + /// Check if UserAgent support is enabled + /// + /// Server session + /// Response message + protected void CheckUserAgentSupport(ITDSServerSession session, TDSMessage responseMessage) + { + // If tests request it, force an ACK for UserAgentSupport with no negotiation + if (session.IsUserAgentSupportEnabled) { - envChange = new TDSEnvChangeToken(TDSEnvChangeTokenType.RealTimeLogShipping, Arguments.FailoverPartner); - - // Log response - TDSUtilities.Log(Arguments.Log, "Response", envChange); - - responseMessage.Add(envChange); - } - - // Create DONE token - TDSDoneToken doneToken = new TDSDoneToken(TDSDoneTokenStatusType.Final); + // Create ack data (1 byte: Version number) + byte[] data = new byte[1]; + data[0] = ServerSupportedUserAgentFeatureExtVersion; - // Log response - TDSUtilities.Log(Arguments.Log, "Response", doneToken); + // Create user agent support as a generic feature extension option + TDSFeatureExtAckGenericOption userAgentSupportOption = new TDSFeatureExtAckGenericOption(TDSFeatureID.UserAgentSupport, (uint)data.Length, data); - // Serialize DONE token into the response packet - responseMessage.Add(doneToken); + // Look for feature extension token + TDSFeatureExtAckToken featureExtAckToken = (TDSFeatureExtAckToken)responseMessage.Where(t => t is TDSFeatureExtAckToken).FirstOrDefault(); - // Invoke delegate for response validation - OnAuthenticationResponseCompleted?.Invoke(responseMessage); + if (featureExtAckToken == null) + { + // Create feature extension ack token + featureExtAckToken = new TDSFeatureExtAckToken(userAgentSupportOption); + responseMessage.Add(featureExtAckToken); + } + else + { + // Update the existing token + featureExtAckToken.Options.Add(userAgentSupportOption); + } + } - // Wrap a single message in a collection - return new TDSMessageCollection(responseMessage); } /// diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServerSession.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServerSession.cs index 2730fa02df..0c0b5fb67e 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServerSession.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServerSession.cs @@ -124,6 +124,11 @@ public class GenericTdsServerSession : ITDSServerSession /// public bool IsVectorSupportEnabled { get; set; } + /// + /// Indicates whether this session supports user agent + /// + public bool IsUserAgentSupportEnabled { get; set; } + #region Session Options /// diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs index 6bb6fbc8d2..7681b72ac1 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs @@ -29,6 +29,11 @@ public enum TDSFeatureID : byte /// VectorSupport = 0x0E, + /// + /// User Agent Support + /// + UserAgentSupport = 0x10, + /// /// End of the list ///