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