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 5c67ef9d5b..d96e7aa247 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -771,9 +771,6 @@
Microsoft\Data\SqlClient\TdsParser.cs
-
- Microsoft\Data\SqlClient\TdsParser.SSPI.cs
-
Microsoft\Data\SqlClient\TdsParserHelperClasses.cs
@@ -783,6 +780,9 @@
Microsoft\Data\SqlClient\TdsParserStateObject.Multiplexer.cs
+
+ Microsoft\Data\SqlClient\TdsParserStateObjectManaged.netcore.cs
+
Microsoft\Data\SqlClient\TdsParserStaticMethods.cs
@@ -834,8 +834,6 @@
-
-
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs
deleted file mode 100644
index 953f8ec123..0000000000
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs
+++ /dev/null
@@ -1,806 +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.Binary;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Security;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Data.Common;
-using Microsoft.Data.ProviderBase;
-
-namespace Microsoft.Data.SqlClient
-{
- internal abstract partial class TdsParserStateObject
- {
- //////////////////
- // Constructors //
- //////////////////
-
- protected TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async)
- {
- // Construct a MARS session
- Debug.Assert(parser != null, "no parser?");
- _parser = parser;
- _onTimeoutAsync = OnTimeoutAsync;
- SniContext = SniContext.Snix_GetMarsSession;
-
- Debug.Assert(_parser._physicalStateObj != null, "no physical session?");
- Debug.Assert(_parser._physicalStateObj._inBuff != null, "no in buffer?");
- Debug.Assert(_parser._physicalStateObj._outBuff != null, "no out buffer?");
- Debug.Assert(_parser._physicalStateObj._outBuff.Length ==
- _parser._physicalStateObj._inBuff.Length, "Unexpected unequal buffers.");
-
- // Determine packet size based on physical connection buffer lengths.
- SetPacketSize(_parser._physicalStateObj._outBuff.Length);
-
- CreateSessionHandle(physicalConnection, async);
-
- if (IsFailedHandle())
- {
- AddError(parser.ProcessSNIError(this));
- ThrowExceptionAndWarning();
- }
-
- // we post a callback that represents the call to dispose; once the
- // object is disposed, the next callback will cause the GC Handle to
- // be released.
- IncrementPendingCallbacks();
- _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer;
- }
-
- /////////////////////
- // General methods //
- /////////////////////
-
- internal int DecrementPendingCallbacks(bool release)
- {
- int remaining = Interlocked.Decrement(ref _pendingCallbacks);
- SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.DecrementPendingCallbacks | ADV | State Object Id {0}, after decrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks);
-
- FreeGcHandle(remaining, release);
-
- // NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed
- // This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it
- Debug.Assert((remaining == -1 && SessionHandle.IsNull) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}");
- return remaining;
- }
-
- ///
- /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections)
- /// NOTE: This is not safe to do on a connection that is currently in use
- /// NOTE: This will mark the connection as broken if it is found to be dead
- ///
- /// True if the connection is still alive, otherwise false
- internal bool ValidateSNIConnection()
- {
- if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed)))
- {
- return false;
- }
-
- if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value <= CheckConnectionWindow)
- {
- return true;
- }
-
- uint error = TdsEnums.SNI_SUCCESS;
- SniContext = SniContext.Snix_Connect;
- try
- {
- Interlocked.Increment(ref _readingCount);
- error = CheckConnection();
- }
- finally
- {
- Interlocked.Decrement(ref _readingCount);
- }
- return (error == TdsEnums.SNI_SUCCESS) || (error == TdsEnums.SNI_WAIT_TIMEOUT);
- }
-
- // This method should only be called by ReadSni! If not - it may have problems with timeouts!
- private void ReadSniError(TdsParserStateObject stateObj, uint error)
- {
- if (TdsEnums.SNI_WAIT_TIMEOUT == error)
- {
- Debug.Assert(_syncOverAsync, "Should never reach here with async on!");
- bool fail = false;
-
- if (IsTimeoutStateExpired)
- { // This is now our second timeout - time to give up.
- fail = true;
- }
- else
- {
- stateObj.SetTimeoutStateStopped();
- Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point.");
- AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT));
-
- if (!stateObj._attentionSent)
- {
- if (stateObj.Parser.State == TdsParserState.OpenLoggedIn)
- {
- stateObj.SendAttention(mustTakeWriteLock: true);
-
- PacketHandle syncReadPacket = default;
- bool readFromNetwork = true;
- bool shouldDecrement = false;
- try
- {
- Interlocked.Increment(ref _readingCount);
- shouldDecrement = true;
- readFromNetwork = !PartialPacketContainsCompletePacket();
- if (readFromNetwork)
- {
- syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error);
- }
- else
- {
- error = TdsEnums.SNI_SUCCESS;
- }
-
- Interlocked.Decrement(ref _readingCount);
- shouldDecrement = false;
-
- if (TdsEnums.SNI_SUCCESS == error)
- {
- // We will end up letting the run method deal with the expected done:done_attn token stream.
- stateObj.ProcessSniPacket(syncReadPacket, TdsEnums.SNI_SUCCESS);
- return;
- }
- else
- {
- Debug.Assert(!readFromNetwork || !IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease");
- fail = true; // Subsequent read failed, time to give up.
- }
- }
- finally
- {
- if (shouldDecrement)
- {
- Interlocked.Decrement(ref _readingCount);
- }
-
- if (readFromNetwork && !IsPacketEmpty(syncReadPacket))
- {
- // Be sure to release packet, otherwise it will be leaked by native.
- ReleasePacket(syncReadPacket);
- }
- }
- }
- else
- {
- if (_parser._loginWithFailover)
- {
- // For DbMirroring Failover during login, never break the connection, just close the TdsParser
- _parser.Disconnect();
- }
- else if ((_parser.State == TdsParserState.OpenNotLoggedIn) && (_parser.Connection.ConnectionOptions.MultiSubnetFailover))
- {
- // For MultiSubnet Failover during login, never break the connection, just close the TdsParser
- _parser.Disconnect();
- }
- else
- {
- fail = true; // We aren't yet logged in - just fail.
- }
- }
- }
- }
-
- if (fail)
- {
- _parser.State = TdsParserState.Broken; // We failed subsequent read, we have to quit!
- _parser.Connection.BreakConnection();
- }
- }
- else
- {
- // Caution: ProcessSNIError always returns a fatal error!
- AddError(_parser.ProcessSNIError(stateObj));
- }
- ThrowExceptionAndWarning();
-
- AssertValidState();
- }
-
- private uint GetSniPacket(PacketHandle packet, ref uint dataSize)
- {
- return SniPacketGetData(packet, _inBuff, ref dataSize);
- }
-
- private bool TrySetBufferSecureStrings()
- {
- bool mustClearBuffer = false;
-
- if (_securePasswords != null)
- {
- for (int i = 0; i < _securePasswords.Length; i++)
- {
- if (_securePasswords[i] != null)
- {
- IntPtr str = IntPtr.Zero;
- try
- {
- str = Marshal.SecureStringToBSTR(_securePasswords[i]);
- byte[] data = new byte[_securePasswords[i].Length * 2];
- Marshal.Copy(str, data, 0, _securePasswords[i].Length * 2);
- if (!BitConverter.IsLittleEndian)
- {
- Span span = data.AsSpan();
- for (int ii = 0; ii < _securePasswords[i].Length * 2; ii += 2)
- {
- short value = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(ii));
- BinaryPrimitives.WriteInt16BigEndian(span.Slice(ii), value);
- }
- }
- TdsParserStaticMethods.ObfuscatePassword(data);
- data.CopyTo(_outBuff, _securePasswordOffsetsInBuffer[i]);
-
- mustClearBuffer = true;
- }
- finally
- {
- Marshal.ZeroFreeBSTR(str);
- }
- }
- }
- }
-
- return mustClearBuffer;
- }
-
- public void ReadAsyncCallback(PacketHandle packet, uint error) =>
- ReadAsyncCallback(IntPtr.Zero, packet, error);
-
- public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error)
- {
- // Key never used.
- // Note - it's possible that when native calls managed that an asynchronous exception
- // could occur in the native->managed transition, which would
- // have two impacts:
- // 1) user event not called
- // 2) DecrementPendingCallbacks not called, which would mean this object would be leaked due
- // to the outstanding GCRoot until AppDomain.Unload.
- // We live with the above for the time being due to the constraints of the current
- // reliability infrastructure provided by the CLR.
-
- TaskCompletionSource source = _networkPacketTaskSource;
-#if DEBUG
- if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null))
- {
- source = _realNetworkPacketTaskSource;
- }
-#endif
-
- // The mars physical connection can get a callback
- // with a packet but no result after the connection is closed.
- if (source == null && _parser._pMarsPhysicalConObj == this)
- {
- return;
- }
-
- bool processFinallyBlock = true;
- try
- {
- Debug.Assert((packet.Type == 0 && PartialPacketContainsCompletePacket()) || (CheckPacket(packet, source) && source != null), "AsyncResult null on callback");
-
- if (_parser.MARSOn)
- {
- // Only take reset lock on MARS and Async.
- CheckSetResetConnectionState(error, CallbackType.Read);
- }
-
- ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite);
-
- // The timer thread may be unreliable under high contention scenarios. It cannot be
- // assumed that the timeout has happened on the timer thread callback. Check the timeout
- // synchronously and then call OnTimeoutSync to force an atomic change of state.
- if (TimeoutHasExpired)
- {
- OnTimeoutSync(asyncClose: true);
- }
-
- // try to change to the stopped state but only do so if currently in the running state
- // and use cmpexch so that all changes out of the running state are atomic
- int previousState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Stopped, TimeoutState.Running);
-
- // if the state is anything other than running then this query has reached an end so
- // set the correlation _timeoutIdentityValue to 0 to prevent late callbacks executing
- if (_timeoutState != TimeoutState.Running)
- {
- _timeoutIdentityValue = 0;
- }
-
- ProcessSniPacket(packet, error);
- }
- catch (Exception e)
- {
- processFinallyBlock = ADP.IsCatchableExceptionType(e);
- throw;
- }
- finally
- {
- // pendingCallbacks may be 2 after decrementing, this indicates that a fatal timeout is occurring, and therefore we shouldn't complete the task
- int pendingCallbacks = DecrementPendingCallbacks(false); // may dispose of GC handle.
- if ((processFinallyBlock) && (source != null) && (pendingCallbacks < 2))
- {
- if (error == 0)
- {
- if (_executionContext != null)
- {
- ExecutionContext.Run(_executionContext, s_readAsyncCallbackComplete, source);
- }
- else
- {
- source.TrySetResult(null);
- }
- }
- else
- {
- if (_executionContext != null)
- {
- ExecutionContext.Run(_executionContext, state => ReadAsyncCallbackCaptureException((TaskCompletionSource)state), source);
- }
- else
- {
- ReadAsyncCallbackCaptureException(source);
- }
- }
- }
-
- AssertValidState();
- }
- }
-
- public void WriteAsyncCallback(PacketHandle packet, uint sniError) =>
- WriteAsyncCallback(IntPtr.Zero, packet, sniError);
-
- public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError)
- { // Key never used.
- RemovePacketFromPendingList(packet);
- try
- {
- if (sniError != TdsEnums.SNI_SUCCESS)
- {
- SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.WriteAsyncCallback | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError);
- try
- {
- AddError(_parser.ProcessSNIError(this));
- ThrowExceptionAndWarning(asyncClose: true);
- }
- catch (Exception e)
- {
- TaskCompletionSource writeCompletionSource = _writeCompletionSource;
- if (writeCompletionSource != null)
- {
- writeCompletionSource.TrySetException(e);
- }
- else
- {
- _delayedWriteAsyncCallbackException = e;
-
- // Ensure that _delayedWriteAsyncCallbackException is set before checking _writeCompletionSource
- Interlocked.MemoryBarrier();
-
- // Double check that _writeCompletionSource hasn't been created in the meantime
- writeCompletionSource = _writeCompletionSource;
- if (writeCompletionSource != null)
- {
- Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null);
- if (delayedException != null)
- {
- writeCompletionSource.TrySetException(delayedException);
- }
- }
- }
-
- return;
- }
- }
- else
- {
- _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks;
- }
- }
- finally
- {
-#if DEBUG
- if (SqlCommand.DebugForceAsyncWriteDelay > 0)
- {
- new Timer(obj =>
- {
- Interlocked.Decrement(ref _asyncWriteCount);
- TaskCompletionSource writeCompletionSource = _writeCompletionSource;
- if (_asyncWriteCount == 0 && writeCompletionSource != null)
- {
- writeCompletionSource.TrySetResult(null);
- }
- }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite);
- }
- else
- {
-#else
- {
-#endif
- Interlocked.Decrement(ref _asyncWriteCount);
- }
- }
-#if DEBUG
- if (SqlCommand.DebugForceAsyncWriteDelay > 0)
- {
- return;
- }
-#endif
- TaskCompletionSource completionSource = _writeCompletionSource;
- if (_asyncWriteCount == 0 && completionSource != null)
- {
- completionSource.TrySetResult(null);
- }
- }
-
- /////////////////////////////////////////
- // Network/Packet Writing & Processing //
- /////////////////////////////////////////
-
- // Dumps contents of buffer to SNI for network write.
- internal Task WritePacket(byte flushMode, bool canAccumulate = false)
- {
- TdsParserState state = _parser.State;
- if ((state == TdsParserState.Closed) || (state == TdsParserState.Broken))
- {
- throw ADP.ClosedConnectionError();
- }
-
- if (
- // This appears to be an optimization to avoid writing empty packets in 2005
- // However, since we don't know the version prior to login Is2005OrNewer was always false prior to login
- // So removing the Is2005OrNewer check causes issues since the login packet happens to meet the rest of the conditions below
- // So we need to avoid this check prior to login completing
- state == TdsParserState.OpenLoggedIn
- && !_bulkCopyOpperationInProgress // ignore the condition checking for bulk copy
- && _outBytesUsed == (_outputHeaderLen +
- BinaryPrimitives.ReadInt32LittleEndian(_outBuff.AsSpan(_outputHeaderLen)))
- && _outputPacketCount == 0
- || _outBytesUsed == _outputHeaderLen
- && _outputPacketCount == 0)
- {
- return null;
- }
-
- byte status;
- byte packetNumber = _outputPacketNumber;
-
- // Set Status byte based whether this is end of message or not
- bool willCancel = (_cancelled) && (_parser._asyncWrite);
- if (willCancel)
- {
- status = TdsEnums.ST_EOM | TdsEnums.ST_IGNORE;
- ResetPacketCounters();
- }
- else if (TdsEnums.HARDFLUSH == flushMode)
- {
- status = TdsEnums.ST_EOM;
- ResetPacketCounters();
- }
- else if (TdsEnums.SOFTFLUSH == flushMode)
- {
- status = TdsEnums.ST_BATCH;
- _outputPacketNumber++;
- _outputPacketCount++;
- }
- else
- {
- status = TdsEnums.ST_EOM;
- Debug.Fail($"Unexpected argument {flushMode,-2:x2} to WritePacket");
- }
-
- _outBuff[0] = _outputMessageType; // Message Type
- _outBuff[1] = status;
- _outBuff[2] = (byte)(_outBytesUsed >> 8); // length - upper byte
- _outBuff[3] = (byte)(_outBytesUsed & 0xff); // length - lower byte
- _outBuff[4] = 0; // channel
- _outBuff[5] = 0;
- _outBuff[6] = packetNumber; // packet
- _outBuff[7] = 0; // window
-
- Task task = null;
- _parser.CheckResetConnection(this); // HAS SIDE EFFECTS - re-org at a later time if possible
-
- task = WriteSni(canAccumulate);
- AssertValidState();
-
- if (willCancel)
- {
- // If we have been canceled, then ensure that we write the ATTN packet as well
-#if NET
- task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket);
-#else
- task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket, _parser.Connection);
-#endif
- }
-
- return task;
- }
-
-#pragma warning disable 420 // a reference to a volatile field will not be treated as volatile
-
- private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false)
- {
- // Check for a stored exception
- Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null);
- if (delayedException != null)
- {
- throw delayedException;
- }
-
- Task task = null;
- _writeCompletionSource = null;
- PacketHandle packetPointer = EmptyReadPacket;
- bool sync = !_parser._asyncWrite;
- if (sync && _asyncWriteCount > 0)
- { // for example, SendAttention while there are writes pending
- Task waitForWrites = WaitForAccumulatedWrites();
- if (waitForWrites != null)
- {
- try
- {
- waitForWrites.Wait();
- }
- catch (AggregateException ae)
- {
- throw ae.InnerException;
- }
- }
- Debug.Assert(_asyncWriteCount == 0, "All async write should be finished");
- }
- if (!sync)
- {
- // Add packet to the pending list (since the callback can happen any time after we call SNIWritePacket)
- packetPointer = AddPacketToPendingList(packet);
- }
-
- // Async operation completion may be delayed (success pending).
- sniError = WritePacket(packet, sync);
-
- if (sniError == TdsEnums.SNI_SUCCESS_IO_PENDING)
- {
- Debug.Assert(!sync, "Completion should be handled in SniManagedWrapper");
- Interlocked.Increment(ref _asyncWriteCount);
- Debug.Assert(_asyncWriteCount >= 0);
- if (!canAccumulate)
- {
- // Create completion source (for callback to complete)
- _writeCompletionSource = new TaskCompletionSource();
- task = _writeCompletionSource.Task;
-
- // Ensure that setting _writeCompletionSource completes before checking _delayedWriteAsyncCallbackException
- Interlocked.MemoryBarrier();
-
- // Check for a stored exception
- delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null);
- if (delayedException != null)
- {
- throw delayedException;
- }
-
- // If there are no outstanding writes, see if we can shortcut and return null
- if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null)))
- {
- task = null;
- }
- }
- }
-#if DEBUG
- else if (!sync && !canAccumulate && SqlCommand.DebugForceAsyncWriteDelay > 0)
- {
- // Executed synchronously - callback will not be called
- TaskCompletionSource completion = new TaskCompletionSource();
- uint error = sniError;
- new Timer(obj =>
- {
- try
- {
- if (_parser.MARSOn)
- { // Only take reset lock on MARS.
- CheckSetResetConnectionState(error, CallbackType.Write);
- }
-
- if (error != TdsEnums.SNI_SUCCESS)
- {
- SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)error);
- AddError(_parser.ProcessSNIError(this));
- ThrowExceptionAndWarning(false, asyncClose);
- }
- AssertValidState();
- completion.SetResult(null);
- }
- catch (Exception e)
- {
- completion.SetException(e);
- }
- }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite);
- task = completion.Task;
- }
-#endif
- else
- {
- if (_parser.MARSOn)
- { // Only take reset lock on MARS.
- CheckSetResetConnectionState(sniError, CallbackType.Write);
- }
-
- if (sniError == TdsEnums.SNI_SUCCESS)
- {
- _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks;
-
- if (!sync)
- {
- // Since there will be no callback, remove the packet from the pending list
- Debug.Assert(IsValidPacket(packetPointer), "Packet added to list has an invalid pointer, can not remove from pending list");
- RemovePacketFromPendingList(packetPointer);
- }
- }
- else
- {
- SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError);
- AddError(_parser.ProcessSNIError(this));
- ThrowExceptionAndWarning(callerHasConnectionLock, asyncClose);
- }
- AssertValidState();
- }
- return task;
- }
-
- // Sends an attention signal - executing thread will consume attn.
- internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false)
- {
- if (!_attentionSent)
- {
- // Dumps contents of buffer to OOB write (currently only used for
- // attentions. There is no body for this message
- // Doesn't touch this._outBytesUsed
- if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken)
- {
- return;
- }
-
- PacketHandle attnPacket = CreateAndSetAttentionPacket();
-
- try
- {
- // Dev11 #344723: SqlClient stress test suspends System_Data!Tcp::ReadSync via a call to SqlDataReader::Close
- // Set _attentionSending to true before sending attention and reset after setting _attentionSent
- // This prevents a race condition between receiving the attention ACK and setting _attentionSent
- _attentionSending = true;
-#if DEBUG
- if (!s_skipSendAttention)
- {
-#endif
- // Take lock and send attention
- bool releaseLock = false;
- if ((mustTakeWriteLock) && (!_parser.Connection.ThreadHasParserLockForClose))
- {
- releaseLock = true;
- _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false);
- _parser.Connection.ThreadHasParserLockForClose = true;
- }
- try
- {
- // Check again (just in case the connection was closed while we were waiting)
- if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken)
- {
- return;
- }
-
- _parser._asyncWrite = false; // stop async write
- SNIWritePacket(attnPacket, out _, canAccumulate: false, callerHasConnectionLock: false, asyncClose);
- SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Sent Attention.", _objectID);
- }
- finally
- {
- if (releaseLock)
- {
- _parser.Connection.ThreadHasParserLockForClose = false;
- _parser.Connection._parserLock.Release();
- }
- }
-#if DEBUG
- }
-#endif
-
- SetTimeoutSeconds(AttentionTimeoutSeconds); // Initialize new attention timeout of 5 seconds.
- _attentionSent = true;
- }
- finally
- {
- _attentionSending = false;
- }
-
- SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParserStateObject.SendAttention | INFO | ADV | State Object Id {0}, Packet sent. Out Buffer: {1}, Out Bytes Used: {2}", _objectID, _outBuff, _outBytesUsed);
- SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Attention sent to the server.", _objectID);
-
- AssertValidState();
- }
- }
-
- private Task WriteSni(bool canAccumulate)
- {
- // Prepare packet, and write to packet.
- PacketHandle packet = GetResetWritePacket(_outBytesUsed);
- bool mustClearBuffer = TrySetBufferSecureStrings();
-
- SetPacketData(packet, _outBuff, _outBytesUsed);
- if (mustClearBuffer)
- {
- _outBuff.AsSpan(0, _outBytesUsed).Clear();
- }
-
- Debug.Assert(Parser.Connection._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock");
- Task task = SNIWritePacket(packet, out _, canAccumulate, callerHasConnectionLock: true);
-
- // Check to see if the timeout has occurred. This time out code is special case code to allow BCP writes to timeout. Eventually we should make all writes timeout.
- if (_bulkCopyOpperationInProgress && 0 == GetTimeoutRemaining())
- {
- _parser.Connection.ThreadHasParserLockForClose = true;
- try
- {
- Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point.");
- AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT));
- _bulkCopyWriteTimeout = true;
- SendAttention();
- _parser.ProcessPendingAck(this);
- ThrowExceptionAndWarning();
- }
- finally
- {
- _parser.Connection.ThreadHasParserLockForClose = false;
- }
- }
-
- // Special case logic for encryption removal.
- if (_parser.State == TdsParserState.OpenNotLoggedIn &&
- _parser.EncryptionOptions == EncryptionOptions.LOGIN)
- {
- // If no error occurred, and we are Open but not logged in, and
- // our encryptionOption state is login, remove the SSL Provider.
- // We only need encrypt the very first packet of the login message to the server.
-
- // We wanted to encrypt entire login channel, but there is
- // currently no mechanism to communicate this. Removing encryption post 1st packet
- // is a hard-coded agreement between client and server. We need some mechanism or
- // common change to be able to make this change in a non-breaking fashion.
- _parser.RemoveEncryption(); // Remove the SSL Provider.
- _parser.EncryptionOptions = EncryptionOptions.OFF; // Turn encryption off.
-
- // Since this packet was associated with encryption, dispose and re-create.
- ClearAllWritePackets();
- }
-
- SniWriteStatisticsAndTracing();
-
- ResetBuffer();
-
- AssertValidState();
- return task;
- }
-
- //////////////////////////////////////////////
- // Statistics, Tracing, and related methods //
- //////////////////////////////////////////////
-
- private void SniWriteStatisticsAndTracing()
- {
- SqlStatistics statistics = _parser.Statistics;
- if (statistics != null)
- {
- statistics.SafeIncrement(ref statistics._buffersSent);
- statistics.SafeAdd(ref statistics._bytesSent, _outBytesUsed);
- statistics.RequestNetworkServerTimer();
- }
- }
- }
-}
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 4ab2e2fdc5..63c4ee6647 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -444,9 +444,6 @@
Microsoft\Data\SqlClient\TdsParser.cs
-
- Microsoft\Data\SqlClient\TdsParser.SSPI.cs
-
Microsoft\Data\Sql\SqlDataSourceEnumerator.cs
@@ -1011,7 +1008,6 @@
-
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
deleted file mode 100644
index 4c3f1cc468..0000000000
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
+++ /dev/null
@@ -1,843 +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.Binary;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Security;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Data.Common;
-using Microsoft.Data.ProviderBase;
-
-namespace Microsoft.Data.SqlClient
-{
- internal partial class TdsParserStateObject
- {
- // Used for blanking out password in trace.
- internal int _tracePasswordOffset = 0;
- internal int _tracePasswordLength = 0;
- internal int _traceChangePasswordOffset = 0;
- internal int _traceChangePasswordLength = 0;
-
- //////////////////
- // Constructors //
- //////////////////
-
- protected TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async)
- {
- // Construct a MARS session
- Debug.Assert(parser != null, "no parser?");
- _parser = parser;
- _onTimeoutAsync = OnTimeoutAsync;
- SniContext = SniContext.Snix_GetMarsSession;
-
- Debug.Assert(_parser._physicalStateObj != null, "no physical session?");
- Debug.Assert(_parser._physicalStateObj._inBuff != null, "no in buffer?");
- Debug.Assert(_parser._physicalStateObj._outBuff != null, "no out buffer?");
- Debug.Assert(_parser._physicalStateObj._outBuff.Length ==
- _parser._physicalStateObj._inBuff.Length, "Unexpected unequal buffers.");
-
- // Determine packet size based on physical connection buffer lengths.
- SetPacketSize(_parser._physicalStateObj._outBuff.Length);
-
- CreateSessionHandle(physicalConnection, async);
-
- if (IsFailedHandle())
- {
- AddError(parser.ProcessSNIError(this));
- ThrowExceptionAndWarning();
- }
-
- // we post a callback that represents the call to dispose; once the
- // object is disposed, the next callback will cause the GC Handle to
- // be released.
- IncrementPendingCallbacks();
- _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer;
- }
-
- /////////////////////
- // General methods //
- /////////////////////
-
- internal int DecrementPendingCallbacks(bool release)
- {
- int remaining = Interlocked.Decrement(ref _pendingCallbacks);
- SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, after decrementing _pendingCallbacks: {1}", ObjectID, _pendingCallbacks);
-
- FreeGcHandle(remaining, release);
-
- // NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed
- // This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it
- Debug.Assert((remaining == -1 && SessionHandle.IsNull) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}");
- return remaining;
- }
-
- ///
- /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections)
- /// NOTE: This is not safe to do on a connection that is currently in use
- /// NOTE: This will mark the connection as broken if it is found to be dead
- ///
- /// True if the connection is still alive, otherwise false
- internal bool ValidateSNIConnection()
- {
- if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed)))
- {
- return false;
- }
-
- if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value <= CheckConnectionWindow)
- {
- return true;
- }
-
- uint error = TdsEnums.SNI_SUCCESS;
- SniContext = SniContext.Snix_Connect;
- try
- {
- Interlocked.Increment(ref _readingCount);
- error = CheckConnection();
- }
- finally
- {
- Interlocked.Decrement(ref _readingCount);
- }
- return (error == TdsEnums.SNI_SUCCESS) || (error == TdsEnums.SNI_WAIT_TIMEOUT);
- }
-
- // This method should only be called by ReadSni! If not - it may have problems with timeouts!
- private void ReadSniError(TdsParserStateObject stateObj, uint error)
- {
- if (TdsEnums.SNI_WAIT_TIMEOUT == error)
- {
- Debug.Assert(_syncOverAsync, "Should never reach here with async on!");
- bool fail = false;
-
- if (IsTimeoutStateExpired)
- { // This is now our second timeout - time to give up.
- fail = true;
- }
- else
- {
- stateObj.SetTimeoutStateStopped();
- Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point.");
- AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT));
-
- if (!stateObj._attentionSent)
- {
- if (stateObj.Parser.State == TdsParserState.OpenLoggedIn)
- {
- stateObj.SendAttention(mustTakeWriteLock: true);
-
- PacketHandle syncReadPacket = default;
- bool readFromNetwork = true;
- bool shouldDecrement = false;
- try
- {
- Interlocked.Increment(ref _readingCount);
- shouldDecrement = true;
- readFromNetwork = !PartialPacketContainsCompletePacket();
- if (readFromNetwork)
- {
- syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error);
- }
- else
- {
- error = TdsEnums.SNI_SUCCESS;
- }
-
- Interlocked.Decrement(ref _readingCount);
- shouldDecrement = false;
-
- if (TdsEnums.SNI_SUCCESS == error)
- {
- // We will end up letting the run method deal with the expected done:done_attn token stream.
- stateObj.ProcessSniPacket(syncReadPacket, TdsEnums.SNI_SUCCESS);
- return;
- }
- else
- {
- Debug.Assert(!readFromNetwork || !IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease");
- fail = true; // Subsequent read failed, time to give up.
- }
- }
- finally
- {
- if (shouldDecrement)
- {
- Interlocked.Decrement(ref _readingCount);
- }
-
- if (readFromNetwork && !IsPacketEmpty(syncReadPacket))
- {
- // Be sure to release packet, otherwise it will be leaked by native.
- ReleasePacket(syncReadPacket);
- }
- }
- }
- else
- {
- if (_parser._loginWithFailover)
- {
- // For DbMirroring Failover during login, never break the connection, just close the TdsParser
- _parser.Disconnect();
- }
- else if ((_parser.State == TdsParserState.OpenNotLoggedIn) && (_parser.Connection.ConnectionOptions.MultiSubnetFailover || _parser.Connection.ConnectionOptions.TransparentNetworkIPResolution))
- {
- // For MultiSubnet Failover during login, never break the connection, just close the TdsParser
- _parser.Disconnect();
- }
- else
- {
- fail = true; // We aren't yet logged in - just fail.
- }
- }
- }
- }
-
- if (fail)
- {
- _parser.State = TdsParserState.Broken; // We failed subsequent read, we have to quit!
- _parser.Connection.BreakConnection();
- }
- }
- else
- {
- // Caution: ProcessSNIError always returns a fatal error!
- AddError(_parser.ProcessSNIError(stateObj));
- }
- ThrowExceptionAndWarning();
-
- AssertValidState();
- }
-
- private uint GetSniPacket(PacketHandle packet, ref uint dataSize)
- {
- return SniPacketGetData(packet, _inBuff, ref dataSize);
- }
-
- private bool TrySetBufferSecureStrings()
- {
- bool mustClearBuffer = false;
-
- if (_securePasswords != null)
- {
- for (int i = 0; i < _securePasswords.Length; i++)
- {
- if (_securePasswords[i] != null)
- {
- IntPtr str = IntPtr.Zero;
- try
- {
- str = Marshal.SecureStringToBSTR(_securePasswords[i]);
- byte[] data = new byte[_securePasswords[i].Length * 2];
- Marshal.Copy(str, data, 0, _securePasswords[i].Length * 2);
- if (!BitConverter.IsLittleEndian)
- {
- Span span = data.AsSpan();
- for (int ii = 0; ii < _securePasswords[i].Length * 2; ii += 2)
- {
- short value = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(ii));
- BinaryPrimitives.WriteInt16BigEndian(span.Slice(ii), value);
- }
- }
- TdsParserStaticMethods.ObfuscatePassword(data);
- data.CopyTo(_outBuff, _securePasswordOffsetsInBuffer[i]);
-
- mustClearBuffer = true;
- }
- finally
- {
- Marshal.ZeroFreeBSTR(str);
- }
- }
- }
- }
-
- return mustClearBuffer;
- }
-
- public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error)
- {
- // Key never used.
- // Note - it's possible that when native calls managed that an asynchronous exception
- // could occur in the native->managed transition, which would
- // have two impacts:
- // 1) user event not called
- // 2) DecrementPendingCallbacks not called, which would mean this object would be leaked due
- // to the outstanding GCRoot until AppDomain.Unload.
- // We live with the above for the time being due to the constraints of the current
- // reliability infrastructure provided by the CLR.
-
- TaskCompletionSource source = _networkPacketTaskSource;
-#if DEBUG
- if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null))
- {
- source = _realNetworkPacketTaskSource;
- }
-#endif
-
- // The mars physical connection can get a callback
- // with a packet but no result after the connection is closed.
- if (source == null && _parser._pMarsPhysicalConObj == this)
- {
- return;
- }
-
- bool processFinallyBlock = true;
- try
- {
- Debug.Assert(CheckPacket(packet, source), "AsyncResult null on callback");
-
- if (_parser.MARSOn)
- {
- // Only take reset lock on MARS and Async.
- CheckSetResetConnectionState(error, CallbackType.Read);
- }
-
- ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite);
-
- // The timer thread may be unreliable under high contention scenarios. It cannot be
- // assumed that the timeout has happened on the timer thread callback. Check the timeout
- // synchronously and then call OnTimeoutSync to force an atomic change of state.
- if (TimeoutHasExpired)
- {
- OnTimeoutSync(asyncClose: true);
- }
-
- // try to change to the stopped state but only do so if currently in the running state
- // and use cmpexch so that all changes out of the running state are atomic
- int previousState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Stopped, TimeoutState.Running);
-
- // if the state is anything other than running then this query has reached an end so
- // set the correlation _timeoutIdentityValue to 0 to prevent late callbacks executing
- if (_timeoutState != TimeoutState.Running)
- {
- _timeoutIdentityValue = 0;
- }
-
- ProcessSniPacket(packet, error);
- }
- catch (Exception e)
- {
- processFinallyBlock = ADP.IsCatchableExceptionType(e);
- throw;
- }
- finally
- {
- // pendingCallbacks may be 2 after decrementing, this indicates that a fatal timeout is occurring, and therefore we shouldn't complete the task
- int pendingCallbacks = DecrementPendingCallbacks(false); // may dispose of GC handle.
- if ((processFinallyBlock) && (source != null) && (pendingCallbacks < 2))
- {
- if (error == 0)
- {
- if (_executionContext != null)
- {
- ExecutionContext.Run(_executionContext, s_readAsyncCallbackComplete, source);
- }
- else
- {
- source.TrySetResult(null);
- }
- }
- else
- {
- if (_executionContext != null)
- {
- ExecutionContext.Run(_executionContext, state => ReadAsyncCallbackCaptureException((TaskCompletionSource)state), source);
- }
- else
- {
- ReadAsyncCallbackCaptureException(source);
- }
- }
- }
-
- AssertValidState();
- }
- }
-
-#pragma warning disable 420 // a reference to a volatile field will not be treated as volatile
-
- public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError)
- { // Key never used.
- RemovePacketFromPendingList(packet);
- try
- {
- if (sniError != TdsEnums.SNI_SUCCESS)
- {
- SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.WriteAsyncCallback | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError);
- try
- {
- AddError(_parser.ProcessSNIError(this));
- ThrowExceptionAndWarning(asyncClose: true);
- }
- catch (Exception e)
- {
- TaskCompletionSource writeCompletionSource = _writeCompletionSource;
- if (writeCompletionSource != null)
- {
- writeCompletionSource.TrySetException(e);
- }
- else
- {
- _delayedWriteAsyncCallbackException = e;
-
- // Ensure that _delayedWriteAsyncCallbackException is set before checking _writeCompletionSource
- Interlocked.MemoryBarrier();
-
- // Double check that _writeCompletionSource hasn't been created in the meantime
- writeCompletionSource = _writeCompletionSource;
- if (writeCompletionSource != null)
- {
- Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null);
- if (delayedException != null)
- {
- writeCompletionSource.TrySetException(delayedException);
- }
- }
- }
-
- return;
- }
- }
- else
- {
- _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks;
- }
- }
- finally
- {
-#if DEBUG
- if (SqlCommand.DebugForceAsyncWriteDelay > 0)
- {
- new Timer(obj =>
- {
- Interlocked.Decrement(ref _asyncWriteCount);
- TaskCompletionSource writeCompletionSource = _writeCompletionSource;
- if (_asyncWriteCount == 0 && writeCompletionSource != null)
- {
- writeCompletionSource.TrySetResult(null);
- }
- }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite);
- }
- else
- {
-#else
- {
-#endif
- Interlocked.Decrement(ref _asyncWriteCount);
- }
- }
-#if DEBUG
- if (SqlCommand.DebugForceAsyncWriteDelay > 0)
- {
- return;
- }
-#endif
- TaskCompletionSource completionSource = _writeCompletionSource;
- if (_asyncWriteCount == 0 && completionSource != null)
- {
- completionSource.TrySetResult(null);
- }
- }
-
-#pragma warning restore 420
-
- /////////////////////////////////////////
- // Network/Packet Writing & Processing //
- /////////////////////////////////////////
-
- // Dumps contents of buffer to SNI for network write.
- internal Task WritePacket(byte flushMode, bool canAccumulate = false)
- {
- TdsParserState state = _parser.State;
- if ((state == TdsParserState.Closed) || (state == TdsParserState.Broken))
- {
- throw ADP.ClosedConnectionError();
- }
-
- if (
- // This appears to be an optimization to avoid writing empty packets in 2005
- // However, since we don't know the version prior to login Is2005OrNewer was always false prior to login
- // So removing the Is2005OrNewer check causes issues since the login packet happens to meet the rest of the conditions below
- // So we need to avoid this check prior to login completing
- state == TdsParserState.OpenLoggedIn
- && !_bulkCopyOpperationInProgress // ignore the condition checking for bulk copy
- && _outBytesUsed == (_outputHeaderLen +
- BinaryPrimitives.ReadInt32LittleEndian(_outBuff.AsSpan(_outputHeaderLen)))
- && _outputPacketCount == 0
- || _outBytesUsed == _outputHeaderLen
- && _outputPacketCount == 0)
- {
- return null;
- }
-
- byte status;
- byte packetNumber = _outputPacketNumber;
-
- // Set Status byte based whether this is end of message or not
- bool willCancel = (_cancelled) && (_parser._asyncWrite);
- if (willCancel)
- {
- status = TdsEnums.ST_EOM | TdsEnums.ST_IGNORE;
- ResetPacketCounters();
- }
- else if (TdsEnums.HARDFLUSH == flushMode)
- {
- status = TdsEnums.ST_EOM;
- ResetPacketCounters();
- }
- else if (TdsEnums.SOFTFLUSH == flushMode)
- {
- status = TdsEnums.ST_BATCH;
- _outputPacketNumber++;
- _outputPacketCount++;
- }
- else
- {
- status = TdsEnums.ST_EOM;
- Debug.Fail($"Unexpected argument {flushMode,-2:x2} to WritePacket");
- }
-
- _outBuff[0] = _outputMessageType; // Message Type
- _outBuff[1] = status;
- _outBuff[2] = (byte)(_outBytesUsed >> 8); // length - upper byte
- _outBuff[3] = (byte)(_outBytesUsed & 0xff); // length - lower byte
- _outBuff[4] = 0; // channel
- _outBuff[5] = 0;
- _outBuff[6] = packetNumber; // packet
- _outBuff[7] = 0; // window
-
- Task task = null;
- _parser.CheckResetConnection(this); // HAS SIDE EFFECTS - re-org at a later time if possible
-
- task = WriteSni(canAccumulate);
- AssertValidState();
-
- if (willCancel)
- {
- // If we have been canceled, then ensure that we write the ATTN packet as well
-#if NET
- task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket);
-#else
- task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket, _parser.Connection);
-#endif
- }
-
- return task;
- }
-
-#pragma warning disable 420 // a reference to a volatile field will not be treated as volatile
-
- private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false)
- {
- // Check for a stored exception
- Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null);
- if (delayedException != null)
- {
- throw delayedException;
- }
-
- Task task = null;
- _writeCompletionSource = null;
- PacketHandle packetPointer = EmptyReadPacket;
- bool sync = !_parser._asyncWrite;
- if (sync && _asyncWriteCount > 0)
- { // for example, SendAttention while there are writes pending
- Task waitForWrites = WaitForAccumulatedWrites();
- if (waitForWrites != null)
- {
- try
- {
- waitForWrites.Wait();
- }
- catch (AggregateException ae)
- {
- throw ae.InnerException;
- }
- }
- Debug.Assert(_asyncWriteCount == 0, "All async write should be finished");
- }
- if (!sync)
- {
- // Add packet to the pending list (since the callback can happen any time after we call SNIWritePacket)
- packetPointer = AddPacketToPendingList(packet);
- }
-
- // Async operation completion may be delayed (success pending).
- sniError = WritePacket(packet, sync);
-
- if (sniError == TdsEnums.SNI_SUCCESS_IO_PENDING)
- {
- Debug.Assert(!sync, "Completion should be handled in SniManagedWrapper");
- Interlocked.Increment(ref _asyncWriteCount);
- Debug.Assert(_asyncWriteCount >= 0);
- if (!canAccumulate)
- {
- // Create completion source (for callback to complete)
- _writeCompletionSource = new TaskCompletionSource();
- task = _writeCompletionSource.Task;
-
- // Ensure that setting _writeCompletionSource completes before checking _delayedWriteAsyncCallbackException
- Interlocked.MemoryBarrier();
-
- // Check for a stored exception
- delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null);
- if (delayedException != null)
- {
- throw delayedException;
- }
-
- // If there are no outstanding writes, see if we can shortcut and return null
- if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null)))
- {
- task = null;
- }
- }
- }
-#if DEBUG
- else if (!sync && !canAccumulate && SqlCommand.DebugForceAsyncWriteDelay > 0)
- {
- // Executed synchronously - callback will not be called
- TaskCompletionSource completion = new TaskCompletionSource();
- uint error = sniError;
- new Timer(obj =>
- {
- try
- {
- if (_parser.MARSOn)
- { // Only take reset lock on MARS.
- CheckSetResetConnectionState(error, CallbackType.Write);
- }
-
- if (error != TdsEnums.SNI_SUCCESS)
- {
- SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)error);
- AddError(_parser.ProcessSNIError(this));
- ThrowExceptionAndWarning(false, asyncClose);
- }
- AssertValidState();
- completion.SetResult(null);
- }
- catch (Exception e)
- {
- completion.SetException(e);
- }
- }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite);
- task = completion.Task;
- }
-#endif
- else
- {
- if (_parser.MARSOn)
- { // Only take reset lock on MARS.
- CheckSetResetConnectionState(sniError, CallbackType.Write);
- }
-
- if (sniError == TdsEnums.SNI_SUCCESS)
- {
- _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks;
-
- if (!sync)
- {
- // Since there will be no callback, remove the packet from the pending list
- Debug.Assert(IsValidPacket(packetPointer), "Packet added to list has an invalid pointer, can not remove from pending list");
- RemovePacketFromPendingList(packetPointer);
- }
- }
- else
- {
- SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError);
- AddError(_parser.ProcessSNIError(this));
- ThrowExceptionAndWarning(callerHasConnectionLock, asyncClose);
- }
- AssertValidState();
- }
- return task;
- }
-
-#pragma warning restore 420
-
- // Sends an attention signal - executing thread will consume attn.
- internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false)
- {
- if (!_attentionSent)
- {
- // Dumps contents of buffer to OOB write (currently only used for
- // attentions. There is no body for this message
- // Doesn't touch this._outBytesUsed
- if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken)
- {
- return;
- }
-
- PacketHandle attnPacket = CreateAndSetAttentionPacket();
-
- try
- {
- // Dev11 #344723: SqlClient stress test suspends System_Data!Tcp::ReadSync via a call to SqlDataReader::Close
- // Set _attentionSending to true before sending attention and reset after setting _attentionSent
- // This prevents a race condition between receiving the attention ACK and setting _attentionSent
- _attentionSending = true;
-#if DEBUG
- if (!s_skipSendAttention)
- {
-#endif
- // Take lock and send attention
- bool releaseLock = false;
- if ((mustTakeWriteLock) && (!_parser.Connection.ThreadHasParserLockForClose))
- {
- releaseLock = true;
- _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false);
- _parser.Connection.ThreadHasParserLockForClose = true;
- }
- try
- {
- // Check again (just in case the connection was closed while we were waiting)
- if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken)
- {
- return;
- }
-
- _parser._asyncWrite = false; // stop async write
- SNIWritePacket(attnPacket, out _, canAccumulate: false, callerHasConnectionLock: false, asyncClose);
- SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Sent Attention.", _objectID);
- }
- finally
- {
- if (releaseLock)
- {
- _parser.Connection.ThreadHasParserLockForClose = false;
- _parser.Connection._parserLock.Release();
- }
- }
-#if DEBUG
- }
-#endif
-
- SetTimeoutSeconds(AttentionTimeoutSeconds); // Initialize new attention timeout of 5 seconds.
- _attentionSent = true;
- }
- finally
- {
- _attentionSending = false;
- }
-
- SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParserStateObject.SendAttention | INFO | ADV | State Object Id {0}, Packet sent. Out Buffer: {1}, Out Bytes Used: {2}", _objectID, _outBuff, _outBytesUsed);
- SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Attention sent to the server.", _objectID);
-
- AssertValidState();
- }
- }
-
- private Task WriteSni(bool canAccumulate)
- {
- // Prepare packet, and write to packet.
- PacketHandle packet = GetResetWritePacket(_outBytesUsed);
- bool mustClearBuffer = TrySetBufferSecureStrings();
-
- SetPacketData(packet, _outBuff, _outBytesUsed);
- if (mustClearBuffer)
- {
- _outBuff.AsSpan(0, _outBytesUsed).Clear();
- }
-
- Debug.Assert(Parser.Connection._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock");
- Task task = SNIWritePacket(packet, out _, canAccumulate, callerHasConnectionLock: true);
-
- // Check to see if the timeout has occurred. This time out code is special case code to allow BCP writes to timeout. Eventually we should make all writes timeout.
- if (_bulkCopyOpperationInProgress && 0 == GetTimeoutRemaining())
- {
- _parser.Connection.ThreadHasParserLockForClose = true;
- try
- {
- Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point.");
- AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT));
- _bulkCopyWriteTimeout = true;
- SendAttention();
- _parser.ProcessPendingAck(this);
- ThrowExceptionAndWarning();
- }
- finally
- {
- _parser.Connection.ThreadHasParserLockForClose = false;
- }
- }
-
- // Special case logic for encryption removal.
- if (_parser.State == TdsParserState.OpenNotLoggedIn &&
- (_parser.EncryptionOptions & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN)
- {
- // If no error occurred, and we are Open but not logged in, and
- // our encryptionOption state is login, remove the SSL Provider.
- // We only need encrypt the very first packet of the login message to the server.
-
- // We wanted to encrypt entire login channel, but there is
- // currently no mechanism to communicate this. Removing encryption post 1st packet
- // is a hard-coded agreement between client and server. We need some mechanism or
- // common change to be able to make this change in a non-breaking fashion.
- _parser.RemoveEncryption(); // Remove the SSL Provider.
- _parser.EncryptionOptions = EncryptionOptions.OFF | (_parser.EncryptionOptions & ~EncryptionOptions.OPTIONS_MASK); // Turn encryption off.
-
- // Since this packet was associated with encryption, dispose and re-create.
- ClearAllWritePackets();
- }
-
- SniWriteStatisticsAndTracing();
-
- ResetBuffer();
-
- AssertValidState();
- return task;
- }
-
- //////////////////////////////////////////////
- // Statistics, Tracing, and related methods //
- //////////////////////////////////////////////
-
- private void SniWriteStatisticsAndTracing()
- {
- SqlStatistics statistics = _parser.Statistics;
- if (statistics != null)
- {
- statistics.SafeIncrement(ref statistics._buffersSent);
- statistics.SafeAdd(ref statistics._bytesSent, _outBytesUsed);
- statistics.RequestNetworkServerTimer();
- }
- if (SqlClientEventSource.Log.IsAdvancedTraceOn())
- {
- // If we have tracePassword variables set, we are flushing TDSLogin and so we need to
- // blank out password in buffer. Buffer has already been sent to netlib, so no danger
- // of losing info.
- if (_tracePasswordOffset != 0)
- {
- for (int i = _tracePasswordOffset; i < _tracePasswordOffset +
- _tracePasswordLength; i++)
- {
- _outBuff[i] = 0;
- }
-
- // Reset state.
- _tracePasswordOffset = 0;
- _tracePasswordLength = 0;
- }
- if (_traceChangePasswordOffset != 0)
- {
- for (int i = _traceChangePasswordOffset; i < _traceChangePasswordOffset +
- _traceChangePasswordLength; i++)
- {
- _outBuff[i] = 0;
- }
-
- // Reset state.
- _traceChangePasswordOffset = 0;
- _traceChangePasswordLength = 0;
- }
- }
- SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.WritePacket | INFO | ADV | State Object Id {0}, Packet sent. Out buffer: {1}, Out Bytes Used: {2}", ObjectID, _outBuff, _outBytesUsed);
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs
deleted file mode 100644
index d888c2e637..0000000000
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs
+++ /dev/null
@@ -1,96 +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.
-
-// @TODO: This is only a stub class for clearing errors while merging other files.
-
-using System;
-using System.Data.Common;
-using System.Threading.Tasks;
-using Microsoft.Data.Common.ConnectionString;
-using Microsoft.Data.ProviderBase;
-using Microsoft.Data.SqlClient.ConnectionPool;
-using Microsoft.SqlServer.Server;
-
-namespace Microsoft.Data.SqlClient
-{
- public class SqlConnection : DbConnection
- {
- internal bool _applyTransientFaultHandling = false;
- internal Task _currentReconnectionTask = null;
- internal SessionData _recoverySessionData = null;
-
- #region Constructors
-
- internal SqlConnection() {}
-
- internal SqlConnection(string connectionString) {}
-
- #endregion
-
- #region Properties
-
- internal Guid ClientConnectionId { get; set; }
-
- #if NETFRAMEWORK
- internal static System.Security.CodeAccessPermission ExecutePermission { get; set; }
- #endif
-
- internal bool HasLocalTransaction { get; set; }
-
- internal DbConnectionInternal InnerConnection { get; set; }
-
- internal bool Is2008OrNewer { get; set; }
-
- internal int ObjectID { get; set; }
-
- internal TdsParser Parser { get; set; }
-
- internal DbConnectionPoolGroup PoolGroup { get; set; }
-
- internal SqlStatistics Statistics { get; set; }
-
- internal bool StatisticsEnabled { get; set; }
-
- internal DbConnectionOptions UserConnectionOptions { get; set; }
-
- #endregion
-
- #region Methods
-
- internal void Abort(Exception e) { }
-
- internal void AddWeakReference(object value, int tag) { }
-
- internal SqlTransaction BeginTransaction() =>
- null;
-
- internal byte[] GetBytes(object o, out Format format, out int maxSize)
- {
- format = Format.Unknown;
- maxSize = 0;
- return null;
- }
-
- internal SqlInternalConnectionTds GetOpenTdsConnection() => null;
-
- internal void PermissionDemand() { }
-
- internal Task RegisterForConnectionCloseNotification(Task outerTask, object value, int tag) =>
- Task.FromResult(default);
-
- internal void SetInnerConnectionEvent(DbConnectionInternal to) { }
-
- internal bool SetInnerConnectionFrom(DbConnectionInternal to, DbConnectionInternal from) =>
- false;
-
- internal void SetInnerConnectionTo(DbConnectionInternal to) { }
-
- internal void ValidateConnectionForExecute(string method, SqlCommand command) { }
-
- internal Task ValidateAndReconnect(Action beforeDisconnect, int timeout) =>
- null;
-
- #endregion
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.stub.cs
deleted file mode 100644
index 005e7eb633..0000000000
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.stub.cs
+++ /dev/null
@@ -1,17 +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.
-
-// @TODO: This is only a stub class for clearing errors while merging other files.
-
-using System.Data;
-
-namespace Microsoft.Data.SqlClient
-{
- internal class SqlDataReaderSmi : SqlDataReader
- {
- internal SqlDataReaderSmi() : base(null, CommandBehavior.Default)
- {
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnectionSmi.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnectionSmi.stub.cs
deleted file mode 100644
index 333cc3bf5f..0000000000
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnectionSmi.stub.cs
+++ /dev/null
@@ -1,22 +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;
-
-#if NETFRAMEWORK
-
-namespace Microsoft.Data.SqlClient
-{
- // DO NOT USE THIS FILE IN ANY PROJECT!
- // This is a temporary stub to enable migrating DbConnectionInternal to the common project.
- internal abstract class SqlInternalConnectionSmi : SqlInternalConnection
- {
- protected SqlInternalConnectionSmi(SqlConnectionString connectionOptions) : base(connectionOptions)
- {
- throw new NotImplementedException();
- }
- }
-}
-
-#endif
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs
deleted file mode 100644
index 6226f958a5..0000000000
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs
+++ /dev/null
@@ -1,227 +0,0 @@
-using System;
-using System.Buffers;
-using System.Diagnostics;
-using System.Text;
-using Microsoft.Data.SqlClient.Utilities;
-
-#nullable enable
-
-namespace Microsoft.Data.SqlClient
-{
-
- internal partial class TdsParser
- {
- private static readonly Encoding s_utf8EncodingWithoutBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
-
- internal void ProcessSSPI(int receivedLength)
- {
- Debug.Assert(_authenticationProvider is not null);
-
- SniContext outerContext = _physicalStateObj.SniContext;
- _physicalStateObj.SniContext = SniContext.Snix_ProcessSspi;
-
- // allocate received buffer based on length from SSPI message
- byte[] receivedBuff = ArrayPool.Shared.Rent(receivedLength);
-
- try
- {
- // read SSPI data received from server
- Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
- TdsOperationStatus result = _physicalStateObj.TryReadByteArray(receivedBuff, receivedLength);
- if (result != TdsOperationStatus.Done)
- {
- throw SQL.SynchronousCallMayNotPend();
- }
-
- // allocate send buffer and initialize length
- var writer = ObjectPools.BufferWriter.Rent();
-
- try
- {
- // make call for SSPI data
- _authenticationProvider!.WriteSSPIContext(receivedBuff.AsSpan(0, receivedLength), writer);
-
- // DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA!
- _physicalStateObj.WriteByteSpan(writer.WrittenSpan);
-
- }
- finally
- {
- ObjectPools.BufferWriter.Return(writer);
- }
- }
- finally
- {
- ArrayPool.Shared.Return(receivedBuff, clearArray: true);
- }
-
- // set message type so server knows its a SSPI response
- _physicalStateObj._outputMessageType = TdsEnums.MT_SSPI;
-
- // send to server
- _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
- _physicalStateObj.SniContext = outerContext;
- }
-
-#nullable disable
-
- internal void TdsLogin(
- SqlLogin rec,
- TdsEnums.FeatureExtension requestedFeatures,
- SessionData recoverySessionData,
- FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData,
- SqlConnectionEncryptOption encrypt)
- {
- _physicalStateObj.SetTimeoutSeconds(rec.timeout);
-
- Debug.Assert(recoverySessionData == null || (requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0, "Recovery session data without session recovery feature request");
- Debug.Assert(TdsEnums.MAXLEN_HOSTNAME >= rec.hostName.Length, "_workstationId.Length exceeds the max length for this value");
-
- Debug.Assert(!(rec.useSSPI && _connHandler._fedAuthRequired), "Cannot use SSPI when server has responded 0x01 for FedAuthRequired PreLogin Option.");
- Debug.Assert(!rec.useSSPI || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Cannot use both SSPI and FedAuth");
- Debug.Assert(fedAuthFeatureExtensionData == null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0, "fedAuthFeatureExtensionData provided without fed auth feature request");
- Debug.Assert(fedAuthFeatureExtensionData != null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Fed Auth feature requested without specifying fedAuthFeatureExtensionData.");
-
- Debug.Assert(rec.userName == null || (rec.userName != null && TdsEnums.MAXLEN_CLIENTID >= rec.userName.Length), "_userID.Length exceeds the max length for this value");
- Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTID >= rec.credential.UserId.Length), "_credential.UserId.Length exceeds the max length for this value");
-
- Debug.Assert(rec.password == null || (rec.password != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.password.Length), "_password.Length exceeds the max length for this value");
- Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.credential.Password.Length), "_credential.Password.Length exceeds the max length for this value");
-
- Debug.Assert(rec.credential != null || rec.userName != null || rec.password != null, "cannot mix the new secure password system and the connection string based password");
- Debug.Assert(rec.newSecurePassword != null || rec.newPassword != null, "cannot have both new secure change password and string based change password");
- Debug.Assert(TdsEnums.MAXLEN_APPNAME >= rec.applicationName.Length, "_applicationName.Length exceeds the max length for this value");
- Debug.Assert(TdsEnums.MAXLEN_SERVERNAME >= rec.serverName.Length, "_dataSource.Length exceeds the max length for this value");
- Debug.Assert(TdsEnums.MAXLEN_LANGUAGE >= rec.language.Length, "_currentLanguage .Length exceeds the max length for this value");
- Debug.Assert(TdsEnums.MAXLEN_DATABASE >= rec.database.Length, "_initialCatalog.Length exceeds the max length for this value");
- Debug.Assert(TdsEnums.MAXLEN_ATTACHDBFILE >= rec.attachDBFilename.Length, "_attachDBFileName.Length exceeds the max length for this value");
-
- Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point.");
- _connHandler!.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.LoginBegin);
- _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth);
-
- // get the password up front to use in sspi logic below
- byte[] encryptedPassword = null;
- byte[] encryptedChangePassword = null;
- int encryptedPasswordLengthInBytes;
- int encryptedChangePasswordLengthInBytes;
- bool useFeatureExt = (requestedFeatures != TdsEnums.FeatureExtension.None);
-
- string userName;
-
- if (rec.credential != null)
- {
- userName = rec.credential.UserId;
- encryptedPasswordLengthInBytes = rec.credential.Password.Length * 2;
- }
- else
- {
- userName = rec.userName;
- encryptedPassword = TdsParserStaticMethods.ObfuscatePassword(rec.password);
- encryptedPasswordLengthInBytes = encryptedPassword.Length; // password in clear text is already encrypted and its length is in byte
- }
-
- if (rec.newSecurePassword != null)
- {
- encryptedChangePasswordLengthInBytes = rec.newSecurePassword.Length * 2;
- }
- else
- {
- encryptedChangePassword = TdsParserStaticMethods.ObfuscatePassword(rec.newPassword);
- encryptedChangePasswordLengthInBytes = encryptedChangePassword.Length;
- }
-
- // set the message type
- _physicalStateObj._outputMessageType = TdsEnums.MT_LOGIN7;
-
- // length in bytes
- int length = TdsEnums.SQL2005_LOG_REC_FIXED_LEN;
-
- string clientInterfaceName = TdsEnums.SQL_PROVIDER_NAME;
- Debug.Assert(TdsEnums.MAXLEN_CLIENTINTERFACE >= clientInterfaceName.Length, "cchCltIntName can specify at most 128 unicode characters. See Tds spec");
-
- // add up variable-len portions (multiply by 2 for byte len of char strings)
- //
- checked
- {
- length += (rec.hostName.Length + rec.applicationName.Length +
- rec.serverName.Length + clientInterfaceName.Length +
- rec.language.Length + rec.database.Length +
- rec.attachDBFilename.Length) * 2;
- if (useFeatureExt)
- {
- length += 4;
- }
- }
-
- // allocate memory for SSPI variables
- ArrayBufferWriter sspiWriter = null;
-
- try
- {
- // only add lengths of password and username if not using SSPI or requesting federated authentication info
- if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested))
- {
- checked
- {
- length += (userName.Length * 2) + encryptedPasswordLengthInBytes
- + encryptedChangePasswordLengthInBytes;
- }
- }
- else
- {
- if (rec.useSSPI)
- {
- sspiWriter = ObjectPools.BufferWriter.Rent();
-
- // Call helper function for SSPI data and actual length.
- // Since we don't have SSPI data from the server, send null for the
- // byte[] buffer and 0 for the int length.
- Debug.Assert(SniContext.Snix_Login == _physicalStateObj.SniContext, $"Unexpected SniContext. Expecting Snix_Login, actual value is '{_physicalStateObj.SniContext}'");
- _physicalStateObj.SniContext = SniContext.Snix_LoginSspi;
- _authenticationProvider.WriteSSPIContext(ReadOnlySpan.Empty, sspiWriter);
-
- _physicalStateObj.SniContext = SniContext.Snix_Login;
-
- checked
- {
- length += (int)sspiWriter.WrittenCount;
- }
- }
- }
-
- int feOffset = length;
- // calculate and reserve the required bytes for the featureEx
- length = ApplyFeatureExData(requestedFeatures, recoverySessionData, fedAuthFeatureExtensionData, useFeatureExt, length);
-
- WriteLoginData(rec,
- requestedFeatures,
- recoverySessionData,
- fedAuthFeatureExtensionData,
- encrypt,
- encryptedPassword,
- encryptedChangePassword,
- encryptedPasswordLengthInBytes,
- encryptedChangePasswordLengthInBytes,
- useFeatureExt,
- userName,
- length,
- feOffset,
- clientInterfaceName,
- sspiWriter is { } ? sspiWriter.WrittenSpan : ReadOnlySpan.Empty);
- }
- finally
- {
- if (sspiWriter is not null)
- {
- ObjectPools.BufferWriter.Return(sspiWriter);
- }
- }
-
- _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
- _physicalStateObj.ResetSecurePasswordsInformation(); // Password information is needed only from Login process; done with writing login packet and should clear information
- _physicalStateObj.HasPendingData = true;
- _physicalStateObj._messageStatus = 0;
- }// tdsLogin
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
index 35df415a7c..4bfcabf65f 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -29,6 +29,7 @@
using Microsoft.Data.SqlClient.DataClassification;
using Microsoft.Data.SqlClient.LocalDb;
using Microsoft.Data.SqlClient.Server;
+using Microsoft.Data.SqlClient.Utilities;
#if NETFRAMEWORK
using Microsoft.Data.SqlTypes;
#endif
@@ -83,6 +84,7 @@ internal sealed partial class TdsParser
private int _defaultLCID;
+ private static readonly Encoding s_utf8EncodingWithoutBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
internal Encoding _defaultEncoding = null; // for sql character data
private static EncryptionOptions s_sniSupportedEncryptionOption = TdsParserStateObjectFactory.Singleton.EncryptionOptions;
@@ -1232,6 +1234,217 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
return PreLoginHandshakeStatus.Successful;
}
+ internal void TdsLogin(
+ SqlLogin rec,
+ TdsEnums.FeatureExtension requestedFeatures,
+ SessionData recoverySessionData,
+ FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData,
+ SqlConnectionEncryptOption encrypt)
+ {
+ _physicalStateObj.SetTimeoutSeconds(rec.timeout);
+
+ Debug.Assert(recoverySessionData == null || (requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0, "Recovery session data without session recovery feature request");
+ Debug.Assert(TdsEnums.MAXLEN_HOSTNAME >= rec.hostName.Length, "_workstationId.Length exceeds the max length for this value");
+
+ Debug.Assert(!(rec.useSSPI && _connHandler._fedAuthRequired), "Cannot use SSPI when server has responded 0x01 for FedAuthRequired PreLogin Option.");
+ Debug.Assert(!rec.useSSPI || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Cannot use both SSPI and FedAuth");
+ Debug.Assert(fedAuthFeatureExtensionData == null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0, "fedAuthFeatureExtensionData provided without fed auth feature request");
+ Debug.Assert(fedAuthFeatureExtensionData != null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Fed Auth feature requested without specifying fedAuthFeatureExtensionData.");
+
+ Debug.Assert(rec.userName == null || (rec.userName != null && TdsEnums.MAXLEN_CLIENTID >= rec.userName.Length), "_userID.Length exceeds the max length for this value");
+ Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTID >= rec.credential.UserId.Length), "_credential.UserId.Length exceeds the max length for this value");
+
+ Debug.Assert(rec.password == null || (rec.password != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.password.Length), "_password.Length exceeds the max length for this value");
+ Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.credential.Password.Length), "_credential.Password.Length exceeds the max length for this value");
+
+ Debug.Assert(rec.credential != null || rec.userName != null || rec.password != null, "cannot mix the new secure password system and the connection string based password");
+ Debug.Assert(rec.newSecurePassword != null || rec.newPassword != null, "cannot have both new secure change password and string based change password");
+ Debug.Assert(TdsEnums.MAXLEN_APPNAME >= rec.applicationName.Length, "_applicationName.Length exceeds the max length for this value");
+ Debug.Assert(TdsEnums.MAXLEN_SERVERNAME >= rec.serverName.Length, "_dataSource.Length exceeds the max length for this value");
+ Debug.Assert(TdsEnums.MAXLEN_LANGUAGE >= rec.language.Length, "_currentLanguage .Length exceeds the max length for this value");
+ Debug.Assert(TdsEnums.MAXLEN_DATABASE >= rec.database.Length, "_initialCatalog.Length exceeds the max length for this value");
+ Debug.Assert(TdsEnums.MAXLEN_ATTACHDBFILE >= rec.attachDBFilename.Length, "_attachDBFileName.Length exceeds the max length for this value");
+
+ Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point.");
+ _connHandler!.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.LoginBegin);
+ _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth);
+
+ // get the password up front to use in sspi logic below
+ byte[] encryptedPassword = null;
+ byte[] encryptedChangePassword = null;
+ int encryptedPasswordLengthInBytes;
+ int encryptedChangePasswordLengthInBytes;
+ bool useFeatureExt = (requestedFeatures != TdsEnums.FeatureExtension.None);
+
+ string userName;
+
+ if (rec.credential != null)
+ {
+ userName = rec.credential.UserId;
+ encryptedPasswordLengthInBytes = rec.credential.Password.Length * 2;
+ }
+ else
+ {
+ userName = rec.userName;
+ encryptedPassword = TdsParserStaticMethods.ObfuscatePassword(rec.password);
+ encryptedPasswordLengthInBytes = encryptedPassword.Length; // password in clear text is already encrypted and its length is in byte
+ }
+
+ if (rec.newSecurePassword != null)
+ {
+ encryptedChangePasswordLengthInBytes = rec.newSecurePassword.Length * 2;
+ }
+ else
+ {
+ encryptedChangePassword = TdsParserStaticMethods.ObfuscatePassword(rec.newPassword);
+ encryptedChangePasswordLengthInBytes = encryptedChangePassword.Length;
+ }
+
+ // set the message type
+ _physicalStateObj._outputMessageType = TdsEnums.MT_LOGIN7;
+
+ // length in bytes
+ int length = TdsEnums.SQL2005_LOG_REC_FIXED_LEN;
+
+ string clientInterfaceName = TdsEnums.SQL_PROVIDER_NAME;
+ Debug.Assert(TdsEnums.MAXLEN_CLIENTINTERFACE >= clientInterfaceName.Length, "cchCltIntName can specify at most 128 unicode characters. See Tds spec");
+
+ // add up variable-len portions (multiply by 2 for byte len of char strings)
+ //
+ checked
+ {
+ length += (rec.hostName.Length + rec.applicationName.Length +
+ rec.serverName.Length + clientInterfaceName.Length +
+ rec.language.Length + rec.database.Length +
+ rec.attachDBFilename.Length) * 2;
+ if (useFeatureExt)
+ {
+ length += 4;
+ }
+ }
+
+ // allocate memory for SSPI variables
+ ArrayBufferWriter sspiWriter = null;
+
+ try
+ {
+ // only add lengths of password and username if not using SSPI or requesting federated authentication info
+ if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested))
+ {
+ checked
+ {
+ length += (userName.Length * 2) + encryptedPasswordLengthInBytes
+ + encryptedChangePasswordLengthInBytes;
+ }
+ }
+ else
+ {
+ if (rec.useSSPI)
+ {
+ sspiWriter = ObjectPools.BufferWriter.Rent();
+
+ // Call helper function for SSPI data and actual length.
+ // Since we don't have SSPI data from the server, send null for the
+ // byte[] buffer and 0 for the int length.
+ Debug.Assert(SniContext.Snix_Login == _physicalStateObj.SniContext, $"Unexpected SniContext. Expecting Snix_Login, actual value is '{_physicalStateObj.SniContext}'");
+ _physicalStateObj.SniContext = SniContext.Snix_LoginSspi;
+ _authenticationProvider.WriteSSPIContext(ReadOnlySpan.Empty, sspiWriter);
+
+ _physicalStateObj.SniContext = SniContext.Snix_Login;
+
+ checked
+ {
+ length += (int)sspiWriter.WrittenCount;
+ }
+ }
+ }
+
+ int feOffset = length;
+ // calculate and reserve the required bytes for the featureEx
+ length = ApplyFeatureExData(requestedFeatures, recoverySessionData, fedAuthFeatureExtensionData, useFeatureExt, length);
+
+ WriteLoginData(rec,
+ requestedFeatures,
+ recoverySessionData,
+ fedAuthFeatureExtensionData,
+ encrypt,
+ encryptedPassword,
+ encryptedChangePassword,
+ encryptedPasswordLengthInBytes,
+ encryptedChangePasswordLengthInBytes,
+ useFeatureExt,
+ userName,
+ length,
+ feOffset,
+ clientInterfaceName,
+ sspiWriter is { } ? sspiWriter.WrittenSpan : ReadOnlySpan.Empty);
+ }
+ finally
+ {
+ if (sspiWriter is not null)
+ {
+ ObjectPools.BufferWriter.Return(sspiWriter);
+ }
+ }
+
+ _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
+ _physicalStateObj.ResetSecurePasswordsInformation(); // Password information is needed only from Login process; done with writing login packet and should clear information
+ _physicalStateObj.HasPendingData = true;
+ _physicalStateObj._messageStatus = 0;
+ }
+
+#nullable enable
+ internal void ProcessSSPI(int receivedLength)
+ {
+ Debug.Assert(_authenticationProvider is not null);
+
+ SniContext outerContext = _physicalStateObj.SniContext;
+ _physicalStateObj.SniContext = SniContext.Snix_ProcessSspi;
+
+ // allocate received buffer based on length from SSPI message
+ byte[] receivedBuff = ArrayPool.Shared.Rent(receivedLength);
+
+ try
+ {
+ // read SSPI data received from server
+ Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
+ TdsOperationStatus result = _physicalStateObj.TryReadByteArray(receivedBuff, receivedLength);
+ if (result != TdsOperationStatus.Done)
+ {
+ throw SQL.SynchronousCallMayNotPend();
+ }
+
+ // allocate send buffer and initialize length
+ var writer = ObjectPools.BufferWriter.Rent();
+
+ try
+ {
+ // make call for SSPI data
+ _authenticationProvider!.WriteSSPIContext(receivedBuff.AsSpan(0, receivedLength), writer);
+
+ // DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA!
+ _physicalStateObj.WriteByteSpan(writer.WrittenSpan);
+
+ }
+ finally
+ {
+ ObjectPools.BufferWriter.Return(writer);
+ }
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(receivedBuff, clearArray: true);
+ }
+
+ // set message type so server knows its a SSPI response
+ _physicalStateObj._outputMessageType = TdsEnums.MT_SSPI;
+
+ // send to server
+ _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
+ _physicalStateObj.SniContext = outerContext;
+ }
+#nullable disable
+
internal void Deactivate(bool connectionIsDoomed)
{
// Called when the connection that owns us is deactivated.
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.stub.cs
deleted file mode 100644
index 27bd25a989..0000000000
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.stub.cs
+++ /dev/null
@@ -1,109 +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.
-
-// @TODO: This is only a stub partial for clearing errors while merging other files.
-
-using System;
-using System.Data.SqlTypes;
-using System.Threading.Tasks;
-using Microsoft.Data.Sql;
-
-namespace Microsoft.Data.SqlClient
-{
- internal partial class TdsParser
- {
- internal bool _asyncWrite = false;
-
- internal SqlInternalConnectionTds Connection { get; set; }
-
- internal SqlInternalTransaction CurrentTransaction { get; set; }
-
- internal TdsParserState State { get; set; }
-
- internal static SqlDecimal AdjustSqlDecimalScale(SqlDecimal sqlValue, int scale) =>
- SqlDecimal.Null;
-
- internal object EncryptColumnValue(
- object value,
- SqlMetaDataPriv metadata,
- string column,
- TdsParserStateObject stateObj,
- bool isDataFeed,
- bool isSqlType) =>
- null;
-
- internal TdsParserStateObject GetSession(object owner) =>
- null;
-
- internal void LoadColumnEncryptionKeys(
- _SqlMetaDataSet metadataCollection,
- SqlConnection connection,
- SqlCommand command = null)
- {
- }
-
- internal void Run(
- RunBehavior runBehavior,
- SqlCommand cmdHandler,
- SqlDataReader dataStream,
- BulkCopySimpleResultSet bulkCopyHandler,
- TdsParserStateObject stateObj)
- {
- }
-
- #if NETFRAMEWORK
- internal void RunReliably(
- RunBehavior runBehavior,
- SqlCommand cmdHandler,
- SqlDataReader dataStream,
- BulkCopySimpleResultSet bulkCopyHandler,
- TdsParserStateObject stateObj)
- {
- }
- #endif
-
- internal Task TdsExecuteSQLBatch(
- string text,
- int timeout,
- SqlNotificationRequest notificationRequest,
- TdsParserStateObject stateObj,
- bool sync,
- bool callerHasConnectionLock = false,
- byte[] enclavePackage = null) =>
- null;
-
- internal bool ShouldEncryptValuesForBulkCopy() =>
- false;
-
- internal Task WriteBulkCopyDone(TdsParserStateObject stateObj) =>
- Task.CompletedTask;
-
- internal void WriteBulkCopyMetaData(
- _SqlMetaDataSet metadataCollection,
- int count,
- TdsParserStateObject stateObj)
- {
- }
-
- internal Task WriteBulkCopyValue(
- object value,
- SqlMetaDataPriv metadata,
- TdsParserStateObject stateObj,
- bool isSqlType,
- bool isDataFeed,
- bool isNull) =>
- Task.CompletedTask;
-
- internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject stateObje) =>
- Task.CompletedTask;
-
- internal void WriteSqlVariantDate(DateTime value, TdsParserStateObject stateObj)
- {
- }
-
- internal void WriteSqlVariantDateTime2(DateTime value, TdsParserStateObject stateObj)
- {
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
index 967c70250a..d48e49a7f4 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
@@ -6,6 +6,7 @@
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Runtime.InteropServices;
using System.Security;
using System.Security.Authentication;
using System.Security.Cryptography;
@@ -290,6 +291,14 @@ private enum SnapshotStatus
// as the error handling may end up calling Dispose.
private int _readingCount;
+#if NETFRAMEWORK
+ // Used for blanking out password in trace.
+ internal int _tracePasswordOffset = 0;
+ internal int _tracePasswordLength = 0;
+ internal int _traceChangePasswordOffset = 0;
+ internal int _traceChangePasswordLength = 0;
+#endif
+
// Test hooks
#if DEBUG
// This is a test hook to enable testing of the retry paths.
@@ -352,6 +361,38 @@ protected TdsParserStateObject(TdsParser parser)
_lastSuccessfulIOTimer = new LastIOTimer();
}
+ protected TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async)
+ {
+ // Construct a MARS session
+ Debug.Assert(parser != null, "no parser?");
+ _parser = parser;
+ _onTimeoutAsync = OnTimeoutAsync;
+ SniContext = SniContext.Snix_GetMarsSession;
+
+ Debug.Assert(_parser._physicalStateObj != null, "no physical session?");
+ Debug.Assert(_parser._physicalStateObj._inBuff != null, "no in buffer?");
+ Debug.Assert(_parser._physicalStateObj._outBuff != null, "no out buffer?");
+ Debug.Assert(_parser._physicalStateObj._outBuff.Length ==
+ _parser._physicalStateObj._inBuff.Length, "Unexpected unequal buffers.");
+
+ // Determine packet size based on physical connection buffer lengths.
+ SetPacketSize(_parser._physicalStateObj._outBuff.Length);
+
+ CreateSessionHandle(physicalConnection, async);
+
+ if (IsFailedHandle())
+ {
+ AddError(parser.ProcessSNIError(this));
+ ThrowExceptionAndWarning();
+ }
+
+ // we post a callback that represents the call to dispose; once the
+ // object is disposed, the next callback will cause the GC Handle to
+ // be released.
+ IncrementPendingCallbacks();
+ _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer;
+ }
+
private void SetSnapshottedState(SnapshottedStateFlags flag, bool value)
{
if (value)
@@ -911,6 +952,38 @@ internal void CancelRequest()
}
}
+ ///
+ /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections)
+ /// NOTE: This is not safe to do on a connection that is currently in use
+ /// NOTE: This will mark the connection as broken if it is found to be dead
+ ///
+ /// True if the connection is still alive, otherwise false
+ internal bool ValidateSNIConnection()
+ {
+ if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed)))
+ {
+ return false;
+ }
+
+ if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value <= CheckConnectionWindow)
+ {
+ return true;
+ }
+
+ uint error = TdsEnums.SNI_SUCCESS;
+ SniContext = SniContext.Snix_Connect;
+ try
+ {
+ Interlocked.Increment(ref _readingCount);
+ error = CheckConnection();
+ }
+ finally
+ {
+ Interlocked.Decrement(ref _readingCount);
+ }
+ return (error == TdsEnums.SNI_SUCCESS) || (error == TdsEnums.SNI_WAIT_TIMEOUT);
+ }
+
public void CheckSetResetConnectionState(uint error, CallbackType callbackType)
{
// Should only be called for MARS - that is the only time we need to take
@@ -974,6 +1047,19 @@ internal int IncrementPendingCallbacks()
return remaining;
}
+ internal int DecrementPendingCallbacks(bool release)
+ {
+ int remaining = Interlocked.Decrement(ref _pendingCallbacks);
+ SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.DecrementPendingCallbacks | ADV | State Object Id {0}, after decrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks);
+
+ FreeGcHandle(remaining, release);
+
+ // NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed
+ // This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it
+ Debug.Assert((remaining == -1 && SessionHandle.IsNull) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}");
+ return remaining;
+ }
+
internal bool Deactivate()
{
bool goodForReuse = false;
@@ -1301,6 +1387,47 @@ internal void SetBuffer(byte[] buffer, int inBytesUsed, int inBytesRead/*, [Call
_inBytesRead = inBytesRead;
}
+ private bool TrySetBufferSecureStrings()
+ {
+ bool mustClearBuffer = false;
+
+ if (_securePasswords != null)
+ {
+ for (int i = 0; i < _securePasswords.Length; i++)
+ {
+ if (_securePasswords[i] != null)
+ {
+ IntPtr str = IntPtr.Zero;
+ try
+ {
+ str = Marshal.SecureStringToBSTR(_securePasswords[i]);
+ byte[] data = new byte[_securePasswords[i].Length * 2];
+ Marshal.Copy(str, data, 0, _securePasswords[i].Length * 2);
+ if (!BitConverter.IsLittleEndian)
+ {
+ Span span = data.AsSpan();
+ for (int ii = 0; ii < _securePasswords[i].Length * 2; ii += 2)
+ {
+ short value = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(ii));
+ BinaryPrimitives.WriteInt16BigEndian(span.Slice(ii), value);
+ }
+ }
+ TdsParserStaticMethods.ObfuscatePassword(data);
+ data.CopyTo(_outBuff, _securePasswordOffsetsInBuffer[i]);
+
+ mustClearBuffer = true;
+ }
+ finally
+ {
+ Marshal.ZeroFreeBSTR(str);
+ }
+ }
+ }
+ }
+
+ return mustClearBuffer;
+ }
+
internal void NewBuffer(int size)
{
_inBuff = new byte[size];
@@ -2441,6 +2568,50 @@ private void SniReadStatisticsAndTracing()
}
}
+ private void SniWriteStatisticsAndTracing()
+ {
+ SqlStatistics statistics = _parser.Statistics;
+ if (statistics != null)
+ {
+ statistics.SafeIncrement(ref statistics._buffersSent);
+ statistics.SafeAdd(ref statistics._bytesSent, _outBytesUsed);
+ statistics.RequestNetworkServerTimer();
+ }
+#if NETFRAMEWORK
+ if (SqlClientEventSource.Log.IsAdvancedTraceOn())
+ {
+ // If we have tracePassword variables set, we are flushing TDSLogin and so we need to
+ // blank out password in buffer. Buffer has already been sent to netlib, so no danger
+ // of losing info.
+ if (_tracePasswordOffset != 0)
+ {
+ for (int i = _tracePasswordOffset; i < _tracePasswordOffset +
+ _tracePasswordLength; i++)
+ {
+ _outBuff[i] = 0;
+ }
+
+ // Reset state.
+ _tracePasswordOffset = 0;
+ _tracePasswordLength = 0;
+ }
+ if (_traceChangePasswordOffset != 0)
+ {
+ for (int i = _traceChangePasswordOffset; i < _traceChangePasswordOffset +
+ _traceChangePasswordLength; i++)
+ {
+ _outBuff[i] = 0;
+ }
+
+ // Reset state.
+ _traceChangePasswordOffset = 0;
+ _traceChangePasswordLength = 0;
+ }
+ }
+ SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.WritePacket | INFO | ADV | State Object Id {0}, Packet sent. Out buffer: {1}, Out Bytes Used: {2}", ObjectID, _outBuff, _outBytesUsed);
+#endif
+ }
+
[Conditional("DEBUG")]
private void AssertValidState()
{
@@ -2724,6 +2895,360 @@ internal void ResetSecurePasswordsInformation()
}
}
+ public void WriteAsyncCallback(PacketHandle packet, uint sniError) =>
+ WriteAsyncCallback(IntPtr.Zero, packet, sniError);
+
+ public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError)
+ { // Key never used.
+ RemovePacketFromPendingList(packet);
+ try
+ {
+ if (sniError != TdsEnums.SNI_SUCCESS)
+ {
+ SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.WriteAsyncCallback | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError);
+ try
+ {
+ AddError(_parser.ProcessSNIError(this));
+ ThrowExceptionAndWarning(asyncClose: true);
+ }
+ catch (Exception e)
+ {
+ TaskCompletionSource writeCompletionSource = _writeCompletionSource;
+ if (writeCompletionSource != null)
+ {
+ writeCompletionSource.TrySetException(e);
+ }
+ else
+ {
+ _delayedWriteAsyncCallbackException = e;
+
+ // Ensure that _delayedWriteAsyncCallbackException is set before checking _writeCompletionSource
+ Interlocked.MemoryBarrier();
+
+ // Double check that _writeCompletionSource hasn't been created in the meantime
+ writeCompletionSource = _writeCompletionSource;
+ if (writeCompletionSource != null)
+ {
+ Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null);
+ if (delayedException != null)
+ {
+ writeCompletionSource.TrySetException(delayedException);
+ }
+ }
+ }
+
+ return;
+ }
+ }
+ else
+ {
+ _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks;
+ }
+ }
+ finally
+ {
+#if DEBUG
+ if (SqlCommand.DebugForceAsyncWriteDelay > 0)
+ {
+ new Timer(obj =>
+ {
+ Interlocked.Decrement(ref _asyncWriteCount);
+ TaskCompletionSource writeCompletionSource = _writeCompletionSource;
+ if (_asyncWriteCount == 0 && writeCompletionSource != null)
+ {
+ writeCompletionSource.TrySetResult(null);
+ }
+ }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite);
+ }
+ else
+ {
+#else
+ {
+#endif
+ Interlocked.Decrement(ref _asyncWriteCount);
+ }
+ }
+#if DEBUG
+ if (SqlCommand.DebugForceAsyncWriteDelay > 0)
+ {
+ return;
+ }
+#endif
+ TaskCompletionSource completionSource = _writeCompletionSource;
+ if (_asyncWriteCount == 0 && completionSource != null)
+ {
+ completionSource.TrySetResult(null);
+ }
+ }
+
+ // Dumps contents of buffer to SNI for network write.
+ internal Task WritePacket(byte flushMode, bool canAccumulate = false)
+ {
+ TdsParserState state = _parser.State;
+ if ((state == TdsParserState.Closed) || (state == TdsParserState.Broken))
+ {
+ throw ADP.ClosedConnectionError();
+ }
+
+ if (
+ // This appears to be an optimization to avoid writing empty packets in 2005
+ // However, since we don't know the version prior to login Is2005OrNewer was always false prior to login
+ // So removing the Is2005OrNewer check causes issues since the login packet happens to meet the rest of the conditions below
+ // So we need to avoid this check prior to login completing
+ state == TdsParserState.OpenLoggedIn
+ && !_bulkCopyOpperationInProgress // ignore the condition checking for bulk copy
+ && _outBytesUsed == (_outputHeaderLen +
+ BinaryPrimitives.ReadInt32LittleEndian(_outBuff.AsSpan(_outputHeaderLen)))
+ && _outputPacketCount == 0
+ || _outBytesUsed == _outputHeaderLen
+ && _outputPacketCount == 0)
+ {
+ return null;
+ }
+
+ byte status;
+ byte packetNumber = _outputPacketNumber;
+
+ // Set Status byte based whether this is end of message or not
+ bool willCancel = (_cancelled) && (_parser._asyncWrite);
+ if (willCancel)
+ {
+ status = TdsEnums.ST_EOM | TdsEnums.ST_IGNORE;
+ ResetPacketCounters();
+ }
+ else if (TdsEnums.HARDFLUSH == flushMode)
+ {
+ status = TdsEnums.ST_EOM;
+ ResetPacketCounters();
+ }
+ else if (TdsEnums.SOFTFLUSH == flushMode)
+ {
+ status = TdsEnums.ST_BATCH;
+ _outputPacketNumber++;
+ _outputPacketCount++;
+ }
+ else
+ {
+ status = TdsEnums.ST_EOM;
+ Debug.Fail($"Unexpected argument {flushMode,-2:x2} to WritePacket");
+ }
+
+ _outBuff[0] = _outputMessageType; // Message Type
+ _outBuff[1] = status;
+ _outBuff[2] = (byte)(_outBytesUsed >> 8); // length - upper byte
+ _outBuff[3] = (byte)(_outBytesUsed & 0xff); // length - lower byte
+ _outBuff[4] = 0; // channel
+ _outBuff[5] = 0;
+ _outBuff[6] = packetNumber; // packet
+ _outBuff[7] = 0; // window
+
+ Task task = null;
+ _parser.CheckResetConnection(this); // HAS SIDE EFFECTS - re-org at a later time if possible
+
+ task = WriteSni(canAccumulate);
+ AssertValidState();
+
+ if (willCancel)
+ {
+ // If we have been canceled, then ensure that we write the ATTN packet as well
+#if NET
+ task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket);
+#else
+ task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket, _parser.Connection);
+#endif
+ }
+
+ return task;
+ }
+
+ private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false)
+ {
+ // Check for a stored exception
+ Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null);
+ if (delayedException != null)
+ {
+ throw delayedException;
+ }
+
+ Task task = null;
+ _writeCompletionSource = null;
+ PacketHandle packetPointer = EmptyReadPacket;
+ bool sync = !_parser._asyncWrite;
+ if (sync && _asyncWriteCount > 0)
+ { // for example, SendAttention while there are writes pending
+ Task waitForWrites = WaitForAccumulatedWrites();
+ if (waitForWrites != null)
+ {
+ try
+ {
+ waitForWrites.Wait();
+ }
+ catch (AggregateException ae)
+ {
+ throw ae.InnerException;
+ }
+ }
+ Debug.Assert(_asyncWriteCount == 0, "All async write should be finished");
+ }
+ if (!sync)
+ {
+ // Add packet to the pending list (since the callback can happen any time after we call SNIWritePacket)
+ packetPointer = AddPacketToPendingList(packet);
+ }
+
+ // Async operation completion may be delayed (success pending).
+ sniError = WritePacket(packet, sync);
+
+ if (sniError == TdsEnums.SNI_SUCCESS_IO_PENDING)
+ {
+ Debug.Assert(!sync, "Completion should be handled in SniManagedWrapper");
+ Interlocked.Increment(ref _asyncWriteCount);
+ Debug.Assert(_asyncWriteCount >= 0);
+ if (!canAccumulate)
+ {
+ // Create completion source (for callback to complete)
+ _writeCompletionSource = new TaskCompletionSource();
+ task = _writeCompletionSource.Task;
+
+ // Ensure that setting _writeCompletionSource completes before checking _delayedWriteAsyncCallbackException
+ Interlocked.MemoryBarrier();
+
+ // Check for a stored exception
+ delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null);
+ if (delayedException != null)
+ {
+ throw delayedException;
+ }
+
+ // If there are no outstanding writes, see if we can shortcut and return null
+ if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null)))
+ {
+ task = null;
+ }
+ }
+ }
+#if DEBUG
+ else if (!sync && !canAccumulate && SqlCommand.DebugForceAsyncWriteDelay > 0)
+ {
+ // Executed synchronously - callback will not be called
+ TaskCompletionSource completion = new TaskCompletionSource();
+ uint error = sniError;
+ new Timer(obj =>
+ {
+ try
+ {
+ if (_parser.MARSOn)
+ { // Only take reset lock on MARS.
+ CheckSetResetConnectionState(error, CallbackType.Write);
+ }
+
+ if (error != TdsEnums.SNI_SUCCESS)
+ {
+ SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)error);
+ AddError(_parser.ProcessSNIError(this));
+ ThrowExceptionAndWarning(false, asyncClose);
+ }
+ AssertValidState();
+ completion.SetResult(null);
+ }
+ catch (Exception e)
+ {
+ completion.SetException(e);
+ }
+ }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite);
+ task = completion.Task;
+ }
+#endif
+ else
+ {
+ if (_parser.MARSOn)
+ { // Only take reset lock on MARS.
+ CheckSetResetConnectionState(sniError, CallbackType.Write);
+ }
+
+ if (sniError == TdsEnums.SNI_SUCCESS)
+ {
+ _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks;
+
+ if (!sync)
+ {
+ // Since there will be no callback, remove the packet from the pending list
+ Debug.Assert(IsValidPacket(packetPointer), "Packet added to list has an invalid pointer, can not remove from pending list");
+ RemovePacketFromPendingList(packetPointer);
+ }
+ }
+ else
+ {
+ SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError);
+ AddError(_parser.ProcessSNIError(this));
+ ThrowExceptionAndWarning(callerHasConnectionLock, asyncClose);
+ }
+ AssertValidState();
+ }
+ return task;
+ }
+
+ private Task WriteSni(bool canAccumulate)
+ {
+ // Prepare packet, and write to packet.
+ PacketHandle packet = GetResetWritePacket(_outBytesUsed);
+ bool mustClearBuffer = TrySetBufferSecureStrings();
+
+ SetPacketData(packet, _outBuff, _outBytesUsed);
+ if (mustClearBuffer)
+ {
+ _outBuff.AsSpan(0, _outBytesUsed).Clear();
+ }
+
+ Debug.Assert(Parser.Connection._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock");
+ Task task = SNIWritePacket(packet, out _, canAccumulate, callerHasConnectionLock: true);
+
+ // Check to see if the timeout has occurred. This time out code is special case code to allow BCP writes to timeout. Eventually we should make all writes timeout.
+ if (_bulkCopyOpperationInProgress && 0 == GetTimeoutRemaining())
+ {
+ _parser.Connection.ThreadHasParserLockForClose = true;
+ try
+ {
+ Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point.");
+ AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT));
+ _bulkCopyWriteTimeout = true;
+ SendAttention();
+ _parser.ProcessPendingAck(this);
+ ThrowExceptionAndWarning();
+ }
+ finally
+ {
+ _parser.Connection.ThreadHasParserLockForClose = false;
+ }
+ }
+
+ // Special case logic for encryption removal.
+ if (_parser.State == TdsParserState.OpenNotLoggedIn &&
+ (_parser.EncryptionOptions & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN)
+ {
+ // If no error occurred, and we are Open but not logged in, and
+ // our encryptionOption state is login, remove the SSL Provider.
+ // We only need encrypt the very first packet of the login message to the server.
+
+ // We wanted to encrypt entire login channel, but there is
+ // currently no mechanism to communicate this. Removing encryption post 1st packet
+ // is a hard-coded agreement between client and server. We need some mechanism or
+ // common change to be able to make this change in a non-breaking fashion.
+ _parser.RemoveEncryption(); // Remove the SSL Provider.
+ _parser.EncryptionOptions = EncryptionOptions.OFF | (_parser.EncryptionOptions & ~EncryptionOptions.OPTIONS_MASK); // Turn encryption off.
+
+ // Since this packet was associated with encryption, dispose and re-create.
+ ClearAllWritePackets();
+ }
+
+ SniWriteStatisticsAndTracing();
+
+ ResetBuffer();
+
+ AssertValidState();
+ return task;
+ }
+
internal Task WaitForAccumulatedWrites()
{
// Checked for stored exceptions
@@ -2802,6 +3327,78 @@ private void CancelWritePacket()
}
}
+ // Sends an attention signal - executing thread will consume attn.
+ internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false)
+ {
+ if (!_attentionSent)
+ {
+ // Dumps contents of buffer to OOB write (currently only used for
+ // attentions. There is no body for this message
+ // Doesn't touch this._outBytesUsed
+ if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken)
+ {
+ return;
+ }
+
+ PacketHandle attnPacket = CreateAndSetAttentionPacket();
+
+ try
+ {
+ // Dev11 #344723: SqlClient stress test suspends System_Data!Tcp::ReadSync via a call to SqlDataReader::Close
+ // Set _attentionSending to true before sending attention and reset after setting _attentionSent
+ // This prevents a race condition between receiving the attention ACK and setting _attentionSent
+ _attentionSending = true;
+#if DEBUG
+ if (!s_skipSendAttention)
+ {
+#endif
+ // Take lock and send attention
+ bool releaseLock = false;
+ if ((mustTakeWriteLock) && (!_parser.Connection.ThreadHasParserLockForClose))
+ {
+ releaseLock = true;
+ _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false);
+ _parser.Connection.ThreadHasParserLockForClose = true;
+ }
+ try
+ {
+ // Check again (just in case the connection was closed while we were waiting)
+ if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken)
+ {
+ return;
+ }
+
+ _parser._asyncWrite = false; // stop async write
+ SNIWritePacket(attnPacket, out _, canAccumulate: false, callerHasConnectionLock: false, asyncClose);
+ SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Sent Attention.", _objectID);
+ }
+ finally
+ {
+ if (releaseLock)
+ {
+ _parser.Connection.ThreadHasParserLockForClose = false;
+ _parser.Connection._parserLock.Release();
+ }
+ }
+#if DEBUG
+ }
+#endif
+
+ SetTimeoutSeconds(AttentionTimeoutSeconds); // Initialize new attention timeout of 5 seconds.
+ _attentionSent = true;
+ }
+ finally
+ {
+ _attentionSending = false;
+ }
+
+ SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParserStateObject.SendAttention | INFO | ADV | State Object Id {0}, Packet sent. Out Buffer: {1}, Out Bytes Used: {2}", _objectID, _outBuff, _outBytesUsed);
+ SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Attention sent to the server.", _objectID);
+
+ AssertValidState();
+ }
+ }
+
/////////////////////////////////////////
// Network/Packet Reading & Processing //
/////////////////////////////////////////
@@ -3005,6 +3602,113 @@ internal void ReadSniSyncOverAsync()
}
}
+ public void ReadAsyncCallback(PacketHandle packet, uint error) =>
+ ReadAsyncCallback(IntPtr.Zero, packet, error);
+
+ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error)
+ {
+ // Key never used.
+ // Note - it's possible that when native calls managed that an asynchronous exception
+ // could occur in the native->managed transition, which would
+ // have two impacts:
+ // 1) user event not called
+ // 2) DecrementPendingCallbacks not called, which would mean this object would be leaked due
+ // to the outstanding GCRoot until AppDomain.Unload.
+ // We live with the above for the time being due to the constraints of the current
+ // reliability infrastructure provided by the CLR.
+
+ TaskCompletionSource source = _networkPacketTaskSource;
+#if DEBUG
+ if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null))
+ {
+ source = _realNetworkPacketTaskSource;
+ }
+#endif
+
+ // The mars physical connection can get a callback
+ // with a packet but no result after the connection is closed.
+ if (source == null && _parser._pMarsPhysicalConObj == this)
+ {
+ return;
+ }
+
+ bool processFinallyBlock = true;
+ try
+ {
+#if NET
+ Debug.Assert((packet.Type == 0 && PartialPacketContainsCompletePacket()) || (CheckPacket(packet, source) && source != null), "AsyncResult null on callback");
+#else
+ Debug.Assert(CheckPacket(packet, source), "AsyncResult null on callback");
+#endif
+
+ if (_parser.MARSOn)
+ {
+ // Only take reset lock on MARS and Async.
+ CheckSetResetConnectionState(error, CallbackType.Read);
+ }
+
+ ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite);
+
+ // The timer thread may be unreliable under high contention scenarios. It cannot be
+ // assumed that the timeout has happened on the timer thread callback. Check the timeout
+ // synchronously and then call OnTimeoutSync to force an atomic change of state.
+ if (TimeoutHasExpired)
+ {
+ OnTimeoutSync(asyncClose: true);
+ }
+
+ // try to change to the stopped state but only do so if currently in the running state
+ // and use cmpexch so that all changes out of the running state are atomic
+ int previousState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Stopped, TimeoutState.Running);
+
+ // if the state is anything other than running then this query has reached an end so
+ // set the correlation _timeoutIdentityValue to 0 to prevent late callbacks executing
+ if (_timeoutState != TimeoutState.Running)
+ {
+ _timeoutIdentityValue = 0;
+ }
+
+ ProcessSniPacket(packet, error);
+ }
+ catch (Exception e)
+ {
+ processFinallyBlock = ADP.IsCatchableExceptionType(e);
+ throw;
+ }
+ finally
+ {
+ // pendingCallbacks may be 2 after decrementing, this indicates that a fatal timeout is occurring, and therefore we shouldn't complete the task
+ int pendingCallbacks = DecrementPendingCallbacks(false); // may dispose of GC handle.
+ if ((processFinallyBlock) && (source != null) && (pendingCallbacks < 2))
+ {
+ if (error == 0)
+ {
+ if (_executionContext != null)
+ {
+ ExecutionContext.Run(_executionContext, s_readAsyncCallbackComplete, source);
+ }
+ else
+ {
+ source.TrySetResult(null);
+ }
+ }
+ else
+ {
+ if (_executionContext != null)
+ {
+ ExecutionContext.Run(_executionContext, state => ReadAsyncCallbackCaptureException((TaskCompletionSource)state), source);
+ }
+ else
+ {
+ ReadAsyncCallbackCaptureException(source);
+ }
+ }
+ }
+
+ AssertValidState();
+ }
+ }
+
internal void OnConnectionClosed()
{
// the stateObj is not null, so the async invocation that registered this callback
@@ -3196,6 +3900,11 @@ private bool OnTimeoutCore(int expectedState, int targetState, bool asyncClose =
return retval;
}
+ private uint GetSniPacket(PacketHandle packet, ref uint dataSize)
+ {
+ return SniPacketGetData(packet, _inBuff, ref dataSize);
+ }
+
internal void ReadSni(TaskCompletionSource completion)
{
Debug.Assert(_networkPacketTaskSource == null || ((_asyncReadWithoutSnapshot) && (_networkPacketTaskSource.Task.IsCompleted)), "Pending async call or failed to replay snapshot when calling ReadSni");
@@ -3355,6 +4064,117 @@ internal void ReadSni(TaskCompletionSource completion)
}
}
+ // This method should only be called by ReadSni! If not - it may have problems with timeouts!
+ private void ReadSniError(TdsParserStateObject stateObj, uint error)
+ {
+ if (TdsEnums.SNI_WAIT_TIMEOUT == error)
+ {
+ Debug.Assert(_syncOverAsync, "Should never reach here with async on!");
+ bool fail = false;
+
+ if (IsTimeoutStateExpired)
+ { // This is now our second timeout - time to give up.
+ fail = true;
+ }
+ else
+ {
+ stateObj.SetTimeoutStateStopped();
+ Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point.");
+ AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT));
+
+ if (!stateObj._attentionSent)
+ {
+ if (stateObj.Parser.State == TdsParserState.OpenLoggedIn)
+ {
+ stateObj.SendAttention(mustTakeWriteLock: true);
+
+ PacketHandle syncReadPacket = default;
+ bool readFromNetwork = true;
+ bool shouldDecrement = false;
+ try
+ {
+ Interlocked.Increment(ref _readingCount);
+ shouldDecrement = true;
+ readFromNetwork = !PartialPacketContainsCompletePacket();
+ if (readFromNetwork)
+ {
+ syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error);
+ }
+ else
+ {
+ error = TdsEnums.SNI_SUCCESS;
+ }
+
+ Interlocked.Decrement(ref _readingCount);
+ shouldDecrement = false;
+
+ if (TdsEnums.SNI_SUCCESS == error)
+ {
+ // We will end up letting the run method deal with the expected done:done_attn token stream.
+ stateObj.ProcessSniPacket(syncReadPacket, TdsEnums.SNI_SUCCESS);
+ return;
+ }
+ else
+ {
+ Debug.Assert(!readFromNetwork || !IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease");
+ fail = true; // Subsequent read failed, time to give up.
+ }
+ }
+ finally
+ {
+ if (shouldDecrement)
+ {
+ Interlocked.Decrement(ref _readingCount);
+ }
+
+ if (readFromNetwork && !IsPacketEmpty(syncReadPacket))
+ {
+ // Be sure to release packet, otherwise it will be leaked by native.
+ ReleasePacket(syncReadPacket);
+ }
+ }
+ }
+ else
+ {
+ if (_parser._loginWithFailover)
+ {
+ // For DbMirroring Failover during login, never break the connection, just close the TdsParser
+ _parser.Disconnect();
+ }
+ else if ((_parser.State == TdsParserState.OpenNotLoggedIn)
+#if NETFRAMEWORK
+ && (_parser.Connection.ConnectionOptions.MultiSubnetFailover || _parser.Connection.ConnectionOptions.TransparentNetworkIPResolution))
+#else
+ && (_parser.Connection.ConnectionOptions.MultiSubnetFailover))
+#endif
+ {
+ // For MultiSubnet Failover during login, never break the connection, just close the TdsParser
+ _parser.Disconnect();
+ }
+ else
+ {
+ fail = true; // We aren't yet logged in - just fail.
+ }
+ }
+ }
+ }
+
+ if (fail)
+ {
+ _parser.State = TdsParserState.Broken; // We failed subsequent read, we have to quit!
+ _parser.Connection.BreakConnection();
+ }
+ }
+ else
+ {
+ // Caution: ProcessSNIError always returns a fatal error!
+ AddError(_parser.ProcessSNIError(stateObj));
+ }
+ ThrowExceptionAndWarning();
+
+ AssertValidState();
+ }
+
///
/// Checks to see if the underlying connection is still alive (used by connection pool resiliency)
/// NOTE: This is not safe to do on a connection that is currently in use
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.netcore.cs
similarity index 100%
rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.netcore.cs