From 27a879595fb2ce588cecb4cc0d549109c3b50ab7 Mon Sep 17 00:00:00 2001 From: Saransh Sharma Date: Thu, 26 Jun 2025 17:46:31 -0700 Subject: [PATCH 1/5] Add Feature Extension for User Agent --- .../SqlClient/SqlInternalConnectionTds.cs | 7 +++++ .../src/Microsoft/Data/SqlClient/TdsParser.cs | 26 +++++++++++++++++++ .../SqlClient/SqlInternalConnectionTds.cs | 7 +++++ .../src/Microsoft/Data/SqlClient/TdsParser.cs | 26 +++++++++++++++++++ .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 7 ++++- 5 files changed, 72 insertions(+), 1 deletion(-) 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 0d1da114cc..6f2c6a2432 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 @@ -207,6 +207,9 @@ internal bool IsDNSCachingBeforeRedirectSupported // Json Support Flag internal bool IsJsonSupportEnabled = false; + // User Agent Flag + internal bool IsUserAgentEnabled = true; + // TCE flags internal byte _tceVersionSupported; @@ -1426,6 +1429,10 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching; requestedFeatures |= TdsEnums.FeatureExtension.JsonSupport; + #if DEBUG + requestedFeatures |= TdsEnums.FeatureExtension.UserAgent; + #endif + _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData, encrypt); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 5320d1e51f..64031e5c24 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -8529,6 +8529,32 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD return len; } + internal int WriteUserAgentFeatureRequest(byte[] userAgentJsonPayload, + bool write /* if false just calculates the length */) + { + // 1byte (Feature Version) + size of UTF-8 encoded JSON payload + int dataLen = 1 + userAgentJsonPayload.Length; + // 1byte (Feature ID) + 4bytes (Feature Data Length) + 1byte (Version) + N(JSON payload size) + int totalLen = 1+ 4 + dataLen; + + if (write) + { + // Write Feature ID + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_USERAGENT); + + // Feature Data Length + WriteInt(dataLen, _physicalStateObj); + + // Write Feature Version + _physicalStateObj.WriteByte(TdsEnums.SUPPORTED_USER_AGENT_VERSION); + + // Write encoded JSON payload + _physicalStateObj.WriteByteArray(userAgentJsonPayload, userAgentJsonPayload.Length, 0); + } + + return totalLen; + } + private void WriteLoginData(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, 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 d460c61619..644122e7b3 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 @@ -208,6 +208,9 @@ internal bool IsDNSCachingBeforeRedirectSupported // Json Support Flag internal bool IsJsonSupportEnabled = false; + // User Agent Flag + internal bool IsUserAgentEnabled = true; + // TCE flags internal byte _tceVersionSupported; @@ -1432,6 +1435,10 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching; requestedFeatures |= TdsEnums.FeatureExtension.JsonSupport; + #if DEBUG + requestedFeatures |= TdsEnums.FeatureExtension.UserAgent; + #endif + _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData, encrypt); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index d602928be8..7f4207d17c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -8712,6 +8712,32 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD return len; } + internal int WriteUserAgentFeatureRequest(byte[] userAgentJsonPayload, + bool write /* if false just calculates the length */) + { + // 1byte (Feature Version) + size of UTF-8 encoded JSON payload + int dataLen = 1 + userAgentJsonPayload.Length; + // 1byte (Feature ID) + 4bytes (Feature Data Length) + 1byte (Version) + N(JSON payload size) + int totalLen = 1 + 4 + dataLen; + + if (write) + { + // Write Feature ID + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_USERAGENT); + + // Feature Data Length + WriteInt(dataLen, _physicalStateObj); + + // Write Feature Version + _physicalStateObj.WriteByte(TdsEnums.SUPPORTED_USER_AGENT_VERSION); + + // Write encoded JSON payload + _physicalStateObj.WriteByteArray(userAgentJsonPayload, userAgentJsonPayload.Length, 0); + } + + return totalLen; + } + private void WriteLoginData(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, 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 280469a5e0..c08b6c5ad0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -240,6 +240,7 @@ public enum EnvChangeType : byte public const byte FEATUREEXT_UTF8SUPPORT = 0x0A; public const byte FEATUREEXT_SQLDNSCACHING = 0x0B; public const byte FEATUREEXT_JSONSUPPORT = 0x0D; + public const byte FEATUREEXT_USERAGENT = 0x0F; [Flags] public enum FeatureExtension : uint @@ -253,7 +254,8 @@ public enum FeatureExtension : uint DataClassification = 1 << (TdsEnums.FEATUREEXT_DATACLASSIFICATION - 1), UTF8Support = 1 << (TdsEnums.FEATUREEXT_UTF8SUPPORT - 1), SQLDNSCaching = 1 << (TdsEnums.FEATUREEXT_SQLDNSCACHING - 1), - JsonSupport = 1 << (TdsEnums.FEATUREEXT_JSONSUPPORT - 1) + JsonSupport = 1 << (TdsEnums.FEATUREEXT_JSONSUPPORT - 1), + UserAgent = 1 << (TdsEnums.FEATUREEXT_USERAGENT - 1) } public const uint UTF8_IN_TDSCOLLATION = 0x4000000; @@ -978,6 +980,9 @@ internal enum FedAuthInfoId : byte // JSON Support constants internal const byte MAX_SUPPORTED_JSON_VERSION = 0x01; + // User Agent constants + internal const byte SUPPORTED_USER_AGENT_VERSION = 0x01; + // TCE Related constants 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 From a27be5dad15e7486e809073d62cfee429b8430a4 Mon Sep 17 00:00:00 2001 From: Saransh Sharma Date: Fri, 27 Jun 2025 13:24:30 -0700 Subject: [PATCH 2/5] resolve conflicts --- .../SqlClient/SqlInternalConnectionTds.cs | 4 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 53 +++++++++---------- .../SqlClient/SqlInternalConnectionTds.cs | 8 ++- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 44 +++++++-------- .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 19 +------ 5 files changed, 54 insertions(+), 74 deletions(-) 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 ded323cd51..c75eb35a0e 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 @@ -207,13 +207,11 @@ internal bool IsDNSCachingBeforeRedirectSupported // Json Support Flag internal bool IsJsonSupportEnabled = false; -<<<<<<< HEAD // User Agent Flag internal bool IsUserAgentEnabled = true; -======= + // Vector Support Flag internal bool IsVectorSupportEnabled = false; ->>>>>>> origin/main // TCE flags internal byte _tceVersionSupported; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 64981fb978..00b47114b4 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -8549,32 +8549,6 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD return len; } -<<<<<<< HEAD - internal int WriteUserAgentFeatureRequest(byte[] userAgentJsonPayload, - bool write /* if false just calculates the length */) - { - // 1byte (Feature Version) + size of UTF-8 encoded JSON payload - int dataLen = 1 + userAgentJsonPayload.Length; - // 1byte (Feature ID) + 4bytes (Feature Data Length) + 1byte (Version) + N(JSON payload size) - int totalLen = 1+ 4 + dataLen; - - if (write) - { - // Write Feature ID - _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_USERAGENT); - - // Feature Data Length - WriteInt(dataLen, _physicalStateObj); - - // Write Feature Version - _physicalStateObj.WriteByte(TdsEnums.SUPPORTED_USER_AGENT_VERSION); - - // Write encoded JSON payload - _physicalStateObj.WriteByteArray(userAgentJsonPayload, userAgentJsonPayload.Length, 0); - } - - return totalLen; -======= /// /// Writes the Vector Support feature request to the physical state object. /// The request includes the feature ID, feature data length, and version number. @@ -8603,7 +8577,32 @@ internal int WriteVectorSupportFeatureRequest(bool write) } return len; ->>>>>>> origin/main + } + + internal int WriteUserAgentFeatureRequest(byte[] userAgentJsonPayload, + bool write /* if false just calculates the length */) + { + // 1byte (Feature Version) + size of UTF-8 encoded JSON payload + int dataLen = 1 + userAgentJsonPayload.Length; + // 1byte (Feature ID) + 4bytes (Feature Data Length) + 1byte (Version) + N(JSON payload size) + int totalLen = 1+ 4 + dataLen; + + if (write) + { + // Write Feature ID + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_USERAGENT); + + // Feature Data Length + WriteInt(dataLen, _physicalStateObj); + + // Write Feature Version + _physicalStateObj.WriteByte(TdsEnums.SUPPORTED_USER_AGENT_VERSION); + + // Write encoded JSON payload + _physicalStateObj.WriteByteArray(userAgentJsonPayload, userAgentJsonPayload.Length, 0); + } + + return totalLen; } private void WriteLoginData(SqlLogin rec, 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 33966e40f5..1ef8f728a8 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 @@ -208,13 +208,11 @@ internal bool IsDNSCachingBeforeRedirectSupported // Json Support Flag internal bool IsJsonSupportEnabled = false; -<<<<<<< HEAD - // User Agent Flag - internal bool IsUserAgentEnabled = true; -======= // Vector Support Flag internal bool IsVectorSupportEnabled = false; ->>>>>>> origin/main + + // User Agent Flag + internal bool IsUserAgentEnabled = true; // TCE flags internal byte _tceVersionSupported; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 9040e30cc1..76c6c880da 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -8732,15 +8732,6 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD return len; } -<<<<<<< HEAD - internal int WriteUserAgentFeatureRequest(byte[] userAgentJsonPayload, - bool write /* if false just calculates the length */) - { - // 1byte (Feature Version) + size of UTF-8 encoded JSON payload - int dataLen = 1 + userAgentJsonPayload.Length; - // 1byte (Feature ID) + 4bytes (Feature Data Length) + 1byte (Version) + N(JSON payload size) - int totalLen = 1 + 4 + dataLen; -======= /// /// Writes the Vector Support feature request to the physical state object. /// The request includes the feature ID, feature data length, and version number. @@ -8756,12 +8747,32 @@ internal int WriteUserAgentFeatureRequest(byte[] userAgentJsonPayload, internal int WriteVectorSupportFeatureRequest(bool write) { const int len = 6; ->>>>>>> origin/main if (write) { // Write Feature ID -<<<<<<< HEAD + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_VECTORSUPPORT); + + // Feature Data Length + WriteInt(1, _physicalStateObj); + + _physicalStateObj.WriteByte(TdsEnums.MAX_SUPPORTED_VECTOR_VERSION); + } + + return len; + } + + internal int WriteUserAgentFeatureRequest(byte[] userAgentJsonPayload, + bool write /* if false just calculates the length */) + { + // 1byte (Feature Version) + size of UTF-8 encoded JSON payload + int dataLen = 1 + userAgentJsonPayload.Length; + // 1byte (Feature ID) + 4bytes (Feature Data Length) + 1byte (Version) + N(JSON payload size) + int totalLen = 1 + 4 + dataLen; + + if (write) + { + // Write Feature ID _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_USERAGENT); // Feature Data Length @@ -8775,17 +8786,6 @@ internal int WriteVectorSupportFeatureRequest(bool write) } return totalLen; -======= - _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_VECTORSUPPORT); - - // Feature Data Length - WriteInt(1, _physicalStateObj); - - _physicalStateObj.WriteByte(TdsEnums.MAX_SUPPORTED_VECTOR_VERSION); - } - - return len; ->>>>>>> origin/main } private void WriteLoginData(SqlLogin rec, 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 a2501948a5..43e33471ac 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -240,11 +240,8 @@ public enum EnvChangeType : byte public const byte FEATUREEXT_UTF8SUPPORT = 0x0A; public const byte FEATUREEXT_SQLDNSCACHING = 0x0B; public const byte FEATUREEXT_JSONSUPPORT = 0x0D; -<<<<<<< HEAD - public const byte FEATUREEXT_USERAGENT = 0x0F; -======= public const byte FEATUREEXT_VECTORSUPPORT = 0x0E; ->>>>>>> origin/main + public const byte FEATUREEXT_USERAGENT = 0x0F; [Flags] public enum FeatureExtension : uint @@ -259,11 +256,8 @@ public enum FeatureExtension : uint UTF8Support = 1 << (TdsEnums.FEATUREEXT_UTF8SUPPORT - 1), SQLDNSCaching = 1 << (TdsEnums.FEATUREEXT_SQLDNSCACHING - 1), JsonSupport = 1 << (TdsEnums.FEATUREEXT_JSONSUPPORT - 1), -<<<<<<< HEAD + VectorSupport = 1 << (TdsEnums.FEATUREEXT_VECTORSUPPORT - 1), UserAgent = 1 << (TdsEnums.FEATUREEXT_USERAGENT - 1) -======= - VectorSupport = 1 << (TdsEnums.FEATUREEXT_VECTORSUPPORT - 1) ->>>>>>> origin/main } public const uint UTF8_IN_TDSCOLLATION = 0x4000000; @@ -989,15 +983,6 @@ internal enum FedAuthInfoId : byte // JSON Support constants internal const byte MAX_SUPPORTED_JSON_VERSION = 0x01; -<<<<<<< HEAD - // User Agent constants - internal const byte SUPPORTED_USER_AGENT_VERSION = 0x01; -======= - // Vector Support constants - internal const byte MAX_SUPPORTED_VECTOR_VERSION = 0x01; - internal const int VECTOR_HEADER_SIZE = 8; ->>>>>>> origin/main - // TCE Related constants 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 From 9509553de42b69dee12f686e95781224c2c647b1 Mon Sep 17 00:00:00 2001 From: Saransh Sharma Date: Fri, 27 Jun 2025 14:24:31 -0700 Subject: [PATCH 3/5] Add summary and review changes --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 21 +++++++++++++++++-- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 19 ++++++++++++++++- .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 8 +++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 00b47114b4..79228a3449 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -8579,13 +8579,30 @@ internal int WriteVectorSupportFeatureRequest(bool write) return len; } + /// + /// Writes the User Agent feature request to the physical state object. + /// The request includes the feature ID, feature data length, version number and encoded JSON payload. + /// + /// Byte array of UTF-8 encoded JSON payload for User Agent + /// + /// If true, writes the feature request to the physical state object. + /// If false, just calculates the length. + /// + /// The length of the feature request in bytes. + /// + /// The feature request consists of: + /// - 1 byte for the feature ID. + /// - 4 bytes for the feature data length. + /// - 1 byte for the version number. + /// - N bytes for the JSON payload + /// internal int WriteUserAgentFeatureRequest(byte[] userAgentJsonPayload, - bool write /* if false just calculates the length */) + bool write) { // 1byte (Feature Version) + size of UTF-8 encoded JSON payload int dataLen = 1 + userAgentJsonPayload.Length; // 1byte (Feature ID) + 4bytes (Feature Data Length) + 1byte (Version) + N(JSON payload size) - int totalLen = 1+ 4 + dataLen; + int totalLen = 1 + 4 + dataLen; if (write) { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 76c6c880da..f871da2c6c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -8762,8 +8762,25 @@ internal int WriteVectorSupportFeatureRequest(bool write) return len; } + /// + /// Writes the User Agent feature request to the physical state object. + /// The request includes the feature ID, feature data length, version number and encoded JSON payload. + /// + /// Byte array of UTF-8 encoded JSON payload for User Agent + /// + /// If true, writes the feature request to the physical state object. + /// If false, just calculates the length. + /// + /// The length of the feature request in bytes. + /// + /// The feature request consists of: + /// - 1 byte for the feature ID. + /// - 4 bytes for the feature data length. + /// - 1 byte for the version number. + /// - N bytes for the JSON payload + /// internal int WriteUserAgentFeatureRequest(byte[] userAgentJsonPayload, - bool write /* if false just calculates the length */) + bool write) { // 1byte (Feature Version) + size of UTF-8 encoded JSON payload int dataLen = 1 + userAgentJsonPayload.Length; 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 43e33471ac..cf8ae33606 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -241,6 +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; [Flags] @@ -983,6 +984,13 @@ internal enum FedAuthInfoId : byte // JSON Support constants internal const byte MAX_SUPPORTED_JSON_VERSION = 0x01; + // Vector Support constants + internal const byte MAX_SUPPORTED_VECTOR_VERSION = 0x01; + internal const int VECTOR_HEADER_SIZE = 8; + + // User Agent constants + internal const byte SUPPORTED_USER_AGENT_VERSION = 0x01; + // TCE Related constants 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 From c3bacb9c45dca2ef4c6fd10bae39d86e4458966f Mon Sep 17 00:00:00 2001 From: Saransh Sharma Date: Wed, 2 Jul 2025 08:53:42 -0700 Subject: [PATCH 4/5] Add unit tests for WriteUserAgentFeatureRequest --- .../tests/UnitTests/TdsParserInternalsTest.cs | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs new file mode 100644 index 0000000000..9f1f866420 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs @@ -0,0 +1,115 @@ +// 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.Linq; +using System.Reflection; +using System.Text; +using Xunit; + +#nullable enable + +namespace Microsoft.Data.SqlClient.UnitTests +{ + public class TdsParserInternalsTest + { + // Selects and returns the first non-public instance constructor for TdsParser + private static TdsParser CreateParserInstance() + { + Type parserType = typeof(TdsParser); + + ConstructorInfo ctor = parserType + .GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic) + .First(); + + // build default args for each parameter + object?[] ctorArgs = ctor.GetParameters() + .Select(p => p.ParameterType.IsValueType + ? Activator.CreateInstance(p.ParameterType) + : null) + .ToArray(); + + return (TdsParser)ctor.Invoke(ctorArgs); + } + + // Helper function to extract private _physicalStateObj fields raw buffer and no. of bytes written so far + private static (byte[] buffer, int count) ExtractOutputBuffer(TdsParser parser) + { + FieldInfo stateField = typeof(TdsParser) + .GetField("_physicalStateObj", BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("_physicalStateObj not found"); + + object stateObj = stateField.GetValue(parser) + ?? throw new InvalidOperationException("physical state object is null"); + + Type stateType = stateObj.GetType(); + + FieldInfo buffField = stateType + .GetField("_outBuff", BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("_outBuff not found"); + + byte[] buffer = (byte[])buffField.GetValue(stateObj)!; + + FieldInfo usedField = stateType + .GetField("_outBytesUsed", BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("_outBytesUsed not found"); + + int count = (int)usedField.GetValue(stateObj)!; + + return (buffer, count); + } + + [Fact] + public void WriteUserAgentFeatureRequest_WriteFalse_LengthOnlyReturn() + { + byte[] payload = Encoding.UTF8.GetBytes("{\"kel\":\"sier\"}"); + var parser = CreateParserInstance(); + + int lengthOnly = parser.WriteUserAgentFeatureRequest(payload, write: false); + + // assert: total = 1 (feat-ID) + 4 (len field) + [1 (version) + payload.Length] + int expectedDataLen = 1 + payload.Length; + int expectedTotalLen = 1 + 4 + expectedDataLen; + Assert.Equal(expectedTotalLen, lengthOnly); + } + + [Fact] + public void WriteUserAgentFeatureRequest_WriteTrue_AppendsOnlyExtensionBytes() + { + byte[] payload = Encoding.UTF8.GetBytes("{\"kel\":\"sier\"}"); + var parser = CreateParserInstance(); + + var (bufferBefore, countBefore) = ExtractOutputBuffer(parser); + + int returnedLength = parser.WriteUserAgentFeatureRequest(payload, write: true); + + var (bufferAfter, countAfter) = ExtractOutputBuffer(parser); + + int appended = countAfter - countBefore; + Assert.Equal(returnedLength, appended); + + int start = countBefore; + + Assert.Equal( + TdsEnums.FEATUREEXT_USERAGENT, + bufferAfter[start]); + + int dataLenFromStream = BitConverter.ToInt32(bufferAfter, start + 1); + int expectedDataLen = 1 + payload.Length; + Assert.Equal(expectedDataLen, dataLenFromStream); + + Assert.Equal( + TdsEnums.SUPPORTED_USER_AGENT_VERSION, + bufferAfter[start + 5]); + + byte[] writtenPayload = bufferAfter + .Skip(start + 6) + .Take(payload.Length) + .ToArray(); + Assert.Equal(payload, writtenPayload); + + Assert.Equal(returnedLength, appended); + } + + } +} From 56949db03f9bf245d1f9bda714c531223eb7e1cd Mon Sep 17 00:00:00 2001 From: Saransh Sharma Date: Mon, 7 Jul 2025 11:44:04 -0700 Subject: [PATCH 5/5] Update tests to avoid reflection --- .../tests/UnitTests/TdsParserInternalsTest.cs | 54 ++++++++----------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs index 9f1f866420..f0b3729d6f 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/TdsParserInternalsTest.cs @@ -13,25 +13,9 @@ namespace Microsoft.Data.SqlClient.UnitTests { public class TdsParserInternalsTest { - // Selects and returns the first non-public instance constructor for TdsParser - private static TdsParser CreateParserInstance() - { - Type parserType = typeof(TdsParser); - - ConstructorInfo ctor = parserType - .GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic) - .First(); - - // build default args for each parameter - object?[] ctorArgs = ctor.GetParameters() - .Select(p => p.ParameterType.IsValueType - ? Activator.CreateInstance(p.ParameterType) - : null) - .ToArray(); - - return (TdsParser)ctor.Invoke(ctorArgs); - } + private readonly TdsParser _parser = new(false, false); + // TODO(ADO-37888): Avoid reflection by exposing a way for tests to intercept outbound TDS packets. // Helper function to extract private _physicalStateObj fields raw buffer and no. of bytes written so far private static (byte[] buffer, int count) ExtractOutputBuffer(TdsParser parser) { @@ -63,28 +47,33 @@ private static (byte[] buffer, int count) ExtractOutputBuffer(TdsParser parser) public void WriteUserAgentFeatureRequest_WriteFalse_LengthOnlyReturn() { byte[] payload = Encoding.UTF8.GetBytes("{\"kel\":\"sier\"}"); - var parser = CreateParserInstance(); + var (_, countBefore) = ExtractOutputBuffer(_parser); - int lengthOnly = parser.WriteUserAgentFeatureRequest(payload, write: false); + int lengthOnly = _parser.WriteUserAgentFeatureRequest(payload, write: false); + + var (_, countAfter) = ExtractOutputBuffer(_parser); // assert: total = 1 (feat-ID) + 4 (len field) + [1 (version) + payload.Length] int expectedDataLen = 1 + payload.Length; int expectedTotalLen = 1 + 4 + expectedDataLen; Assert.Equal(expectedTotalLen, lengthOnly); + + // assert: no bytes were written when write == false + Assert.Equal(countBefore, countAfter); } [Fact] public void WriteUserAgentFeatureRequest_WriteTrue_AppendsOnlyExtensionBytes() { byte[] payload = Encoding.UTF8.GetBytes("{\"kel\":\"sier\"}"); - var parser = CreateParserInstance(); - - var (bufferBefore, countBefore) = ExtractOutputBuffer(parser); + var (bufferBefore, countBefore) = ExtractOutputBuffer(_parser); - int returnedLength = parser.WriteUserAgentFeatureRequest(payload, write: true); + int returnedLength = _parser.WriteUserAgentFeatureRequest(payload, write: true); - var (bufferAfter, countAfter) = ExtractOutputBuffer(parser); + var (bufferAfter, countAfter) = ExtractOutputBuffer(_parser); + // We expect both of these to be the same object + Assert.Same(bufferBefore, bufferAfter); int appended = countAfter - countBefore; Assert.Equal(returnedLength, appended); @@ -102,13 +91,16 @@ public void WriteUserAgentFeatureRequest_WriteTrue_AppendsOnlyExtensionBytes() TdsEnums.SUPPORTED_USER_AGENT_VERSION, bufferAfter[start + 5]); - byte[] writtenPayload = bufferAfter - .Skip(start + 6) - .Take(payload.Length) - .ToArray(); - Assert.Equal(payload, writtenPayload); + // slice into the existing buffer + ReadOnlySpan writtenSpan = new ReadOnlySpan( + bufferAfter, + start + 6, + appended - 6); + + Assert.True( + writtenSpan.SequenceEqual(payload), + "Payload bytes did not match"); - Assert.Equal(returnedLength, appended); } }