From 4689f8b85edcc82a6ed0a4b71d4b1592f9d6484b Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 15:42:23 +0100 Subject: [PATCH 01/31] netfx, netcore: sync tracing --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 19 +++-- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 74 ++++++++----------- 2 files changed, 40 insertions(+), 53 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 4d1eb4e75e..b007d093f1 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 @@ -1457,8 +1457,6 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) #if DEBUG // There is an exception here for MARS as its possible that another thread has closed the connection just as we see an error Debug.Assert(SniContext.Undefined != stateObj.DebugOnlyCopyOfSniContext || ((_fMARS) && ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken))), "SniContext must not be None"); - SqlClientEventSource.Log.TryTraceEvent(" SNIContext must not be None = {0}, _fMARS = {1}, TDS Parser State = {2}", stateObj.DebugOnlyCopyOfSniContext, _fMARS, _state); - #endif TdsParserStateObject.SniErrorDetails details = stateObj.GetErrorDetails(); @@ -1487,7 +1485,6 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // PInvoke code automatically sets the length of the string for us // So no need to look for \0 string errorMessage = details.ErrorMessage; - SqlClientEventSource.Log.TryAdvancedTraceEvent("< sc.TdsParser.ProcessSNIError |ERR|ADV > Error message Detail: {0}", details.ErrorMessage); /* Format SNI errors and add Context Information * @@ -1502,15 +1499,17 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) * !=null | == 0 | replace text left of errorMessage */ +#if NET if (LocalAppContextSwitches.UseManagedNetworking) { Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage) || details.SniErrorNumber != 0, "Empty error message received from SNI"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.ErrorMessage, details.SniErrorNumber); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.ErrorMessage, details.SniErrorNumber); } else +#endif { Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage), "Empty error message received from SNI"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}", details.ErrorMessage); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Error message received from SNI. Error Message = {0}", details.ErrorMessage); } string sqlContextInfo = StringsHelper.GetResourceString(stateObj.SniContext.ToString()); @@ -4702,8 +4701,9 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) // we actually MUST use the code page (i.e. don't error if no ANSI data is sent). success = true; } - catch (ArgumentException) + catch (ArgumentException e) { + ADP.TraceExceptionWithoutRethrow(e); } // If we failed, it is quite possible this is because certain culture id's @@ -4734,8 +4734,9 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) codePage = new CultureInfo(cultureId).TextInfo.ANSICodePage; success = true; } - catch (ArgumentException) + catch (ArgumentException e) { + ADP.TraceExceptionWithoutRethrow(e); } break; case 0x827: // Mapping Non-supported Lithuanian code page to supported Lithuanian. @@ -4744,8 +4745,9 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) codePage = new CultureInfo(0x427).TextInfo.ANSICodePage; success = true; } - catch (ArgumentException) + catch (ArgumentException e) { + ADP.TraceExceptionWithoutRethrow(e); } break; case 0x43f: @@ -8375,6 +8377,7 @@ private void ProcessAttention(TdsParserStateObject stateObj) // If an exception occurs - break the connection. // Attention error will not be thrown in this case by Run(), but other failures may. + ADP.TraceExceptionWithoutRethrow(e); _state = TdsParserState.Broken; _connHandler.BreakConnection(); 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 670ed07dac..233e34f882 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 @@ -340,6 +340,7 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) { if (stateObj._attentionSent) { + SqlClientEventSource.Log.TryTraceEvent("TdsParser.ProcessPendingAck | INFO | Connection Object Id {0}, State Obj Id {1}, Processing Attention.", _connHandler.ObjectID, stateObj.ObjectID); ProcessAttention(stateObj); } } @@ -404,48 +405,11 @@ internal void Connect(ServerInfo serverInfo, { _authenticationProvider = Connection._sspiContextProvider ?? _physicalStateObj.CreateSspiContextProvider(); - SqlClientEventSource.Log.TryTraceEvent(" SSPI or Active Directory Authentication Library for SQL Server based integrated authentication"); + SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | SSPI or Active Directory Authentication Library loaded for SQL Server based integrated authentication"); } else { _authenticationProvider = null; - - switch (authType) - { - case SqlAuthenticationMethod.ActiveDirectoryPassword: - SqlClientEventSource.Log.TryTraceEvent(" Active Directory Password authentication"); - break; - case SqlAuthenticationMethod.ActiveDirectoryIntegrated: - SqlClientEventSource.Log.TryTraceEvent(" Active Directory Integrated authentication"); - break; - case SqlAuthenticationMethod.ActiveDirectoryInteractive: - SqlClientEventSource.Log.TryTraceEvent(" Active Directory Interactive authentication"); - break; - case SqlAuthenticationMethod.ActiveDirectoryServicePrincipal: - SqlClientEventSource.Log.TryTraceEvent(" Active Directory Service Principal authentication"); - break; - case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: - SqlClientEventSource.Log.TryTraceEvent(" Active Directory Device Code Flow authentication"); - break; - case SqlAuthenticationMethod.ActiveDirectoryManagedIdentity: - SqlClientEventSource.Log.TryTraceEvent(" Active Directory Managed Identity authentication"); - break; - case SqlAuthenticationMethod.ActiveDirectoryMSI: - SqlClientEventSource.Log.TryTraceEvent(" Active Directory MSI authentication"); - break; - case SqlAuthenticationMethod.ActiveDirectoryDefault: - SqlClientEventSource.Log.TryTraceEvent(" Active Directory Default authentication"); - break; - case SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity: - SqlClientEventSource.Log.TryTraceEvent(" Active Directory Workload Identity authentication"); - break; - case SqlAuthenticationMethod.SqlPassword: - SqlClientEventSource.Log.TryTraceEvent(" SQL Password authentication"); - break; - default: - SqlClientEventSource.Log.TryTraceEvent(" SQL authentication"); - break; - } } // if Strict encryption (i.e. isTlsFirst) is chosen trust server certificate should always be false. @@ -901,7 +865,7 @@ private void SendPreLoginHandshake( offset += actIdSize; optionDataSize += actIdSize; - SqlClientEventSource.Log.TryTraceEvent(" ClientConnectionID {0}, ActivityID {1}", _connHandler._clientConnectionId, actId); + SqlClientEventSource.Log.TryTraceEvent(" ClientConnectionID {0}, ActivityID {1}", _connHandler?._clientConnectionId, actId); break; case (int)PreLoginOptions.FEDAUTHREQUIRED: @@ -1242,7 +1206,7 @@ internal void Deactivate(bool connectionIsDoomed) SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} deactivating", ObjectID); if (SqlClientEventSource.Log.IsStateDumpEnabled()) { - SqlClientEventSource.Log.StateDumpEvent(" {0}, {1}", ObjectID, TraceString()); + SqlClientEventSource.Log.StateDumpEvent(" {0} {1}", ObjectID, TraceString()); } if (MARSOn) @@ -1525,14 +1489,17 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) { case SniErrors.MultiSubnetFailoverWithMoreThan64IPs: // Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported. + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported."); throw SQL.MultiSubnetFailoverWithMoreThan64IPs(); case SniErrors.MultiSubnetFailoverWithInstanceSpecified: // Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported. + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported."); throw SQL.MultiSubnetFailoverWithInstanceSpecified(); case SniErrors.MultiSubnetFailoverWithNonTcpProtocol: // Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol. + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol."); throw SQL.MultiSubnetFailoverWithNonTcpProtocol(); // continue building SqlError instance @@ -1555,7 +1522,18 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) * !=null | == 0 | replace text left of errorMessage */ - Debug.Assert(!string.IsNullOrEmpty(errorMessage), "Empty error message received from SNI"); +#if NET + if (LocalAppContextSwitches.UseManagedNetworking) + { + Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage) || details.SniErrorNumber != 0, "Empty error message received from SNI"); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.ErrorMessage, details.SniErrorNumber); + } + else +#endif + { + Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage), "Empty error message received from SNI"); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Error message received from SNI. Error Message = {0}", details.ErrorMessage); + } string sqlContextInfo = StringsHelper.GetString(Enum.GetName(typeof(SniContext), stateObj.SniContext)); string providerRid = string.Format("SNI_PN{0}", (int)details.Provider); @@ -1563,6 +1541,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); int win32ErrorCode = details.NativeError; + SqlClientEventSource.Log.TryAdvancedTraceEvent(" SNI Native Error Code = {0}", win32ErrorCode); if (details.SniErrorNumber == 0) { // Provider error. The message from provider is preceeded with non-localizable info from SNI @@ -1570,7 +1549,9 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // int iColon = errorMessage.IndexOf(':'); Debug.Assert(0 <= iColon, "':' character missing in sni errorMessage"); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" ':' character missing in sni errorMessage. Error Message index of ':' = {0}", iColon); Debug.Assert(errorMessage.Length > iColon + 1 && errorMessage[iColon + 1] == ' ', "Expecting a space after the ':' character"); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Expecting a space after the ':' character. Error Message Length = {0}", errorMessage.Length); // extract the message excluding the colon and trailing cr/lf chars if (0 <= iColon) @@ -1602,10 +1583,14 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError); win32ErrorCode = 0; } + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage); } errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})", sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage); + SqlClientEventSource.Log.TryAdvancedTraceErrorEvent(" SNI Error Message. Native Error = {0}, Line Number ={1}, Function ={2}, Exception ={3}, Server = {4}", + details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server); + return new SqlError(details.NativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode); } @@ -2493,8 +2478,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle { _connHandler._federatedAuthenticationInfoReceived = true; SqlFedAuthInfo info; - SqlClientEventSource.Log.TryTraceEvent(" Received federated authentication info token"); - + result = TryProcessFedAuthInfo(stateObj, tokenLength, out info); if (result != TdsOperationStatus.Done) { @@ -4040,7 +4024,7 @@ private TdsOperationStatus TryProcessFedAuthInfo(TdsParserStateObject stateObj, tokenLen -= sizeof(uint); // remaining length is shortened since we read optCount if (SqlClientEventSource.Log.IsAdvancedTraceOn()) { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" CountOfInfoIDs = {0}", optionsCount.ToString(CultureInfo.InvariantCulture)); + SqlClientEventSource.Log.AdvancedTraceEvent(" CountOfInfoIDs = {0}", optionsCount.ToString(CultureInfo.InvariantCulture)); } if (tokenLen > 0) { @@ -9546,8 +9530,8 @@ internal void FailureCleanup(TdsParserStateObject stateObj, Exception e) // Reset the ThreadHasParserLock value in case our caller expects it to be set\not set _connHandler.ThreadHasParserLockForClose = originalThreadHasParserLock; } + SqlClientEventSource.Log.TryTraceEvent(" Exception rethrown."); } - SqlClientEventSource.Log.TryTraceEvent(" Exception rethrown."); } internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool sync, bool callerHasConnectionLock = false, byte[] enclavePackage = null) From 19163f5624620bab030ad292da2e98fa076ab5c0 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 16:10:02 +0100 Subject: [PATCH 02/31] netfx, netcore: whitespace, comment and naming changes --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 74 ++++++++++++------- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 41 +++++----- 2 files changed, 67 insertions(+), 48 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 b007d093f1..e9881816eb 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 @@ -386,10 +386,13 @@ internal void Connect(ServerInfo serverInfo, } // Encryption is not supported on SQL Local DB - disable it if they have only specified Mandatory - if (connHandler.ConnectionOptions.LocalDBInstance != null && encrypt == SqlConnectionEncryptOption.Mandatory) + if (connHandler.ConnectionOptions.LocalDBInstance != null) { - encrypt = SqlConnectionEncryptOption.Optional; - SqlClientEventSource.Log.TryTraceEvent(" Encryption will be disabled as target server is a SQL Local DB instance."); + if (encrypt == SqlConnectionEncryptOption.Mandatory) + { + encrypt = SqlConnectionEncryptOption.Optional; + SqlClientEventSource.Log.TryTraceEvent(" Encryption will be disabled as target server is a SQL Local DB instance."); + } } _authenticationProvider = null; @@ -398,6 +401,7 @@ internal void Connect(ServerInfo serverInfo, if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { _authenticationProvider = Connection._sspiContextProvider ?? _physicalStateObj.CreateSspiContextProvider(); + SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | SSPI or Active Directory Authentication Library loaded for SQL Server based integrated authentication"); } @@ -848,6 +852,7 @@ private void SendPreLoginHandshake( int actIdSize = GUID_SIZE + sizeof(uint); offset += actIdSize; optionDataSize += actIdSize; + SqlClientEventSource.Log.TryTraceEvent(" ClientConnectionID {0}, ActivityID {1}", _connHandler?._clientConnectionId, actId); break; @@ -1479,11 +1484,12 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol. SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol."); throw SQL.MultiSubnetFailoverWithNonTcpProtocol(); - // continue building SqlError instance } } - // PInvoke code automatically sets the length of the string for us - // So no need to look for \0 + // continue building SqlError instance + + // details.ErrorMessage is null terminated with garbage beyond that, since fixed length + // PInvoke code automatically sets the length of the string for us, so no need to look for \0 string errorMessage = details.ErrorMessage; /* Format SNI errors and add Context Information @@ -1529,6 +1535,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) SqlClientEventSource.Log.TryAdvancedTraceEvent(" ':' character missing in sni errorMessage. Error Message index of ':' = {0}", iColon); Debug.Assert(errorMessage.Length > iColon + 1 && errorMessage[iColon + 1] == ' ', "Expecting a space after the ':' character"); SqlClientEventSource.Log.TryAdvancedTraceEvent(" Expecting a space after the ':' character. Error Message Length = {0}", errorMessage.Length); + // extract the message excluding the colon and trailing cr/lf chars if (0 <= iColon) { @@ -6994,8 +7001,8 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp { return result; } - value.SqlBinary = SqlBinary.WrapBytes(b); // doesn't copy the byte array + value.SqlBinary = SqlBinary.WrapBytes(b); // doesn't copy the byte array break; } @@ -7868,13 +7875,17 @@ internal byte[] SerializeSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) // sign if (d.IsPositive) + { bytes[current++] = 1; + } else + { bytes[current++] = 0; - + } Span data = stackalloc uint[4]; d.WriteTdsValue(data); + byte[] bytesPart = SerializeUnsignedInt(data[0], stateObj); Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); current += 4; @@ -7894,12 +7905,17 @@ internal void WriteSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) { // sign if (d.IsPositive) + { stateObj.WriteByte(1); + } else + { stateObj.WriteByte(0); + } Span data = stackalloc uint[4]; d.WriteTdsValue(data); + WriteUnsignedInt(data[0], stateObj); WriteUnsignedInt(data[1], stateObj); WriteUnsignedInt(data[2], stateObj); @@ -10103,7 +10119,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet maxsize = 1; } - WriteParameterVarLen(mt, maxsize, false /*IsNull*/, stateObj); + WriteParameterVarLen(mt, maxsize, isNull: false, stateObj); } } else @@ -10227,9 +10243,9 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet // vector value when communicating with SQL Server. var sqlVectorProps = ((ISqlVector)param.Value); maxsize = sqlVectorProps.Size; - } + } - WriteParameterVarLen(mt, maxsize, false /*IsNull*/, stateObj); + WriteParameterVarLen(mt, maxsize, isNull: false, stateObj); } } @@ -10256,6 +10272,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet // For vector type we need to write scale as the element type of the vector. stateObj.WriteByte(((ISqlVector)param.Value).ElementType); } + // write out collation or xml metadata if ((mt.SqlDbType == SqlDbType.Xml || mt.SqlDbType == SqlDbTypeExtensions.Json)) @@ -10372,7 +10389,7 @@ private void TDSExecuteRPCParameterSetupWriteCompletion(SqlCommand cmd, IList<_S ); } - // This is in its own method to avoid always allocating the lambda in TDSExecuteRPCParameter + // This is in its own method to avoid always allocating the lambda in TDSExecuteRPCParameter private void TDSExecuteRPCParameterSetupFlushCompletion(TdsParserStateObject stateObj, TaskCompletionSource completion, Task execFlushTask, bool taskReleaseConnectionLock) { execFlushTask.ContinueWith(tsk => ExecuteFlushTaskCallback(tsk, stateObj, completion, taskReleaseConnectionLock), TaskScheduler.Default); @@ -10504,7 +10521,7 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa // Value for TVP default is empty list, not NULL if (SqlDbType.Structured == metaData.SqlDbType && metaData.IsMultiValued) { - value = Array.Empty(); + value = Array.Empty(); typeCode = ExtendedClrTypeCode.IEnumerableOfSqlDataRecord; } else @@ -10526,7 +10543,10 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa { value = param.GetCoercedValue(); typeCode = MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType( - metaData.SqlDbType, metaData.IsMultiValued, value, null); + metaData.SqlDbType, + metaData.IsMultiValued, + value, + udtType: null); } if (advancedTraceIsOn) @@ -11673,21 +11693,19 @@ private void WriteTokenLength(byte token, int length, TdsParserStateObject state // For Plp fields, this should only be used when writing to metadata header. // For actual data length, WriteDataLength should be used. // For Xml fields, there is no token length field. For MAX fields it is 0xffff. + if (TdsEnums.SQLUDT == token) { - if (TdsEnums.SQLUDT == token) - { - tokenLength = 8; - } - else if (token == TdsEnums.SQLXMLTYPE) - { - tokenLength = 8; - } - else if (token == TdsEnums.SQLVECTOR) - { - tokenLength = 2; - WriteShort(length, stateObj); - return; - } + tokenLength = 8; + } + else if (token == TdsEnums.SQLXMLTYPE) + { + tokenLength = 8; + } + else if (token == TdsEnums.SQLVECTOR) + { + tokenLength = 2; + WriteShort(length, stateObj); + return; } if (tokenLength == 0) 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 233e34f882..7d85e2c716 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 @@ -388,18 +388,20 @@ internal void Connect(ServerInfo serverInfo, authType == SqlAuthenticationMethod.NotSpecified ? SqlAuthenticationMethod.SqlPassword.ToString() : authType.ToString()); } - //Create LocalDB instance if necessary + // Encryption is not supported on SQL Local DB - disable it if they have only specified Mandatory if (connHandler.ConnectionOptions.LocalDBInstance != null) { + // Create LocalDB instance if necessary LocalDbApi.CreateLocalDbInstance(connHandler.ConnectionOptions.LocalDBInstance); if (encrypt == SqlConnectionEncryptOption.Mandatory) { - // Encryption is not supported on SQL Local DB - disable it for current session. encrypt = SqlConnectionEncryptOption.Optional; SqlClientEventSource.Log.TryTraceEvent(" Encryption will be disabled as target server is a SQL Local DB instance."); } } + _authenticationProvider = null; + // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { @@ -407,10 +409,6 @@ internal void Connect(ServerInfo serverInfo, SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | SSPI or Active Directory Authentication Library loaded for SQL Server based integrated authentication"); } - else - { - _authenticationProvider = null; - } // if Strict encryption (i.e. isTlsFirst) is chosen trust server certificate should always be false. if (isTlsFirst) @@ -447,6 +445,7 @@ internal void Connect(ServerInfo serverInfo, FQDNforDNSCache = FQDNforDNSCache.Substring(0, commaPos); } + // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server _physicalStateObj.CreatePhysicalSNIHandle( serverInfo.ExtendedServerName, timeout, @@ -571,6 +570,7 @@ internal void Connect(ServerInfo serverInfo, } uint retCode = _physicalStateObj.SniGetConnectionId(ref _connHandler._clientConnectionId); + Debug.Assert(retCode == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); @@ -1501,14 +1501,15 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol. SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol."); throw SQL.MultiSubnetFailoverWithNonTcpProtocol(); - - // continue building SqlError instance } } + // continue building SqlError instance - // error.errorMessage is null terminated with garbage beyond that, since fixed length + // details.ErrorMessage is null terminated with garbage beyond that, since fixed length + // PInvoke code automatically sets the length of the string for us, so no need to look for \0 string errorMessage; errorMessage = string.IsNullOrEmpty(details.ErrorMessage) ? string.Empty : details.ErrorMessage; + /* Format SNI errors and add Context Information * * General syntax is: @@ -1544,7 +1545,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) SqlClientEventSource.Log.TryAdvancedTraceEvent(" SNI Native Error Code = {0}", win32ErrorCode); if (details.SniErrorNumber == 0) { - // Provider error. The message from provider is preceeded with non-localizable info from SNI + // Provider error. The message from provider is preceded with non-localizable info from SNI // strip provider info from SNI // int iColon = errorMessage.IndexOf(':'); @@ -1591,8 +1592,8 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) SqlClientEventSource.Log.TryAdvancedTraceErrorEvent(" SNI Error Message. Native Error = {0}, Line Number ={1}, Function ={2}, Exception ={3}, Server = {4}", details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server); - return new SqlError(details.NativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS, - _server, errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode); + return new SqlError(infoNumber: details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, + errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: win32ErrorCode); } internal void CheckResetConnection(TdsParserStateObject stateObj) @@ -4782,7 +4783,7 @@ internal void DrainData(TdsParserStateObject stateObj) SqlDataReader.SharedState sharedState = stateObj._readerState; if (sharedState != null && sharedState._dataReady) { - var metadata = stateObj._cleanupMetaData; + _SqlMetaDataSet metadata = stateObj._cleanupMetaData; TdsOperationStatus result; if (stateObj._partialHeaderBytesRead > 0) { @@ -4826,7 +4827,6 @@ internal void DrainData(TdsParserStateObject stateObj) throw SQL.SynchronousCallMayNotPend(); } } - } @@ -4840,12 +4840,12 @@ internal void DrainData(TdsParserStateObject stateObj) } Run(RunBehavior.Clean, null, null, null, stateObj); } + // @TODO: CER Exception Handling was removed here (see GH#3581) catch { _connHandler.DoomThisConnection(); throw; } - // @TODO: CER Exception Handling was removed here (see GH#3581) } @@ -9780,7 +9780,8 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout if ( !(cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled || - (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled))) + (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled)) + ) { throw SQL.ParamInvalidForceColumnEncryptionSetting(param.ParameterName, rpcext.GetCommandTextOrRpcName()); } @@ -10163,7 +10164,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet maxsize = 1; } - WriteParameterVarLen(mt, maxsize, false/*IsNull*/, stateObj); + WriteParameterVarLen(mt, maxsize, isNull: false, stateObj); } } else @@ -10289,7 +10290,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet maxsize = sqlVectorProps.Size; } - WriteParameterVarLen(mt, maxsize, false/*IsNull*/, stateObj); + WriteParameterVarLen(mt, maxsize, isNull: false, stateObj); } } @@ -10311,9 +10312,9 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet { stateObj.WriteByte(param.GetActualScale()); } - // For vector type we need to write scale as the element type of the vector. else if (mt.SqlDbType == SqlDbTypeExtensions.Vector) { + // For vector type we need to write scale as the element type of the vector. stateObj.WriteByte(((ISqlVector)param.Value).ElementType); } @@ -10479,6 +10480,7 @@ private void ExecuteFlushTaskCallback(Task tsk, TdsParserStateObject stateObj, T if (tsk.Exception != null) { Exception exc = tsk.Exception.InnerException; + try { FailureCleanup(stateObj, tsk.Exception); @@ -13447,7 +13449,6 @@ bool writeDataSizeToSnapshot charsLeft -= totalCharsRead; offst += totalCharsRead; - while (charsLeft > 0) { if (!partialReadInProgress) From 69099ac18b9cdc750cf52f68a61c5787de3b16d6 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 16:33:58 +0100 Subject: [PATCH 03/31] netfx, netcore: merge TNIR handling --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 45 ++++++++++++++++--- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 30 ++++++++++--- 2 files changed, 65 insertions(+), 10 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 e9881816eb..70f61ee021 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 @@ -348,7 +348,14 @@ internal void Connect(ServerInfo serverInfo, SqlInternalConnectionTds connHandler, TimeoutTimer timeout, SqlConnectionString connectionOptions, - bool withFailover) +#if NETFRAMEWORK + bool withFailover, + bool isFirstTransparentAttempt, + bool disableTnir +#else + bool withFailover +#endif + ) { SqlConnectionEncryptOption encrypt = connectionOptions.Encrypt; bool isTlsFirst = (encrypt == SqlConnectionEncryptOption.Strict); @@ -418,6 +425,26 @@ internal void Connect(ServerInfo serverInfo, _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.InitializeConnection); bool fParallel = _connHandler.ConnectionOptions.MultiSubnetFailover; + TransparentNetworkResolutionState transparentNetworkResolutionState; + int totalTimeout; + +#if NETFRAMEWORK + if (_connHandler.ConnectionOptions.TransparentNetworkIPResolution && !disableTnir) + { + if (isFirstTransparentAttempt) + transparentNetworkResolutionState = TransparentNetworkResolutionState.SequentialMode; + else + transparentNetworkResolutionState = TransparentNetworkResolutionState.ParallelMode; + } + else + { + transparentNetworkResolutionState = TransparentNetworkResolutionState.DisabledMode; + } + totalTimeout = _connHandler.ConnectionOptions.ConnectTimeout; +#else + transparentNetworkResolutionState = TransparentNetworkResolutionState.DisabledMode; + totalTimeout = -1; +#endif FQDNforDNSCache = serverInfo.ResolvedServerName; @@ -438,8 +465,8 @@ internal void Connect(ServerInfo serverInfo, false, true, fParallel, - TransparentNetworkResolutionState.DisabledMode, - -1, + transparentNetworkResolutionState, + totalTimeout, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, @@ -538,8 +565,8 @@ internal void Connect(ServerInfo serverInfo, true, true, fParallel, - TransparentNetworkResolutionState.DisabledMode, - -1, + transparentNetworkResolutionState, + totalTimeout, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, @@ -1354,7 +1381,11 @@ internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand breakConnection &= (TdsParserState.Closed != _state); if (breakConnection) { +#if NETFRAMEWORK + if ((_state == TdsParserState.OpenNotLoggedIn) && (_connHandler.ConnectionOptions.TransparentNetworkIPResolution || _connHandler.ConnectionOptions.MultiSubnetFailover || _loginWithFailover) && (temp.Count == 1) && ((temp[0].Number == TdsEnums.TIMEOUT_EXPIRED) || (temp[0].Number == TdsEnums.SNI_WAIT_TIMEOUT))) +#else if ((_state == TdsParserState.OpenNotLoggedIn) && (_connHandler.ConnectionOptions.MultiSubnetFailover || _loginWithFailover) && (temp.Count == 1) && ((temp[0].Number == TdsEnums.TIMEOUT_EXPIRED) || (temp[0].Number == TdsEnums.SNI_WAIT_TIMEOUT))) +#endif { // DevDiv2 Bug 459546: With "MultiSubnetFailover=yes" in the Connection String, SQLClient incorrectly throws a Timeout using shorter time slice (3-4 seconds), not honoring the actual 'Connect Timeout' // http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/459546 @@ -13792,7 +13823,11 @@ internal string TraceString() _statisticsIsInTransaction ? bool.TrueString : bool.FalseString, _fPreserveTransaction ? bool.TrueString : bool.FalseString, _connHandler == null ? "(null)" : _connHandler.ConnectionOptions.MultiSubnetFailover.ToString((IFormatProvider)null), +#if NETFRAMEWORK + _connHandler == null ? "(null)" : _connHandler.ConnectionOptions.TransparentNetworkIPResolution.ToString((IFormatProvider)null)); +#else _connHandler == null ? "(null)" : bool.FalseString); +#endif } private string TraceObjectClass(object instance) 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 7d85e2c716..d430eb219f 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 @@ -349,9 +349,14 @@ internal void Connect(ServerInfo serverInfo, SqlInternalConnectionTds connHandler, TimeoutTimer timeout, SqlConnectionString connectionOptions, +#if NETFRAMEWORK bool withFailover, bool isFirstTransparentAttempt, - bool disableTnir) + bool disableTnir +#else + bool withFailover +#endif + ) { SqlConnectionEncryptOption encrypt = connectionOptions.Encrypt; bool isTlsFirst = (encrypt == SqlConnectionEncryptOption.Strict); @@ -423,8 +428,10 @@ internal void Connect(ServerInfo serverInfo, _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.InitializeConnection); bool fParallel = _connHandler.ConnectionOptions.MultiSubnetFailover; - TransparentNetworkResolutionState transparentNetworkResolutionState; + int totalTimeout; + +#if NETFRAMEWORK if (_connHandler.ConnectionOptions.TransparentNetworkIPResolution && !disableTnir) { if (isFirstTransparentAttempt) @@ -433,9 +440,14 @@ internal void Connect(ServerInfo serverInfo, transparentNetworkResolutionState = TransparentNetworkResolutionState.ParallelMode; } else + { transparentNetworkResolutionState = TransparentNetworkResolutionState.DisabledMode; - - int totalTimeout = _connHandler.ConnectionOptions.ConnectTimeout; + } + totalTimeout = _connHandler.ConnectionOptions.ConnectTimeout; +#else + transparentNetworkResolutionState = TransparentNetworkResolutionState.DisabledMode; + totalTimeout = -1; +#endif FQDNforDNSCache = serverInfo.ResolvedServerName; @@ -1371,7 +1383,11 @@ internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand breakConnection &= (TdsParserState.Closed != _state); if (breakConnection) { +#if NETFRAMEWORK if ((_state == TdsParserState.OpenNotLoggedIn) && (_connHandler.ConnectionOptions.TransparentNetworkIPResolution || _connHandler.ConnectionOptions.MultiSubnetFailover || _loginWithFailover) && (temp.Count == 1) && ((temp[0].Number == TdsEnums.TIMEOUT_EXPIRED) || (temp[0].Number == TdsEnums.SNI_WAIT_TIMEOUT))) +#else + if ((_state == TdsParserState.OpenNotLoggedIn) && (_connHandler.ConnectionOptions.MultiSubnetFailover || _loginWithFailover) && (temp.Count == 1) && ((temp[0].Number == TdsEnums.TIMEOUT_EXPIRED) || (temp[0].Number == TdsEnums.SNI_WAIT_TIMEOUT))) +#endif { // DevDiv2 Bug 459546: With "MultiSubnetFailover=yes" in the Connection String, SQLClient incorrectly throws a Timeout using shorter time slice (3-4 seconds), not honoring the actual 'Connect Timeout' // http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/459546 @@ -13838,7 +13854,11 @@ internal string TraceString() _statisticsIsInTransaction ? bool.TrueString : bool.FalseString, _fPreserveTransaction ? bool.TrueString : bool.FalseString, _connHandler == null ? "(null)" : _connHandler.ConnectionOptions.MultiSubnetFailover.ToString((IFormatProvider)null), - _connHandler == null ? "(null)" : _connHandler.ConnectionOptions.TransparentNetworkIPResolution.ToString((IFormatProvider)null)); +#if NETFRAMEWORK + _connHandler == null ? "(null)" : _connHandler.ConnectionOptions.TransparentNetworkIPResolution.ToString((IFormatProvider)null)); +#else + _connHandler == null ? "(null)" : bool.FalseString); +#endif } private string TraceObjectClass(object instance) From a5f114f9532dcf2ef8971b71bd3f2a71c55b44d7 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:07:09 +0100 Subject: [PATCH 04/31] netfx: sync DNS caching --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) 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 d430eb219f..6011af175c 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 @@ -451,12 +451,14 @@ bool withFailover FQDNforDNSCache = serverInfo.ResolvedServerName; - int commaPos = FQDNforDNSCache.IndexOf(","); + int commaPos = FQDNforDNSCache.IndexOf(",", StringComparison.Ordinal); if (commaPos != -1) { FQDNforDNSCache = FQDNforDNSCache.Substring(0, commaPos); } + _connHandler.pendingSQLDNSObject = null; + // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server _physicalStateObj.CreatePhysicalSNIHandle( serverInfo.ExtendedServerName, @@ -515,8 +517,11 @@ bool withFailover uint result = _physicalStateObj.SniGetConnectionId(ref _connHandler._clientConnectionId); Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); - // for DNS Caching phase 1 - _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); + if (_connHandler.pendingSQLDNSObject == null) + { + // for DNS Caching phase 1 + _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); + } if (!ClientOSEncryptionSupport) { @@ -586,8 +591,11 @@ bool withFailover Debug.Assert(retCode == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); - // for DNS Caching phase 1 - _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); + if (_connHandler.pendingSQLDNSObject == null) + { + // for DNS Caching phase 1 + _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); + } SendPreLoginHandshake(instanceName, encrypt, integratedSecurity, serverCertificateFilename); status = ConsumePreLoginHandshake( From 96ef625d4a0090b024d945bb11b6d3ec24dddfb0 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:13:25 +0100 Subject: [PATCH 05/31] netfx, netcore: merge CER mopup netfx: remove empty try/finally structure netcore: add connection dooming --- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 6 ++++-- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 9 ++------- 2 files changed, 6 insertions(+), 9 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 70f61ee021..580fb8840e 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 @@ -10416,7 +10416,8 @@ private void TDSExecuteRPCParameterSetupWriteCompletion(SqlCommand cmd, IList<_S startRpc, startParam ), - onFailure: exc => TdsExecuteRPC_OnFailure(exc, stateObj) + onFailure: exc => TdsExecuteRPC_OnFailure(exc, stateObj), + connectionToDoom: _connHandler ); } @@ -11838,7 +11839,8 @@ private Task GetTerminationTask(Task unterminatedWriteTask, object value, MetaTy else { return AsyncHelper.CreateContinuationTask(unterminatedWriteTask, - WriteInt, 0, stateObj + WriteInt, 0, stateObj, + connectionToDoom: _connHandler ); } } 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 6011af175c..58e1f7d9b0 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 @@ -647,13 +647,8 @@ internal void RemoveEncryption() } // create a new packet encryption changes the internal packet size Bug# 228403 - try - { } // EmptyTry/Finally to avoid FXCop violation - finally - { - _physicalStateObj.ClearAllWritePackets(); - } - } + _physicalStateObj.ClearAllWritePackets(); + } internal void EnableMars() { From f2de25f5d3cf91c09fbeca05ae4caf70d7946095 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:24:11 +0100 Subject: [PATCH 06/31] netfx, netcore: merge debug assertions --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 5 ++--- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 8 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 580fb8840e..c7f6bdac01 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 @@ -1369,12 +1369,12 @@ internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand //_errorAndWarningsLock lock is implemented inside GetFullErrorAndWarningCollection SqlErrorCollection temp = stateObj.GetFullErrorAndWarningCollection(out breakConnection); + Debug.Assert(temp != null, "TdsParser::ThrowExceptionAndWarning: null errors collection!"); + Debug.Assert(temp.Count > 0, "TdsParser::ThrowExceptionAndWarning called with no exceptions or warnings!"); if (temp.Count == 0) { SqlClientEventSource.Log.TryTraceEvent(" Potential multi-threaded misuse of connection, unexpectedly empty warnings/errors under lock {0}", ObjectID); } - Debug.Assert(temp != null, "TdsParser::ThrowExceptionAndWarning: null errors collection!"); - Debug.Assert(temp.Count > 0, "TdsParser::ThrowExceptionAndWarning called with no exceptions or warnings!"); Debug.Assert(_connHandler != null, "TdsParser::ThrowExceptionAndWarning called with null connectionHandler!"); // Don't break the connection if it is already closed @@ -2220,7 +2220,6 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle #else throw SQL.ParsingErrorToken(ParsingErrorState.InvalidTdsTokenReceived, token); // MDAC 82443 #endif - } int tokenLength; 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 58e1f7d9b0..15aa770fec 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 @@ -636,7 +636,7 @@ bool withFailover internal void RemoveEncryption() { - Debug.Assert((_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN, "Invalid encryption option state"); + Debug.Assert(_encryptionOption == EncryptionOptions.LOGIN, "Invalid encryption option state"); uint error = _physicalStateObj.DisableSsl(); @@ -1375,6 +1375,7 @@ internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand //_errorAndWarningsLock lock is implemented inside GetFullErrorAndWarningCollection SqlErrorCollection temp = stateObj.GetFullErrorAndWarningCollection(out breakConnection); + Debug.Assert(temp != null, "TdsParser::ThrowExceptionAndWarning: null errors collection!"); Debug.Assert(temp.Count > 0, "TdsParser::ThrowExceptionAndWarning called with no exceptions or warnings!"); if (temp.Count == 0) { @@ -1405,7 +1406,6 @@ internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand } } - Debug.Assert(temp != null, "TdsParser::ThrowExceptionAndWarning: 0 errors in collection"); if (temp != null && temp.Count > 0) { // Construct the exception now that we've collected all the errors @@ -1502,7 +1502,6 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) if (details.SniErrorNumber != 0) { - // handle special SNI error codes that are converted into exception which is not a SqlException. switch (details.SniErrorNumber) { @@ -2198,11 +2197,18 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle if (!IsValidTdsToken(token)) { - Debug.Fail($"unexpected token; token = {token,-2:X2}"); +#if DEBUG + string message = stateObj.DumpBuffer(); + Debug.Fail(message); +#endif _state = TdsParserState.Broken; _connHandler.BreakConnection(); SqlClientEventSource.Log.TryTraceEvent(" Potential multi-threaded misuse of connection, unexpected TDS token found {0}", ObjectID); +#if DEBUG + throw new InvalidOperationException(message); +#else throw SQL.ParsingErrorToken(ParsingErrorState.InvalidTdsTokenReceived, token); // MDAC 82443 +#endif } int tokenLength; @@ -4788,6 +4794,7 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) { codePage = ci.TextInfo.ANSICodePage; } + Debug.Assert(codePage >= 0, $"Invalid code page. codePage: {codePage}. cultureId: {cultureId}"); } } @@ -10614,9 +10621,9 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa udtType: null); } - var sendDefaultValue = sendDefault ? 1 : 0; if (advancedTraceIsOn) { + int sendDefaultValue = sendDefault ? 1 : 0; SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Sending parameter '{1}', default flag={2}, metadata:{3}", ObjectID, param.ParameterName, sendDefaultValue, metaData.TraceString(3)); } From 83f112be02787b8de2cc2e2a1490d6d97d3763c4 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:35:03 +0100 Subject: [PATCH 07/31] netfx: sync SNI-specific and endian-specific logic --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 6 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 60 +++++++++++++++---- 2 files changed, 53 insertions(+), 13 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 c7f6bdac01..11f81092e4 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 @@ -654,8 +654,10 @@ internal void EnableMars() // Cache physical stateObj and connection. _pMarsPhysicalConObj = _physicalStateObj; +#if NET if (LocalAppContextSwitches.UseManagedNetworking) _pMarsPhysicalConObj.IncrementPendingCallbacks(); +#endif uint info = 0; uint error = _pMarsPhysicalConObj.EnableMars(ref info); @@ -1588,6 +1590,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) } else { +#if NET if (LocalAppContextSwitches.UseManagedNetworking) { // SNI error. Append additional error message info if available and hasn't been included. @@ -1597,6 +1600,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) : (sniLookupMessage + ": " + errorMessage); } else +#endif { // SNI error. Replace the entire message. errorMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); @@ -6477,7 +6481,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt length = checked((int)length - 1); int[] bits = new int[4]; int decLength = length >> 2; - var span = unencryptedBytes.AsSpan(); + ReadOnlySpan span = unencryptedBytes.AsSpan(); for (int i = 0; i < decLength; i++) { // up to 16 bytes of data following the sign byte 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 15aa770fec..95d7e080b5 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 @@ -657,6 +657,11 @@ internal void EnableMars() // Cache physical stateObj and connection. _pMarsPhysicalConObj = _physicalStateObj; +#if NET + if (LocalAppContextSwitches.UseManagedNetworking) + _pMarsPhysicalConObj.IncrementPendingCallbacks(); +#endif + uint info = 0; uint error = _pMarsPhysicalConObj.EnableMars(ref info); @@ -1593,16 +1598,29 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) } else { - // SNI error. Replace the entire message. - errorMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); - - // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code - if (details.SniErrorNumber == SniErrors.LocalDBErrorCode) +#if NET + if (LocalAppContextSwitches.UseManagedNetworking) + { + // SNI error. Append additional error message info if available and hasn't been included. + string sniLookupMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); + errorMessage = (string.IsNullOrEmpty(errorMessage) || errorMessage.Contains(sniLookupMessage)) + ? sniLookupMessage + : (sniLookupMessage + ": " + errorMessage); + } + else +#endif { - errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError); - win32ErrorCode = 0; + // SNI error. Replace the entire message. + errorMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); + + // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code + if (details.SniErrorNumber == SniErrors.LocalDBErrorCode) + { + errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError); + win32ErrorCode = 0; + } + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage); } - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage); } errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})", sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage); @@ -1900,7 +1918,9 @@ internal byte[] SerializeFloat(float v) throw ADP.ParameterValueOutOfRange(v.ToString()); } - return BitConverter.GetBytes(v); + var bytes = new byte[4]; + BinaryPrimitives.WriteInt32LittleEndian(bytes, BitConverterCompatible.SingleToInt32Bits(v)); + return bytes; } /// @@ -2049,7 +2069,9 @@ internal byte[] SerializeDouble(double v) throw ADP.ParameterValueOutOfRange(v.ToString()); } - return BitConverter.GetBytes(v); + var bytes = new byte[8]; + BinaryPrimitives.WriteInt64LittleEndian(bytes, BitConverter.DoubleToInt64Bits(v)); + return bytes; } /// @@ -6523,10 +6545,11 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt length = checked((int)length - 1); int[] bits = new int[4]; int decLength = length >> 2; + ReadOnlySpan span = unencryptedBytes.AsSpan(); for (int i = 0; i < decLength; i++) { // up to 16 bytes of data following the sign byte - bits[i] = BitConverter.ToInt32(unencryptedBytes, index); + bits[i] = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(index)); index += 4; } value.SetToDecimal(md.baseTI.precision, md.baseTI.scale, fPositive, bits); @@ -8181,7 +8204,20 @@ internal Task WriteString(string s, int length, int offset, TdsParserStateObject private static void CopyCharsToBytes(char[] source, int sourceOffset, byte[] dest, int destOffset, int charLength) { - Buffer.BlockCopy(source, sourceOffset, dest, destOffset, charLength * ADP.CharSize); + if (!BitConverter.IsLittleEndian) + { + int desti = 0; + Span span = dest.AsSpan(); + for (int srci = 0; srci < charLength; srci++) + { + BinaryPrimitives.WriteUInt16LittleEndian(span.Slice(desti + destOffset), (ushort)source[srci + sourceOffset]); + desti += 2; + } + } + else + { + Buffer.BlockCopy(source, sourceOffset, dest, destOffset, charLength * ADP.CharSize); + } } private static void CopyStringToBytes(string source, int sourceOffset, byte[] dest, int destOffset, int charLength) From b2e4fed49d87158881b431c3d198ecf154d16f62 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:40:35 +0100 Subject: [PATCH 08/31] netcore: merge netfx synthesis of default column name from operand --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 23 ++++++- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 69 +++++-------------- 2 files changed, 40 insertions(+), 52 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 11f81092e4..3ae0c185cc 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 @@ -4938,7 +4938,7 @@ internal TdsOperationStatus TryProcessAltMetaData(int cColumns, TdsParserStateOb // internal meta data class _SqlMetaData col = altMetaDataSet[i]; - result = stateObj.TryReadByte(out _); + result = stateObj.TryReadByte(out col.op); if (result != TdsOperationStatus.Done) { return result; @@ -4956,6 +4956,27 @@ internal TdsOperationStatus TryProcessAltMetaData(int cColumns, TdsParserStateOb { return result; } + + if (string.IsNullOrEmpty(col.column)) + { + // create column name from op + col.column = col.op switch + { + TdsEnums.AOPAVG => "avg", + TdsEnums.AOPCNT => "cnt", + TdsEnums.AOPCNTB => "cntb", + TdsEnums.AOPMAX => "max", + TdsEnums.AOPMIN => "min", + TdsEnums.AOPSUM => "sum", + TdsEnums.AOPANY => "any", + TdsEnums.AOPNOOP => "noop", + TdsEnums.AOPSTDEV => "stdev", + TdsEnums.AOPSTDEVP => "stdevp", + TdsEnums.AOPVAR => "var", + TdsEnums.AOPVARP => "varp", + _ => col.column, + }; + } } metaData = altMetaDataSet; 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 95d7e080b5..5f5c56833b 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 @@ -4953,7 +4953,8 @@ internal TdsOperationStatus TryProcessAltMetaData(int cColumns, TdsParserStateOb { return result; } - result = stateObj.TryReadUInt16(out col.operand); + + result = stateObj.TryReadUInt16(out _); if (result != TdsOperationStatus.Done) { return result; @@ -4969,56 +4970,22 @@ internal TdsOperationStatus TryProcessAltMetaData(int cColumns, TdsParserStateOb if (string.IsNullOrEmpty(col.column)) { // create column name from op - switch (col.op) - { - case TdsEnums.AOPAVG: - col.column = "avg"; - break; - - case TdsEnums.AOPCNT: - col.column = "cnt"; - break; - - case TdsEnums.AOPCNTB: - col.column = "cntb"; - break; - - case TdsEnums.AOPMAX: - col.column = "max"; - break; - - case TdsEnums.AOPMIN: - col.column = "min"; - break; - - case TdsEnums.AOPSUM: - col.column = "sum"; - break; - - case TdsEnums.AOPANY: - col.column = "any"; - break; - - case TdsEnums.AOPNOOP: - col.column = "noop"; - break; - - case TdsEnums.AOPSTDEV: - col.column = "stdev"; - break; - - case TdsEnums.AOPSTDEVP: - col.column = "stdevp"; - break; - - case TdsEnums.AOPVAR: - col.column = "var"; - break; - - case TdsEnums.AOPVARP: - col.column = "varp"; - break; - } + col.column = col.op switch + { + TdsEnums.AOPAVG => "avg", + TdsEnums.AOPCNT => "cnt", + TdsEnums.AOPCNTB => "cntb", + TdsEnums.AOPMAX => "max", + TdsEnums.AOPMIN => "min", + TdsEnums.AOPSUM => "sum", + TdsEnums.AOPANY => "any", + TdsEnums.AOPNOOP => "noop", + TdsEnums.AOPSTDEV => "stdev", + TdsEnums.AOPSTDEVP => "stdevp", + TdsEnums.AOPVAR => "var", + TdsEnums.AOPVARP => "varp", + _ => col.column, + }; } } From d937f90676b8f67ff7fd0675db192ced869e04a8 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:50:42 +0100 Subject: [PATCH 09/31] netfx: ensure disposal of ConstrainedTextWriter and XmlWriter Also eliminates allocation of UnicodeEncoding --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 52 +++++++++---------- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 7 +-- 2 files changed, 30 insertions(+), 29 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 3ae0c185cc..a24a625b5a 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 @@ -12354,44 +12354,44 @@ private async Task WriteXmlFeed(XmlDataFeed feed, TdsParserStateObject stateObj, { writerSettings.Async = true; } - using (ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, preambleToSkip), encoding), size)) - using (XmlWriter ww = XmlWriter.Create(writer, writerSettings)) + + using ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, preambleToSkip), encoding), size); + using XmlWriter ww = XmlWriter.Create(writer, writerSettings); + + if (feed._source.ReadState == ReadState.Initial) { - if (feed._source.ReadState == ReadState.Initial) - { - feed._source.Read(); - } + feed._source.Read(); + } - while (!feed._source.EOF && !writer.IsComplete) + while (!feed._source.EOF && !writer.IsComplete) + { + // We are copying nodes from a reader to a writer. This will cause the + // XmlDeclaration to be emitted despite ConformanceLevel.Fragment above. + // Therefore, we filter out the XmlDeclaration while copying. + if (feed._source.NodeType == XmlNodeType.XmlDeclaration) { - // We are copying nodes from a reader to a writer. This will cause the - // XmlDeclaration to be emitted despite ConformanceLevel.Fragment above. - // Therefore, we filter out the XmlDeclaration while copying. - if (feed._source.NodeType == XmlNodeType.XmlDeclaration) - { - feed._source.Read(); - continue; - } - - if (_asyncWrite) - { - await ww.WriteNodeAsync(feed._source, true).ConfigureAwait(false); - } - else - { - ww.WriteNode(feed._source, true); - } + feed._source.Read(); + continue; } if (_asyncWrite) { - await ww.FlushAsync().ConfigureAwait(false); + await ww.WriteNodeAsync(feed._source, true).ConfigureAwait(false); } else { - ww.Flush(); + ww.WriteNode(feed._source, true); } } + + if (_asyncWrite) + { + await ww.FlushAsync().ConfigureAwait(false); + } + else + { + ww.Flush(); + } } private async Task WriteTextFeed(TextDataFeed feed, Encoding encoding, bool needBom, TdsParserStateObject stateObj, int size, bool useReadBlock) 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 5f5c56833b..fda9a9c4ea 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 @@ -12365,7 +12365,6 @@ private async Task WriteXmlFeed(XmlDataFeed feed, TdsParserStateObject stateObj, { preambleToSkip = encoding.GetPreamble(); } - ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, preambleToSkip), encoding), size); XmlWriterSettings writerSettings = new XmlWriterSettings(); writerSettings.CloseOutput = false; // don't close the memory stream @@ -12374,7 +12373,9 @@ private async Task WriteXmlFeed(XmlDataFeed feed, TdsParserStateObject stateObj, { writerSettings.Async = true; } - XmlWriter ww = XmlWriter.Create(writer, writerSettings); + + using ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, preambleToSkip), encoding), size); + using XmlWriter ww = XmlWriter.Create(writer, writerSettings); if (feed._source.ReadState == ReadState.Initial) { @@ -12417,7 +12418,7 @@ private async Task WriteTextFeed(TextDataFeed feed, Encoding encoding, bool need Debug.Assert(encoding == null || !needBom); char[] inBuff = ArrayPool.Shared.Rent(constTextBufferSize); - encoding = encoding ?? new UnicodeEncoding(false, false); + encoding = encoding ?? TextDataFeed.DefaultEncoding; using (ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, null), encoding), size)) { From 1c03e7c6bd3472d18ef0d336062be7c67d05cfb7 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 18:03:10 +0100 Subject: [PATCH 10/31] netfx, netcore: sync handling of SqlError generation --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 8 ++++++-- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 15 +++++++++------ 2 files changed, 15 insertions(+), 8 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 a24a625b5a..74fcfb7179 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 @@ -1523,7 +1523,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // details.ErrorMessage is null terminated with garbage beyond that, since fixed length // PInvoke code automatically sets the length of the string for us, so no need to look for \0 - string errorMessage = details.ErrorMessage; + string errorMessage = details.ErrorMessage ?? string.Empty; /* Format SNI errors and add Context Information * @@ -1552,7 +1552,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) } string sqlContextInfo = StringsHelper.GetResourceString(stateObj.SniContext.ToString()); - string providerRid = string.Format("SNI_PN{0}", details.Provider); + string providerRid = $"SNI_PN{details.Provider}"; string providerName = StringsHelper.GetResourceString(providerRid); Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); int win32ErrorCode = details.NativeError; @@ -1621,7 +1621,11 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server); return new SqlError(infoNumber: details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, +#if NET errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: details.NativeError, details.Exception); +#else + errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: win32ErrorCode); +#endif } } 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 fda9a9c4ea..09a5c4c386 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 @@ -1530,8 +1530,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // details.ErrorMessage is null terminated with garbage beyond that, since fixed length // PInvoke code automatically sets the length of the string for us, so no need to look for \0 - string errorMessage; - errorMessage = string.IsNullOrEmpty(details.ErrorMessage) ? string.Empty : details.ErrorMessage; + string errorMessage = details.ErrorMessage ?? string.Empty; /* Format SNI errors and add Context Information * @@ -1559,9 +1558,9 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) SqlClientEventSource.Log.TryAdvancedTraceEvent(" Error message received from SNI. Error Message = {0}", details.ErrorMessage); } - string sqlContextInfo = StringsHelper.GetString(Enum.GetName(typeof(SniContext), stateObj.SniContext)); - string providerRid = string.Format("SNI_PN{0}", (int)details.Provider); - string providerName = StringsHelper.GetString(providerRid); + string sqlContextInfo = StringsHelper.GetResourceString(stateObj.SniContext.ToString()); + string providerRid = $"SNI_PN{details.Provider}"; + string providerName = StringsHelper.GetResourceString(providerRid); Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); int win32ErrorCode = details.NativeError; @@ -1581,7 +1580,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) if (0 <= iColon) { int len = errorMessage.Length; - len -= 2; // exclude newline sequence + len -= Environment.NewLine.Length; // exclude newline sequence iColon += 2; // skip over ": " sequence len -= iColon; /* @@ -1629,7 +1628,11 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server); return new SqlError(infoNumber: details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, +#if NET + errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: details.NativeError, details.Exception); +#else errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: win32ErrorCode); +#endif } internal void CheckResetConnection(TdsParserStateObject stateObj) From 13f7a22d5fc5996900204383ff417b3897435442 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 18:09:26 +0100 Subject: [PATCH 11/31] netfx: port #232 --- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 6 ++++++ 1 file changed, 6 insertions(+) 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 09a5c4c386..5a2dec31fe 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 @@ -1313,6 +1313,12 @@ internal void Disconnect() _pMarsPhysicalConObj = null; } } + + _resetConnectionEvent?.Dispose(); + _resetConnectionEvent = null; + + _defaultEncoding = null; + _defaultCollation = null; } // Fires a single InfoMessageEvent From 894f8cae965af21609dc178cb1a6c5717047352e Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 18:15:37 +0100 Subject: [PATCH 12/31] netfx: port TryRunSetupSpinWaitContinuation --- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 5a2dec31fe..90a32f25b7 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 @@ -2860,7 +2860,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle { // Dev11 #344723: SqlClient stress test suspends System_Data!Tcp::ReadSync via a call to SqlDataReader::Close // Spin until SendAttention has cleared _attentionSending, this prevents a race condition between receiving the attention ACK and setting _attentionSent - SpinWait.SpinUntil(() => !stateObj._attentionSending); + TryRunSetupSpinWaitContinuation(stateObj); Debug.Assert(stateObj._attentionSent, "Attention ACK has been received without attention sent"); if (stateObj._attentionSent) @@ -2884,6 +2884,9 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle return TdsOperationStatus.Done; } + // This is in its own method to avoid always allocating the lambda in TryRun + private static void TryRunSetupSpinWaitContinuation(TdsParserStateObject stateObj) => SpinWait.SpinUntil(() => !stateObj._attentionSending); + private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, out SqlEnvChange sqlEnvChange) { // There could be multiple environment change messages following this token. From b73d2402ead2558d627cad627a54fde89a016566 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:01:33 +0100 Subject: [PATCH 13/31] netcore: sync _resetConnectionEvent.WaitOne The exitContext parameter is ignored on netcore and applied on netfx --- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 74fcfb7179..e13c9a4aa8 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 @@ -1640,7 +1640,7 @@ internal void CheckResetConnection(TdsParserStateObject stateObj) { // If using Async & MARS and we do not own ResetEvent - grab it. We need to not grab lock here // for case where multiple packets are sent to server from one execute. - stateObj._fResetEventOwned = _resetConnectionEvent.WaitOne(stateObj.GetTimeoutRemaining()); + stateObj._fResetEventOwned = _resetConnectionEvent.WaitOne(stateObj.GetTimeoutRemaining(), false); if (stateObj._fResetEventOwned) { From 4eb869741d6c87c663fb53f10ac68955433635a1 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:06:10 +0100 Subject: [PATCH 14/31] netfx: port skipping read of fields from SqlLoginAck and SqlReturnValue --- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 4 ++-- .../src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) 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 90a32f25b7..1a596e7ecf 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 @@ -4009,7 +4009,7 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out return result; } - result = stateObj.TryReadString(len, out a.programName); + result = stateObj.TrySkipBytes(len * ADP.CharSize); if (result != TdsOperationStatus.Done) { return result; @@ -4291,7 +4291,7 @@ internal TdsOperationStatus TryProcessReturnValue(int length, returnValue = null; SqlReturnValue rec = new SqlReturnValue(); rec.length = length; // In 2005 this length is -1 - TdsOperationStatus result = stateObj.TryReadUInt16(out rec.parmIndex); + TdsOperationStatus result = stateObj.TryReadUInt16(out _); if (result != TdsOperationStatus.Done) { return result; 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 75eed599ce..abceeb05b7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs @@ -126,10 +126,6 @@ internal sealed class SqlLoginAck internal byte minorVersion; internal short buildNum; internal uint tdsVersion; - - #if NETFRAMEWORK - internal string programName; - #endif } internal sealed class SqlFedAuthInfo From 5c0087984a310a478d84515d8fd9a3cf079cad96 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:07:45 +0100 Subject: [PATCH 15/31] netcore: supporting infrastructure for CER cleanup --- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs index 55e41147b6..81af50e112 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -133,10 +133,10 @@ internal static void ContinueTask(Task task, Action onSuccess, Action onFailure = null, Action onCancellation = null, + Func exceptionConverter = null, #if NET - Func exceptionConverter = null + SqlInternalConnectionTds connectionToDoom = null #else - Func exceptionConverter = null, SqlInternalConnectionTds connectionToDoom = null, SqlConnection connectionToAbort = null #endif From 70a622ffb5109a640d2e898547f28a160bc64e5e Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:12:51 +0100 Subject: [PATCH 16/31] netfx: sync GetCodePage --- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) 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 1a596e7ecf..f83804bd1c 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 @@ -4773,7 +4773,6 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) // This should be treated as an error and functionality switches into the following logic. if (!success || codePage == 0) { - CultureInfo ci = null; switch (cultureId) { case 0x10404: // zh-TW @@ -4789,7 +4788,7 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) try { - ci = new CultureInfo(cultureId); + codePage = new CultureInfo(cultureId).TextInfo.ANSICodePage; success = true; } catch (ArgumentException e) @@ -4800,7 +4799,7 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) case 0x827: // Mapping Non-supported Lithuanian code page to supported Lithuanian. try { - ci = new CultureInfo(0x427); + codePage = new CultureInfo(0x427).TextInfo.ANSICodePage; success = true; } catch (ArgumentException e) @@ -4818,16 +4817,11 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) break; } - // I don't believe we should still be in failure case, but just in case. if (!success) { ThrowUnsupportedCollationEncountered(stateObj); } - if (ci != null) - { - codePage = ci.TextInfo.ANSICodePage; - } Debug.Assert(codePage >= 0, $"Invalid code page. codePage: {codePage}. cultureId: {cultureId}"); } } From aeab751c1c122277b77c9b0cd8989d259bfb4491 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:20:47 +0100 Subject: [PATCH 17/31] netcore, netfx: sync SqlGuid handling --- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 7 +++++++ .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 6 ++++++ 2 files changed, 13 insertions(+) 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 e13c9a4aa8..d1028f7b53 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 @@ -142,6 +142,9 @@ internal sealed partial class TdsParser // size of Guid (e.g. _clientConnectionId, ActivityId.Id) private const int GUID_SIZE = 16; +#if NETFRAMEWORK + private byte[] _tempGuidBytes; +#endif // now data length is 1 byte // First bit is 1 indicating client support failover partner with readonly intent @@ -6470,7 +6473,11 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt case TdsEnums.SQLUNIQUEID: { Debug.Assert(length == 16, "invalid length for SqlGuid type!"); +#if NET value.SqlGuid = new SqlGuid(unencryptedBytes); // doesn't copy the byte array +#else + value.SqlGuid = SqlTypeWorkarounds.ByteArrayToSqlGuid(unencryptedBytes); +#endif break; } 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 f83804bd1c..ff6791e5c1 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 @@ -142,7 +142,9 @@ internal sealed partial class TdsParser // size of Guid (e.g. _clientConnectionId, ActivityId.Id) private const int GUID_SIZE = 16; +#if NETFRAMEWORK private byte[] _tempGuidBytes; +#endif // now data length is 1 byte // First bit is 1 indicating client support failover partner with readonly intent @@ -6482,7 +6484,11 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt case TdsEnums.SQLUNIQUEID: { Debug.Assert(length == 16, "invalid length for SqlGuid type!"); +#if NET + value.SqlGuid = new SqlGuid(unencryptedBytes); // doesn't copy the byte array +#else value.SqlGuid = SqlTypeWorkarounds.ByteArrayToSqlGuid(unencryptedBytes); +#endif break; } From b2cb0fe88ef0e0f04281844eadc04c7ed1ec7b9d Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:25:18 +0100 Subject: [PATCH 18/31] netcore, netfx: sync SqlBinary handling --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 16 ++++++++++++++++ .../src/Microsoft/Data/SqlClient/TdsParser.cs | 18 +++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) 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 d1028f7b53..9b4ba9bdf1 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 @@ -6498,7 +6498,11 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt unencryptedBytes = bytes; } +#if NET value.SqlBinary = new SqlBinary(unencryptedBytes); // doesn't copy the byte array +#else + value.SqlBinary = SqlTypeWorkarounds.ByteArrayToSqlBinary(unencryptedBytes); +#endif break; } @@ -6724,7 +6728,11 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, } else { +#if NET value.SqlBinary = SqlBinary.WrapBytes(b); // doesn't copy the byte array +#else + value.SqlBinary = SqlTypeWorkarounds.ByteArrayToSqlBinary(b); +#endif } break; @@ -6739,7 +6747,11 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, // Internally, we use Sqlbinary to deal with varbinary data and store it in // SqlBuffer as SqlBinary value. +#if NET value.SqlBinary = SqlBinary.WrapBytes(b); +#else + value.SqlBinary = SqlTypeWorkarounds.ByteArrayToSqlBinary(b); +#endif // Extract the metadata from the payload and set it as the vector attributes // in the SqlBuffer. This metadata is further used when constructing a SqlVector @@ -7068,7 +7080,11 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp return result; } +#if NET value.SqlBinary = SqlBinary.WrapBytes(b); // doesn't copy the byte array +#else + value.SqlBinary = SqlTypeWorkarounds.ByteArrayToSqlBinary(b); +#endif break; } 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 ff6791e5c1..0f541f135d 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 @@ -6509,7 +6509,11 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt unencryptedBytes = bytes; } +#if NET + value.SqlBinary = new SqlBinary(unencryptedBytes); // doesn't copy the byte array +#else value.SqlBinary = SqlTypeWorkarounds.ByteArrayToSqlBinary(unencryptedBytes); +#endif break; } @@ -6735,7 +6739,11 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, } else { +#if NET + value.SqlBinary = SqlBinary.WrapBytes(b); // doesn't copy the byte array +#else value.SqlBinary = SqlTypeWorkarounds.ByteArrayToSqlBinary(b); +#endif } break; @@ -6750,7 +6758,11 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, // Internally, we use Sqlbinary to deal with varbinary data and store it in // SqlBuffer as SqlBinary value. +#if NET + value.SqlBinary = SqlBinary.WrapBytes(b); +#else value.SqlBinary = SqlTypeWorkarounds.ByteArrayToSqlBinary(b); +#endif // Extract the metadata from the payload and set it as the vector attributes // in the SqlBuffer. This metadata is further used when constructing a SqlVector @@ -7078,8 +7090,12 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp { return result; } - + +#if NET + value.SqlBinary = SqlBinary.WrapBytes(b); // doesn't copy the byte array +#else value.SqlBinary = SqlTypeWorkarounds.ByteArrayToSqlBinary(b); +#endif break; } From 844ad1e14cfbb95fcbb83b47149dc46baa224d23 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:30:00 +0100 Subject: [PATCH 19/31] netcore: sync date/time handling No need to call AsSpan, this conversion is implicit. No need to slice to length bytes, length is already defined as the length of unencryptedBytes. --- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 6 +++--- 1 file changed, 3 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 9b4ba9bdf1..e6dfc7bc65 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 @@ -6581,19 +6581,19 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt case TdsEnums.SQLTIME: // We normalize to maximum precision to allow conversion across different precisions. Debug.Assert(length == 5, "invalid length for time type!"); - value.SetToTime(unencryptedBytes.AsSpan().Slice(0, length), TdsEnums.MAX_TIME_SCALE, denormalizedScale); + value.SetToTime(unencryptedBytes, TdsEnums.MAX_TIME_SCALE, denormalizedScale); break; case TdsEnums.SQLDATETIME2: // We normalize to maximum precision to allow conversion across different precisions. Debug.Assert(length == 8, "invalid length for datetime2 type!"); - value.SetToDateTime2(unencryptedBytes.AsSpan().Slice(0, length), TdsEnums.MAX_TIME_SCALE, denormalizedScale); + value.SetToDateTime2(unencryptedBytes, TdsEnums.MAX_TIME_SCALE, denormalizedScale); break; case TdsEnums.SQLDATETIMEOFFSET: // We normalize to maximum precision to allow conversion across different precisions. Debug.Assert(length == 10, "invalid length for datetimeoffset type!"); - value.SetToDateTimeOffset(unencryptedBytes.AsSpan().Slice(0, length), TdsEnums.MAX_TIME_SCALE, denormalizedScale); + value.SetToDateTimeOffset(unencryptedBytes, TdsEnums.MAX_TIME_SCALE, denormalizedScale); break; default: From 3249a81a6beaafad47d7d396a2c31102df9c2548 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:35:00 +0100 Subject: [PATCH 20/31] netcore, netfx: sync decimal handling Also enables use of cached _decimalBits in netfx, this wasn't present before --- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 8 ++++++++ .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 11 ++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) 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 e6dfc7bc65..06787712c1 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 @@ -7966,7 +7966,11 @@ internal byte[] SerializeSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) } Span data = stackalloc uint[4]; +#if NET d.WriteTdsValue(data); +#else + SqlTypeWorkarounds.SqlDecimalWriteTdsValue(d, data); +#endif byte[] bytesPart = SerializeUnsignedInt(data[0], stateObj); Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); @@ -7996,7 +8000,11 @@ internal void WriteSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) } Span data = stackalloc uint[4]; +#if NET d.WriteTdsValue(data); +#else + SqlTypeWorkarounds.SqlDecimalWriteTdsValue(d, data); +#endif WriteUnsignedInt(data[0], stateObj); WriteUnsignedInt(data[1], stateObj); 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 0f541f135d..e4155c3336 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 @@ -7903,6 +7903,7 @@ private TdsOperationStatus TryReadDecimalBits(int length, TdsParserStateObject s if (bits == null) { bits = new int[4]; + stateObj._decimalBits = bits; } else { @@ -7976,8 +7977,12 @@ internal byte[] SerializeSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) } Span data = stackalloc uint[4]; +#if NET + d.WriteTdsValue(data); +#else SqlTypeWorkarounds.SqlDecimalWriteTdsValue(d, data); - +#endif + byte[] bytesPart = SerializeUnsignedInt(data[0], stateObj); Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); current += 4; @@ -8006,7 +8011,11 @@ internal void WriteSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) } Span data = stackalloc uint[4]; +#if NET + d.WriteTdsValue(data); +#else SqlTypeWorkarounds.SqlDecimalWriteTdsValue(d, data); +#endif WriteUnsignedInt(data[0], stateObj); WriteUnsignedInt(data[1], stateObj); From 1d1e925f7924b1a3d32134bb5a28e3970c6f066a Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:37:11 +0100 Subject: [PATCH 21/31] netfx, netcore: sync minor cleanup of if conditions Remove Yoda condition from netcore. Remove comparison which is always true from netfx. --- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- 2 files changed, 2 insertions(+), 2 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 06787712c1..de7a7402bc 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 @@ -3543,7 +3543,7 @@ private TdsOperationStatus TryProcessFeatureExtAck(TdsParserStateObject stateObj else { // Check if the attestation protocol is specified and supports the enclave type. - if (SqlConnectionAttestationProtocol.NotSpecified != attestationProtocol && !IsValidAttestationProtocol(attestationProtocol, EnclaveType)) + if (attestationProtocol != SqlConnectionAttestationProtocol.NotSpecified && !IsValidAttestationProtocol(attestationProtocol, EnclaveType)) { throw SQL.AttestationProtocolNotSupportEnclaveType(attestationProtocol.ToString(), EnclaveType); } 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 e4155c3336..4d9b515081 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 @@ -10289,7 +10289,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet Debug.Assert(udtVal != null, "GetBytes returned null instance. Make sure that it always returns non-null value"); size = udtVal.Length; - if (size < 0 || (size >= maxSupportedSize && maxsize != -1)) + if (size >= maxSupportedSize && maxsize != -1) { throw SQL.UDTInvalidSize(maxsize, maxSupportedSize); } From 52dd5515a67e5158ea23e58cfc3ff6fac3d0e4dd Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:39:23 +0100 Subject: [PATCH 22/31] netfx, netcore: sync creation of LocalDB instances Only enabled on netfx for now --- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 4 ++++ .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 2 ++ 2 files changed, 6 insertions(+) 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 de7a7402bc..f9fe07a171 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 @@ -398,6 +398,10 @@ bool withFailover // Encryption is not supported on SQL Local DB - disable it if they have only specified Mandatory if (connHandler.ConnectionOptions.LocalDBInstance != null) { +#if NETFRAMEWORK + // Create LocalDB instance if necessary + LocalDbApi.CreateLocalDbInstance(connHandler.ConnectionOptions.LocalDBInstance); +#endif if (encrypt == SqlConnectionEncryptOption.Mandatory) { encrypt = SqlConnectionEncryptOption.Optional; 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 4d9b515081..cee2897ba6 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 @@ -398,8 +398,10 @@ bool withFailover // Encryption is not supported on SQL Local DB - disable it if they have only specified Mandatory if (connHandler.ConnectionOptions.LocalDBInstance != null) { +#if NETFRAMEWORK // Create LocalDB instance if necessary LocalDbApi.CreateLocalDbInstance(connHandler.ConnectionOptions.LocalDBInstance); +#endif if (encrypt == SqlConnectionEncryptOption.Mandatory) { encrypt = SqlConnectionEncryptOption.Optional; From 7880b5f66bd536c24b0cc37892acbf92be44226c Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:41:45 +0100 Subject: [PATCH 23/31] netcore: run ApplyFeatureExData in a checked context --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 93 ++++++++++--------- 1 file changed, 48 insertions(+), 45 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 f9fe07a171..68b254f646 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 @@ -9191,56 +9191,59 @@ private int ApplyFeatureExData(TdsEnums.FeatureExtension requestedFeatures, { if (useFeatureExt) { - if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0) + checked { - length += WriteSessionRecoveryFeatureRequest(recoverySessionData, write); - } - if ((requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0) - { - SqlClientEventSource.Log.TryTraceEvent(" Sending federated authentication feature request & wirte = {0}", write); - Debug.Assert(fedAuthFeatureExtensionData != null, "fedAuthFeatureExtensionData should not null."); - length += WriteFedAuthFeatureRequest(fedAuthFeatureExtensionData, write: write); - } - if ((requestedFeatures & TdsEnums.FeatureExtension.Tce) != 0) - { - length += WriteTceFeatureRequest(write); - } - if ((requestedFeatures & TdsEnums.FeatureExtension.GlobalTransactions) != 0) - { - length += WriteGlobalTransactionsFeatureRequest(write); - } - if ((requestedFeatures & TdsEnums.FeatureExtension.AzureSQLSupport) != 0) - { - length += WriteAzureSQLSupportFeatureRequest(write); - } - if ((requestedFeatures & TdsEnums.FeatureExtension.DataClassification) != 0) - { - length += WriteDataClassificationFeatureRequest(write); - } - if ((requestedFeatures & TdsEnums.FeatureExtension.UTF8Support) != 0) - { - length += WriteUTF8SupportFeatureRequest(write); - } + if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0) + { + length += WriteSessionRecoveryFeatureRequest(recoverySessionData, write); + } + if ((requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0) + { + SqlClientEventSource.Log.TryTraceEvent(" Sending federated authentication feature request & wirte = {0}", write); + Debug.Assert(fedAuthFeatureExtensionData != null, "fedAuthFeatureExtensionData should not null."); + length += WriteFedAuthFeatureRequest(fedAuthFeatureExtensionData, write: write); + } + if ((requestedFeatures & TdsEnums.FeatureExtension.Tce) != 0) + { + length += WriteTceFeatureRequest(write); + } + if ((requestedFeatures & TdsEnums.FeatureExtension.GlobalTransactions) != 0) + { + length += WriteGlobalTransactionsFeatureRequest(write); + } + if ((requestedFeatures & TdsEnums.FeatureExtension.AzureSQLSupport) != 0) + { + length += WriteAzureSQLSupportFeatureRequest(write); + } + if ((requestedFeatures & TdsEnums.FeatureExtension.DataClassification) != 0) + { + length += WriteDataClassificationFeatureRequest(write); + } + if ((requestedFeatures & TdsEnums.FeatureExtension.UTF8Support) != 0) + { + length += WriteUTF8SupportFeatureRequest(write); + } - if ((requestedFeatures & TdsEnums.FeatureExtension.SQLDNSCaching) != 0) - { - length += WriteSQLDNSCachingFeatureRequest(write); - } + if ((requestedFeatures & TdsEnums.FeatureExtension.SQLDNSCaching) != 0) + { + length += WriteSQLDNSCachingFeatureRequest(write); + } - if ((requestedFeatures & TdsEnums.FeatureExtension.JsonSupport) != 0) - { - length += WriteJsonSupportFeatureRequest(write); - } + if ((requestedFeatures & TdsEnums.FeatureExtension.JsonSupport) != 0) + { + length += WriteJsonSupportFeatureRequest(write); + } - if ((requestedFeatures & TdsEnums.FeatureExtension.VectorSupport) != 0) - { - length += WriteVectorSupportFeatureRequest(write); - } + if ((requestedFeatures & TdsEnums.FeatureExtension.VectorSupport) != 0) + { + length += WriteVectorSupportFeatureRequest(write); + } - length++; // for terminator - if (write) - { - _physicalStateObj.WriteByte(0xFF); // terminator + length++; // for terminator + if (write) + { + _physicalStateObj.WriteByte(0xFF); // terminator + } } } From 143de69b988bbdfbc75069496c2e8514f67ccc65 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:45:45 +0100 Subject: [PATCH 24/31] netcore, netfx: sync creation of a TryEventScope Also reduced indentation level in ProcessSNIError by swapping using block for using statement --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 216 +++++++++--------- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 1 + 2 files changed, 108 insertions(+), 109 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 68b254f646..b93fe080e5 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 @@ -1497,143 +1497,141 @@ internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand internal SqlError ProcessSNIError(TdsParserStateObject stateObj) { - using (TryEventScope.Create(nameof(TdsParser))) - { + using TryEventScope _ = TryEventScope.Create(nameof(TdsParser)); #if DEBUG - // There is an exception here for MARS as its possible that another thread has closed the connection just as we see an error - Debug.Assert(SniContext.Undefined != stateObj.DebugOnlyCopyOfSniContext || ((_fMARS) && ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken))), "SniContext must not be None"); + // There is an exception here for MARS as its possible that another thread has closed the connection just as we see an error + Debug.Assert(SniContext.Undefined != stateObj.DebugOnlyCopyOfSniContext || ((_fMARS) && ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken))), "SniContext must not be None"); #endif - TdsParserStateObject.SniErrorDetails details = stateObj.GetErrorDetails(); + TdsParserStateObject.SniErrorDetails details = stateObj.GetErrorDetails(); - if (details.SniErrorNumber != 0) + if (details.SniErrorNumber != 0) + { + // handle special SNI error codes that are converted into exception which is not a SqlException. + switch (details.SniErrorNumber) { - // handle special SNI error codes that are converted into exception which is not a SqlException. - switch (details.SniErrorNumber) - { - case SniErrors.MultiSubnetFailoverWithMoreThan64IPs: - // Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported."); - throw SQL.MultiSubnetFailoverWithMoreThan64IPs(); + case SniErrors.MultiSubnetFailoverWithMoreThan64IPs: + // Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported. + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported."); + throw SQL.MultiSubnetFailoverWithMoreThan64IPs(); - case SniErrors.MultiSubnetFailoverWithInstanceSpecified: - // Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported."); - throw SQL.MultiSubnetFailoverWithInstanceSpecified(); + case SniErrors.MultiSubnetFailoverWithInstanceSpecified: + // Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported. + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported."); + throw SQL.MultiSubnetFailoverWithInstanceSpecified(); - case SniErrors.MultiSubnetFailoverWithNonTcpProtocol: - // Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol."); - throw SQL.MultiSubnetFailoverWithNonTcpProtocol(); - } + case SniErrors.MultiSubnetFailoverWithNonTcpProtocol: + // Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol. + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol."); + throw SQL.MultiSubnetFailoverWithNonTcpProtocol(); } - // continue building SqlError instance + } + // continue building SqlError instance - // details.ErrorMessage is null terminated with garbage beyond that, since fixed length - // PInvoke code automatically sets the length of the string for us, so no need to look for \0 - string errorMessage = details.ErrorMessage ?? string.Empty; + // details.ErrorMessage is null terminated with garbage beyond that, since fixed length + // PInvoke code automatically sets the length of the string for us, so no need to look for \0 + string errorMessage = details.ErrorMessage ?? string.Empty; - /* Format SNI errors and add Context Information - * - * General syntax is: - * - * (provider:, error: - ) - * - * errorMessage | sniError | - * ------------------------------------------- - * ==null | x | must never happen - * !=null | != 0 | retrieve corresponding errorMessage from resources - * !=null | == 0 | replace text left of errorMessage - */ + /* Format SNI errors and add Context Information + * + * General syntax is: + * + * (provider:, error: - ) + * + * errorMessage | sniError | + * ------------------------------------------- + * ==null | x | must never happen + * !=null | != 0 | retrieve corresponding errorMessage from resources + * !=null | == 0 | replace text left of errorMessage + */ #if NET - if (LocalAppContextSwitches.UseManagedNetworking) - { - Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage) || details.SniErrorNumber != 0, "Empty error message received from SNI"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.ErrorMessage, details.SniErrorNumber); - } - else + if (LocalAppContextSwitches.UseManagedNetworking) + { + Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage) || details.SniErrorNumber != 0, "Empty error message received from SNI"); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.ErrorMessage, details.SniErrorNumber); + } + else #endif - { - Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage), "Empty error message received from SNI"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Error message received from SNI. Error Message = {0}", details.ErrorMessage); - } + { + Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage), "Empty error message received from SNI"); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Error message received from SNI. Error Message = {0}", details.ErrorMessage); + } - string sqlContextInfo = StringsHelper.GetResourceString(stateObj.SniContext.ToString()); - string providerRid = $"SNI_PN{details.Provider}"; - string providerName = StringsHelper.GetResourceString(providerRid); - Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); - int win32ErrorCode = details.NativeError; + string sqlContextInfo = StringsHelper.GetResourceString(stateObj.SniContext.ToString()); + string providerRid = $"SNI_PN{details.Provider}"; + string providerName = StringsHelper.GetResourceString(providerRid); + Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); + int win32ErrorCode = details.NativeError; - SqlClientEventSource.Log.TryAdvancedTraceEvent(" SNI Native Error Code = {0}", win32ErrorCode); - if (details.SniErrorNumber == 0) - { - // Provider error. The message from provider is preceded with non-localizable info from SNI - // strip provider info from SNI - // - int iColon = errorMessage.IndexOf(':'); - Debug.Assert(0 <= iColon, "':' character missing in sni errorMessage"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" ':' character missing in sni errorMessage. Error Message index of ':' = {0}", iColon); - Debug.Assert(errorMessage.Length > iColon + 1 && errorMessage[iColon + 1] == ' ', "Expecting a space after the ':' character"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Expecting a space after the ':' character. Error Message Length = {0}", errorMessage.Length); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" SNI Native Error Code = {0}", win32ErrorCode); + if (details.SniErrorNumber == 0) + { + // Provider error. The message from provider is preceded with non-localizable info from SNI + // strip provider info from SNI + // + int iColon = errorMessage.IndexOf(':'); + Debug.Assert(0 <= iColon, "':' character missing in sni errorMessage"); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" ':' character missing in sni errorMessage. Error Message index of ':' = {0}", iColon); + Debug.Assert(errorMessage.Length > iColon + 1 && errorMessage[iColon + 1] == ' ', "Expecting a space after the ':' character"); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Expecting a space after the ':' character. Error Message Length = {0}", errorMessage.Length); - // extract the message excluding the colon and trailing cr/lf chars - if (0 <= iColon) + // extract the message excluding the colon and trailing cr/lf chars + if (0 <= iColon) + { + int len = errorMessage.Length; + len -= Environment.NewLine.Length; // exclude newline sequence + iColon += 2; // skip over ": " sequence + len -= iColon; + /* + The error message should come back in the following format: "TCP Provider: MESSAGE TEXT" + Fix Bug 370686, if the message is received on a Win9x OS, the error message will not contain MESSAGE TEXT + per Bug: 269574. If we get a errormessage with no message text, just return the entire message otherwise + return just the message text. + */ + if (len > 0) { - int len = errorMessage.Length; - len -= Environment.NewLine.Length; // exclude newline sequence - iColon += 2; // skip over ": " sequence - len -= iColon; - /* - The error message should come back in the following format: "TCP Provider: MESSAGE TEXT" - Fix Bug 370686, if the message is received on a Win9x OS, the error message will not contain MESSAGE TEXT - per Bug: 269574. If we get a errormessage with no message text, just return the entire message otherwise - return just the message text. - */ - if (len > 0) - { - errorMessage = errorMessage.Substring(iColon, len); - } + errorMessage = errorMessage.Substring(iColon, len); } } - else - { + } + else + { #if NET - if (LocalAppContextSwitches.UseManagedNetworking) - { - // SNI error. Append additional error message info if available and hasn't been included. - string sniLookupMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); - errorMessage = (string.IsNullOrEmpty(errorMessage) || errorMessage.Contains(sniLookupMessage)) - ? sniLookupMessage - : (sniLookupMessage + ": " + errorMessage); - } - else + if (LocalAppContextSwitches.UseManagedNetworking) + { + // SNI error. Append additional error message info if available and hasn't been included. + string sniLookupMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); + errorMessage = (string.IsNullOrEmpty(errorMessage) || errorMessage.Contains(sniLookupMessage)) + ? sniLookupMessage + : (sniLookupMessage + ": " + errorMessage); + } + else #endif - { - // SNI error. Replace the entire message. - errorMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); + { + // SNI error. Replace the entire message. + errorMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); - // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code - if (details.SniErrorNumber == SniErrors.LocalDBErrorCode) - { - errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError); - win32ErrorCode = 0; - } - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage); + // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code + if (details.SniErrorNumber == SniErrors.LocalDBErrorCode) + { + errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError); + win32ErrorCode = 0; } + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage); } - errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})", - sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage); + } + errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})", + sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage); - SqlClientEventSource.Log.TryAdvancedTraceErrorEvent(" SNI Error Message. Native Error = {0}, Line Number ={1}, Function ={2}, Exception ={3}, Server = {4}", - details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server); + SqlClientEventSource.Log.TryAdvancedTraceErrorEvent(" SNI Error Message. Native Error = {0}, Line Number ={1}, Function ={2}, Exception ={3}, Server = {4}", + details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server); - return new SqlError(infoNumber: details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, + return new SqlError(infoNumber: details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, #if NET - errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: details.NativeError, details.Exception); + errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: details.NativeError, details.Exception); #else - errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: win32ErrorCode); + errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: win32ErrorCode); #endif - } } internal void CheckResetConnection(TdsParserStateObject stateObj) 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 cee2897ba6..35c09680cf 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 @@ -1509,6 +1509,7 @@ internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand internal SqlError ProcessSNIError(TdsParserStateObject stateObj) { + using TryEventScope _ = TryEventScope.Create(nameof(TdsParser)); #if DEBUG // There is an exception here for MARS as its possible that another thread has closed the connection just as we see an error Debug.Assert(SniContext.Undefined != stateObj.DebugOnlyCopyOfSniContext || ((_fMARS) && ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken))), "SniContext must not be None"); From c9585a39405aa5d957bd1fe3a9c3a66e510558b8 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:47:55 +0100 Subject: [PATCH 25/31] netfx: sync order of variable assignment --- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 35c09680cf..b3d18a8056 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 @@ -2988,7 +2988,6 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb // Give the parser the new collation values in case parameters don't specify one _defaultCollation = env._newCollation; - _defaultLCID = env._newCollation.LCID; // UTF8 collation if (env._newCollation.IsUTF8) @@ -3004,6 +3003,7 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); } } + _defaultLCID = env._newCollation.LCID; } result = stateObj.TryReadByte(out byteLength); From 5a213616c5d6064e9ccaa1fa54949072e6086bed Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 20 Sep 2025 20:17:42 +0100 Subject: [PATCH 26/31] netcore: merge static constructor --- .../src/Microsoft.Data.SqlClient.csproj | 1 - .../SqlClient/TdsParser.RegisterEncoding.cs | 20 ------------------- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 11 ++++++++++ .../src/Microsoft/Data/SqlClient/TdsParser.cs | 11 ++++++++++ 4 files changed, 22 insertions(+), 21 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.RegisterEncoding.cs 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 50b04819ba..e083dd8f70 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -822,7 +822,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.RegisterEncoding.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.RegisterEncoding.cs deleted file mode 100644 index 433e5007c7..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.RegisterEncoding.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Text; - -namespace Microsoft.Data.SqlClient -{ - internal sealed partial class TdsParser - { - static TdsParser() - { - // For CoreCLR, we need to register the ANSI Code Page encoding provider before attempting to get an Encoding from a CodePage - // For a default installation of SqlServer the encoding exchanged during Login is 1252. This encoding is not loaded by default - // See Remarks at https://msdn.microsoft.com/en-us/library/system.text.encodingprovider(v=vs.110).aspx - // SqlClient needs to register the encoding providers to make sure that even basic scenarios work with Sql Server. - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index b93fe080e5..c095f9b116 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 @@ -189,6 +189,17 @@ internal sealed partial class TdsParser private SqlCollation _cachedCollation; +#if NET + static TdsParser() + { + // For CoreCLR, we need to register the ANSI Code Page encoding provider before attempting to get an Encoding from a CodePage + // For a default installation of SqlServer the encoding exchanged during Login is 1252. This encoding is not loaded by default + // See Remarks at https://msdn.microsoft.com/en-us/library/system.text.encodingprovider(v=vs.110).aspx + // SqlClient needs to register the encoding providers to make sure that even basic scenarios work with Sql Server. + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } +#endif + internal TdsParser(bool MARS, bool fAsynchronous) { _fMARS = MARS; // may change during Connect to pre 2005 servers 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 b3d18a8056..daeb7e998c 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 @@ -189,6 +189,17 @@ internal sealed partial class TdsParser private SqlCollation _cachedCollation; +#if NET + static TdsParser() + { + // For CoreCLR, we need to register the ANSI Code Page encoding provider before attempting to get an Encoding from a CodePage + // For a default installation of SqlServer the encoding exchanged during Login is 1252. This encoding is not loaded by default + // See Remarks at https://msdn.microsoft.com/en-us/library/system.text.encodingprovider(v=vs.110).aspx + // SqlClient needs to register the encoding providers to make sure that even basic scenarios work with Sql Server. + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } +#endif + internal TdsParser(bool MARS, bool fAsynchronous) { _fMARS = MARS; // may change during Connect to pre 2005 servers From e2d846ba47100b0159a0596b95ab25e327a2415b Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 24 Sep 2025 19:13:25 +0100 Subject: [PATCH 27/31] Merge conditional compilation of netfx-only features netfx-only features: * trigger debug assertion when client certificate bit is set. * hide passwords from TdsParserStateObject (when merged) --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 15 +++++++++++++++ .../src/Microsoft/Data/SqlClient/TdsParser.cs | 6 ++++++ 2 files changed, 21 insertions(+) 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 bf04ab3e61..74147700ad 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 @@ -941,6 +941,9 @@ private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integ info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS; } +#if NETFRAMEWORK + Debug.Assert((_encryptionOption & EncryptionOptions.CLIENT_CERT) == 0, "Client certificate authentication support has been removed"); +#endif error = _physicalStateObj.EnableSsl(ref info, encrypt == SqlConnectionEncryptOption.Strict, serverCertificateFilename); if (error != TdsEnums.SNI_SUCCESS) @@ -6663,6 +6666,7 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, case TdsEnums.SQLVARBINARY: case TdsEnums.SQLIMAGE: byte[] b = null; + result = stateObj.TryReadByteArrayWithContinue(length, isPlp, out b); if (result != TdsOperationStatus.Done) { @@ -9115,6 +9119,12 @@ private void WriteLoginData(SqlLogin rec, { WriteString(userName, _physicalStateObj); +#if NETFRAMEWORK + // Cache offset in packet for tracing. + _physicalStateObj._tracePasswordOffset = _physicalStateObj._outBytesUsed; + _physicalStateObj._tracePasswordLength = encryptedPasswordLengthInBytes; +#endif + if (rec.credential != null) { _physicalStateObj.WriteSecureString(rec.credential.Password); @@ -9150,6 +9160,11 @@ private void WriteLoginData(SqlLogin rec, WriteString(rec.attachDBFilename, _physicalStateObj); if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) { +#if NETFRAMEWORK + // Cache offset in packet for tracing. + _physicalStateObj._traceChangePasswordOffset = _physicalStateObj._outBytesUsed; + _physicalStateObj._traceChangePasswordLength = encryptedChangePasswordLengthInBytes; +#endif if (rec.newSecurePassword != null) { _physicalStateObj.WriteSecureString(rec.newSecurePassword); 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 3ca2e0f2ce..dd8dede90a 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 @@ -941,7 +941,9 @@ private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integ info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS; } +#if NETFRAMEWORK Debug.Assert((_encryptionOption & EncryptionOptions.CLIENT_CERT) == 0, "Client certificate authentication support has been removed"); +#endif error = _physicalStateObj.EnableSsl(ref info, encrypt == SqlConnectionEncryptOption.Strict, serverCertificateFilename); if (error != TdsEnums.SNI_SUCCESS) @@ -9117,9 +9119,11 @@ private void WriteLoginData(SqlLogin rec, { WriteString(userName, _physicalStateObj); +#if NETFRAMEWORK // Cache offset in packet for tracing. _physicalStateObj._tracePasswordOffset = _physicalStateObj._outBytesUsed; _physicalStateObj._tracePasswordLength = encryptedPasswordLengthInBytes; +#endif if (rec.credential != null) { @@ -9156,9 +9160,11 @@ private void WriteLoginData(SqlLogin rec, WriteString(rec.attachDBFilename, _physicalStateObj); if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) { +#if NETFRAMEWORK // Cache offset in packet for tracing. _physicalStateObj._traceChangePasswordOffset = _physicalStateObj._outBytesUsed; _physicalStateObj._traceChangePasswordLength = encryptedChangePasswordLengthInBytes; +#endif if (rec.newSecurePassword != null) { _physicalStateObj.WriteSecureString(rec.newSecurePassword); From af79e6be5fae5c859f95ae99317b69ea4ab0a04e Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 24 Sep 2025 19:16:11 +0100 Subject: [PATCH 28/31] Port TryReadByteArray usage from netcore to netfx See line 6778 of change to netcore TdsParser in #3605 --- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 dd8dede90a..4d32c5236b 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 @@ -7076,7 +7076,7 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp // Note: Better not come here with plp data!! Debug.Assert(length <= TdsEnums.MAXSIZE); byte[] b = new byte[length]; - result = stateObj.TryReadByteArray(b, length); + result = stateObj.TryReadByteArrayWithContinue(length, isPlp: false, out b); if (result != TdsOperationStatus.Done) { return result; From 69c81be128531ee14fa838b5f27594d8fcff0346 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 24 Sep 2025 19:21:57 +0100 Subject: [PATCH 29/31] Rename existing shared TdsParser.cs to TdsParser.SSPI.cs --- .../netcore/src/Microsoft.Data.SqlClient.csproj | 4 ++-- .../netfx/src/Microsoft.Data.SqlClient.csproj | 4 ++-- .../Data/SqlClient/{TdsParser.cs => TdsParser.SSPI.cs} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{TdsParser.cs => TdsParser.SSPI.cs} (100%) 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 a206001be5..c7f8ac5b1e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -762,8 +762,8 @@ Microsoft\Data\SqlClient\TdsParameterSetter.cs - - Microsoft\Data\SqlClient\TdsParser.cs + + Microsoft\Data\SqlClient\TdsParser.SSPI.cs Microsoft\Data\SqlClient\TdsParserHelperClasses.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 41a3fe3f68..07c85e2ec2 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -438,8 +438,8 @@ Microsoft\Data\SqlClient\SSPI\SspiAuthenticationParameters.cs - - Microsoft\Data\SqlClient\TdsParser.cs + + Microsoft\Data\SqlClient\TdsParser.SSPI.cs Microsoft\Data\Sql\SqlDataSourceEnumerator.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs From 6bfb6ef39802a9076f20ef9dbbb841eca006e716 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 24 Sep 2025 19:26:49 +0100 Subject: [PATCH 30/31] Final merge of TdsParser.cs and redirection of references --- .../src/Microsoft.Data.SqlClient.csproj | 4 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 13922 ---------------- .../netfx/src/Microsoft.Data.SqlClient.csproj | 4 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 0 4 files changed, 6 insertions(+), 13924 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs rename src/Microsoft.Data.SqlClient/{netfx => }/src/Microsoft/Data/SqlClient/TdsParser.cs (100%) 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 c7f8ac5b1e..0847907f3b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -762,6 +762,9 @@ Microsoft\Data\SqlClient\TdsParameterSetter.cs + + Microsoft\Data\SqlClient\TdsParser.cs + Microsoft\Data\SqlClient\TdsParser.SSPI.cs @@ -826,7 +829,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs deleted file mode 100644 index 74147700ad..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ /dev/null @@ -1,13922 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Buffers; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Data; -using System.Data.SqlTypes; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Security.Authentication; -#if NETFRAMEWORK -using System.Runtime.CompilerServices; -#endif -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using Interop.Common.Sni; -#if NETFRAMEWORK -using Interop.Windows.Sni; -#endif -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; -using Microsoft.Data.Sql; -using Microsoft.Data.SqlClient.DataClassification; -using Microsoft.Data.SqlClient.LocalDb; -using Microsoft.Data.SqlClient.Server; -#if NETFRAMEWORK -using Microsoft.Data.SqlTypes; -#endif -using Microsoft.SqlServer.Server; - -namespace Microsoft.Data.SqlClient -{ - // The TdsParser Object controls reading/writing to the netlib, parsing the tds, - // and surfacing objects to the user. - internal sealed partial class TdsParser - { - private static int _objectTypeCount; // EventSource counter - private readonly SqlClientLogger _logger = new SqlClientLogger(); - - private SspiContextProvider _authenticationProvider; - - internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount); - internal int ObjectID => _objectID; - - /// - /// Verify client encryption possibility. - /// - private bool ClientOSEncryptionSupport => TdsParserStateObjectFactory.Singleton.ClientOSEncryptionSupport; - - // Default state object for parser - internal TdsParserStateObject _physicalStateObj = null; // Default stateObj and connection for Dbnetlib and non-MARS SNI. - - // Also, default logical stateObj and connection for MARS over SNI. - internal TdsParserStateObject _pMarsPhysicalConObj = null; // With MARS enabled, cached physical stateObj and connection. - - // Must keep this around - especially for callbacks on pre-MARS - // ReadAsync which will return if physical connection broken! - // - // Per Instance TDS Parser variables - // - - // Constants - private const int constBinBufferSize = 4096; // Size of the buffer used to read input parameter of type Stream - private const int constTextBufferSize = 4096; // Size of the buffer (in chars) user to read input parameter of type TextReader - - // State variables - internal TdsParserState _state = TdsParserState.Closed; // status flag for connection - - private string _server = ""; // name of server that the parser connects to - - internal volatile bool _fResetConnection = false; // flag to denote whether we are needing to call sp_reset - internal volatile bool _fPreserveTransaction = false; // flag to denote whether we need to preserve the transaction when reseting - - private SqlCollation _defaultCollation; // default collation from the server - - private int _defaultCodePage; - - private int _defaultLCID; - - internal Encoding _defaultEncoding = null; // for sql character data - - private static EncryptionOptions s_sniSupportedEncryptionOption = TdsParserStateObjectFactory.Singleton.EncryptionOptions; - - private EncryptionOptions _encryptionOption = s_sniSupportedEncryptionOption; - - private SqlInternalTransaction _currentTransaction; - private SqlInternalTransaction _pendingTransaction; // pending transaction for 2005 and beyond. - // SQLHOT 483 - // need to hold on to the transaction id if distributed transaction merely rolls back without defecting. - private long _retainedTransactionId = SqlInternalTransaction.NullTransactionId; - - // This counter is used for the entire connection to track the open result count for all - // operations not under a transaction. - private int _nonTransactedOpenResultCount = 0; - - // Connection reference - private SqlInternalConnectionTds _connHandler; - - // Async/Mars variables - private bool _fMARS = false; - - internal bool _loginWithFailover = false; // set to true while connect in failover mode so parser state object can adjust its logic - - internal AutoResetEvent _resetConnectionEvent = null; // Used to serialize executes and call reset on first execute only. - - internal TdsParserSessionPool _sessionPool = null; // initialized only when we're a MARS parser. - - // Version variables - - private bool _is2008 = false; - - private bool _is2012 = false; - - private bool _is2022 = false; - - // SqlStatistics - private SqlStatistics _statistics = null; - - private bool _statisticsIsInTransaction = false; - - // - // STATIC TDS Parser variables - // - - // NIC address caching - private static byte[] s_nicAddress; // cache the NIC address from the registry - - // textptr sequence - private static readonly byte[] s_longDataHeader = { 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; - - // XML metadata substitute sequence - private static readonly byte[] s_xmlMetadataSubstituteSequence = { 0xe7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 }; - - // JSON metadata substitute sequence - private static readonly byte[] s_jsonMetadataSubstituteSequence = { 0xa7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 }; - - // size of Guid (e.g. _clientConnectionId, ActivityId.Id) - private const int GUID_SIZE = 16; -#if NETFRAMEWORK - private byte[] _tempGuidBytes; -#endif - - // now data length is 1 byte - // First bit is 1 indicating client support failover partner with readonly intent - private static readonly byte[] s_featureExtDataAzureSQLSupportFeatureRequest = { 0x01 }; - - // NOTE: You must take the internal connection's _parserLock before modifying this - internal bool _asyncWrite = false; - - /// - /// Get or set if column encryption is supported by the server. - /// - internal bool IsColumnEncryptionSupported { get; set; } = false; - - /// - /// TCE version supported by the server - /// - internal byte TceVersionSupported { get; set; } - - /// - /// Server supports retrying when the enclave CEKs sent by the client do not match what is needed for the query to run. - /// - internal bool AreEnclaveRetriesSupported { get; set; } - - /// - /// Type of enclave being used by the server - /// - internal string EnclaveType { get; set; } - - internal bool isTcpProtocol { get; set; } - internal string FQDNforDNSCache { get; set; } - - /// - /// Get if data classification is enabled by the server. - /// - internal bool IsDataClassificationEnabled => - (DataClassificationVersion != TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED); - - /// - /// Get or set data classification version. A value of 0 means that sensitivity classification is not enabled. - /// - internal int DataClassificationVersion { get; set; } - - private SqlCollation _cachedCollation; - -#if NET - static TdsParser() - { - // For CoreCLR, we need to register the ANSI Code Page encoding provider before attempting to get an Encoding from a CodePage - // For a default installation of SqlServer the encoding exchanged during Login is 1252. This encoding is not loaded by default - // See Remarks at https://msdn.microsoft.com/en-us/library/system.text.encodingprovider(v=vs.110).aspx - // SqlClient needs to register the encoding providers to make sure that even basic scenarios work with Sql Server. - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - } -#endif - - internal TdsParser(bool MARS, bool fAsynchronous) - { - _fMARS = MARS; // may change during Connect to pre 2005 servers - - _physicalStateObj = TdsParserStateObjectFactory.Singleton.CreateTdsParserStateObject(this); - DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED; - } - - internal SqlInternalConnectionTds Connection - { - get - { - return _connHandler; - } - } - - internal SqlInternalTransaction CurrentTransaction - { - get - { - return _currentTransaction; - } - set - { - Debug.Assert(value == _currentTransaction - || _currentTransaction == null - || value == null - || (_currentTransaction != null && !_currentTransaction.IsLocal), "attempting to change current transaction?"); - - // If there is currently a transaction active, we don't want to - // change it; this can occur when there is a delegated transaction - // and the user attempts to do an API begin transaction; in these - // cases, it's safe to ignore the set. - if ((_currentTransaction == null && value != null) - || (_currentTransaction != null && value == null)) - { - _currentTransaction = value; - } - } - } - - internal int DefaultLCID - { - get - { - return _defaultLCID; - } - } - - internal EncryptionOptions EncryptionOptions - { - get - { - return _encryptionOption; - } - set - { - _encryptionOption = value; - } - } - - internal bool Is2008OrNewer - { - get - { - return _is2008; - } - } - - internal bool MARSOn - { - get - { - return _fMARS; - } - } - - internal SqlInternalTransaction PendingTransaction - { - get - { - return _pendingTransaction; - } - set - { - Debug.Assert(value != null, "setting a non-null PendingTransaction?"); - _pendingTransaction = value; - } - } - - internal string Server - { - get - { - return _server; - } - } - - internal TdsParserState State - { - get - { - return _state; - } - set - { - _state = value; - } - } - - internal SqlStatistics Statistics - { - get - { - return _statistics; - } - set - { - _statistics = value; - } - } - - private bool IncludeTraceHeader - { - get - { - return (_is2012 && SqlClientEventSource.Log.IsEnabled()); - } - } - - internal int IncrementNonTransactedOpenResultCount() - { - // IMPORTANT - this increments the connection wide open result count for all - // operations not under a transaction! Do not call if you intend to modify the - // count for a transaction! - Debug.Assert(_nonTransactedOpenResultCount >= 0, "Unexpected result count state"); - int result = Interlocked.Increment(ref _nonTransactedOpenResultCount); - return result; - } - - internal void DecrementNonTransactedOpenResultCount() - { - // IMPORTANT - this decrements the connection wide open result count for all - // operations not under a transaction! Do not call if you intend to modify the - // count for a transaction! - Interlocked.Decrement(ref _nonTransactedOpenResultCount); - Debug.Assert(_nonTransactedOpenResultCount >= 0, "Unexpected result count state"); - } - - internal void ProcessPendingAck(TdsParserStateObject stateObj) - { - if (stateObj._attentionSent) - { - SqlClientEventSource.Log.TryTraceEvent("TdsParser.ProcessPendingAck | INFO | Connection Object Id {0}, State Obj Id {1}, Processing Attention.", _connHandler.ObjectID, stateObj.ObjectID); - ProcessAttention(stateObj); - } - } - - internal void Connect(ServerInfo serverInfo, - SqlInternalConnectionTds connHandler, - TimeoutTimer timeout, - SqlConnectionString connectionOptions, -#if NETFRAMEWORK - bool withFailover, - bool isFirstTransparentAttempt, - bool disableTnir -#else - bool withFailover -#endif - ) - { - SqlConnectionEncryptOption encrypt = connectionOptions.Encrypt; - bool isTlsFirst = (encrypt == SqlConnectionEncryptOption.Strict); - bool trustServerCert = connectionOptions.TrustServerCertificate; - bool integratedSecurity = connectionOptions.IntegratedSecurity; - SqlAuthenticationMethod authType = connectionOptions.Authentication; - string hostNameInCertificate = connectionOptions.HostNameInCertificate; - string serverCertificateFilename = connectionOptions.ServerCertificate; - - if (_state != TdsParserState.Closed) - { - Debug.Fail("TdsParser.Connect called on non-closed connection!"); - return; - } - - _connHandler = connHandler; - _loginWithFailover = withFailover; - - // Clean up IsSQLDNSCachingSupported flag from previous status - _connHandler.IsSQLDNSCachingSupported = false; - - uint sniStatus = TdsParserStateObjectFactory.Singleton.SNIStatus; - - if (sniStatus != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - Debug.Fail("SNI returned status != success, but no error thrown?"); - } - else - { - SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | Connection Object Id {0}, Authentication Mode: {1}", _connHandler.ObjectID, - authType == SqlAuthenticationMethod.NotSpecified ? SqlAuthenticationMethod.SqlPassword.ToString() : authType.ToString()); - } - - // Encryption is not supported on SQL Local DB - disable it if they have only specified Mandatory - if (connHandler.ConnectionOptions.LocalDBInstance != null) - { -#if NETFRAMEWORK - // Create LocalDB instance if necessary - LocalDbApi.CreateLocalDbInstance(connHandler.ConnectionOptions.LocalDBInstance); -#endif - if (encrypt == SqlConnectionEncryptOption.Mandatory) - { - encrypt = SqlConnectionEncryptOption.Optional; - SqlClientEventSource.Log.TryTraceEvent(" Encryption will be disabled as target server is a SQL Local DB instance."); - } - } - - _authenticationProvider = null; - - // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server - if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) - { - _authenticationProvider = Connection._sspiContextProvider ?? _physicalStateObj.CreateSspiContextProvider(); - - SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | SSPI or Active Directory Authentication Library loaded for SQL Server based integrated authentication"); - } - - // if Strict encryption (i.e. isTlsFirst) is chosen trust server certificate should always be false. - if (isTlsFirst) - { - trustServerCert = false; - } - - byte[] instanceName = null; - - Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point."); - _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin); - _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.InitializeConnection); - - bool fParallel = _connHandler.ConnectionOptions.MultiSubnetFailover; - TransparentNetworkResolutionState transparentNetworkResolutionState; - int totalTimeout; - -#if NETFRAMEWORK - if (_connHandler.ConnectionOptions.TransparentNetworkIPResolution && !disableTnir) - { - if (isFirstTransparentAttempt) - transparentNetworkResolutionState = TransparentNetworkResolutionState.SequentialMode; - else - transparentNetworkResolutionState = TransparentNetworkResolutionState.ParallelMode; - } - else - { - transparentNetworkResolutionState = TransparentNetworkResolutionState.DisabledMode; - } - totalTimeout = _connHandler.ConnectionOptions.ConnectTimeout; -#else - transparentNetworkResolutionState = TransparentNetworkResolutionState.DisabledMode; - totalTimeout = -1; -#endif - - FQDNforDNSCache = serverInfo.ResolvedServerName; - - int commaPos = FQDNforDNSCache.IndexOf(",", StringComparison.Ordinal); - if (commaPos != -1) - { - FQDNforDNSCache = FQDNforDNSCache.Substring(0, commaPos); - } - - _connHandler.pendingSQLDNSObject = null; - - // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server - _physicalStateObj.CreatePhysicalSNIHandle( - serverInfo.ExtendedServerName, - timeout, - out instanceName, - out var resolvedServerSpn, - false, - true, - fParallel, - transparentNetworkResolutionState, - totalTimeout, - _connHandler.ConnectionOptions.IPAddressPreference, - FQDNforDNSCache, - ref _connHandler.pendingSQLDNSObject, - serverInfo.ServerSPN, - integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated, - isTlsFirst, - hostNameInCertificate, - serverCertificateFilename); - - if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - - // Since connect failed, free the unmanaged connection memory. - // HOWEVER - only free this after the netlib error was processed - if you - // don't, the memory for the connection object might not be accurate and thus - // a bad error could be returned (as it was when it was freed to early for me). - _physicalStateObj.Dispose(); - SqlClientEventSource.Log.TryTraceEvent(" Login failure"); - ThrowExceptionAndWarning(_physicalStateObj); - Debug.Fail("SNI returned status != success, but no error thrown?"); - } - - _server = serverInfo.ResolvedServerName; - - if (connHandler.PoolGroupProviderInfo != null) - { - // If we are pooling, check to see if we were processing an - // alias which has changed, which means we need to clean out - // the pool. See Webdata 104293. - // This should not apply to routing, as it is not an alias change, routed connection - // should still use VNN of AlwaysOn cluster as server for pooling purposes. - connHandler.PoolGroupProviderInfo.AliasCheck(serverInfo.PreRoutingServerName == null ? - serverInfo.ResolvedServerName : serverInfo.PreRoutingServerName); - } - _state = TdsParserState.OpenNotLoggedIn; - _physicalStateObj.SniContext = SniContext.Snix_PreLoginBeforeSuccessfulWrite; - _physicalStateObj.TimeoutTime = timeout.LegacyTimerExpire; - - bool marsCapable = false; - - _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.InitializeConnection); - _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake); - - uint result = _physicalStateObj.SniGetConnectionId(ref _connHandler._clientConnectionId); - Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); - - if (_connHandler.pendingSQLDNSObject == null) - { - // for DNS Caching phase 1 - _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); - } - - if (!ClientOSEncryptionSupport) - { - //If encryption is required, an error will be thrown. - if (encrypt != SqlConnectionEncryptOption.Optional) - { - _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - _encryptionOption = EncryptionOptions.NOT_SUP; - } - - // UNDONE - send "" for instance now, need to fix later - SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); - SendPreLoginHandshake(instanceName, encrypt, integratedSecurity, serverCertificateFilename); - - _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake); - _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); - - _physicalStateObj.SniContext = SniContext.Snix_PreLogin; - SqlClientEventSource.Log.TryTraceEvent(" Consuming prelogin handshake"); - PreLoginHandshakeStatus status = ConsumePreLoginHandshake( - encrypt, - trustServerCert, - integratedSecurity, - out marsCapable, - out _connHandler._fedAuthRequired, - isTlsFirst, - serverCertificateFilename); - - if (status == PreLoginHandshakeStatus.InstanceFailure) - { - SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake unsuccessful. Reattempting prelogin handshake"); - _physicalStateObj.Dispose(); // Close previous connection - - // On Instance failure re-connect and flush SNI named instance cache. - _physicalStateObj.SniContext = SniContext.Snix_Connect; - _physicalStateObj.CreatePhysicalSNIHandle( - serverInfo.ExtendedServerName, - timeout, - out instanceName, - out resolvedServerSpn, - true, - true, - fParallel, - transparentNetworkResolutionState, - totalTimeout, - _connHandler.ConnectionOptions.IPAddressPreference, - FQDNforDNSCache, - ref _connHandler.pendingSQLDNSObject, - serverInfo.ServerSPN, - integratedSecurity, - isTlsFirst, - hostNameInCertificate, - serverCertificateFilename); - - if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - SqlClientEventSource.Log.TryTraceEvent(" Login failure"); - ThrowExceptionAndWarning(_physicalStateObj); - } - - uint retCode = _physicalStateObj.SniGetConnectionId(ref _connHandler._clientConnectionId); - - Debug.Assert(retCode == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); - SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); - - if (_connHandler.pendingSQLDNSObject == null) - { - // for DNS Caching phase 1 - _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); - } - - SendPreLoginHandshake(instanceName, encrypt, integratedSecurity, serverCertificateFilename); - status = ConsumePreLoginHandshake( - encrypt, - trustServerCert, - integratedSecurity, - out marsCapable, - out _connHandler._fedAuthRequired, - isTlsFirst, - serverCertificateFilename); - - // Don't need to check for 7.0 failure, since we've already consumed - // one pre-login packet and know we are connecting to 2000. - if (status == PreLoginHandshakeStatus.InstanceFailure) - { - SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake unsuccessful. Login failure"); - throw SQL.InstanceFailure(); - } - } - SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake successful"); - - if (_authenticationProvider is { }) - { - _authenticationProvider.Initialize(serverInfo, _physicalStateObj, this, resolvedServerSpn.Primary, resolvedServerSpn.Secondary); - } - - if (_fMARS && marsCapable) - { - // if user explicitly disables mars or mars not supported, don't create the session pool - _sessionPool = new TdsParserSessionPool(this); - } - else - { - _fMARS = false; - } - return; - } - - internal void RemoveEncryption() - { - Debug.Assert(_encryptionOption == EncryptionOptions.LOGIN, "Invalid encryption option state"); - - uint error = _physicalStateObj.DisableSsl(); - - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - // create a new packet encryption changes the internal packet size Bug# 228403 - _physicalStateObj.ClearAllWritePackets(); - } - - internal void EnableMars() - { - if (_fMARS) - { - // Cache physical stateObj and connection. - _pMarsPhysicalConObj = _physicalStateObj; - -#if NET - if (LocalAppContextSwitches.UseManagedNetworking) - _pMarsPhysicalConObj.IncrementPendingCallbacks(); -#endif - - uint info = 0; - uint error = _pMarsPhysicalConObj.EnableMars(ref info); - - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - error = _pMarsPhysicalConObj.PostReadAsyncForMars(_physicalStateObj); - if (error != TdsEnums.SNI_SUCCESS_IO_PENDING) - { - Debug.Assert(error != TdsEnums.SNI_SUCCESS, "Unexpected successful read async on physical connection before enabling MARS!"); - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - _physicalStateObj = CreateSession(); // Create and open default MARS stateObj and connection. - } - } - - internal TdsParserStateObject CreateSession() - { - TdsParserStateObject session = TdsParserStateObjectFactory.Singleton.CreateSessionObject(this, _pMarsPhysicalConObj, true); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} created session {1}", ObjectID, session.ObjectID); - return session; - } - - internal TdsParserStateObject GetSession(object owner) - { - TdsParserStateObject session = null; - - // TODO: Ideally, we would not care what we do here -- the session pooler would know whether it should have either one (non-MARS) or many (MARS) sessions. - - if (MARSOn) - { - session = _sessionPool.GetSession(owner); - - Debug.Assert(!session.HasPendingData, "pending data on a pooled MARS session"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} getting session {1} from pool", ObjectID, session.ObjectID); - } - else - { - session = _physicalStateObj; - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} getting physical session {1}", ObjectID, session.ObjectID); - } - Debug.Assert(session._outputPacketNumber == 1, "The packet number is expected to be 1"); - return session; - } - - internal void PutSession(TdsParserStateObject session) - { - session.AssertStateIsClean(); - - if (MARSOn) - { - // This will take care of disposing if the parser is closed - _sessionPool.PutSession(session); - } - else if ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken)) - { - // Parser is closed\broken - dispose the stateObj - Debug.Assert(session == _physicalStateObj, "MARS is off, but session to close is not the _physicalStateObj"); - _physicalStateObj.SniContext = SniContext.Snix_Close; -#if DEBUG - _physicalStateObj.InvalidateDebugOnlyCopyOfSniContext(); -#endif - _physicalStateObj.Dispose(); - } - else - { - // Non-MARS, and session is ok - remove its owner - _physicalStateObj.Owner = null; - } - } - - private void SendPreLoginHandshake( - byte[] instanceName, - SqlConnectionEncryptOption encrypt, - bool integratedSecurity, - string serverCertificateFilename) - { - if (encrypt == SqlConnectionEncryptOption.Strict) - { - //Always validate the certificate when in strict encryption mode - uint info = TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE | TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE | TdsEnums.SNI_SSL_SEND_ALPN_EXTENSION; - - EnableSsl(info, encrypt, integratedSecurity, serverCertificateFilename); - - // Since encryption has already been negotiated, we need to set encryption not supported in - // prelogin so that we don't try to negotiate encryption again during ConsumePreLoginHandshake. - _encryptionOption = EncryptionOptions.NOT_SUP; - } - - // PreLoginHandshake buffer consists of: - // 1) Standard header, with type = MT_PRELOGIN - // 2) Consecutive 5 bytes for each option, (1 byte length, 2 byte offset, 2 byte payload length) - // 3) Consecutive data blocks for each option - - // NOTE: packet data needs to be big endian - not the standard little endian used by - // the rest of the parser. - - _physicalStateObj._outputMessageType = TdsEnums.MT_PRELOGIN; - - // Initialize option offset into payload buffer - // 5 bytes for each option (1 byte length, 2 byte offset, 2 byte payload length) - int offset = (int)PreLoginOptions.NUMOPT * 5 + 1; - - byte[] payload = new byte[(int)PreLoginOptions.NUMOPT * 5 + TdsEnums.MAX_PRELOGIN_PAYLOAD_LENGTH]; - int payloadLength = 0; - - // UNDONE - need to do some length verification to ensure packet does not - // get too big!!! Not beyond it's max length! - - for (int option = (int)PreLoginOptions.VERSION; option < (int)PreLoginOptions.NUMOPT; option++) - { - int optionDataSize = 0; - - // Fill in the option - _physicalStateObj.WriteByte((byte)option); - - // Fill in the offset of the option data - _physicalStateObj.WriteByte((byte)((offset & 0xff00) >> 8)); // send upper order byte - _physicalStateObj.WriteByte((byte)(offset & 0x00ff)); // send lower order byte - - switch (option) - { - case (int)PreLoginOptions.VERSION: - Version systemDataVersion = ADP.GetAssemblyVersion(); - - // Major and minor - payload[payloadLength++] = (byte)(systemDataVersion.Major & 0xff); - payload[payloadLength++] = (byte)(systemDataVersion.Minor & 0xff); - - // Build (Big Endian) - payload[payloadLength++] = (byte)((systemDataVersion.Build & 0xff00) >> 8); - payload[payloadLength++] = (byte)(systemDataVersion.Build & 0xff); - - // Sub-build (Little Endian) - payload[payloadLength++] = (byte)(systemDataVersion.Revision & 0xff); - payload[payloadLength++] = (byte)((systemDataVersion.Revision & 0xff00) >> 8); - offset += 6; - optionDataSize = 6; - break; - - case (int)PreLoginOptions.ENCRYPT: - if (_encryptionOption == EncryptionOptions.NOT_SUP) - { - //If OS doesn't support encryption and encryption is not required, inform server "not supported" by client. - payload[payloadLength] = (byte)EncryptionOptions.NOT_SUP; - } - else - { - // Else, inform server of user request. - if (encrypt == SqlConnectionEncryptOption.Mandatory) - { - payload[payloadLength] = (byte)EncryptionOptions.ON; - _encryptionOption = EncryptionOptions.ON; - } - else - { - payload[payloadLength] = (byte)EncryptionOptions.OFF; - _encryptionOption = EncryptionOptions.OFF; - } - } - - payloadLength += 1; - offset += 1; - optionDataSize = 1; - break; - - case (int)PreLoginOptions.INSTANCE: - int i = 0; - - while (instanceName[i] != 0) - { - payload[payloadLength] = instanceName[i]; - payloadLength++; - i++; - } - - payload[payloadLength] = 0; // null terminate - payloadLength++; - i++; - - offset += i; - optionDataSize = i; - break; - - case (int)PreLoginOptions.THREADID: - int threadID = TdsParserStaticMethods.GetCurrentThreadIdForTdsLoginOnly(); - - payload[payloadLength++] = (byte)((0xff000000 & threadID) >> 24); - payload[payloadLength++] = (byte)((0x00ff0000 & threadID) >> 16); - payload[payloadLength++] = (byte)((0x0000ff00 & threadID) >> 8); - payload[payloadLength++] = (byte)(0x000000ff & threadID); - offset += 4; - optionDataSize = 4; - break; - - case (int)PreLoginOptions.MARS: - payload[payloadLength++] = (byte)(_fMARS ? 1 : 0); - offset += 1; - optionDataSize += 1; - break; - - case (int)PreLoginOptions.TRACEID: - SerializeGuid(_connHandler._clientConnectionId, payload.AsSpan(payloadLength, GUID_SIZE)); - payloadLength += GUID_SIZE; - offset += GUID_SIZE; - optionDataSize = GUID_SIZE; - - ActivityCorrelator.ActivityId actId = ActivityCorrelator.Next(); - SerializeGuid(actId.Id, payload.AsSpan(payloadLength, GUID_SIZE)); - payloadLength += GUID_SIZE; - payload[payloadLength++] = (byte)(0x000000ff & actId.Sequence); - payload[payloadLength++] = (byte)((0x0000ff00 & actId.Sequence) >> 8); - payload[payloadLength++] = (byte)((0x00ff0000 & actId.Sequence) >> 16); - payload[payloadLength++] = (byte)((0xff000000 & actId.Sequence) >> 24); - int actIdSize = GUID_SIZE + sizeof(uint); - offset += actIdSize; - optionDataSize += actIdSize; - - SqlClientEventSource.Log.TryTraceEvent(" ClientConnectionID {0}, ActivityID {1}", _connHandler?._clientConnectionId, actId); - break; - - case (int)PreLoginOptions.FEDAUTHREQUIRED: - payload[payloadLength++] = 0x01; - offset += 1; - optionDataSize += 1; - break; - - default: - Debug.Fail("UNKNOWN option in SendPreLoginHandshake"); - break; - } - - // Write data length - _physicalStateObj.WriteByte((byte)((optionDataSize & 0xff00) >> 8)); - _physicalStateObj.WriteByte((byte)(optionDataSize & 0x00ff)); - } - - // Write out last option - to let server know the second part of packet completed - _physicalStateObj.WriteByte((byte)PreLoginOptions.LASTOPT); - - // Write out payload - _physicalStateObj.WriteByteArray(payload, payloadLength, 0); - - // Flush packet - _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); - } - - private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integratedSecurity, string serverCertificateFilename) - { - uint error = 0; - - if (encrypt && !integratedSecurity) - { - // optimization: in case of SQL Authentication and encryption in TDS, set SNI_SSL_IGNORE_CHANNEL_BINDINGS - // to let SNI know that it does not need to allocate/retrieve the Channel Bindings from the SSL context. - // This applies to Native SNI - info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS; - } - -#if NETFRAMEWORK - Debug.Assert((_encryptionOption & EncryptionOptions.CLIENT_CERT) == 0, "Client certificate authentication support has been removed"); -#endif - error = _physicalStateObj.EnableSsl(ref info, encrypt == SqlConnectionEncryptOption.Strict, serverCertificateFilename); - - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - SslProtocols protocol = 0; - - // in the case where an async connection is made, encryption is used and Windows Authentication is used, - // wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its - // Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete - // before calling SNISecGenClientContext). -#if NET - if (OperatingSystem.IsWindows()) -#endif - { - error = _physicalStateObj.WaitForSSLHandShakeToComplete(out protocol); - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - } - - string warningMessage = protocol.GetProtocolWarning(); - if (!string.IsNullOrEmpty(warningMessage)) - { - if (!encrypt && LocalAppContextSwitches.SuppressInsecureTlsWarning) - { - // Skip console warning - SqlClientEventSource.Log.TryTraceEvent("{3}", nameof(TdsParser), nameof(EnableSsl), SqlClientLogger.LogLevel.Warning, warningMessage); - } - else - { - // This logs console warning of insecure protocol in use. - _logger.LogWarning(nameof(TdsParser), nameof(EnableSsl), warningMessage); - } - } - - // create a new packet encryption changes the internal packet size Bug# 228403 - _physicalStateObj.ClearAllWritePackets(); - } - - private PreLoginHandshakeStatus ConsumePreLoginHandshake( - SqlConnectionEncryptOption encrypt, - bool trustServerCert, - bool integratedSecurity, - out bool marsCapable, - out bool fedAuthRequired, - bool tlsFirst, - string serverCertificateFilename) - { - // Assign default values - marsCapable = _fMARS; - fedAuthRequired = false; - Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = _physicalStateObj.TryReadNetworkPacket(); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - if (_physicalStateObj._inBytesRead == 0) - { - // If the server did not respond then something has gone wrong and we need to close the connection - _physicalStateObj.AddError(new SqlError(0, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.PreloginError(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - - if (_physicalStateObj.TryProcessHeader() != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - if (_physicalStateObj._inBytesPacket > TdsEnums.MAX_PACKET_SIZE || _physicalStateObj._inBytesPacket <= 0) - { - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); - } - byte[] payload = new byte[_physicalStateObj._inBytesPacket]; - - Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - result = _physicalStateObj.TryReadByteArray(payload, payload.Length); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - if (payload[0] == 0xaa) - { - // If the first byte is 0xAA, we are connecting to a 6.5 or earlier server, which - // is not supported. SQL BU DT 296425 - throw SQL.InvalidSQLServerVersionUnknown(); - } - - int offset = 0; - int payloadOffset = 0; - int payloadLength = 0; - int option = payload[offset++]; - bool serverSupportsEncryption = false; - - while (option != (byte)PreLoginOptions.LASTOPT) - { - switch (option) - { - case (int)PreLoginOptions.VERSION: - payloadOffset = payload[offset++] << 8 | payload[offset++]; - payloadLength = payload[offset++] << 8 | payload[offset++]; - - byte majorVersion = payload[payloadOffset]; - byte minorVersion = payload[payloadOffset + 1]; - int level = (payload[payloadOffset + 2] << 8) | - payload[payloadOffset + 3]; - break; - - case (int)PreLoginOptions.ENCRYPT: - if (tlsFirst) - { - // Can skip/ignore this option if we are doing TDS 8. - offset += 4; - break; - } - - payloadOffset = payload[offset++] << 8 | payload[offset++]; - payloadLength = payload[offset++] << 8 | payload[offset++]; - - EncryptionOptions serverOption = ((EncryptionOptions)payload[payloadOffset]) & EncryptionOptions.OPTIONS_MASK; - - /* internal enum EncryptionOptions { - OFF, - ON, - NOT_SUP, - REQ, - LOGIN, - OPTIONS_MASK = 0x3f - } */ - - // Any response other than NOT_SUP means the server supports encryption. - serverSupportsEncryption = serverOption != EncryptionOptions.NOT_SUP; - - switch (_encryptionOption) - { - case (EncryptionOptions.OFF): - if (serverOption == EncryptionOptions.OFF) - { - // Only encrypt login. - _encryptionOption = EncryptionOptions.LOGIN; - } - else if (serverOption == EncryptionOptions.REQ) - { - // Encrypt all. - _encryptionOption = EncryptionOptions.ON; - } - // NOT_SUP: No encryption. - break; - - case (EncryptionOptions.NOT_SUP): - if (serverOption == EncryptionOptions.REQ) - { - // Server requires encryption, but client does not support it. - _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - - break; - default: - // Any other client option needs encryption - if (serverOption == EncryptionOptions.NOT_SUP) - { - _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - break; - } - - break; - - case (int)PreLoginOptions.INSTANCE: - payloadOffset = payload[offset++] << 8 | payload[offset++]; - payloadLength = payload[offset++] << 8 | payload[offset++]; - - byte ERROR_INST = 0x1; - byte instanceResult = payload[payloadOffset]; - - if (instanceResult == ERROR_INST) - { - // Check if server says ERROR_INST. That either means the cached info - // we used to connect is not valid or we connected to a named instance - // listening on default params. - return PreLoginHandshakeStatus.InstanceFailure; - } - - break; - - case (int)PreLoginOptions.THREADID: - // DO NOTHING FOR THREADID - offset += 4; - break; - - case (int)PreLoginOptions.MARS: - payloadOffset = payload[offset++] << 8 | payload[offset++]; - payloadLength = payload[offset++] << 8 | payload[offset++]; - - marsCapable = (payload[payloadOffset] == 0 ? false : true); - - Debug.Assert(payload[payloadOffset] == 0 || payload[payloadOffset] == 1, "Value for Mars PreLoginHandshake option not equal to 1 or 0!"); - break; - - case (int)PreLoginOptions.TRACEID: - // DO NOTHING FOR TRACEID - offset += 4; - break; - - case (int)PreLoginOptions.FEDAUTHREQUIRED: - payloadOffset = payload[offset++] << 8 | payload[offset++]; - payloadLength = payload[offset++] << 8 | payload[offset++]; - - // Only 0x00 and 0x01 are accepted values from the server. - if (payload[payloadOffset] != 0x00 && payload[payloadOffset] != 0x01) - { - SqlClientEventSource.Log.TryTraceEvent(" {0}, " + - "Server sent an unexpected value for FedAuthRequired PreLogin Option. Value was {1}.", ObjectID, (int)payload[payloadOffset]); - throw SQL.ParsingErrorValue(ParsingErrorState.FedAuthRequiredPreLoginResponseInvalidValue, (int)payload[payloadOffset]); - } - - // We must NOT use the response for the FEDAUTHREQUIRED PreLogin option, if the connection string option - // was not using the new Authentication keyword or in other words, if Authentication=NotSpecified - // Or AccessToken is not null, mean token based authentication is used. - if ((_connHandler.ConnectionOptions != null - && _connHandler.ConnectionOptions.Authentication != SqlAuthenticationMethod.NotSpecified) - || _connHandler._accessTokenInBytes != null || _connHandler._accessTokenCallback != null) - { - fedAuthRequired = payload[payloadOffset] == 0x01 ? true : false; - } - break; - - default: - Debug.Fail("UNKNOWN option in ConsumePreLoginHandshake, option:" + option); - - // DO NOTHING FOR THESE UNKNOWN OPTIONS - offset += 4; - - break; - } - - if (offset < payload.Length) - { - option = payload[offset++]; - } - else - { - break; - } - } - - if (_encryptionOption == EncryptionOptions.ON || - _encryptionOption == EncryptionOptions.LOGIN) - { - if (!serverSupportsEncryption) - { - _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - - // Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server. - bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || - ((_connHandler._accessTokenInBytes != null || _connHandler._accessTokenCallback != null) && !trustServerCert); - - uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) - | TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE; - - EnableSsl(info, encrypt, integratedSecurity, serverCertificateFilename); - } - - return PreLoginHandshakeStatus.Successful; - } - - internal void Deactivate(bool connectionIsDoomed) - { - // Called when the connection that owns us is deactivated. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} deactivating", ObjectID); - if (SqlClientEventSource.Log.IsStateDumpEnabled()) - { - SqlClientEventSource.Log.StateDumpEvent(" {0} {1}", ObjectID, TraceString()); - } - - if (MARSOn) - { - _sessionPool.Deactivate(); - } - - Debug.Assert(connectionIsDoomed || _pendingTransaction == null, "pending transaction at disconnect?"); - - if (!connectionIsDoomed && _physicalStateObj != null) - { - if (_physicalStateObj.HasPendingData) - { - DrainData(_physicalStateObj); - } - - if (_physicalStateObj.HasOpenResult) - { // SQL BU DT 383773 - need to decrement openResultCount for all pending operations. - _physicalStateObj.DecrementOpenResultCount(); - } - } - - // Any active, non-distributed transaction must be rolled back. We - // need to wait for distributed transactions to be completed by the - // transaction manager -- we don't want to automatically roll them - // back. - // - // Note that when there is a transaction delegated to this connection, - // we will defer the deactivation of this connection until the - // transaction manager completes the transaction. - SqlInternalTransaction currentTransaction = CurrentTransaction; - - if (currentTransaction != null && currentTransaction.HasParentTransaction) - { - currentTransaction.CloseFromConnection(); - Debug.Assert(CurrentTransaction == null, "rollback didn't clear current transaction?"); - } - - Statistics = null; // must come after CleanWire or we won't count the stuff that happens there... - } - - // Used to close the connection and then free the memory allocated for the netlib connection. - internal void Disconnect() - { - if (_sessionPool != null) - { - // MARSOn may be true, but _sessionPool not yet created - _sessionPool.Dispose(); - } - - // Can close the connection if its open or broken - if (_state != TdsParserState.Closed) - { - //benign assert - the user could close the connection before consuming all the data - //Debug.Assert(_physicalStateObj._inBytesUsed == _physicalStateObj._inBytesRead && _physicalStateObj._outBytesUsed == _physicalStateObj._inputHeaderLen, "TDSParser closed with data not fully sent or consumed."); - - _state = TdsParserState.Closed; - - try - { - // If the _physicalStateObj has an owner, we will delay the disposal until the owner is finished with it - if (!_physicalStateObj.HasOwner) - { - _physicalStateObj.SniContext = SniContext.Snix_Close; -#if DEBUG - _physicalStateObj.InvalidateDebugOnlyCopyOfSniContext(); -#endif - _physicalStateObj.Dispose(); - } - else - { - // Remove the "initial" callback (this will allow the stateObj to be GC collected if need be) - _physicalStateObj.DecrementPendingCallbacks(false); - } - - // Not allocated until MARS is actually enabled in SNI. - if (_pMarsPhysicalConObj != null) - { - _pMarsPhysicalConObj.Dispose(); - } - } - finally - { - _pMarsPhysicalConObj = null; - } - } - - _resetConnectionEvent?.Dispose(); - _resetConnectionEvent = null; - - _defaultEncoding = null; - _defaultCollation = null; - } - - // Fires a single InfoMessageEvent - private void FireInfoMessageEvent(SqlConnection connection, SqlCommand command, TdsParserStateObject stateObj, SqlError error) - { - string serverVersion = null; - - Debug.Assert(connection != null && _connHandler.Connection == connection); - - if (_state == TdsParserState.OpenLoggedIn) - { - serverVersion = _connHandler.ServerVersion; - } - - SqlErrorCollection sqlErs = new SqlErrorCollection(); - - sqlErs.Add(error); - - SqlException exc = SqlException.CreateException(sqlErs, serverVersion, _connHandler, innerException: null, batchCommand: command?.GetCurrentBatchCommand()); - - bool notified; - connection.OnInfoMessage(new SqlInfoMessageEventArgs(exc), out notified); - if (notified) - { - // observable side-effects, no retry - stateObj._syncOverAsync = true; - } - return; - } - - internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) - { - Debug.Assert(_currentTransaction != null && _currentTransaction == internalTransaction, "disconnecting different transaction"); - - if (_currentTransaction != null && _currentTransaction == internalTransaction) - { - _currentTransaction = null; - } - } - - internal void RollbackOrphanedAPITransactions() - { - // Any active, non-distributed transaction must be rolled back. - SqlInternalTransaction currentTransaction = CurrentTransaction; - - if (currentTransaction != null && currentTransaction.HasParentTransaction && currentTransaction.IsOrphaned) - { - currentTransaction.CloseFromConnection(); - Debug.Assert(CurrentTransaction == null, "rollback didn't clear current transaction?"); - } - } - - internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command = null, bool callerHasConnectionLock = false, bool asyncClose = false) - { - Debug.Assert(!callerHasConnectionLock || _connHandler._parserLock.ThreadMayHaveLock(), "Caller claims to have lock, but connection lock is not taken"); - - SqlException exception = null; - bool breakConnection; - - // This function should only be called when there was an error or warning. If there aren't any - // errors, the handler will be called for the warning(s). If there was an error, the warning(s) will - // be copied to the end of the error collection so that the user may see all the errors and also the - // warnings that occurred. - // can be deleted) - //_errorAndWarningsLock lock is implemented inside GetFullErrorAndWarningCollection - SqlErrorCollection temp = stateObj.GetFullErrorAndWarningCollection(out breakConnection); - - Debug.Assert(temp != null, "TdsParser::ThrowExceptionAndWarning: null errors collection!"); - Debug.Assert(temp.Count > 0, "TdsParser::ThrowExceptionAndWarning called with no exceptions or warnings!"); - if (temp.Count == 0) - { - SqlClientEventSource.Log.TryTraceEvent(" Potential multi-threaded misuse of connection, unexpectedly empty warnings/errors under lock {0}", ObjectID); - } - Debug.Assert(_connHandler != null, "TdsParser::ThrowExceptionAndWarning called with null connectionHandler!"); - - // Don't break the connection if it is already closed - breakConnection &= (TdsParserState.Closed != _state); - if (breakConnection) - { -#if NETFRAMEWORK - if ((_state == TdsParserState.OpenNotLoggedIn) && (_connHandler.ConnectionOptions.TransparentNetworkIPResolution || _connHandler.ConnectionOptions.MultiSubnetFailover || _loginWithFailover) && (temp.Count == 1) && ((temp[0].Number == TdsEnums.TIMEOUT_EXPIRED) || (temp[0].Number == TdsEnums.SNI_WAIT_TIMEOUT))) -#else - if ((_state == TdsParserState.OpenNotLoggedIn) && (_connHandler.ConnectionOptions.MultiSubnetFailover || _loginWithFailover) && (temp.Count == 1) && ((temp[0].Number == TdsEnums.TIMEOUT_EXPIRED) || (temp[0].Number == TdsEnums.SNI_WAIT_TIMEOUT))) -#endif - { - // DevDiv2 Bug 459546: With "MultiSubnetFailover=yes" in the Connection String, SQLClient incorrectly throws a Timeout using shorter time slice (3-4 seconds), not honoring the actual 'Connect Timeout' - // http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/459546 - // For Multisubnet Failover we slice the timeout to make reconnecting faster (with the assumption that the server will not failover instantaneously) - // However, when timeout occurs we need to not doom the internal connection and also to mark the TdsParser as closed such that the login will be will retried - breakConnection = false; - Disconnect(); - } - else - { - _state = TdsParserState.Broken; - } - } - - if (temp != null && temp.Count > 0) - { - // Construct the exception now that we've collected all the errors - string serverVersion = null; - if (_state == TdsParserState.OpenLoggedIn) - { - serverVersion = _connHandler.ServerVersion; - } - - if (temp.Count == 1 && temp[0].Exception != null) - { - exception = SqlException.CreateException(temp, serverVersion, _connHandler, temp[0].Exception, command?.GetBatchCommand(temp[0].BatchIndex)); - } - else - { - SqlBatchCommand batchCommand = null; - if (temp[0]?.BatchIndex is var index and >= 0 && command is not null) - { - batchCommand = command.GetBatchCommand(index.Value); - } - exception = SqlException.CreateException(temp, serverVersion, _connHandler, innerException: null, batchCommand: batchCommand); - } - } - - if (exception != null) - { - if (breakConnection) - { - // report exception to pending async operation - // before OnConnectionClosed overrides the exception - // due to connection close notification through references - TaskCompletionSource taskSource = stateObj._networkPacketTaskSource; - if (taskSource != null) - { - taskSource.TrySetException(ADP.ExceptionWithStackTrace(exception)); - } - } - - if (asyncClose) - { - // Wait until we have the parser lock, then try to close - SqlInternalConnectionTds connHandler = _connHandler; - Action wrapCloseAction = closeAction => - { - Task.Factory.StartNew(() => - { - connHandler._parserLock.Wait(canReleaseFromAnyThread: false); - connHandler.ThreadHasParserLockForClose = true; - try - { - closeAction(); - } - finally - { - connHandler.ThreadHasParserLockForClose = false; - connHandler._parserLock.Release(); - } - }); - }; - - _connHandler.OnError(exception, breakConnection, wrapCloseAction); - } - else - { - // Let close know that we already have the _parserLock - bool threadAlreadyHadParserLockForClose = _connHandler.ThreadHasParserLockForClose; - if (callerHasConnectionLock) - { - _connHandler.ThreadHasParserLockForClose = true; - } - try - { - // the following handler will throw an exception or generate a warning event - _connHandler.OnError(exception, breakConnection); - } - finally - { - if (callerHasConnectionLock) - { - _connHandler.ThreadHasParserLockForClose = threadAlreadyHadParserLockForClose; - } - } - } - } - } - - internal SqlError ProcessSNIError(TdsParserStateObject stateObj) - { - using TryEventScope _ = TryEventScope.Create(nameof(TdsParser)); -#if DEBUG - // There is an exception here for MARS as its possible that another thread has closed the connection just as we see an error - Debug.Assert(SniContext.Undefined != stateObj.DebugOnlyCopyOfSniContext || ((_fMARS) && ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken))), "SniContext must not be None"); -#endif - TdsParserStateObject.SniErrorDetails details = stateObj.GetErrorDetails(); - - if (details.SniErrorNumber != 0) - { - // handle special SNI error codes that are converted into exception which is not a SqlException. - switch (details.SniErrorNumber) - { - case SniErrors.MultiSubnetFailoverWithMoreThan64IPs: - // Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported."); - throw SQL.MultiSubnetFailoverWithMoreThan64IPs(); - - case SniErrors.MultiSubnetFailoverWithInstanceSpecified: - // Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported."); - throw SQL.MultiSubnetFailoverWithInstanceSpecified(); - - case SniErrors.MultiSubnetFailoverWithNonTcpProtocol: - // Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol."); - throw SQL.MultiSubnetFailoverWithNonTcpProtocol(); - } - } - // continue building SqlError instance - - // details.ErrorMessage is null terminated with garbage beyond that, since fixed length - // PInvoke code automatically sets the length of the string for us, so no need to look for \0 - string errorMessage = details.ErrorMessage ?? string.Empty; - - /* Format SNI errors and add Context Information - * - * General syntax is: - * - * (provider:, error: - ) - * - * errorMessage | sniError | - * ------------------------------------------- - * ==null | x | must never happen - * !=null | != 0 | retrieve corresponding errorMessage from resources - * !=null | == 0 | replace text left of errorMessage - */ - -#if NET - if (LocalAppContextSwitches.UseManagedNetworking) - { - Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage) || details.SniErrorNumber != 0, "Empty error message received from SNI"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.ErrorMessage, details.SniErrorNumber); - } - else -#endif - { - Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage), "Empty error message received from SNI"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Error message received from SNI. Error Message = {0}", details.ErrorMessage); - } - - string sqlContextInfo = StringsHelper.GetResourceString(stateObj.SniContext.ToString()); - string providerRid = $"SNI_PN{details.Provider}"; - string providerName = StringsHelper.GetResourceString(providerRid); - Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); - int win32ErrorCode = details.NativeError; - - SqlClientEventSource.Log.TryAdvancedTraceEvent(" SNI Native Error Code = {0}", win32ErrorCode); - if (details.SniErrorNumber == 0) - { - // Provider error. The message from provider is preceded with non-localizable info from SNI - // strip provider info from SNI - // - int iColon = errorMessage.IndexOf(':'); - Debug.Assert(0 <= iColon, "':' character missing in sni errorMessage"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" ':' character missing in sni errorMessage. Error Message index of ':' = {0}", iColon); - Debug.Assert(errorMessage.Length > iColon + 1 && errorMessage[iColon + 1] == ' ', "Expecting a space after the ':' character"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Expecting a space after the ':' character. Error Message Length = {0}", errorMessage.Length); - - // extract the message excluding the colon and trailing cr/lf chars - if (0 <= iColon) - { - int len = errorMessage.Length; - len -= Environment.NewLine.Length; // exclude newline sequence - iColon += 2; // skip over ": " sequence - len -= iColon; - /* - The error message should come back in the following format: "TCP Provider: MESSAGE TEXT" - Fix Bug 370686, if the message is received on a Win9x OS, the error message will not contain MESSAGE TEXT - per Bug: 269574. If we get a errormessage with no message text, just return the entire message otherwise - return just the message text. - */ - if (len > 0) - { - errorMessage = errorMessage.Substring(iColon, len); - } - } - } - else - { -#if NET - if (LocalAppContextSwitches.UseManagedNetworking) - { - // SNI error. Append additional error message info if available and hasn't been included. - string sniLookupMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); - errorMessage = (string.IsNullOrEmpty(errorMessage) || errorMessage.Contains(sniLookupMessage)) - ? sniLookupMessage - : (sniLookupMessage + ": " + errorMessage); - } - else -#endif - { - // SNI error. Replace the entire message. - errorMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); - - // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code - if (details.SniErrorNumber == SniErrors.LocalDBErrorCode) - { - errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError); - win32ErrorCode = 0; - } - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage); - } - } - errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})", - sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage); - - SqlClientEventSource.Log.TryAdvancedTraceErrorEvent(" SNI Error Message. Native Error = {0}, Line Number ={1}, Function ={2}, Exception ={3}, Server = {4}", - details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server); - - return new SqlError(infoNumber: details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, -#if NET - errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: details.NativeError, details.Exception); -#else - errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: win32ErrorCode); -#endif - } - - internal void CheckResetConnection(TdsParserStateObject stateObj) - { - if (_fResetConnection && !stateObj._fResetConnectionSent) - { - Debug.Assert(stateObj._outputPacketNumber == 1 || stateObj._outputPacketNumber == 2, "In ResetConnection logic unexpectedly!"); - try - { - if (_fMARS && !stateObj._fResetEventOwned) - { - // If using Async & MARS and we do not own ResetEvent - grab it. We need to not grab lock here - // for case where multiple packets are sent to server from one execute. - stateObj._fResetEventOwned = _resetConnectionEvent.WaitOne(stateObj.GetTimeoutRemaining(), false); - - if (stateObj._fResetEventOwned) - { - if (stateObj.TimeoutHasExpired) - { - // We didn't timeout on the WaitOne, but we timed out by the time we decremented stateObj._timeRemaining. - stateObj._fResetEventOwned = !_resetConnectionEvent.Set(); - stateObj.TimeoutTime = 0; - } - } - - if (!stateObj._fResetEventOwned) - { - // We timed out waiting for ResetEvent. Throw timeout exception and reset - // the buffer. Nothing else to do since we did not actually send anything - // to the server. - stateObj.ResetBuffer(); - Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point."); - stateObj.AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, (byte)0x00, TdsEnums.MIN_ERROR_CLASS, _server, _connHandler.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); - Debug.Assert(_connHandler._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock"); - ThrowExceptionAndWarning(stateObj, callerHasConnectionLock: true); - } - } - - if (_fResetConnection) - { - // Check again to see if we need to send reset. - - Debug.Assert(!stateObj._fResetConnectionSent, "Unexpected state for sending reset connection"); - - if (_fPreserveTransaction) - { - // if we are reseting, set bit in header by or'ing with other value - stateObj._outBuff[1] = (byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION_PRESERVE_TRANSACTION); - } - else - { - // if we are reseting, set bit in header by or'ing with other value - stateObj._outBuff[1] = (byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION); - } - - if (!_fMARS) - { - _fResetConnection = false; // If not MARS, can turn off flag now. - _fPreserveTransaction = false; - } - else - { - stateObj._fResetConnectionSent = true; // Otherwise set flag so we don't resend on multiple packet execute. - } - } - else if (_fMARS && stateObj._fResetEventOwned) - { - Debug.Assert(!stateObj._fResetConnectionSent, "Unexpected state on WritePacket ResetConnection"); - - // Otherwise if 2005 and we grabbed the event, free it. Another execute grabbed the event and - // took care of sending the reset. - stateObj._fResetEventOwned = !_resetConnectionEvent.Set(); - Debug.Assert(!stateObj._fResetEventOwned, "Invalid AutoResetEvent state!"); - } - } - catch (Exception) - { - if (_fMARS && stateObj._fResetEventOwned) - { - // If exception thrown, and we are on 2005 and own the event, release it! - stateObj._fResetConnectionSent = false; - stateObj._fResetEventOwned = !_resetConnectionEvent.Set(); - Debug.Assert(!stateObj._fResetEventOwned, "Invalid AutoResetEvent state!"); - } - - throw; - } - } -#if DEBUG - else - { - Debug.Assert(!_fResetConnection || - (_fResetConnection && stateObj._fResetConnectionSent && stateObj._fResetEventOwned), - "Unexpected state on else ResetConnection block in WritePacket"); - } -#endif - } - - /// - /// Serializes a 16 bit short to the returned buffer. - /// - /// The value to serialize. - /// containing the cached buffer bytes. - /// The serialized 16 bit short. - internal byte[] SerializeShort(int v, TdsParserStateObject stateObj) - { - if (stateObj._bShortBytes == null) - { - stateObj._bShortBytes = new byte[2]; - } - else - { - Debug.Assert(2 == stateObj._bShortBytes.Length); - } - - byte[] bytes = stateObj._bShortBytes; - int current = 0; - bytes[current++] = (byte)(v & 0xff); - bytes[current++] = (byte)((v >> 8) & 0xff); - return bytes; - } - - /// - /// Writes a 16 bit short to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteShort(int v, TdsParserStateObject stateObj) - { - if ((stateObj._outBytesUsed + 2) > stateObj._outBuff.Length) - { - // if all of the short doesn't fit into the buffer - stateObj.WriteByte((byte)(v & 0xff)); - stateObj.WriteByte((byte)((v >> 8) & 0xff)); - } - else - { - // all of the short fits into the buffer - stateObj._outBuff[stateObj._outBytesUsed] = (byte)(v & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 1] = (byte)((v >> 8) & 0xff); - stateObj._outBytesUsed += 2; - } - } - - /// - /// Writes a 16 bit unsigned short to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteUnsignedShort(ushort us, TdsParserStateObject stateObj) - { - WriteShort((short)us, stateObj); - } - - /// - /// Serializes a Guid to the specified buffer. - /// - /// The value to serialize. - /// The buffer to serialize to. The size of this buffer must be 16 bytes or larger. - private static void SerializeGuid(in Guid v, Span buffer) - { - Debug.Assert(buffer.Length >= GUID_SIZE); -#if NET - v.TryWriteBytes(buffer, bigEndian: false, out _); -#else - byte[] guidBytes = v.ToByteArray(); - guidBytes.AsSpan().CopyTo(buffer); -#endif - } - - /// - /// Writes a SqlGuid to the wire. - /// - /// The value to write. - /// containing the wire buffer. - private static void WriteGuid(in SqlGuid v, TdsParserStateObject stateObj) - { - Guid innerValue = v.IsNull ? Guid.Empty : v.Value; - - WriteGuid(in innerValue, stateObj); - } - - /// - /// Writes a Guid to the wire. - /// - /// The value to write. - /// containing the wire buffer. - private static void WriteGuid(in Guid v, TdsParserStateObject stateObj) - { - if ((stateObj._outBytesUsed + GUID_SIZE) > stateObj._outBuff.Length) - { - Span buffer = stackalloc byte[GUID_SIZE]; - - SerializeGuid(in v, buffer); - // if all of the guid doesn't fit into the buffer - for (int index = 0; index < buffer.Length; index++) - { - stateObj.WriteByte(buffer[index]); - } - } - else - { - // all of the guid fits into the buffer - SerializeGuid(in v, stateObj._outBuff.AsSpan(stateObj._outBytesUsed, GUID_SIZE)); - stateObj._outBytesUsed += GUID_SIZE; - } - } - - /// - /// Serializes an unsigned 32 bit integer to the returned buffer. - /// - /// The value to serialize. - /// containing the cached buffer bytes. - /// The serialized unsigned 32 bit integer. - internal byte[] SerializeUnsignedInt(uint i, TdsParserStateObject stateObj) - { - return SerializeInt((int)i, stateObj); - } - - /// - /// Writes an unsigned 32 bit integer to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteUnsignedInt(uint i, TdsParserStateObject stateObj) - { - WriteInt((int)i, stateObj); - } - - /// - /// Serializes a signed 32 bit integer to the returned buffer. - /// - /// The value to serialize. - /// containing the cached buffer bytes. - /// The serialized signed 32 bit integer. - internal byte[] SerializeInt(int v, TdsParserStateObject stateObj) - { - if (stateObj._bIntBytes == null) - { - stateObj._bIntBytes = new byte[sizeof(int)]; - } - else - { - Debug.Assert(sizeof(int) == stateObj._bIntBytes.Length); - } - - BinaryPrimitives.WriteInt32LittleEndian(stateObj._bIntBytes, v); - return stateObj._bIntBytes; - } - - /// - /// Writes a signed 32 bit integer to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteInt(int v, TdsParserStateObject stateObj) - { - if ((stateObj._outBytesUsed + 4) > stateObj._outBuff.Length) - { - Span buffer = stackalloc byte[sizeof(int)]; - - BinaryPrimitives.WriteInt32LittleEndian(buffer, v); - // if all of the int doesn't fit into the buffer - for (int index = 0; index < sizeof(int); index++) - { - stateObj.WriteByte(buffer[index]); - } - } - else - { - // all of the int fits into the buffer - BinaryPrimitives.WriteInt32LittleEndian(stateObj._outBuff.AsSpan(stateObj._outBytesUsed, sizeof(int)), v); - stateObj._outBytesUsed += 4; - } - } - - /// - /// Serializes a float to the returned buffer. - /// - /// The value to serialize. - /// The serialized float. - internal byte[] SerializeFloat(float v) - { - if (Single.IsInfinity(v) || Single.IsNaN(v)) - { - throw ADP.ParameterValueOutOfRange(v.ToString()); - } - - var bytes = new byte[4]; - BinaryPrimitives.WriteInt32LittleEndian(bytes, BitConverterCompatible.SingleToInt32Bits(v)); - return bytes; - } - - /// - /// Writes a float to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteFloat(float v, TdsParserStateObject stateObj) - { - Span bytes = stackalloc byte[sizeof(float)]; - BinaryPrimitives.WriteInt32LittleEndian(bytes, BitConverterCompatible.SingleToInt32Bits(v)); - stateObj.WriteByteSpan(bytes); - } - - /// - /// Serializes a signed 64 bit long to the returned buffer. - /// - /// The value to serialize. - /// containing the cached buffer bytes. - /// The serialized signed 64 bit long. - internal byte[] SerializeLong(long v, TdsParserStateObject stateObj) - { - int current = 0; - if (stateObj._bLongBytes == null) - { - stateObj._bLongBytes = new byte[8]; - } - - byte[] bytes = stateObj._bLongBytes; - Debug.Assert(8 == bytes.Length, "Cached buffer has wrong size"); - - bytes[current++] = (byte)(v & 0xff); - bytes[current++] = (byte)((v >> 8) & 0xff); - bytes[current++] = (byte)((v >> 16) & 0xff); - bytes[current++] = (byte)((v >> 24) & 0xff); - bytes[current++] = (byte)((v >> 32) & 0xff); - bytes[current++] = (byte)((v >> 40) & 0xff); - bytes[current++] = (byte)((v >> 48) & 0xff); - bytes[current++] = (byte)((v >> 56) & 0xff); - - return bytes; - } - - /// - /// Writes a signed 64 bit long to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteLong(long v, TdsParserStateObject stateObj) - { - if ((stateObj._outBytesUsed + 8) > stateObj._outBuff.Length) - { - // if all of the long doesn't fit into the buffer - for (int shiftValue = 0; shiftValue < sizeof(long) * 8; shiftValue += 8) - { - stateObj.WriteByte((byte)((v >> shiftValue) & 0xff)); - } - } - else - { - // all of the long fits into the buffer - // NOTE: We don't use a loop here for performance - stateObj._outBuff[stateObj._outBytesUsed] = (byte)(v & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 1] = (byte)((v >> 8) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 2] = (byte)((v >> 16) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 3] = (byte)((v >> 24) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 4] = (byte)((v >> 32) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 5] = (byte)((v >> 40) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 6] = (byte)((v >> 48) & 0xff); - stateObj._outBuff[stateObj._outBytesUsed + 7] = (byte)((v >> 56) & 0xff); - stateObj._outBytesUsed += 8; - } - } - - /// - /// Serializes the first bytes of a signed 64 bit long to the returned buffer. - /// - /// The value to serialize. - /// The number of bytes to serialize. - /// The serialized signed 64 bit long. - internal byte[] SerializePartialLong(long v, int length) - { - Debug.Assert(length <= 8, "Length specified is longer than the size of a long"); - Debug.Assert(length >= 0, "Length should not be negative"); - - byte[] bytes = new byte[length]; - - // all of the long fits into the buffer - for (int index = 0; index < length; index++) - { - bytes[index] = (byte)((v >> (index * 8)) & 0xff); - } - - return bytes; - } - - /// - /// Writes the first bytes of a signed 64 bit long to the wire. - /// - /// The value to write. - /// The number of bytes to serialize. - /// containing the wire buffer. - internal void WritePartialLong(long v, int length, TdsParserStateObject stateObj) - { - Debug.Assert(length <= 8, "Length specified is longer than the size of a long"); - Debug.Assert(length >= 0, "Length should not be negative"); - - if ((stateObj._outBytesUsed + length) > stateObj._outBuff.Length) - { - // if all of the long doesn't fit into the buffer - for (int shiftValue = 0; shiftValue < length * 8; shiftValue += 8) - { - stateObj.WriteByte((byte)((v >> shiftValue) & 0xff)); - } - } - else - { - // all of the long fits into the buffer - for (int index = 0; index < length; index++) - { - stateObj._outBuff[stateObj._outBytesUsed + index] = (byte)((v >> (index * 8)) & 0xff); - } - stateObj._outBytesUsed += length; - } - } - - /// - /// Writes an unsigned 64 bit long to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteUnsignedLong(ulong uv, TdsParserStateObject stateObj) - { - WriteLong((long)uv, stateObj); - } - - /// - /// Serializes a double to the returned buffer. - /// - /// The value to serialize. - /// The serialized double. - internal byte[] SerializeDouble(double v) - { - if (double.IsInfinity(v) || double.IsNaN(v)) - { - throw ADP.ParameterValueOutOfRange(v.ToString()); - } - - var bytes = new byte[8]; - BinaryPrimitives.WriteInt64LittleEndian(bytes, BitConverter.DoubleToInt64Bits(v)); - return bytes; - } - - /// - /// Writes a double to the wire. - /// - /// The value to write. - /// containing the wire buffer. - internal void WriteDouble(double v, TdsParserStateObject stateObj) - { - Span bytes = stackalloc byte[sizeof(double)]; - BinaryPrimitives.WriteInt64LittleEndian(bytes, BitConverter.DoubleToInt64Bits(v)); - stateObj.WriteByteSpan(bytes); - } - - internal void PrepareResetConnection(bool preserveTransaction) - { - // Set flag to reset connection upon next use - only for use on 2000! - _fResetConnection = true; - _fPreserveTransaction = preserveTransaction; - } - - internal bool Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) - { - bool syncOverAsync = stateObj._syncOverAsync; - try - { - stateObj._syncOverAsync = true; - - bool dataReady; - TdsOperationStatus result = TryRun(runBehavior, cmdHandler, dataStream, bulkCopyHandler, stateObj, out dataReady); - Debug.Assert(result == TdsOperationStatus.Done, "Should always return Done when _syncOverAsync is set"); - return dataReady; - } - finally - { - stateObj._syncOverAsync = syncOverAsync; - } - } - - /// - /// Checks if the given token is a valid TDS token - /// - /// Token to check - /// True if the token is a valid TDS token, otherwise false - internal static bool IsValidTdsToken(byte token) - { - return ( - token == TdsEnums.SQLERROR || - token == TdsEnums.SQLINFO || - token == TdsEnums.SQLLOGINACK || - token == TdsEnums.SQLENVCHANGE || - token == TdsEnums.SQLRETURNVALUE || - token == TdsEnums.SQLRETURNSTATUS || - token == TdsEnums.SQLCOLNAME || - token == TdsEnums.SQLCOLFMT || - token == TdsEnums.SQLRESCOLSRCS || - token == TdsEnums.SQLDATACLASSIFICATION || - token == TdsEnums.SQLCOLMETADATA || - token == TdsEnums.SQLALTMETADATA || - token == TdsEnums.SQLTABNAME || - token == TdsEnums.SQLCOLINFO || - token == TdsEnums.SQLORDER || - token == TdsEnums.SQLALTROW || - token == TdsEnums.SQLROW || - token == TdsEnums.SQLNBCROW || - token == TdsEnums.SQLDONE || - token == TdsEnums.SQLDONEPROC || - token == TdsEnums.SQLDONEINPROC || - token == TdsEnums.SQLROWCRC || - token == TdsEnums.SQLSECLEVEL || - token == TdsEnums.SQLPROCID || - token == TdsEnums.SQLOFFSET || - token == TdsEnums.SQLSSPI || - token == TdsEnums.SQLFEATUREEXTACK || - token == TdsEnums.SQLSESSIONSTATE || - token == TdsEnums.SQLFEDAUTHINFO); - } - - // Main parse loop for the top-level tds tokens, calls back into the I*Handler interfaces - internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, out bool dataReady) - { - Debug.Assert((SniContext.Undefined != stateObj.SniContext) && // SniContext must not be Undefined - ((stateObj._attentionSent) || ((SniContext.Snix_Execute != stateObj.SniContext) && (SniContext.Snix_SendRows != stateObj.SniContext))), // SniContext should not be Execute or SendRows unless attention was sent (and, therefore, we are looking for an ACK) - $"Unexpected SniContext on call to TryRun; SniContext={stateObj.SniContext}"); - - if (TdsParserState.Broken == State || TdsParserState.Closed == State) - { - dataReady = true; - return TdsOperationStatus.Done; // Just in case this is called in a loop, expecting data to be returned. - } - - TdsOperationStatus result; - dataReady = false; - - do - { - // If there is data ready, but we didn't exit the loop, then something is wrong - Debug.Assert(!dataReady, "dataReady not expected - did we forget to skip the row?"); - - if (stateObj.IsTimeoutStateExpired) - { - runBehavior = RunBehavior.Attention; - } - - if (TdsParserState.Broken == State || TdsParserState.Closed == State) - { - break; // jump out of the loop if the state is already broken or closed. - } - - if (!stateObj._accumulateInfoEvents && (stateObj._pendingInfoEvents != null)) - { - if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) - { - SqlConnection connection = null; - if (_connHandler != null) - connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref - // We are omitting checks for error.Class in the code below (see processing of INFO) since we know (and assert) that error class - // error.Class < TdsEnums.MIN_ERROR_CLASS for info message. - // Also we know that TdsEnums.MIN_ERROR_CLASS Potential multi-threaded misuse of connection, unexpected TDS token found {0}", ObjectID); -#if DEBUG - throw new InvalidOperationException(message); -#else - throw SQL.ParsingErrorToken(ParsingErrorState.InvalidTdsTokenReceived, token); // MDAC 82443 -#endif - } - - int tokenLength; - result = TryGetTokenLength(token, stateObj, out tokenLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - - switch (token) - { - case TdsEnums.SQLERROR: - case TdsEnums.SQLINFO: - { - if (token == TdsEnums.SQLERROR) - { - stateObj.HasReceivedError = true; // Keep track of the fact error token was received - for Done processing. - } - - SqlError error; - result = TryProcessError(token, stateObj, cmdHandler, out error); - if (result != TdsOperationStatus.Done) - { - return result; - } - - if (token == TdsEnums.SQLINFO && stateObj._accumulateInfoEvents) - { - Debug.Assert(error.Class < TdsEnums.MIN_ERROR_CLASS, "INFO with class > TdsEnums.MIN_ERROR_CLASS"); - - if (stateObj._pendingInfoEvents == null) - stateObj._pendingInfoEvents = new List(); - stateObj._pendingInfoEvents.Add(error); - stateObj._syncOverAsync = true; - break; - } - - if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) - { - // If FireInfoMessageEventOnUserErrors is true, we have to fire event without waiting. - // Otherwise we can go ahead and add it to errors/warnings collection. - SqlConnection connection = null; - if (_connHandler != null) - connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref - - if ((connection != null) && - (connection.FireInfoMessageEventOnUserErrors == true) && - (error.Class <= TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS)) - { - // Fire SqlInfoMessage here - FireInfoMessageEvent(connection, cmdHandler, stateObj, error); - } - else - { - // insert error/info into the appropriate exception - warning if info, exception if error - if (error.Class < TdsEnums.MIN_ERROR_CLASS) - { - stateObj.AddWarning(error); - } - else if (error.Class < TdsEnums.FATAL_ERROR_CLASS) - { - // VSTFDEVDIV 479643: continue results processing for all non-fatal errors (<20) - - stateObj.AddError(error); - - // Add it to collection - but do NOT change run behavior UNLESS - // we are in an ExecuteReader call - at which time we will be throwing - // anyways so we need to consume all errors. This is not the case - // if we have already given out a reader. If we have already given out - // a reader we need to throw the error but not halt further processing. We used to - // halt processing. - - if (dataStream != null) - { // Webdata 104560 - if (!dataStream.IsInitialized) - { - runBehavior = RunBehavior.UntilDone; - } - } - } - else - { - stateObj.AddError(error); - - // Else we have a fatal error and we need to change the behavior - // since we want the complete error information in the exception. - // Besides - no further results will be received. - runBehavior = RunBehavior.UntilDone; - } - } - } - else if (error.Class >= TdsEnums.FATAL_ERROR_CLASS) - { - stateObj.AddError(error); - } - break; - } - - case TdsEnums.SQLCOLINFO: - { - if (dataStream != null) - { - _SqlMetaDataSet metaDataSet; - result = TryProcessColInfo(dataStream.MetaData, dataStream, stateObj, out metaDataSet); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = dataStream.TrySetMetaData(metaDataSet, false); - if (result != TdsOperationStatus.Done) - { - return result; - } - dataStream.BrowseModeInfoConsumed = true; - } - else - { - // no dataStream - result = stateObj.TrySkipBytes(tokenLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - break; - } - - case TdsEnums.SQLDONE: - case TdsEnums.SQLDONEPROC: - case TdsEnums.SQLDONEINPROC: - { - // RunBehavior can be modified - see SQL BU DT 269516 & 290090 - result = TryProcessDone(cmdHandler, dataStream, ref runBehavior, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - if ((token == TdsEnums.SQLDONEPROC) && (cmdHandler != null)) - { - // If the current parse/read is for the results of describe parameter encryption RPC requests, - // call a different handler which will update the describe parameter encryption RPC structures - // with the results, instead of the actual user RPC requests. - if (cmdHandler.IsDescribeParameterEncryptionRPCCurrentlyInProgress) - { - cmdHandler.OnDoneDescribeParameterEncryptionProc(stateObj); - } - else - { - cmdHandler.OnDoneProc(stateObj); - } - } - - break; - } - - case TdsEnums.SQLORDER: - { - // don't do anything with the order token so read off the pipe - result = stateObj.TrySkipBytes(tokenLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - } - - case TdsEnums.SQLENVCHANGE: - { - // ENVCHANGE must be processed synchronously (since it can modify the state of many objects) - stateObj._syncOverAsync = true; - - SqlEnvChange env; - result = TryProcessEnvChange(tokenLength, stateObj, out env); - if (result != TdsOperationStatus.Done) - { - return result; - } - - while (env != null) - { - if (!Connection.IgnoreEnvChange) - { - switch (env._type) - { - case TdsEnums.ENV_BEGINTRAN: - case TdsEnums.ENV_ENLISTDTC: - // When we get notification from the server of a new - // transaction, we move any pending transaction over to - // the current transaction, then we store the token in it. - // if there isn't a pending transaction, then it's either - // a TSQL transaction or a distributed transaction. - Debug.Assert(_currentTransaction == null, "non-null current transaction with an ENV Change"); - _currentTransaction = _pendingTransaction; - _pendingTransaction = null; - - if (_currentTransaction != null) - { - _currentTransaction.TransactionId = env._newLongValue; // this is defined as a ULongLong in the server and in the TDS Spec. - } - else - { - TransactionType transactionType = (TdsEnums.ENV_BEGINTRAN == env._type) ? TransactionType.LocalFromTSQL : TransactionType.Distributed; - _currentTransaction = new SqlInternalTransaction(_connHandler, transactionType, null, env._newLongValue); - } - if (_statistics != null && !_statisticsIsInTransaction) - { - _statistics.SafeIncrement(ref _statistics._transactions); - } - _statisticsIsInTransaction = true; - _retainedTransactionId = SqlInternalTransaction.NullTransactionId; - break; - case TdsEnums.ENV_DEFECTDTC: - case TdsEnums.ENV_TRANSACTIONENDED: - case TdsEnums.ENV_COMMITTRAN: - // SQLHOT 483 - // Must clear the retain id if the server-side transaction ends by anything other - // than rollback. - _retainedTransactionId = SqlInternalTransaction.NullTransactionId; - goto case TdsEnums.ENV_ROLLBACKTRAN; - case TdsEnums.ENV_ROLLBACKTRAN: - // When we get notification of a completed transaction - // we null out the current transaction. - if (_currentTransaction != null) - { -#if DEBUG - // Check null for case where Begin and Rollback obtained in the same message. - if (SqlInternalTransaction.NullTransactionId != _currentTransaction.TransactionId) - { - Debug.Assert(_currentTransaction.TransactionId != env._newLongValue, "transaction id's are not equal!"); - } -#endif - - if (TdsEnums.ENV_COMMITTRAN == env._type) - { - _currentTransaction.Completed(TransactionState.Committed); - } - else if (TdsEnums.ENV_ROLLBACKTRAN == env._type) - { - // Hold onto transaction id if distributed tran is rolled back. This must - // be sent to the server on subsequent executions even though the transaction - // is considered to be rolled back. - if (_currentTransaction.IsDistributed && _currentTransaction.IsActive) - { - _retainedTransactionId = env._oldLongValue; - } - _currentTransaction.Completed(TransactionState.Aborted); - } - else - { - // TODO: While not techically necessary at this point, we are not certain whether TransactionEnded indicates Committed, Aborted or Unknown - this is fine for now, but we should investigate. - _currentTransaction.Completed(TransactionState.Unknown); - } - _currentTransaction = null; - } - _statisticsIsInTransaction = false; - break; - - default: - _connHandler.OnEnvChange(env); - break; - } - } - SqlEnvChange head = env; - env = env._next; - head.Clear(); - head = null; - } - break; - } - case TdsEnums.SQLLOGINACK: - { - SqlClientEventSource.Log.TryTraceEvent(" Received login acknowledgement token"); - SqlLoginAck ack; - result = TryProcessLoginAck(stateObj, out ack); - if (result != TdsOperationStatus.Done) - { - return result; - } - - _connHandler.OnLoginAck(ack); - break; - } - case TdsEnums.SQLFEATUREEXTACK: - { - result = TryProcessFeatureExtAck(stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - } - case TdsEnums.SQLFEDAUTHINFO: - { - _connHandler._federatedAuthenticationInfoReceived = true; - SqlFedAuthInfo info; - - result = TryProcessFedAuthInfo(stateObj, tokenLength, out info); - if (result != TdsOperationStatus.Done) - { - return result; - } - _connHandler.OnFedAuthInfo(info); - break; - } - case TdsEnums.SQLSESSIONSTATE: - { - result = TryProcessSessionState(stateObj, tokenLength, _connHandler._currentSessionData); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - } - case TdsEnums.SQLCOLMETADATA: - { - if (tokenLength != TdsEnums.VARNULL) - { - _SqlMetaDataSet metadata; - result = TryProcessMetaData(tokenLength, stateObj, out metadata, - cmdHandler?.ColumnEncryptionSetting ?? SqlCommandColumnEncryptionSetting.UseConnectionSetting); - if (result != TdsOperationStatus.Done) - { - return result; - } - stateObj._cleanupMetaData = metadata; - } - else - { - if (cmdHandler != null) - { - stateObj._cleanupMetaData = cmdHandler.MetaData; - } - } - - byte peekedToken; - result = stateObj.TryPeekByte(out peekedToken); - if (result != TdsOperationStatus.Done) - { - return result; - } - - if (TdsEnums.SQLDATACLASSIFICATION == peekedToken) - { - byte dataClassificationToken; - result = stateObj.TryReadByte(out dataClassificationToken); - if (result != TdsOperationStatus.Done) - { - return result; - } - Debug.Assert(TdsEnums.SQLDATACLASSIFICATION == dataClassificationToken); - - SensitivityClassification sensitivityClassification; - result = TryProcessDataClassification(stateObj, out sensitivityClassification); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (dataStream != null) - { - result = dataStream.TrySetSensitivityClassification(sensitivityClassification); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - // update peekedToken - result = stateObj.TryPeekByte(out peekedToken); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - if (dataStream != null) - { - result = dataStream.TrySetMetaData(stateObj._cleanupMetaData, (TdsEnums.SQLTABNAME == peekedToken || TdsEnums.SQLCOLINFO == peekedToken)); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else if (bulkCopyHandler != null) - { - bulkCopyHandler.SetMetaData(stateObj._cleanupMetaData); - } - break; - } - case TdsEnums.SQLROW: - case TdsEnums.SQLNBCROW: - { - Debug.Assert(stateObj._cleanupMetaData != null, "Reading a row, but the metadata is null"); - - if (token == TdsEnums.SQLNBCROW) - { - result = stateObj.TryStartNewRow(isNullCompressed: true, nullBitmapColumnsCount: stateObj._cleanupMetaData.Length); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else - { - result = stateObj.TryStartNewRow(isNullCompressed: false); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - if (bulkCopyHandler != null) - { - // TODO: Consider improving Bulk Copy performance by avoiding boxing. - result = TryProcessRow(stateObj._cleanupMetaData, bulkCopyHandler.CreateRowBuffer(), bulkCopyHandler.CreateIndexMap(), stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) - { - result = TrySkipRow(stateObj._cleanupMetaData, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else - { - dataReady = true; - } - - if (_statistics != null) - { - _statistics.WaitForDoneAfterRow = true; - } - break; - } - case TdsEnums.SQLRETURNSTATUS: - int status; - result = stateObj.TryReadInt32(out status); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (cmdHandler != null) - { - cmdHandler.OnReturnStatus(status); - } - break; - case TdsEnums.SQLRETURNVALUE: - { - SqlReturnValue returnValue; - result = TryProcessReturnValue(tokenLength, stateObj, out returnValue, - cmdHandler?.ColumnEncryptionSetting ?? SqlCommandColumnEncryptionSetting.UseConnectionSetting); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (cmdHandler != null) - { - cmdHandler.OnReturnValue(returnValue, stateObj); - } - break; - } - case TdsEnums.SQLSSPI: - { - // token length is length of SSPI data - call ProcessSSPI with it - - Debug.Assert(stateObj._syncOverAsync, "ProcessSSPI does not support retry, do not attempt asynchronously"); - stateObj._syncOverAsync = true; - - ProcessSSPI(tokenLength); - break; - } - case TdsEnums.SQLTABNAME: - { - if (dataStream != null) - { - MultiPartTableName[] tableNames; - result = TryProcessTableName(tokenLength, stateObj, out tableNames); - if (result != TdsOperationStatus.Done) - { - return result; - } - dataStream.TableNames = tableNames; - } - else - { - result = stateObj.TrySkipBytes(tokenLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - break; - } - case TdsEnums.SQLRESCOLSRCS: - { - result = TryProcessResColSrcs(stateObj, tokenLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - } - - // deprecated - case TdsEnums.SQLALTMETADATA: - { - stateObj.CloneCleanupAltMetaDataSetArray(); - - if (stateObj._cleanupAltMetaDataSetArray == null) - { - // create object on demand (lazy creation) - stateObj._cleanupAltMetaDataSetArray = new _SqlMetaDataSetCollection(); - } - - _SqlMetaDataSet cleanupAltMetaDataSet; - result = TryProcessAltMetaData(tokenLength, stateObj, out cleanupAltMetaDataSet); - if (result != TdsOperationStatus.Done) - { - return result; - } - - stateObj._cleanupAltMetaDataSetArray.SetAltMetaData(cleanupAltMetaDataSet); - if (dataStream != null) - { - byte metadataConsumedByte; - result = stateObj.TryPeekByte(out metadataConsumedByte); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = dataStream.TrySetAltMetaDataSet(cleanupAltMetaDataSet, (TdsEnums.SQLALTMETADATA != metadataConsumedByte)); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - break; - } - case TdsEnums.SQLALTROW: - { - result = stateObj.TryStartNewRow(isNullCompressed: false); - if (result != TdsOperationStatus.Done) - { - // altrows are not currently null compressed - return result; - } - - // read will call run until dataReady. Must not read any data if return immediately set - if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) - { - ushort altRowId; - result = stateObj.TryReadUInt16(out altRowId); - if (result != TdsOperationStatus.Done) - { - // get altRowId - return result; - } - - result = TrySkipRow(stateObj._cleanupAltMetaDataSetArray.GetAltMetaData(altRowId), stateObj); - if (result != TdsOperationStatus.Done) - { - // skip altRow - return result; - } - } - else - { - dataReady = true; - } - - break; - } - - default: - Debug.Fail("Unhandled token: " + token.ToString(CultureInfo.InvariantCulture)); - break; - } - - Debug.Assert(stateObj.HasPendingData || !dataReady, "dataReady is set, but there is no pending data"); - } - - // Loop while data pending & runbehavior not return immediately, OR - // if in attention case, loop while no more pending data & attention has not yet been - // received. - while ((stateObj.HasPendingData && - (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior))) || - (!stateObj.HasPendingData && stateObj._attentionSent && !stateObj.HasReceivedAttention)); - -#if DEBUG - if ((stateObj.HasPendingData) && (!dataReady)) - { - byte token; - result = stateObj.TryPeekByte(out token); - if (result != TdsOperationStatus.Done) - { - return result; - } - Debug.Assert(IsValidTdsToken(token), $"DataReady is false, but next token is not valid: {token,-2:X2}"); - } -#endif - - if (!stateObj.HasPendingData) - { - if (CurrentTransaction != null) - { - CurrentTransaction.Activate(); - } - } - - // if we received an attention (but this thread didn't send it) then - // we throw an Operation Cancelled error - if (stateObj.HasReceivedAttention) - { - // Dev11 #344723: SqlClient stress test suspends System_Data!Tcp::ReadSync via a call to SqlDataReader::Close - // Spin until SendAttention has cleared _attentionSending, this prevents a race condition between receiving the attention ACK and setting _attentionSent - TryRunSetupSpinWaitContinuation(stateObj); - - Debug.Assert(stateObj._attentionSent, "Attention ACK has been received without attention sent"); - if (stateObj._attentionSent) - { - // Reset attention state. - stateObj._attentionSent = false; - stateObj.HasReceivedAttention = false; - - if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior) && !stateObj.IsTimeoutStateExpired) - { - // Add attention error to collection - if not RunBehavior.Clean! - stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.OperationCancelled(), "", 0, exception: null, batchIndex: cmdHandler?.GetCurrentBatchIndex() ?? -1)); - } - } - } - - if (stateObj.HasErrorOrWarning) - { - ThrowExceptionAndWarning(stateObj, cmdHandler); - } - return TdsOperationStatus.Done; - } - - // This is in its own method to avoid always allocating the lambda in TryRun - private static void TryRunSetupSpinWaitContinuation(TdsParserStateObject stateObj) => SpinWait.SpinUntil(() => !stateObj._attentionSending); - - private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, out SqlEnvChange sqlEnvChange) - { - // There could be multiple environment change messages following this token. - byte byteLength; - int processedLength = 0; - SqlEnvChange head = null; - SqlEnvChange tail = null; - - sqlEnvChange = null; - - while (tokenLength > processedLength) - { - SqlEnvChange env = new SqlEnvChange(); - TdsOperationStatus result = stateObj.TryReadByte(out env._type); - if (result != TdsOperationStatus.Done) - { - return result; - } - - if (head is null) - { - head = env; - tail = env; - } - else - { - tail._next = env; - tail = env; - } - - switch (env._type) - { - case TdsEnums.ENV_DATABASE: - case TdsEnums.ENV_LANG: - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - - // TdsEnums.ENV_CHARSET (3) is only supported in TDS <= 7 which is no longer supported - - case TdsEnums.ENV_PACKETSIZE: - // take care of packet size right here - Debug.Assert(stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - if (TryReadTwoStringFields(env, stateObj) != TdsOperationStatus.Done) - { - // Changing packet size does not support retry, should not pend" - throw SQL.SynchronousCallMayNotPend(); - } - - // Only set on physical state object - this should only occur on LoginAck prior - // to MARS initialization! - int packetSize = int.Parse(env._newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); - - SqlClientEventSource.Log.TryTraceEvent("{0}.{1} | Info | Server sent env packet size change of {2}, ClientConnectionID {3}", - nameof(TdsParser), nameof(TryProcessEnvChange), packetSize, _connHandler._clientConnectionId); - - if (_physicalStateObj.SetPacketSize(packetSize)) - { - // If packet size changed, we need to release our SNIPackets since - // those are tied to packet size of connection. - _physicalStateObj.ClearAllWritePackets(); - - // Update SNI ConsumerInfo value to be resulting packet size - uint unsignedPacketSize = (uint)packetSize; - uint bufferSizeResult = _physicalStateObj.SetConnectionBufferSize(ref unsignedPacketSize); - - Debug.Assert(bufferSizeResult == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SNISetInfo"); - } - - break; - - // TdsEnums.ENV_LOCALE (5) is only supported in TDS <= 7 which is no longer supported - - // TdsEnums.ENV_COMPFLAGS (6) is only supported in TDS <= 7 which is no longer supported - - case TdsEnums.ENV_COLLATION: - Debug.Assert(env._newLength == 5 || env._newLength == 0, "Improper length in new collation!"); - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._newLength = byteLength; - if (env._newLength == 5) - { - result = TryProcessCollation(stateObj, out env._newCollation); - if (result != TdsOperationStatus.Done) - { - return result; - } - - // Give the parser the new collation values in case parameters don't specify one - _defaultCollation = env._newCollation; - - // UTF8 collation - if (env._newCollation.IsUTF8) - { - _defaultEncoding = s_utf8EncodingWithoutBom; - } - else - { - int newCodePage = GetCodePage(env._newCollation, stateObj); - if (newCodePage != _defaultCodePage) - { - _defaultCodePage = newCodePage; - _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); - } - } - _defaultLCID = env._newCollation.LCID; - } - - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._oldLength = byteLength; - Debug.Assert(env._oldLength == 5 || env._oldLength == 0, "Improper length in old collation!"); - if (env._oldLength == 5) - { - result = TryProcessCollation(stateObj, out env._oldCollation); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - env._length = 3 + env._newLength + env._oldLength; - break; - - case TdsEnums.ENV_BEGINTRAN: - case TdsEnums.ENV_COMMITTRAN: - case TdsEnums.ENV_ROLLBACKTRAN: - case TdsEnums.ENV_ENLISTDTC: - case TdsEnums.ENV_DEFECTDTC: - case TdsEnums.ENV_TRANSACTIONENDED: - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._newLength = byteLength; - Debug.Assert(env._newLength == 0 || env._newLength == 8, "Improper length for new transaction id!"); - - if (env._newLength > 0) - { - result = stateObj.TryReadInt64(out env._newLongValue); - if (result != TdsOperationStatus.Done) - { - return result; - } - Debug.Assert(env._newLongValue != SqlInternalTransaction.NullTransactionId, "New transaction id is null?"); // the server guarantees that zero is an invalid transaction id. - } - else - { - env._newLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. - } - - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._oldLength = byteLength; - Debug.Assert(env._oldLength == 0 || env._oldLength == 8, "Improper length for old transaction id!"); - - if (env._oldLength > 0) - { - result = stateObj.TryReadInt64(out env._oldLongValue); - if (result != TdsOperationStatus.Done) - { - return result; - } - Debug.Assert(env._oldLongValue != SqlInternalTransaction.NullTransactionId, "Old transaction id is null?"); // the server guarantees that zero is an invalid transaction id. - } - else - { - env._oldLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. - } - - // env.length includes 1 byte type token - env._length = 3 + env._newLength + env._oldLength; - break; - - case TdsEnums.ENV_LOGSHIPNODE: - // env.newBinValue is secondary node, env.oldBinValue is witness node - // comes before LoginAck so we can't assert this - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - - case TdsEnums.ENV_PROMOTETRANSACTION: - result = stateObj.TryReadInt32(out env._newLength); - if (result != TdsOperationStatus.Done) - { - // new value has 4 byte length - return result; - } - // read new value with 4 byte length - env._newBinValue = new byte[env._newLength]; - result = stateObj.TryReadByteArray(env._newBinValue, env._newLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._oldLength = byteLength; - Debug.Assert(0 == env._oldLength, "old length should be zero"); - - // env.length includes 1 byte for type token - env._length = 5 + env._newLength; - break; - - case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS: - case TdsEnums.ENV_SPRESETCONNECTIONACK: - result = TryReadTwoBinaryFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - - case TdsEnums.ENV_USERINSTANCE: - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; - - case TdsEnums.ENV_ROUTING: - ushort newLength; - result = stateObj.TryReadUInt16(out newLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._newLength = newLength; - byte protocol; - result = stateObj.TryReadByte(out protocol); - if (result != TdsOperationStatus.Done) - { - return result; - } - ushort port; - result = stateObj.TryReadUInt16(out port); - if (result != TdsOperationStatus.Done) - { - return result; - } - ushort serverLen; - result = stateObj.TryReadUInt16(out serverLen); - if (result != TdsOperationStatus.Done) - { - return result; - } - string serverName; - result = stateObj.TryReadString(serverLen, out serverName); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._newRoutingInfo = new RoutingInfo(protocol, port, serverName); - ushort oldLength; - result = stateObj.TryReadUInt16(out oldLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TrySkipBytes(oldLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._length = env._newLength + oldLength + 5; // 5=2*sizeof(UInt16)+sizeof(byte) [token+newLength+oldLength] - break; - - default: - Debug.Fail("Unknown environment change token: " + env._type); - break; - } - processedLength += env._length; - } - - sqlEnvChange = head; - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryReadTwoBinaryFields(SqlEnvChange env, TdsParserStateObject stateObj) - { - // Used by ProcessEnvChangeToken - byte byteLength; - TdsOperationStatus result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._newLength = byteLength; - env._newBinValue = ArrayPool.Shared.Rent(env._newLength); - env._newBinRented = true; - result = stateObj.TryReadByteArray(env._newBinValue, env._newLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TryReadByte(out byteLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - env._oldLength = byteLength; - env._oldBinValue = ArrayPool.Shared.Rent(env._oldLength); - env._oldBinRented = true; - result = stateObj.TryReadByteArray(env._oldBinValue, env._oldLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - - // env.length includes 1 byte type token - env._length = 3 + env._newLength + env._oldLength; - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryReadTwoStringFields(SqlEnvChange env, TdsParserStateObject stateObj) - { - // Used by ProcessEnvChangeToken - byte newLength, oldLength; - string newValue, oldValue; - TdsOperationStatus result = stateObj.TryReadByte(out newLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TryReadString(newLength, out newValue); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TryReadByte(out oldLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TryReadString(oldLength, out oldValue); - if (result != TdsOperationStatus.Done) - { - return result; - } - - env._newLength = newLength; - env._newValue = newValue; - env._oldLength = oldLength; - env._oldValue = oldValue; - - // env.length includes 1 byte type token - env._length = 3 + env._newLength * 2 + env._oldLength * 2; - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavior run, TdsParserStateObject stateObj) - { - ushort curCmd; - ushort status; - int count; - - if (LocalAppContextSwitches.MakeReadAsyncBlocking) - { - // Don't retry TryProcessDone - stateObj._syncOverAsync = true; - } - - // status - // command - // rowcount (valid only if DONE_COUNT bit is set) - - TdsOperationStatus result = stateObj.TryReadUInt16(out status); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = stateObj.TryReadUInt16(out curCmd); - if (result != TdsOperationStatus.Done) - { - return result; - } - - long longCount; - result = stateObj.TryReadInt64(out longCount); - if (result != TdsOperationStatus.Done) - { - return result; - } - count = (int)longCount; - - // We get a done token with the attention bit set - if (TdsEnums.DONE_ATTN == (status & TdsEnums.DONE_ATTN)) - { - Debug.Assert(TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE), "Not expecting DONE_MORE when receiving DONE_ATTN"); - Debug.Assert(stateObj._attentionSent, "Received attention done without sending one!"); - stateObj.HasReceivedAttention = true; - Debug.Assert(stateObj._inBytesUsed == stateObj._inBytesRead && stateObj._inBytesPacket == 0, "DONE_ATTN received with more data left on wire"); - } - if (cmd != null && (TdsEnums.DONE_COUNT == (status & TdsEnums.DONE_COUNT))) - { - if (curCmd != TdsEnums.SELECT) - { - if (cmd.IsDescribeParameterEncryptionRPCCurrentlyInProgress) - { - // The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. - cmd.RowsAffectedByDescribeParameterEncryption = count; - } - else - { - cmd.InternalRecordsAffected = count; - } - } - // Skip the bogus DONE counts sent by the server - if (stateObj.HasReceivedColumnMetadata || (curCmd != TdsEnums.SELECT)) - { - cmd.OnStatementCompleted(count); - } - } - - stateObj.HasReceivedColumnMetadata = false; - - // Surface exception for DONE_ERROR in the case we did not receive an error token - // in the stream, but an error occurred. In these cases, we throw a general server error. The - // situations where this can occur are: an invalid buffer received from client, login error - // and the server refused our connection, and the case where we are trying to log in but - // the server has reached its max connection limit. Bottom line, we need to throw general - // error in the cases where we did not receive an error token along with the DONE_ERROR. - if ((TdsEnums.DONE_ERROR == (TdsEnums.DONE_ERROR & status)) && stateObj.ErrorCount == 0 && - stateObj.HasReceivedError == false && (RunBehavior.Clean != (RunBehavior.Clean & run))) - { - stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.SevereError(), "", 0, exception: null, batchIndex: cmd?.GetCurrentBatchIndex() ?? -1)); - - if (reader != null) - { // SQL BU DT 269516 - if (!reader.IsInitialized) - { - run = RunBehavior.UntilDone; - } - } - } - - // Similar to above, only with a more severe error. In this case, if we received - // the done_srverror, this exception will be added to the collection regardless. - // MDAC #93896. Also, per Ashwin, the server will always break the connection in this case. - if ((TdsEnums.DONE_SRVERROR == (TdsEnums.DONE_SRVERROR & status)) && (RunBehavior.Clean != (RunBehavior.Clean & run))) - { - stateObj.AddError(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.SevereError(), "", 0, exception: null, batchIndex: cmd?.GetCurrentBatchIndex() ?? -1)); - - if (reader != null) - { // SQL BU DT 269516 - if (!reader.IsInitialized) - { - run = RunBehavior.UntilDone; - } - } - } - - ProcessSqlStatistics(curCmd, status, count); - - // stop if the DONE_MORE bit isn't set (see above for attention handling) - if (TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE)) - { - stateObj.HasReceivedError = false; - if (stateObj._inBytesUsed >= stateObj._inBytesRead) - { - stateObj.HasPendingData = false; - } - } - - // _pendingData set by e.g. 'TdsExecuteSQLBatch' - // _hasOpenResult always set to true by 'WriteMarsHeader' - // - if (!stateObj.HasPendingData && stateObj.HasOpenResult) - { - /* - Debug.Assert(!((sqlTransaction != null && _distributedTransaction != null) || - (_userStartedLocalTransaction != null && _distributedTransaction != null)) - , "ProcessDone - have both distributed and local transactions not null!"); - */ - // WebData 112722 - - stateObj.DecrementOpenResultCount(); - } - - return TdsOperationStatus.Done; - } - - private void ProcessSqlStatistics(ushort curCmd, ushort status, int count) - { - // SqlStatistics bookkeeping stuff - // - if (_statistics != null) - { - // any done after row(s) counts as a resultset - if (_statistics.WaitForDoneAfterRow) - { - _statistics.SafeIncrement(ref _statistics._sumResultSets); - _statistics.WaitForDoneAfterRow = false; - } - - // clear row count DONE_COUNT flag is not set - if (!(TdsEnums.DONE_COUNT == (status & TdsEnums.DONE_COUNT))) - { - count = 0; - } - - switch (curCmd) - { - case TdsEnums.INSERT: - case TdsEnums.DELETE: - case TdsEnums.UPDATE: - case TdsEnums.MERGE: - _statistics.SafeIncrement(ref _statistics._iduCount); - _statistics.SafeAdd(ref _statistics._iduRows, count); - if (!_statisticsIsInTransaction) - { - _statistics.SafeIncrement(ref _statistics._transactions); - } - - break; - - case TdsEnums.SELECT: - _statistics.SafeIncrement(ref _statistics._selectCount); - _statistics.SafeAdd(ref _statistics._selectRows, count); - break; - - case TdsEnums.BEGINXACT: - if (!_statisticsIsInTransaction) - { - _statistics.SafeIncrement(ref _statistics._transactions); - } - _statisticsIsInTransaction = true; - break; - - case TdsEnums.OPENCURSOR: - _statistics.SafeIncrement(ref _statistics._cursorOpens); - break; - - case TdsEnums.ABORT: - _statisticsIsInTransaction = false; - break; - - case TdsEnums.ENDXACT: - _statisticsIsInTransaction = false; - break; - } // switch - } - else - { - switch (curCmd) - { - case TdsEnums.BEGINXACT: - _statisticsIsInTransaction = true; - break; - - case TdsEnums.ABORT: - case TdsEnums.ENDXACT: - _statisticsIsInTransaction = false; - break; - } - } - } - - private TdsOperationStatus TryProcessFeatureExtAck(TdsParserStateObject stateObj) - { - // read feature ID - byte featureId; - do - { - TdsOperationStatus result = stateObj.TryReadByte(out featureId); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (featureId != TdsEnums.FEATUREEXT_TERMINATOR) - { - uint dataLen; - result = stateObj.TryReadUInt32(out dataLen); - if (result != TdsOperationStatus.Done) - { - return result; - } - byte[] data = new byte[dataLen]; - if (dataLen > 0) - { - result = stateObj.TryReadByteArray(data, checked((int)dataLen)); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - _connHandler.OnFeatureExtAck(featureId, data); - } - } while (featureId != TdsEnums.FEATUREEXT_TERMINATOR); - - // Write to DNS Cache or clean up DNS Cache for TCP protocol - bool ret = false; - if (_connHandler._cleanSQLDNSCaching) - { - ret = SQLFallbackDNSCache.Instance.DeleteDNSInfo(FQDNforDNSCache); - } - - if (_connHandler.IsSQLDNSCachingSupported && _connHandler.pendingSQLDNSObject != null - && !SQLFallbackDNSCache.Instance.IsDuplicate(_connHandler.pendingSQLDNSObject)) - { - ret = SQLFallbackDNSCache.Instance.AddDNSInfo(_connHandler.pendingSQLDNSObject); - _connHandler.pendingSQLDNSObject = null; - } - - // Check if column encryption was on and feature wasn't acknowledged and we aren't going to be routed to another server. - if (Connection.RoutingInfo == null - && _connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled - && !IsColumnEncryptionSupported) - { - throw SQL.TceNotSupported(); - } - - // Check if server does not support Enclave Computations and we aren't going to be routed to another server. - if (Connection.RoutingInfo == null) - { - SqlConnectionAttestationProtocol attestationProtocol = _connHandler.ConnectionOptions.AttestationProtocol; - - if (TceVersionSupported < TdsEnums.MIN_TCE_VERSION_WITH_ENCLAVE_SUPPORT) - { - // Check if enclave attestation url was specified and server does not support enclave computations and we aren't going to be routed to another server. - if (!string.IsNullOrWhiteSpace(_connHandler.ConnectionOptions.EnclaveAttestationUrl) && attestationProtocol != SqlConnectionAttestationProtocol.NotSpecified) - { - throw SQL.EnclaveComputationsNotSupported(); - } - else if (!string.IsNullOrWhiteSpace(_connHandler.ConnectionOptions.EnclaveAttestationUrl)) - { - throw SQL.AttestationURLNotSupported(); - } - else if (_connHandler.ConnectionOptions.AttestationProtocol != SqlConnectionAttestationProtocol.NotSpecified) - { - throw SQL.AttestationProtocolNotSupported(); - } - } - - // Check if enclave attestation url was specified and server does not return an enclave type and we aren't going to be routed to another server. - if (!string.IsNullOrWhiteSpace(_connHandler.ConnectionOptions.EnclaveAttestationUrl) || attestationProtocol == SqlConnectionAttestationProtocol.None) - { - if (string.IsNullOrWhiteSpace(EnclaveType)) - { - throw SQL.EnclaveTypeNotReturned(); - } - else - { - // Check if the attestation protocol is specified and supports the enclave type. - if (attestationProtocol != SqlConnectionAttestationProtocol.NotSpecified && !IsValidAttestationProtocol(attestationProtocol, EnclaveType)) - { - throw SQL.AttestationProtocolNotSupportEnclaveType(attestationProtocol.ToString(), EnclaveType); - } - } - } - } - - return TdsOperationStatus.Done; - } - - private bool IsValidAttestationProtocol(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType) - { - switch (enclaveType.ToUpper()) - { - case TdsEnums.ENCLAVE_TYPE_VBS: - if (attestationProtocol != SqlConnectionAttestationProtocol.AAS - && attestationProtocol != SqlConnectionAttestationProtocol.HGS - && attestationProtocol != SqlConnectionAttestationProtocol.None) - { - return false; - } - break; - - case TdsEnums.ENCLAVE_TYPE_SGX: -#if ENCLAVE_SIMULATOR - if (attestationProtocol != SqlConnectionAttestationProtocol.AAS - && attestationProtocol != SqlConnectionAttestationProtocol.None) -#else - if (attestationProtocol != SqlConnectionAttestationProtocol.AAS) -#endif - { - return false; - } - break; - -#if ENCLAVE_SIMULATOR - case TdsEnums.ENCLAVE_TYPE_SIMULATOR: - if (attestationProtocol != SqlConnectionAttestationProtocol.None) - { - return false; - } - break; -#endif - default: - // if we reach here, the enclave type is not supported - throw SQL.EnclaveTypeNotSupported(enclaveType); - } - - return true; - } - - private TdsOperationStatus TryReadByteString(TdsParserStateObject stateObj, out string value) - { - value = string.Empty; - - byte byteLen; - TdsOperationStatus result = stateObj.TryReadByte(out byteLen); - if (result != TdsOperationStatus.Done) - { - return result; - } - - result = stateObj.TryReadString(byteLen, out value); - if (result != TdsOperationStatus.Done) - { - return result; - } - - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryReadSensitivityLabel(TdsParserStateObject stateObj, out string label, out string id) - { - label = string.Empty; - id = string.Empty; - - TdsOperationStatus result = TryReadByteString(stateObj, out label); - if (result != TdsOperationStatus.Done) - { - return result; - } - - result = TryReadByteString(stateObj, out id); - if (result != TdsOperationStatus.Done) - { - return result; - } - - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryReadSensitivityInformationType(TdsParserStateObject stateObj, out string informationType, out string id) - { - informationType = string.Empty; - id = string.Empty; - - TdsOperationStatus result = TryReadByteString(stateObj, out informationType); - if (result != TdsOperationStatus.Done) - { - return result; - } - - result = TryReadByteString(stateObj, out id); - if (result != TdsOperationStatus.Done) - { - return result; - } - - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryProcessDataClassification(TdsParserStateObject stateObj, out SensitivityClassification sensitivityClassification) - { - if (DataClassificationVersion == 0) - { - throw SQL.ParsingError(ParsingErrorState.DataClassificationNotExpected); - } - - sensitivityClassification = null; - - // get the labels - TdsOperationStatus result = stateObj.TryReadUInt16(out ushort numLabels); - if (result != TdsOperationStatus.Done) - { - return result; - } - List