Moving BSTR conv to native code in SecureStringToBSTR.
[mono.git] / mcs / class / referencesource / System.Data / System / Data / SqlClient / SqlInternalConnectionTds.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="SqlInternalConnectionTds.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">[....]</owner>
6 // <owner current="true" primary="false">[....]</owner>
7 //------------------------------------------------------------------------------
8
9 namespace System.Data.SqlClient
10 {
11     using System;
12     using System.Collections.Generic;
13     using System.Data;
14     using System.Data.Common;
15     using System.Data.ProviderBase;
16     using System.Diagnostics;
17     using System.Globalization;
18     using System.Reflection;
19     using System.Runtime.CompilerServices;
20     using System.Runtime.ConstrainedExecution;
21     using System.Runtime.InteropServices;
22     using System.Security;
23     using System.Security.Permissions;
24     using System.Text;
25     using System.Threading;
26     using SysTx = System.Transactions;
27     using System.Diagnostics.CodeAnalysis;
28     using System.Threading.Tasks;
29     
30
31     internal class SessionStateRecord  {
32         internal bool _recoverable;
33         internal UInt32 _version;
34         internal Int32 _dataLength;
35         internal byte[] _data;
36     }
37
38     internal class SessionData {
39         internal const int _maxNumberOfSessionStates = 256;
40         internal UInt32 _tdsVersion;
41         internal bool _encrypted;
42
43         internal string _database;
44         internal SqlCollation _collation;
45         internal string _language;
46
47         internal string _initialDatabase;
48         internal SqlCollation _initialCollation;
49         internal string _initialLanguage;
50         
51         internal byte _unrecoverableStatesCount = 0;
52         internal Dictionary<string, Tuple<string, string>> _resolvedAliases;
53
54 #if DEBUG
55         internal bool _debugReconnectDataApplied;
56 #endif
57
58         internal SessionStateRecord[] _delta = new SessionStateRecord[_maxNumberOfSessionStates];
59         internal bool _deltaDirty = false;
60         internal byte[][] _initialState = new byte[_maxNumberOfSessionStates][];
61
62         public SessionData(SessionData recoveryData) {
63             _initialDatabase = recoveryData._initialDatabase;
64             _initialCollation = recoveryData._initialCollation;
65             _initialLanguage = recoveryData._initialLanguage;
66             _resolvedAliases = recoveryData._resolvedAliases;
67
68             for (int i = 0; i < _maxNumberOfSessionStates; i++) {
69                 if (recoveryData._initialState[i] != null) {
70                     _initialState[i] = (byte[])recoveryData._initialState[i].Clone();
71                 }
72             }
73         }
74
75         public SessionData() {
76             _resolvedAliases = new Dictionary<string, Tuple<string, string>>(2);
77         }
78
79         public void Reset() {
80             _database = null;
81             _collation = null;
82             _language = null;
83             if (_deltaDirty) {
84                 _delta = new SessionStateRecord[_maxNumberOfSessionStates];
85                 _deltaDirty = false;
86             }
87             _unrecoverableStatesCount = 0;
88         }        
89
90         [Conditional("DEBUG")]
91         public void AssertUnrecoverableStateCountIsCorrect() {
92             byte unrecoverableCount = 0;
93             foreach (var state in _delta) {
94                 if (state != null && !state._recoverable)
95                     unrecoverableCount++;
96             }
97             Debug.Assert(unrecoverableCount == _unrecoverableStatesCount, "Unrecoverable count does not match");
98         }
99     }
100
101     sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable {
102         // CONNECTION AND STATE VARIABLES
103         private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance
104         private TdsParser                _parser;
105         private SqlLoginAck              _loginAck;
106         private SqlCredential            _credential;
107         private FederatedAuthenticationFeatureExtensionData?  _fedAuthFeatureExtensionData;
108
109         // Connection Resiliency
110         private bool                     _sessionRecoveryRequested;
111         internal bool                     _sessionRecoveryAcknowledged;
112         internal SessionData             _currentSessionData; // internal for use from TdsParser only, otehr should use CurrentSessionData property that will fix database and language
113         private SessionData              _recoverySessionData;
114
115         // Federated Authentication
116         // Response obtained from the server for FEDAUTHREQUIRED prelogin option.
117         internal bool _fedAuthRequired;
118
119         internal bool _federatedAuthenticationRequested;
120         internal bool _federatedAuthenticationAcknowledged;
121         internal bool _federatedAuthenticationInfoRequested; // Keep this distinct from _federatedAuthenticationRequested, since some fedauth library types may not need more info
122         internal bool _federatedAuthenticationInfoReceived;
123
124         // TCE flags
125         internal byte _tceVersionSupported;
126
127         internal byte[] _accessTokenInBytes;
128
129         // The pool that this connection is associated with, if at all it is.
130         private DbConnectionPool _dbConnectionPool;
131
132         // This is used to preserve the authentication context object if we decide to cache it for subsequent connections in the same pool.
133         // This will finally end up in _dbConnectionPool.AuthenticationContexts, but only after 1 successful login to SQL Server using this context.
134         // This variable is to persist the context after we have generated it, but before we have successfully completed the login with this new context.
135         // If this connection attempt ended up re-using the existing context and not create a new one, this will be null (since the context is not new).
136         private DbConnectionPoolAuthenticationContext _newDbConnectionPoolAuthenticationContext;
137
138         // The key of the authentication context, built from information found in the FedAuthInfoToken.
139         private DbConnectionPoolAuthenticationContextKey _dbConnectionPoolAuthenticationContextKey;
140
141 #if DEBUG
142         // This is a test hook to enable testing of the retry paths for ADAL get access token.
143         // Sample code to enable:
144         //
145         //    Type type = typeof(SqlConnection).Assembly.GetType("System.Data.SqlClient.SqlInternalConnectionTds");
146         //    System.Reflection.FieldInfo field = type.GetField("_forceAdalRetry", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
147         //    if (field != null) {
148         //        field.SetValue(null, true);
149         //    }
150         //
151         internal static bool _forceAdalRetry = false;
152
153         // This is a test hook to simulate a token expiring within the next 45 minutes.
154         private static bool _forceExpiryLocked = false;
155
156         // This is a test hook to simulate a token expiring within the next 10 minutes.
157         private static bool _forceExpiryUnLocked = false;
158 #endif //DEBUG
159
160         // The timespan defining the amount of time the authentication context needs to be valid for at-least, to re-use the cached context,
161         // without making an attempt to refresh it. IF the context is expiring within the next 45 mins, then try to take a lock and refresh
162         // the context, if the lock is acquired.
163         private static readonly TimeSpan _dbAuthenticationContextLockedRefreshTimeSpan = new TimeSpan(hours: 0, minutes: 45, seconds: 00);
164
165         // The timespan defining the minimum amount of time the authentication context needs to be valid for re-using the cached context.
166         // If the context is expiring within the next 10 mins, then create a new context, irrespective of if another thread is trying to do the same.
167         private static readonly TimeSpan _dbAuthenticationContextUnLockedRefreshTimeSpan = new TimeSpan(hours: 0, minutes: 10, seconds: 00);
168
169         private readonly TimeoutTimer _timeout;
170
171         private static HashSet<int> transientErrors = new HashSet<int>();
172
173         internal SessionData CurrentSessionData {
174             get {
175                 if (_currentSessionData != null) {
176                     _currentSessionData._database = CurrentDatabase;
177                     _currentSessionData._language = _currentLanguage;
178                 }
179                 return _currentSessionData;
180             }
181         }
182
183         // FOR POOLING
184         private bool                     _fConnectionOpen = false;
185
186         // FOR CONNECTION RESET MANAGEMENT
187         private bool                     _fResetConnection;
188         private string                   _originalDatabase;
189         private string                   _currentFailoverPartner;                     // only set by ENV change from server
190         private string                   _originalLanguage;
191         private string                   _currentLanguage;
192         private int                      _currentPacketSize;
193         private int                      _asyncCommandCount; // number of async Begins minus number of async Ends.
194
195         // FOR SSE
196         private string                   _instanceName = String.Empty;
197
198         // FOR NOTIFICATIONS
199         private DbConnectionPoolIdentity _identity; // Used to lookup info for notification matching Start().
200
201         // FOR SYNCHRONIZATION IN TdsParser
202         // How to use these locks:
203         // 1. Whenever writing to the connection (with the exception of Cancellation) the _parserLock MUST be taken
204         // 2. _parserLock will also be taken during close (to prevent closing in the middle of a write)
205         // 3. Whenever you have the _parserLock and are calling a method that would cause the connection to close if it failed (with the exception of any writing method), you MUST set ThreadHasParserLockForClose to true
206         //      * This is to prevent the connection deadlocking with itself (since you already have the _parserLock, and Closing the connection will attempt to re-take that lock)
207         //      * It is safe to set ThreadHasParserLockForClose to true when writing as well, but it is unneccesary
208         //      * If you have a method that takes _parserLock, it is a good idea check ThreadHasParserLockForClose first (if you don't expect _parserLock to be taken by something higher on the stack, then you should at least assert that it is false)
209         // 4. ThreadHasParserLockForClose is thread-specific - this means that you must set it to false before returning a Task, and set it back to true in the continuation
210         // 5. ThreadHasParserLockForClose should only be modified if you currently own the _parserLock
211         // 6. Reading ThreadHasParserLockForClose is thread-safe
212         internal class SyncAsyncLock
213         {
214             SemaphoreSlim semaphore = new SemaphoreSlim(1);
215
216             internal void Wait(bool canReleaseFromAnyThread)
217             {
218                 Monitor.Enter(semaphore); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods
219                 if (canReleaseFromAnyThread || semaphore.CurrentCount==0) {
220                     semaphore.Wait();
221                     if (canReleaseFromAnyThread) {                        
222                         Monitor.Exit(semaphore);
223                     }
224                     else {
225                         semaphore.Release();
226                     }
227                 }
228             }
229            
230             internal void Wait(bool canReleaseFromAnyThread, int timeout, ref bool lockTaken) {
231                 lockTaken = false;
232                 bool hasMonitor = false;
233                 try {
234                     Monitor.TryEnter(semaphore, timeout, ref hasMonitor); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods
235                     if (hasMonitor) {
236                         if ((canReleaseFromAnyThread) || (semaphore.CurrentCount == 0)) {
237                             if (semaphore.Wait(timeout)) {
238                                 if (canReleaseFromAnyThread) {                        
239                                     Monitor.Exit(semaphore);
240                                     hasMonitor = false;
241                                 }
242                                 else {
243                                     semaphore.Release();
244                                 }
245                                 lockTaken = true;
246                             }
247                         }
248                         else {
249                             lockTaken = true;
250                         }
251                     }
252                 }
253                 finally
254                 {
255                     if ((!lockTaken) && (hasMonitor)) {
256                         Monitor.Exit(semaphore);
257                     }
258                 }
259             }
260
261             internal void Release()
262             {
263                 if (semaphore.CurrentCount==0) {  //  semaphore methods were used for locking                   
264                     semaphore.Release();
265                 }
266                 else {
267                     Monitor.Exit(semaphore);
268                 }
269             }
270
271
272             internal bool CanBeReleasedFromAnyThread {
273                 get {
274                     return semaphore.CurrentCount==0;
275                 }
276             }
277
278             // Necessary but not sufficient condition for thread to have lock (since sempahore may be obtained by any thread)            
279             internal bool ThreadMayHaveLock() {
280                    return Monitor.IsEntered(semaphore) || semaphore.CurrentCount == 0;
281             }
282         }
283
284
285         internal SyncAsyncLock _parserLock = new SyncAsyncLock();
286         private int _threadIdOwningParserLock = -1;                                          
287
288         private SqlConnectionTimeoutErrorInternal timeoutErrorInternal;
289
290         internal SqlConnectionTimeoutErrorInternal TimeoutErrorInternal
291         {
292             get { return timeoutErrorInternal; }
293         }
294  
295         // OTHER STATE VARIABLES AND REFERENCES
296
297         internal Guid _clientConnectionId = Guid.Empty;
298
299         // Routing information (ROR)
300         RoutingInfo _routingInfo = null;
301         private Guid _originalClientConnectionId = Guid.Empty;
302         private string _routingDestination = null;
303
304         static SqlInternalConnectionTds()
305         {
306             populateTransientErrors();
307         }
308
309         // although the new password is generally not used it must be passed to the c'tor
310         // the new Login7 packet will always write out the new password (or a length of zero and no bytes if not present)
311         //
312         internal SqlInternalConnectionTds(
313                 DbConnectionPoolIdentity    identity, 
314                 SqlConnectionString         connectionOptions,
315                 SqlCredential               credential,
316                 object                      providerInfo, 
317                 string                      newPassword,
318                 SecureString                newSecurePassword,
319                 bool                        redirectedUserInstance,
320                 SqlConnectionString         userConnectionOptions = null, // NOTE: userConnectionOptions may be different to connectionOptions if the connection string has been expanded (see SqlConnectionString.Expand)
321                 SessionData                 reconnectSessionData = null,
322                 DbConnectionPool            pool = null,
323                 string                      accessToken = null,
324                 bool applyTransientFaultHandling = false
325                 ) : base(connectionOptions) {
326
327 #if DEBUG
328             if (reconnectSessionData != null) {
329                 reconnectSessionData._debugReconnectDataApplied = true;
330             }
331             try { // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath
332                 if (userConnectionOptions != null) {
333                     // As mentioned above, userConnectionOptions may be different to connectionOptions, so we need to demand on the correct connection string
334                     userConnectionOptions.DemandPermission();
335                 }
336                 else {
337                     connectionOptions.DemandPermission();
338                 }
339             }
340             catch(System.Security.SecurityException) {
341                 System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath");
342                 throw;
343             }
344 #endif
345             Debug.Assert(reconnectSessionData == null || connectionOptions.ConnectRetryCount > 0, "Reconnect data supplied with CR turned off");
346
347             _dbConnectionPool = pool;
348   
349             if (connectionOptions.ConnectRetryCount > 0) {
350                 _recoverySessionData = reconnectSessionData;          
351                 if (reconnectSessionData == null) {
352                     _currentSessionData = new SessionData();
353                 }
354                 else {
355                     _currentSessionData = new SessionData(_recoverySessionData);
356                     _originalDatabase = _recoverySessionData._initialDatabase;
357                     _originalLanguage = _recoverySessionData._initialLanguage;
358                 }
359             }
360           
361             if (connectionOptions.UserInstance && InOutOfProcHelper.InProc) {
362                 throw SQL.UserInstanceNotAvailableInProc();
363             }
364
365             if (accessToken != null) {
366                 _accessTokenInBytes = System.Text.Encoding.Unicode.GetBytes(accessToken);
367             }
368
369             _identity = identity;
370             Debug.Assert(newSecurePassword != null || newPassword != null, "cannot have both new secure change password and string based change password to be null");
371             Debug.Assert(credential == null || (String.IsNullOrEmpty(connectionOptions.UserID) && String.IsNullOrEmpty(connectionOptions.Password)), "cannot mix the new secure password system and the connection string based password");
372
373             Debug.Assert(credential == null || !connectionOptions.IntegratedSecurity, "Cannot use SqlCredential and Integrated Security");
374             Debug.Assert(credential == null || !connectionOptions.ContextConnection, "Cannot use SqlCredential with context connection");
375
376             _poolGroupProviderInfo = (SqlConnectionPoolGroupProviderInfo)providerInfo;
377             _fResetConnection = connectionOptions.ConnectionReset;
378             if (_fResetConnection && _recoverySessionData == null) {
379                 _originalDatabase = connectionOptions.InitialCatalog;
380                 _originalLanguage = connectionOptions.CurrentLanguage;
381             }
382
383             timeoutErrorInternal = new SqlConnectionTimeoutErrorInternal();
384             _credential = credential;
385
386             _parserLock.Wait(canReleaseFromAnyThread:false);
387             ThreadHasParserLockForClose = true;   // In case of error, let ourselves know that we already own the parser lock
388             RuntimeHelpers.PrepareConstrainedRegions();
389             try {
390 #if DEBUG
391                 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
392
393                 RuntimeHelpers.PrepareConstrainedRegions();
394                 try {
395                     tdsReliabilitySection.Start();
396 #else
397                 {
398 #endif //DEBUG
399                     _timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout);
400
401                     // If transient fault handling is enabled then we can retry the login upto the ConnectRetryCount.
402                     int connectionEstablishCount = applyTransientFaultHandling ? connectionOptions.ConnectRetryCount + 1 : 1;
403                     int transientRetryIntervalInMilliSeconds = connectionOptions.ConnectRetryInterval * 1000; // Max value of transientRetryInterval is 60*1000 ms. The max value allowed for ConnectRetryInterval is 60
404                     for (int i = 0; i < connectionEstablishCount; i++)
405                     {
406                         try
407                         {
408                             OpenLoginEnlist(_timeout, connectionOptions, credential, newPassword, newSecurePassword, redirectedUserInstance);
409                             break;
410                         }
411                         catch (SqlException sqlex)
412                         {
413                             if (i + 1 == connectionEstablishCount 
414                                 || !applyTransientFaultHandling
415                                 || _timeout.IsExpired
416                                 || _timeout.MillisecondsRemaining < transientRetryIntervalInMilliSeconds
417                                 || !IsTransientError(sqlex))
418                             {
419                                 throw sqlex;
420                             }
421                             else
422                             {
423                                 Thread.Sleep(transientRetryIntervalInMilliSeconds);
424                             }
425                         }
426                     }
427                 }
428 #if DEBUG
429                 finally {
430                     tdsReliabilitySection.Stop();
431                 }
432 #endif //DEBUG
433             }
434             catch (System.OutOfMemoryException) {
435                 DoomThisConnection();
436                 throw;
437             }
438             catch (System.StackOverflowException) {
439                 DoomThisConnection();
440                 throw;
441             }
442             catch (System.Threading.ThreadAbortException) {
443                 DoomThisConnection();
444                 throw;
445             }
446             finally {
447                 ThreadHasParserLockForClose = false;
448                 _parserLock.Release();
449             }
450             if (Bid.AdvancedOn) {
451                 Bid.Trace("<sc.SqlInternalConnectionTds.ctor|ADV> %d#, constructed new TDS internal connection\n", ObjectID);
452             }
453         }
454
455         // The erros in the transient error set are contained in
456         // https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-error-messages/#transient-faults-connection-loss-and-other-temporary-errors
457         private static void populateTransientErrors()
458         {
459             // SQL Error Code: 4060
460             // Cannot open database "%.*ls" requested by the login. The login failed.
461             transientErrors.Add(4060);
462             // SQL Error Code: 10928
463             // Resource ID: %d. The %s limit for the database is %d and has been reached.
464             transientErrors.Add(10928);
465             // SQL Error Code: 10929
466             // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. 
467             // However, the server is currently too busy to support requests greater than %d for this database.
468             transientErrors.Add(10929);
469             // SQL Error Code: 40197
470             // You will receive this error, when the service is down due to software or hardware upgrades, hardware failures, 
471             // or any other failover problems. The error code (%d) embedded within the message of error 40197 provides 
472             // additional information about the kind of failure or failover that occurred. Some examples of the error codes are 
473             // embedded within the message of error 40197 are 40020, 40143, 40166, and 40540.
474             transientErrors.Add(40197);
475             transientErrors.Add(40020);
476             transientErrors.Add(40143);
477             transientErrors.Add(40166);
478             // The service has encountered an error processing your request. Please try again.
479             transientErrors.Add(40540);
480             // The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d.
481             transientErrors.Add(40501);
482             // Database '%.*ls' on server '%.*ls' is not currently available. Please retry the connection later. 
483             // If the problem persists, contact customer support, and provide them the session tracing ID of '%.*ls'.
484             transientErrors.Add(40613);
485             // Do federation errors deserve to be here ? 
486             // Note: Federation errors 10053 and 10054 might also deserve inclusion in your retry logic.
487             //transientErrors.Add(10053);
488             //transientErrors.Add(10054);
489         }
490
491
492         // Returns true if the Sql error is a transient.
493         private bool IsTransientError(SqlException exc)
494         {
495             if (exc == null)
496             {
497                 return false;
498             }
499             foreach (SqlError error in exc.Errors)
500             {
501                 if (transientErrors.Contains(error.Number))
502                 {
503                     return true;
504                 }
505             }
506             return false;
507         }
508
509         internal Guid ClientConnectionId {
510             get {
511                 return _clientConnectionId;
512             }
513         }
514        
515         internal Guid OriginalClientConnectionId {
516             get {
517                 return _originalClientConnectionId;
518             }
519         }
520        
521         internal string RoutingDestination {
522             get {
523                 return _routingDestination;
524             }
525         }
526
527         override internal SqlInternalTransaction CurrentTransaction {
528             get {
529                 return _parser.CurrentTransaction;
530             }
531         }
532
533         override internal SqlInternalTransaction AvailableInternalTransaction {
534             get {
535                 return _parser._fResetConnection ? null : CurrentTransaction;
536             }
537         }
538
539
540         override internal SqlInternalTransaction PendingTransaction {
541             get {
542                 return _parser.PendingTransaction;
543             }
544         }
545         
546         internal DbConnectionPoolIdentity Identity {
547             get {
548                 return _identity;
549             }
550         }
551
552         internal string InstanceName {
553             get {
554                 return _instanceName;
555             }
556         }
557
558         override internal bool IsLockedForBulkCopy {
559             get {
560                 return (!Parser.MARSOn && Parser._physicalStateObj.BcpLock);
561             }
562         }
563
564         override protected internal bool IsNonPoolableTransactionRoot {
565             get {
566                 return IsTransactionRoot && (!IsKatmaiOrNewer || null == Pool);
567             }
568         }
569
570         override internal bool IsShiloh {
571             get {
572                 return _loginAck.isVersion8;
573             }
574         }
575
576         override internal bool IsYukonOrNewer {
577             get {
578                 return _parser.IsYukonOrNewer;
579             }
580         }
581
582         override internal bool IsKatmaiOrNewer {
583             get {
584                 return _parser.IsKatmaiOrNewer;
585             }
586         }
587
588         internal int PacketSize {
589             get {
590                 return _currentPacketSize;
591             }
592         }
593
594         internal TdsParser Parser {
595             get {
596                 return _parser;
597             }
598         }
599
600         internal string ServerProvidedFailOverPartner {
601             get {
602                 return  _currentFailoverPartner;
603             }
604         }
605
606         internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo {
607             get {
608                 return _poolGroupProviderInfo;
609             }
610         }
611         
612         override protected bool ReadyToPrepareTransaction {
613             get {
614                 // 
615                 bool result = (null == FindLiveReader(null)); // can't prepare with a live data reader...
616                 return result;
617             }
618         }
619
620         override public string ServerVersion {
621             get {
622                 return(String.Format((IFormatProvider)null, "{0:00}.{1:00}.{2:0000}", _loginAck.majorVersion,
623                        (short) _loginAck.minorVersion, _loginAck.buildNum));
624             }
625         }
626
627         /// <summary>
628         /// Get boolean that specifies whether an enlisted transaction can be unbound from 
629         /// the connection when that transaction completes.
630         /// </summary>
631         /// <value>
632         /// This override always returns false.
633         /// </value>
634         /// <remarks>
635         /// The SqlInternalConnectionTds.CheckEnlistedTransactionBinding method handles implicit unbinding for disposed transactions.
636         /// </remarks>
637         protected override bool UnbindOnTransactionCompletion
638         {
639             get
640             {
641                 return false;
642             }
643         }
644
645
646         ////////////////////////////////////////////////////////////////////////////////////////
647         // GENERAL METHODS
648         ////////////////////////////////////////////////////////////////////////////////////////
649         [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs
650         override protected void ChangeDatabaseInternal(string database) {
651             // MDAC 73598 - add brackets around database
652             database = SqlConnection.FixupDatabaseTransactionName(database);
653             Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true);
654             Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
655             _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
656         }
657
658         override public void Dispose() {
659             if (Bid.AdvancedOn) {
660                 Bid.Trace("<sc.SqlInternalConnectionTds.Dispose|ADV> %d# disposing\n", base.ObjectID);
661             }
662             try {
663                 TdsParser parser = Interlocked.Exchange(ref _parser, null);  // guard against multiple concurrent dispose calls -- Delegated Transactions might cause this.
664                     
665                 Debug.Assert(parser != null && _fConnectionOpen || parser == null && !_fConnectionOpen, "Unexpected state on dispose");
666                 if (null != parser) {
667                     parser.Disconnect();
668                 }
669             }
670             finally { // UNDONE: MDAC 77928
671                 // close will always close, even if exception is thrown
672                 // remember to null out any object references
673                 _loginAck          = null;
674                 _fConnectionOpen   = false; // mark internal connection as closed
675             }
676             base.Dispose();
677         }
678
679         override internal void ValidateConnectionForExecute(SqlCommand command) {
680             TdsParser parser = _parser;
681             if ((parser == null) || (parser.State == TdsParserState.Broken) || (parser.State == TdsParserState.Closed)) {
682                 throw ADP.ClosedConnectionError();
683             }
684             else {
685                 SqlDataReader reader = null;
686                 if (parser.MARSOn) {
687                     if (null != command) { // command can't have datareader already associated with it
688                         reader = FindLiveReader(command);
689                     }
690                 }
691                 else { // single execution/datareader per connection
692                     if (_asyncCommandCount > 0) {
693                         throw SQL.MARSUnspportedOnConnection();
694                     }
695
696                     reader = FindLiveReader(null);
697                 }
698                 if (null != reader) {
699                     // if MARS is on, then a datareader associated with the command exists
700                     // or if MARS is off, then a datareader exists
701                     throw ADP.OpenReaderExists(); // MDAC 66411
702                 }
703                 else if (!parser.MARSOn && parser._physicalStateObj._pendingData) {
704                     parser.DrainData(parser._physicalStateObj);
705                 }
706                 Debug.Assert(!parser._physicalStateObj._pendingData, "Should not have a busy physicalStateObject at this point!");
707
708                 parser.RollbackOrphanedAPITransactions();
709             }
710         }
711
712         /// <summary>
713         /// Validate the enlisted transaction state, taking into consideration the ambient transaction and transaction unbinding mode.
714         /// If there is no enlisted transaction, this method is a nop.
715         /// </summary>
716         /// <remarks>
717         /// <para>
718         /// This method must be called while holding a lock on the SqlInternalConnection instance,
719         /// to ensure we don't accidentally execute after the transaction has completed on a different thread, 
720         /// causing us to unwittingly execute in auto-commit mode.
721         /// </para>
722         /// 
723         /// <para>
724         /// When using Explicit transaction unbinding, 
725         /// verify that the enlisted transaction is active and equal to the current ambient transaction.
726         /// </para>
727         /// 
728         /// <para>
729         /// When using Implicit transaction unbinding,
730         /// verify that the enlisted transaction is active.
731         /// If it is not active, and the transaction object has been diposed, unbind from the transaction.
732         /// If it is not active and not disposed, throw an exception.
733         /// </para>
734         /// </remarks>
735         internal void CheckEnlistedTransactionBinding()
736         {
737             // If we are enlisted in a transaction, check that transaction is active.
738             // When using explicit transaction unbinding, also verify that the enlisted transaction is the current transaction.
739             SysTx.Transaction enlistedTransaction = EnlistedTransaction;
740
741             if (enlistedTransaction != null)
742             {
743                 bool requireExplicitTransactionUnbind = ConnectionOptions.TransactionBinding == SqlConnectionString.TransactionBindingEnum.ExplicitUnbind;
744
745                 if (requireExplicitTransactionUnbind)
746                 {
747                     SysTx.Transaction currentTransaction = SysTx.Transaction.Current;
748
749                     if (SysTx.TransactionStatus.Active != enlistedTransaction.TransactionInformation.Status || !enlistedTransaction.Equals(currentTransaction))
750                     {
751                         throw ADP.TransactionConnectionMismatch();
752                     }
753                 }
754                 else // implicit transaction unbind
755                 {
756                     if (SysTx.TransactionStatus.Active != enlistedTransaction.TransactionInformation.Status)
757                     {
758                         if (EnlistedTransactionDisposed)
759                         {
760                             DetachTransaction(enlistedTransaction, true);
761                         }
762                         else
763                         {
764                             throw ADP.TransactionCompletedButNotDisposed();
765                         }
766                     }
767                 }
768             }
769         }
770
771         internal override bool IsConnectionAlive(bool throwOnException)
772         {
773             bool isAlive = false;
774 #if DEBUG
775             TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
776
777             RuntimeHelpers.PrepareConstrainedRegions();
778             try
779             {
780                 tdsReliabilitySection.Start();
781 #endif //DEBUG
782
783                 isAlive = _parser._physicalStateObj.IsConnectionAlive(throwOnException);
784
785 #if DEBUG
786             }
787             finally
788             {
789                 tdsReliabilitySection.Stop();
790             }
791 #endif //DEBUG
792             return isAlive;
793         }
794
795         ////////////////////////////////////////////////////////////////////////////////////////
796         // POOLING METHODS
797         ////////////////////////////////////////////////////////////////////////////////////////
798
799         override protected void Activate(SysTx.Transaction transaction) {
800             FailoverPermissionDemand(); // Demand for unspecified failover pooled connections
801
802             // When we're required to automatically enlist in transactions and
803             // there is one we enlist in it. On the other hand, if there isn't a
804             // transaction and we are currently enlisted in one, then we
805             // unenlist from it.
806             //
807             // Regardless of whether we're required to automatically enlist,
808             // when there is not a current transaction, we cannot leave the
809             // connection enlisted in a transaction.
810             if (null != transaction){
811                 if (ConnectionOptions.Enlist) {
812                    Enlist(transaction);
813                 }
814             }
815             else {
816                 Enlist(null);
817             }                           
818         }
819         
820         override protected void InternalDeactivate() {
821             // When we're deactivated, the user must have called End on all
822             // the async commands, or we don't know that we're in a state that
823             // we can recover from.  We doom the connection in this case, to
824             // prevent odd cases when we go to the wire.
825             if (0 != _asyncCommandCount) {
826                 DoomThisConnection();
827             }
828
829             // If we're deactivating with a delegated transaction, we 
830             // should not be cleaning up the parser just yet, that will
831             // cause our transaction to be rolled back and the connection
832             // to be reset.  We'll get called again once the delegated
833             // transaction is completed and we can do it all then.
834             if (!IsNonPoolableTransactionRoot) {
835                 Debug.Assert(null != _parser || IsConnectionDoomed, "Deactivating a disposed connection?");
836                 if (_parser != null) {
837
838                     _parser.Deactivate(IsConnectionDoomed);
839
840                     if (!IsConnectionDoomed) {
841                         ResetConnection();
842                     }
843                 }
844             }
845         }
846
847         [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs
848         private void ResetConnection() {
849             // For implicit pooled connections, if connection reset behavior is specified,
850             // reset the database and language properties back to default.  It is important
851             // to do this on activate so that the hashtable is correct before SqlConnection
852             // obtains a clone.
853
854             Debug.Assert(!HasLocalTransactionFromAPI, "Upon ResetConnection SqlInternalConnectionTds has a currently ongoing local transaction.");
855             Debug.Assert(!_parser._physicalStateObj._pendingData, "Upon ResetConnection SqlInternalConnectionTds has pending data.");
856
857             if (_fResetConnection) {
858                 // Ensure we are either going against shiloh, or we are not enlisted in a
859                 // distributed transaction - otherwise don't reset!
860                 if (IsShiloh) {
861                     // Prepare the parser for the connection reset - the next time a trip
862                     // to the server is made.
863                     _parser.PrepareResetConnection(IsTransactionRoot && !IsNonPoolableTransactionRoot);
864                 }
865                 else if (!IsEnlistedInTransaction) {
866                     // If not Shiloh, we are going against Sphinx.  On Sphinx, we
867                     // may only reset if not enlisted in a distributed transaction.
868                     try {
869                         // execute sp
870                         Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("sp_reset_connection", 30, null, _parser._physicalStateObj, sync: true);
871                         Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
872                         _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
873                     }
874                     catch (Exception e) {
875                         // 
876                         if (!ADP.IsCatchableExceptionType(e)) {
877                             throw;
878                         }
879
880                         DoomThisConnection();
881                         ADP.TraceExceptionWithoutRethrow(e);
882                     }
883                 }
884
885                 // Reset hashtable values, since calling reset will not send us env_changes.
886                 CurrentDatabase = _originalDatabase;
887                 _currentLanguage = _originalLanguage;
888             }
889         }
890
891         internal void DecrementAsyncCount() {
892             Interlocked.Decrement(ref _asyncCommandCount);
893         }
894
895         internal void IncrementAsyncCount() {
896             Interlocked.Increment(ref _asyncCommandCount);
897         }
898
899
900         ////////////////////////////////////////////////////////////////////////////////////////
901         // LOCAL TRANSACTION METHODS
902         ////////////////////////////////////////////////////////////////////////////////////////
903
904         override internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) {
905             TdsParser parser = Parser;
906
907             if (null != parser) {
908                 parser.DisconnectTransaction(internalTransaction);
909             }
910         }
911
912         internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso) {
913             ExecuteTransaction(transactionRequest, name, iso, null, false);
914         }
915
916         override internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) {
917             if (IsConnectionDoomed) {  // doomed means we can't do anything else...
918                 if (transactionRequest == TransactionRequest.Rollback
919                  || transactionRequest == TransactionRequest.IfRollback) {
920                     return;
921                 }
922                 throw SQL.ConnectionDoomed();
923             }
924
925             if (transactionRequest == TransactionRequest.Commit
926              || transactionRequest == TransactionRequest.Rollback
927              || transactionRequest == TransactionRequest.IfRollback) {
928                 if (!Parser.MARSOn && Parser._physicalStateObj.BcpLock) {
929                     throw SQL.ConnectionLockedForBcpEvent();
930                 }
931             }
932
933             string transactionName = (null == name) ? String.Empty : name;
934
935             if (!_parser.IsYukonOrNewer) {
936                 ExecuteTransactionPreYukon(transactionRequest, transactionName, iso, internalTransaction);
937             }
938             else {
939                 ExecuteTransactionYukon(transactionRequest, transactionName, iso, internalTransaction, isDelegateControlRequest);
940             }
941         }
942
943         // This function will not handle idle connection resiliency, as older servers will not support it
944         internal void ExecuteTransactionPreYukon(
945                     TransactionRequest      transactionRequest, 
946                     string                  transactionName, 
947                     IsolationLevel          iso, 
948                     SqlInternalTransaction  internalTransaction) {
949             StringBuilder sqlBatch = new StringBuilder();
950
951             switch (iso) {
952                 case IsolationLevel.Unspecified:
953                     break;
954                 case IsolationLevel.ReadCommitted:
955                     sqlBatch.Append(TdsEnums.TRANS_READ_COMMITTED);
956                     sqlBatch.Append(";");
957                     break;
958                 case IsolationLevel.ReadUncommitted:
959                     sqlBatch.Append(TdsEnums.TRANS_READ_UNCOMMITTED);
960                     sqlBatch.Append(";");
961                     break;
962                 case IsolationLevel.RepeatableRead:
963                     sqlBatch.Append(TdsEnums.TRANS_REPEATABLE_READ);
964                     sqlBatch.Append(";");
965                     break;
966                 case IsolationLevel.Serializable:
967                     sqlBatch.Append(TdsEnums.TRANS_SERIALIZABLE);
968                     sqlBatch.Append(";");
969                     break;
970                 case IsolationLevel.Snapshot:
971                     throw SQL.SnapshotNotSupported(IsolationLevel.Snapshot);
972
973                 case IsolationLevel.Chaos:
974                     throw SQL.NotSupportedIsolationLevel(iso);
975
976                 default:
977                     throw ADP.InvalidIsolationLevel(iso);
978             }
979
980             if (!ADP.IsEmpty(transactionName)) {
981                 transactionName = " " + SqlConnection.FixupDatabaseTransactionName(transactionName);
982             }
983                 
984             switch (transactionRequest) {
985                 case TransactionRequest.Begin:
986                     sqlBatch.Append(TdsEnums.TRANS_BEGIN);
987                     sqlBatch.Append(transactionName);
988                     break;
989                 case TransactionRequest.Promote:
990                     Debug.Assert(false, "Promote called with transaction name or on pre-Yukon!");
991                     break;
992                 case TransactionRequest.Commit:
993                     sqlBatch.Append(TdsEnums.TRANS_COMMIT);
994                     sqlBatch.Append(transactionName);
995                     break;
996                 case TransactionRequest.Rollback:
997                     sqlBatch.Append(TdsEnums.TRANS_ROLLBACK);
998                     sqlBatch.Append(transactionName);
999                     break;
1000                 case TransactionRequest.IfRollback:
1001                     sqlBatch.Append(TdsEnums.TRANS_IF_ROLLBACK);
1002                     sqlBatch.Append(transactionName);
1003                     break;
1004                 case TransactionRequest.Save:
1005                     sqlBatch.Append(TdsEnums.TRANS_SAVE);
1006                     sqlBatch.Append(transactionName);
1007                     break;
1008                 default:
1009                     Debug.Assert(false, "Unknown transaction type");
1010                     break;
1011             }
1012
1013             Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch(sqlBatch.ToString(), ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true);
1014             Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");            
1015             _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
1016
1017             // Prior to Yukon, we didn't have any transaction tokens to manage,
1018             // or any feedback to know when one was created, so we just presume
1019             // that successful execution of the request caused the transaction
1020             // to be created, and we set that on the parser.
1021             if (TransactionRequest.Begin == transactionRequest) {
1022                 Debug.Assert(null != internalTransaction, "Begin Transaction request without internal transaction");
1023                 _parser.CurrentTransaction = internalTransaction;
1024             }
1025         }
1026         
1027
1028         internal void ExecuteTransactionYukon(
1029                     TransactionRequest      transactionRequest, 
1030                     string                  transactionName, 
1031                     IsolationLevel          iso, 
1032                     SqlInternalTransaction  internalTransaction, 
1033                     bool                    isDelegateControlRequest) {
1034             TdsEnums.TransactionManagerRequestType    requestType = TdsEnums.TransactionManagerRequestType.Begin;
1035             TdsEnums.TransactionManagerIsolationLevel isoLevel    = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
1036
1037             switch (iso) {
1038                 case IsolationLevel.Unspecified:
1039                     isoLevel = TdsEnums.TransactionManagerIsolationLevel.Unspecified;
1040                     break;
1041                 case IsolationLevel.ReadCommitted:
1042                     isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
1043                     break;
1044                 case IsolationLevel.ReadUncommitted:
1045                     isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadUncommitted;
1046                     break;
1047                 case IsolationLevel.RepeatableRead:
1048                     isoLevel = TdsEnums.TransactionManagerIsolationLevel.RepeatableRead;
1049                     break;
1050                 case IsolationLevel.Serializable:
1051                     isoLevel = TdsEnums.TransactionManagerIsolationLevel.Serializable;
1052                     break;
1053                 case IsolationLevel.Snapshot:
1054                     isoLevel = TdsEnums.TransactionManagerIsolationLevel.Snapshot;
1055                     break;
1056                 case IsolationLevel.Chaos:
1057                     throw SQL.NotSupportedIsolationLevel(iso);
1058                 default:
1059                     throw ADP.InvalidIsolationLevel(iso);
1060             }
1061
1062             TdsParserStateObject stateObj = _parser._physicalStateObj;
1063             TdsParser parser = _parser;
1064             bool mustPutSession = false;
1065             bool releaseConnectionLock = false;
1066
1067             Debug.Assert(!ThreadHasParserLockForClose || _parserLock.ThreadMayHaveLock(), "Thread claims to have parser lock, but lock is not taken");           
1068             if (!ThreadHasParserLockForClose) {
1069                 _parserLock.Wait(canReleaseFromAnyThread:false);
1070                 ThreadHasParserLockForClose = true;   // In case of error, let the connection know that we already own the parser lock
1071                 releaseConnectionLock = true;
1072             }
1073             try {
1074                 switch (transactionRequest) {
1075                     case TransactionRequest.Begin:
1076                         requestType = TdsEnums.TransactionManagerRequestType.Begin;
1077                         break;
1078                     case TransactionRequest.Promote:
1079                         requestType = TdsEnums.TransactionManagerRequestType.Promote;
1080                         break;
1081                     case TransactionRequest.Commit:
1082                         requestType = TdsEnums.TransactionManagerRequestType.Commit;
1083                         break;
1084                     case TransactionRequest.IfRollback:
1085                         // Map IfRollback to Rollback since with Yukon and beyond we should never need
1086                         // the if since the server will inform us when transactions have completed
1087                         // as a result of an error on the server.
1088                     case TransactionRequest.Rollback:
1089                         requestType = TdsEnums.TransactionManagerRequestType.Rollback;
1090                         break;
1091                     case TransactionRequest.Save:
1092                         requestType = TdsEnums.TransactionManagerRequestType.Save;
1093                         break;
1094                     default:
1095                         Debug.Assert(false, "Unknown transaction type");
1096                         break;
1097                 }
1098
1099                 // only restore if connection lock has been taken within the function
1100                 if (internalTransaction != null && internalTransaction.RestoreBrokenConnection && releaseConnectionLock) {
1101                     Task reconnectTask = internalTransaction.Parent.Connection.ValidateAndReconnect(() => {                      
1102                         ThreadHasParserLockForClose = false;
1103                         _parserLock.Release();
1104                         releaseConnectionLock = false;
1105                     }, 0);
1106                     if (reconnectTask != null) {
1107                         AsyncHelper.WaitForCompletion(reconnectTask, 0); // there is no specific timeout for BeginTransaction, uses ConnectTimeout
1108                         internalTransaction.ConnectionHasBeenRestored = true;
1109                         return;
1110                     }
1111                 }
1112                     
1113
1114
1115                 // SQLBUDT #20010853 - Promote, Commit and Rollback requests for
1116                 // delegated transactions often happen while there is an open result
1117                 // set, so we need to handle them by using a different MARS session, 
1118                 // otherwise we'll write on the physical state objects while someone
1119                 // else is using it.  When we don't have MARS enabled, we need to 
1120                 // lock the physical state object to syncronize it's use at least 
1121                 // until we increment the open results count.  Once it's been 
1122                 // incremented the delegated transaction requests will fail, so they
1123                 // won't stomp on anything.
1124                 // 
1125                 // We need to keep this lock through the duration of the TM reqeuest
1126                 // so that we won't hijack a different request's data stream and a
1127                 // different request won't hijack ours, so we have a lock here on 
1128                 // an object that the ExecTMReq will also lock, but since we're on
1129                 // the same thread, the lock is a no-op.
1130
1131                 if (null != internalTransaction && internalTransaction.IsDelegated) {
1132                     if (_parser.MARSOn) {
1133                         stateObj = _parser.GetSession(this);
1134                         mustPutSession = true;
1135                     }
1136                     else if (internalTransaction.OpenResultsCount != 0) {
1137                         throw SQL.CannotCompleteDelegatedTransactionWithOpenResults(this);
1138                     }
1139                 }               
1140
1141                 // SQLBU #406778 - _parser may be nulled out during TdsExecuteTrannsactionManagerRequest.
1142                 //  Only use local variable after this call.
1143                 _parser.TdsExecuteTransactionManagerRequest(null, requestType, transactionName, isoLevel,
1144                     ConnectionOptions.ConnectTimeout, internalTransaction, stateObj, isDelegateControlRequest);
1145             }   
1146             finally {
1147                 if (mustPutSession) {
1148                     parser.PutSession(stateObj);
1149                 }
1150
1151                 if (releaseConnectionLock) {
1152                     ThreadHasParserLockForClose = false;
1153                     _parserLock.Release();
1154                 }
1155             }
1156         }
1157
1158         ////////////////////////////////////////////////////////////////////////////////////////
1159         // DISTRIBUTED TRANSACTION METHODS
1160         ////////////////////////////////////////////////////////////////////////////////////////
1161
1162         override internal void DelegatedTransactionEnded() {
1163             // 
1164             base.DelegatedTransactionEnded();
1165         }
1166         
1167         override protected byte[] GetDTCAddress() {
1168             byte[] dtcAddress = _parser.GetDTCAddress(ConnectionOptions.ConnectTimeout, _parser.GetSession(this));
1169             Debug.Assert(null != dtcAddress, "null dtcAddress?");
1170             return dtcAddress;
1171         }
1172
1173         override protected void PropagateTransactionCookie(byte[] cookie) {
1174             _parser.PropagateDistributedTransaction(cookie, ConnectionOptions.ConnectTimeout, _parser._physicalStateObj);
1175         }
1176       
1177         ////////////////////////////////////////////////////////////////////////////////////////
1178         // LOGIN-RELATED METHODS
1179         ////////////////////////////////////////////////////////////////////////////////////////
1180
1181         private void CompleteLogin(bool enlistOK) {
1182             _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
1183
1184             if (_routingInfo == null) { // ROR should not affect state of connection recovery
1185                 if (_federatedAuthenticationRequested && !_federatedAuthenticationAcknowledged) {
1186                     Bid.Trace("<sc.SqlInternalConnectionTds.CompleteLogin|ERR> %d#, Server did not acknowledge the federated authentication request\n", ObjectID);
1187                     throw SQL.ParsingError(ParsingErrorState.FedAuthNotAcknowledged);
1188                 }
1189                 if (_federatedAuthenticationInfoRequested && !_federatedAuthenticationInfoReceived) {
1190                     Bid.Trace("<sc.SqlInternalConnectionTds.CompleteLogin|ERR> %d#, Server never sent the requested federated authentication info\n", ObjectID);
1191                     throw SQL.ParsingError(ParsingErrorState.FedAuthInfoNotReceived);
1192                 }
1193
1194                 if (!_sessionRecoveryAcknowledged) {
1195                     _currentSessionData = null;
1196                     if (_recoverySessionData != null) {
1197                         throw SQL.CR_NoCRAckAtReconnection(this);
1198                     }
1199                 }
1200                 if (_currentSessionData != null && _recoverySessionData==null) {
1201                         _currentSessionData._initialDatabase = CurrentDatabase;
1202                         _currentSessionData._initialCollation = _currentSessionData._collation;
1203                         _currentSessionData._initialLanguage = _currentLanguage;                        
1204                 }
1205                 bool isEncrypted = _parser.EncryptionOptions == EncryptionOptions.ON;
1206                 if (_recoverySessionData != null) {
1207                     if (_recoverySessionData._encrypted != isEncrypted) {
1208                         throw SQL.CR_EncryptionChanged(this);
1209                     }                                      
1210                 }
1211                 if (_currentSessionData != null) {
1212                     _currentSessionData._encrypted = isEncrypted;
1213                 }
1214                 _recoverySessionData = null;
1215             }
1216
1217             Debug.Assert(SniContext.Snix_Login == Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Snix_Login; actual Value: {0}", Parser._physicalStateObj.SniContext));
1218             _parser._physicalStateObj.SniContext = SniContext.Snix_EnableMars;
1219             _parser.EnableMars();
1220
1221             _fConnectionOpen = true; // mark connection as open
1222
1223             if (Bid.AdvancedOn) {
1224                 Bid.Trace("<sc.SqlInternalConnectionTds.CompleteLogin|ADV> Post-Login Phase: Server connection obtained.\n");
1225             }
1226
1227             // for non-pooled connections, enlist in a distributed transaction
1228             // if present - and user specified to enlist
1229             if(enlistOK && ConnectionOptions.Enlist) {
1230                 _parser._physicalStateObj.SniContext = SniContext.Snix_AutoEnlist;
1231                 SysTx.Transaction tx = ADP.GetCurrentTransaction();
1232                 Enlist(tx);
1233             }
1234             _parser._physicalStateObj.SniContext=SniContext.Snix_Login;
1235         }
1236
1237         private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, SecureString newSecurePassword) {
1238             // create a new login record
1239             SqlLogin login = new SqlLogin();
1240
1241             // gather all the settings the user set in the connection string or
1242             // properties and do the login
1243             CurrentDatabase   = server.ResolvedDatabaseName;
1244             _currentPacketSize = ConnectionOptions.PacketSize;
1245             _currentLanguage   = ConnectionOptions.CurrentLanguage;
1246
1247             int timeoutInSeconds = 0;
1248
1249             // If a timeout tick value is specified, compute the timeout based
1250             // upon the amount of time left in seconds.
1251             if (!timeout.IsInfinite)
1252             {
1253                 long t = timeout.MillisecondsRemaining/1000;
1254                 if ((long)Int32.MaxValue > t)
1255                 {
1256                     timeoutInSeconds = (int)t;
1257                 }
1258             }
1259
1260             login.authentication = ConnectionOptions.Authentication;
1261             login.timeout = timeoutInSeconds;
1262             login.userInstance     = ConnectionOptions.UserInstance;
1263             login.hostName         = ConnectionOptions.ObtainWorkstationId();
1264             login.userName         = ConnectionOptions.UserID;
1265             login.password         = ConnectionOptions.Password;
1266             login.applicationName  = ConnectionOptions.ApplicationName;
1267
1268             login.language         = _currentLanguage;
1269             if (!login.userInstance) { // Do not send attachdbfilename or database to SSE primary instance
1270                 login.database         = CurrentDatabase;;
1271                 login.attachDBFilename = ConnectionOptions.AttachDBFilename;
1272             }
1273
1274             // VSTS#795621 - Ensure ServerName is Sent During TdsLogin To Enable Sql Azure Connectivity.
1275             // Using server.UserServerName (versus ConnectionOptions.DataSource) since TdsLogin requires 
1276             // serverName to always be non-null.
1277             login.serverName = server.UserServerName;
1278
1279             login.useReplication   = ConnectionOptions.Replication;
1280             login.useSSPI          = ConnectionOptions.IntegratedSecurity 
1281                                      || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && !_fedAuthRequired);
1282             login.packetSize       = _currentPacketSize;
1283             login.newPassword      = newPassword;
1284             login.readOnlyIntent   = ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly;
1285             login.credential       = _credential;
1286             if (newSecurePassword != null) {
1287                 login.newSecurePassword = newSecurePassword;
1288             }
1289
1290             TdsEnums.FeatureExtension requestedFeatures = TdsEnums.FeatureExtension.None;
1291             if (ConnectionOptions.ConnectRetryCount>0) {
1292                 requestedFeatures |= TdsEnums.FeatureExtension.SessionRecovery;
1293                 _sessionRecoveryRequested = true;
1294             }
1295
1296             // If the workflow being used is Active Directory Password or Active Directory Integrated and server's prelogin response
1297             // for FEDAUTHREQUIRED option indicates Federated Authentication is required, we have to insert FedAuth Feature Extension
1298             // in Login7, indicating the intent to use Active Directory Authentication Library for SQL Server.
1299             if (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword
1300                 || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired)) {
1301                 requestedFeatures |= TdsEnums.FeatureExtension.FedAuth;
1302                 _federatedAuthenticationInfoRequested = true;
1303                 _fedAuthFeatureExtensionData = 
1304                     new FederatedAuthenticationFeatureExtensionData { 
1305                         libraryType = TdsEnums.FedAuthLibrary.ADAL,
1306                         authentication = ConnectionOptions.Authentication,
1307                         fedAuthRequiredPreLoginResponse = _fedAuthRequired
1308                     };
1309             }
1310             if (_accessTokenInBytes != null) {
1311                 requestedFeatures |= TdsEnums.FeatureExtension.FedAuth;
1312                 _fedAuthFeatureExtensionData = new FederatedAuthenticationFeatureExtensionData {
1313                         libraryType = TdsEnums.FedAuthLibrary.SecurityToken,
1314                         fedAuthRequiredPreLoginResponse = _fedAuthRequired,
1315                         accessToken = _accessTokenInBytes
1316                     };
1317                 // No need any further info from the server for token based authentication. So set _federatedAuthenticationRequested to true
1318                 _federatedAuthenticationRequested = true;
1319             }
1320
1321             // The TCE and GLOBALTRANSACTIONS feature are implicitly requested
1322             requestedFeatures |= TdsEnums.FeatureExtension.Tce | TdsEnums.FeatureExtension.GlobalTransactions;
1323             _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData);
1324         }
1325
1326         private void LoginFailure() {
1327             Bid.Trace("<sc.SqlInternalConnectionTds.LoginFailure|RES|CPOOL> %d#\n", ObjectID);
1328
1329             // If the parser was allocated and we failed, then we must have failed on
1330             // either the Connect or Login, either way we should call Disconnect.
1331             // Disconnect can be called if the connection is already closed - becomes
1332             // no-op, so no issues there.
1333             if (_parser != null) {
1334
1335                 _parser.Disconnect();
1336             }
1337             // 
1338         }
1339
1340         private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential,
1341                     string newPassword, SecureString newSecurePassword, bool redirectedUserInstance) {
1342             bool useFailoverPartner; // should we use primary or secondary first
1343             ServerInfo dataSource = new ServerInfo(connectionOptions);
1344             string failoverPartner;
1345
1346             if (null != PoolGroupProviderInfo) {
1347                 useFailoverPartner = PoolGroupProviderInfo.UseFailoverPartner;
1348                 failoverPartner = PoolGroupProviderInfo.FailoverPartner;
1349             }
1350             else {
1351                 // Only ChangePassword or SSE User Instance comes through this code path.
1352                 useFailoverPartner = false;
1353                 failoverPartner = ConnectionOptions.FailoverPartner; 
1354             }
1355
1356             timeoutErrorInternal.SetInternalSourceType(useFailoverPartner ? SqlConnectionInternalSourceType.Failover : SqlConnectionInternalSourceType.Principle);
1357
1358             bool hasFailoverPartner = !ADP.IsEmpty(failoverPartner);
1359
1360             // Open the connection and Login
1361             try {
1362                 timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
1363                 if (hasFailoverPartner) {
1364                     timeoutErrorInternal.SetFailoverScenario(true); // this is a failover scenario
1365                     LoginWithFailover(
1366                                 useFailoverPartner, 
1367                                 dataSource, 
1368                                 failoverPartner, 
1369                                 newPassword,
1370                                 newSecurePassword,
1371                                 redirectedUserInstance, 
1372                                 connectionOptions,
1373                                 credential,
1374                                 timeout);
1375                 }
1376                 else {
1377                     timeoutErrorInternal.SetFailoverScenario(false); // not a failover scenario
1378                     LoginNoFailover(dataSource, newPassword, newSecurePassword, redirectedUserInstance, 
1379                             connectionOptions, credential, timeout);
1380                 }
1381                 timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
1382             }
1383             catch (Exception e) {
1384                 // 
1385                 if (ADP.IsCatchableExceptionType(e)) {
1386                     LoginFailure();
1387                 }
1388                 throw;
1389             }
1390             timeoutErrorInternal.SetAllCompleteMarker();
1391
1392 #if DEBUG
1393             _parser._physicalStateObj.InvalidateDebugOnlyCopyOfSniContext();
1394 #endif
1395         }
1396
1397     // Is the given Sql error one that should prevent retrying
1398     //   to connect.
1399     private bool IsDoNotRetryConnectError(SqlException exc) {
1400
1401         return (TdsEnums.LOGON_FAILED == exc.Number) // actual logon failed, i.e. bad password
1402             || (TdsEnums.PASSWORD_EXPIRED == exc.Number) // actual logon failed, i.e. password isExpired
1403             || (TdsEnums.IMPERSONATION_FAILED == exc.Number)  // Insuficient privelege for named pipe, among others
1404             || exc._doNotReconnect; // Exception explicitly supressed reconnection attempts
1405     }
1406
1407     // Attempt to login to a host that does not have a failover partner
1408     //
1409     //  Will repeatedly attempt to connect, but back off between each attempt so as not to clog the network.
1410     //  Back off period increases for first few failures: 100ms, 200ms, 400ms, 800ms, then 1000ms for subsequent attempts
1411     //
1412     // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1413     //  DEVNOTE: The logic in this method is paralleled by the logic in LoginWithFailover.
1414     //           Changes to either one should be examined to see if they need to be reflected in the other
1415     // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1416     private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, bool redirectedUserInstance, 
1417                 SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout) {
1418
1419         Debug.Assert(object.ReferenceEquals(connectionOptions, this.ConnectionOptions), "ConnectionOptions argument and property must be the same"); // consider removing the argument
1420         int routingAttempts = 0;
1421         ServerInfo originalServerInfo = serverInfo; // serverInfo may end up pointing to new object due to routing, original object is used to set CurrentDatasource
1422
1423         if (Bid.AdvancedOn) {
1424             Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover|ADV> %d#, host=%ls\n", ObjectID, serverInfo.UserServerName);
1425         }
1426         int  sleepInterval = 100;  //milliseconds to sleep (back off) between attempts.
1427
1428         ResolveExtendedServerName(serverInfo, !redirectedUserInstance, connectionOptions);
1429
1430         long timeoutUnitInterval = 0;
1431         Boolean isParallel = connectionOptions.MultiSubnetFailover || connectionOptions.TransparentNetworkIPResolution;
1432
1433         if(isParallel) {
1434             // Determine unit interval
1435             if (timeout.IsInfinite) {
1436                 timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * (1000L * ADP.DefaultConnectionTimeout)));
1437             }
1438             else {
1439                 timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining));
1440             }
1441         }
1442         // Only three ways out of this loop:
1443         //  1) Successfully connected
1444         //  2) Parser threw exception while main timer was expired
1445         //  3) Parser threw logon failure-related exception 
1446         //  4) Parser threw exception in post-initial connect code,
1447         //      such as pre-login handshake or during actual logon. (parser state != Closed)
1448         //
1449         //  Of these methods, only #1 exits normally. This preserves the call stack on the exception 
1450         //  back into the parser for the error cases.
1451         int attemptNumber = 0;
1452         TimeoutTimer intervalTimer = null;
1453         TimeoutTimer firstTransparentAttemptTimeout = TimeoutTimer.StartMillisecondsTimeout(ADP.FirstTransparentAttemptTimeout);
1454         TimeoutTimer attemptOneLoginTimeout = timeout;
1455         while(true) {
1456
1457             if(isParallel) {
1458                 attemptNumber++;
1459                 // Set timeout for this attempt, but don't exceed original timer                
1460                 long nextTimeoutInterval = checked(timeoutUnitInterval * attemptNumber);
1461                 long milliseconds = timeout.MillisecondsRemaining;
1462                 if (nextTimeoutInterval > milliseconds) {
1463                     nextTimeoutInterval = milliseconds;
1464                 }
1465                 intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval);
1466             }
1467
1468             // Re-allocate parser each time to make sure state is known
1469             // RFC 50002652 - if parser was created by previous attempt, dispose it to properly close the socket, if created
1470             if (_parser != null)
1471                 _parser.Disconnect();
1472  
1473             _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
1474             Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
1475
1476             try {
1477                 // 
1478
1479
1480                 Boolean isFirstTransparentAttempt =  connectionOptions.TransparentNetworkIPResolution && attemptNumber == 1;
1481
1482                 if(isFirstTransparentAttempt) {
1483                     attemptOneLoginTimeout = firstTransparentAttemptTimeout;
1484                 }
1485                 else {
1486                     if(isParallel) {
1487                         attemptOneLoginTimeout = intervalTimer;
1488                     }
1489                 }
1490                 
1491                 AttemptOneLogin(    serverInfo, 
1492                                     newPassword,
1493                                     newSecurePassword,
1494                                     !isParallel,    // ignore timeout for SniOpen call unless MSF , and TNIR
1495                                     attemptOneLoginTimeout,
1496                                     isFirstTransparentAttempt:isFirstTransparentAttempt);
1497                 
1498                 if (connectionOptions.MultiSubnetFailover && null != ServerProvidedFailOverPartner) {
1499                     // connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used.
1500                     throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
1501                 }
1502
1503                 if (_routingInfo != null) {
1504                     Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover> Routed to %ls", serverInfo.ExtendedServerName);
1505
1506                     if (routingAttempts > 0) {
1507                         throw SQL.ROR_RecursiveRoutingNotSupported(this);
1508                     }
1509
1510                     if (timeout.IsExpired) {
1511                         throw SQL.ROR_TimeoutAfterRoutingInfo(this);
1512                     }                    
1513
1514                     serverInfo = new ServerInfo(ConnectionOptions, _routingInfo, serverInfo.ResolvedServerName);
1515                     timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination);
1516                     _originalClientConnectionId = _clientConnectionId;
1517                     _routingDestination = serverInfo.UserServerName;
1518
1519                     // restore properties that could be changed by the environment tokens
1520                     _currentPacketSize = ConnectionOptions.PacketSize;
1521                     _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage;
1522                     CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog;
1523                     _currentFailoverPartner = null;
1524                     _instanceName = String.Empty;
1525
1526                     routingAttempts++;
1527
1528                     continue; // repeat the loop, but skip code reserved for failed connections (after the catch)
1529                 }
1530                 else {
1531                     break; // leave the while loop -- we've successfully connected
1532                 }
1533             }
1534             catch (SqlException sqlex) {
1535                 if (null == _parser
1536                     || TdsParserState.Closed != _parser.State
1537                     || IsDoNotRetryConnectError(sqlex)
1538                     || timeout.IsExpired) {       // no more time to try again
1539                     throw;  // Caller will call LoginFailure()
1540                 }
1541
1542                 // Check sleep interval to make sure we won't exceed the timeout
1543                 //  Do this in the catch block so we can re-throw the current exception
1544                 if (timeout.MillisecondsRemaining <= sleepInterval) {
1545                     throw;
1546                 }
1547
1548                 // 
1549             }
1550
1551             // We only get here when we failed to connect, but are going to re-try
1552
1553             // Switch to failover logic if the server provided a partner
1554             if (null != ServerProvidedFailOverPartner) {
1555                 if (connectionOptions.MultiSubnetFailover) {
1556                     // connection failed: do not allow failover to server-provided failover partner if MultiSubnetFailover is set
1557                     throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
1558                 }
1559                 Debug.Assert(ConnectionOptions.ApplicationIntent != ApplicationIntent.ReadOnly, "FAILOVER+AppIntent=RO: Should already fail (at LOGSHIPNODE in OnEnvChange)");
1560
1561                 timeoutErrorInternal.ResetAndRestartPhase();
1562                 timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
1563                 timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
1564                 timeoutErrorInternal.SetFailoverScenario(true); // this is a failover scenario
1565                 LoginWithFailover(
1566                             true,   // start by using failover partner, since we already failed to connect to the primary
1567                             serverInfo,
1568                             ServerProvidedFailOverPartner,
1569                             newPassword,
1570                             newSecurePassword,
1571                             redirectedUserInstance,
1572                             connectionOptions,
1573                             credential,
1574                             timeout);
1575                 return; // LoginWithFailover successfully connected and handled entire connection setup
1576             }
1577
1578             // Sleep for a bit to prevent clogging the network with requests, 
1579             //  then update sleep interval for next iteration (max 1 second interval)
1580             if (Bid.AdvancedOn) {
1581                 Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover|ADV> %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
1582             }
1583             Thread.Sleep(sleepInterval);
1584             sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
1585         }
1586
1587         if (null != PoolGroupProviderInfo) {
1588             // We must wait for CompleteLogin to finish for to have the
1589             // env change from the server to know its designated failover 
1590             // partner; save this information in _currentFailoverPartner.
1591             PoolGroupProviderInfo.FailoverCheck(this, false, connectionOptions, ServerProvidedFailOverPartner);
1592         }
1593         CurrentDataSource = originalServerInfo.UserServerName;
1594     }
1595
1596     // Attempt to login to a host that has a failover partner
1597     //
1598     // Connection & timeout sequence is
1599     //      First target, timeout = interval * 1
1600     //      second target, timeout = interval * 1
1601     //      sleep for 100ms
1602     //      First target, timeout = interval * 2
1603     //      Second target, timeout = interval * 2
1604     //      sleep for 200ms
1605     //      First Target, timeout = interval * 3
1606     //      etc.
1607     //
1608     // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1609     //  DEVNOTE: The logic in this method is paralleled by the logic in LoginNoFailover.
1610     //           Changes to either one should be examined to see if they need to be reflected in the other
1611     // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1612     private void LoginWithFailover(
1613             bool                useFailoverHost, 
1614             ServerInfo          primaryServerInfo, 
1615             string              failoverHost, 
1616             string              newPassword,
1617             SecureString        newSecurePassword,
1618             bool                redirectedUserInstance, 
1619             SqlConnectionString connectionOptions,
1620             SqlCredential       credential,
1621             TimeoutTimer        timeout
1622         ) {
1623
1624         Debug.Assert(!connectionOptions.MultiSubnetFailover, "MultiSubnetFailover should not be set if failover partner is used");
1625
1626         if (Bid.AdvancedOn) {
1627             Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, useFailover=%d{bool}, primary=", ObjectID, useFailoverHost);
1628             Bid.PutStr(primaryServerInfo.UserServerName);
1629             Bid.PutStr(", failover=");
1630             Bid.PutStr(failoverHost);
1631             Bid.PutStr("\n");
1632         }
1633         int  sleepInterval = 100;  //milliseconds to sleep (back off) between attempts.
1634         long timeoutUnitInterval;
1635
1636         string     protocol = ConnectionOptions.NetworkLibrary;
1637         ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost);
1638
1639         ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions);
1640         if (null == ServerProvidedFailOverPartner) {// No point in resolving the failover partner when we're going to override it below
1641             // Don't resolve aliases if failover == primary // 
1642             ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryServerInfo.UserServerName, connectionOptions);
1643         }
1644
1645         // Determine unit interval
1646         if (timeout.IsInfinite) {
1647             timeoutUnitInterval = checked((long) ADP.FailoverTimeoutStep * ADP.TimerFromSeconds(ADP.DefaultConnectionTimeout));
1648         }
1649         else {
1650             timeoutUnitInterval = checked((long) (ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining));
1651         }
1652
1653         // Initialize loop variables
1654         bool failoverDemandDone = false; // have we demanded for partner information yet (as necessary)?
1655         int attemptNumber = 0;
1656
1657         // Only three ways out of this loop:
1658         //  1) Successfully connected
1659         //  2) Parser threw exception while main timer was expired
1660         //  3) Parser threw logon failure-related exception (LOGON_FAILED, PASSWORD_EXPIRED, etc)
1661         //
1662         //  Of these methods, only #1 exits normally. This preserves the call stack on the exception 
1663         //  back into the parser for the error cases.
1664         while (true) {
1665             // Set timeout for this attempt, but don't exceed original timer
1666             long nextTimeoutInterval = checked(timeoutUnitInterval * ((attemptNumber / 2) + 1));
1667             long milliseconds = timeout.MillisecondsRemaining;
1668             if (nextTimeoutInterval > milliseconds) {
1669                 nextTimeoutInterval = milliseconds;
1670             }
1671
1672             TimeoutTimer intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval);
1673
1674             // Re-allocate parser each time to make sure state is known
1675             // RFC 50002652 - if parser was created by previous attempt, dispose it to properly close the socket, if created
1676             if (_parser != null)
1677                 _parser.Disconnect();
1678
1679             _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
1680             Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
1681
1682             ServerInfo currentServerInfo;
1683             if (useFailoverHost) {
1684                 if (!failoverDemandDone) {
1685                     FailoverPermissionDemand();
1686                     failoverDemandDone = true;
1687                 }
1688
1689                 // Primary server may give us a different failover partner than the connection string indicates.  Update it
1690                 if (null != ServerProvidedFailOverPartner && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner) {
1691                     if (Bid.AdvancedOn) {
1692                         Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, new failover partner=%ls\n", ObjectID, ServerProvidedFailOverPartner);
1693                     }
1694                     failoverServerInfo.SetDerivedNames(protocol, ServerProvidedFailOverPartner);
1695                 }
1696                 currentServerInfo = failoverServerInfo;
1697                 timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
1698             }
1699             else {
1700                 currentServerInfo = primaryServerInfo;
1701                 timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Principle);
1702             }
1703
1704             try {
1705                 // Attempt login.  Use timerInterval for attempt timeout unless infinite timeout was requested.
1706                 AttemptOneLogin(
1707                         currentServerInfo,
1708                         newPassword,
1709                         newSecurePassword,
1710                         false,          // Use timeout in SniOpen
1711                         intervalTimer,
1712                         withFailover:true
1713                         );
1714
1715                 if (_routingInfo != null) {
1716                     // We are in login with failover scenation and server sent routing information
1717                     // If it is read-only routing - we did not supply AppIntent=RO (it should be checked before)
1718                     // If it is something else, not known yet (future server) - this client is not designed to support this.                    
1719                     // In any case, server should not have sent the routing info.
1720                     Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover> Routed to %ls", _routingInfo.ServerName);
1721                     throw SQL.ROR_UnexpectedRoutingInfo(this);
1722                 }
1723
1724                 break; // leave the while loop -- we've successfully connected
1725             }
1726             catch (SqlException sqlex) {
1727                 if (IsDoNotRetryConnectError(sqlex)
1728                         || timeout.IsExpired) 
1729                 {       // no more time to try again
1730                     throw;  // Caller will call LoginFailure()
1731                 }
1732
1733                 if (IsConnectionDoomed) {
1734                     throw;
1735                 }
1736
1737                 if (1 == attemptNumber % 2) {
1738                     // Check sleep interval to make sure we won't exceed the original timeout
1739                     //  Do this in the catch block so we can re-throw the current exception
1740                     if (timeout.MillisecondsRemaining <= sleepInterval) {
1741                         throw;
1742                     }
1743                 }
1744
1745                 // 
1746             }
1747
1748             // We only get here when we failed to connect, but are going to re-try
1749
1750             // After trying to connect to both servers fails, sleep for a bit to prevent clogging 
1751             //  the network with requests, then update sleep interval for next iteration (max 1 second interval)
1752             if (1 == attemptNumber % 2) {
1753                 if (Bid.AdvancedOn) {
1754                     Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
1755                 }
1756                 Thread.Sleep(sleepInterval);
1757                 sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
1758             }
1759
1760             // Update attempt number and target host
1761             attemptNumber++;
1762             useFailoverHost = !useFailoverHost;
1763         }
1764
1765         // If we get here, connection/login succeeded!  Just a few more checks & record-keeping
1766
1767         // if connected to failover host, but said host doesn't have DbMirroring set up, throw an error
1768         if (useFailoverHost && null == ServerProvidedFailOverPartner) {
1769             throw SQL.InvalidPartnerConfiguration(failoverHost, CurrentDatabase);
1770         }
1771
1772         if (null != PoolGroupProviderInfo) {
1773             // We must wait for CompleteLogin to finish for to have the
1774             // env change from the server to know its designated failover 
1775             // partner; save this information in _currentFailoverPartner.
1776             PoolGroupProviderInfo.FailoverCheck(this, useFailoverHost, connectionOptions, ServerProvidedFailOverPartner);
1777         }
1778         CurrentDataSource = (useFailoverHost ? failoverHost : primaryServerInfo.UserServerName);
1779     }
1780
1781     private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, SqlConnectionString options) {
1782         if (serverInfo.ExtendedServerName == null) {
1783             string host = serverInfo.UserServerName;
1784             string protocol = serverInfo.UserProtocol;
1785
1786             if (aliasLookup) { // We skip this for UserInstances...
1787                 // Perform registry lookup to see if host is an alias.  It will appropriately set host and protocol, if an Alias.
1788                 // Check if it was already resolved, during CR reconnection _currentSessionData values will be copied from
1789                 // _reconnectSessonData of the previous connection
1790                 if (_currentSessionData != null && !string.IsNullOrEmpty(host)) {
1791                     Tuple<string, string> hostPortPair;
1792                     if (_currentSessionData._resolvedAliases.TryGetValue(host, out hostPortPair)) {
1793                         host = hostPortPair.Item1;
1794                         protocol = hostPortPair.Item2;
1795                     }
1796                     else {
1797                         TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
1798                         _currentSessionData._resolvedAliases.Add(serverInfo.UserServerName, new Tuple<string, string>(host, protocol));
1799                     }
1800                 }
1801                 else {
1802                     TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
1803                 }
1804
1805                 //
1806                 if (options.EnforceLocalHost) {
1807                     // verify LocalHost for |DataDirectory| usage
1808                     SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/);
1809                 }
1810             }
1811
1812             serverInfo.SetDerivedNames(protocol, host);
1813         }
1814     }
1815
1816     // Common code path for making one attempt to establish a connection and log in to server.
1817     private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, bool ignoreSniOpenTimeout, TimeoutTimer timeout, bool withFailover = false, bool isFirstTransparentAttempt = true) {
1818         if (Bid.AdvancedOn) {
1819             Bid.Trace("<sc.SqlInternalConnectionTds.AttemptOneLogin|ADV> %d#, timout=%I64d{msec}, server=", ObjectID, timeout.MillisecondsRemaining);
1820             Bid.PutStr(serverInfo.ExtendedServerName);
1821             Bid.Trace("\n");
1822         }
1823
1824         _routingInfo = null; // forget routing information 
1825
1826         _parser._physicalStateObj.SniContext = SniContext.Snix_Connect;
1827
1828         _parser.Connect(serverInfo,
1829                         this,
1830                         ignoreSniOpenTimeout,
1831                         timeout.LegacyTimerExpire,
1832                         ConnectionOptions.Encrypt,
1833                         ConnectionOptions.TrustServerCertificate,
1834                         ConnectionOptions.IntegratedSecurity,
1835                         withFailover,
1836                         isFirstTransparentAttempt,
1837                         ConnectionOptions.Authentication);
1838
1839         timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake);
1840         timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin);
1841
1842         _parser._physicalStateObj.SniContext = SniContext.Snix_Login;
1843         this.Login(serverInfo, timeout, newPassword, newSecurePassword);
1844
1845         timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth);
1846         timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
1847
1848         CompleteLogin(!ConnectionOptions.Pooling);
1849
1850         timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
1851     }
1852
1853
1854     internal void FailoverPermissionDemand() {
1855         if (null != PoolGroupProviderInfo) {
1856             PoolGroupProviderInfo.FailoverPermissionDemand();
1857         }
1858     }
1859
1860         ////////////////////////////////////////////////////////////////////////////////////////
1861         // PREPARED COMMAND METHODS
1862         ////////////////////////////////////////////////////////////////////////////////////////
1863         
1864         protected override object ObtainAdditionalLocksForClose() {
1865             bool obtainParserLock = !ThreadHasParserLockForClose;
1866             Debug.Assert(obtainParserLock || _parserLock.ThreadMayHaveLock(), "Thread claims to have lock, but lock is not taken");
1867             if (obtainParserLock) {
1868                 _parserLock.Wait(canReleaseFromAnyThread: false);
1869                 ThreadHasParserLockForClose = true;
1870             }
1871             return obtainParserLock;
1872         }
1873
1874         protected override void ReleaseAdditionalLocksForClose(object lockToken) {
1875             Debug.Assert(lockToken is bool, "Lock token should be boolean");
1876             if ((bool)lockToken) {
1877                 ThreadHasParserLockForClose = false;
1878                 _parserLock.Release();
1879             }
1880         }
1881
1882         // called by SqlConnection.RepairConnection which is a relatevly expensive way of repair inner connection
1883         // prior to execution of request, used from EnlistTransaction, EnlistDistributedTransaction and ChangeDatabase
1884         internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = 0) {
1885
1886             Debug.Assert(!ThreadHasParserLockForClose, "Cannot call this method if caller has parser lock");
1887             if (ThreadHasParserLockForClose) {
1888                 return false; // we cannot restore if we cannot release lock
1889             }
1890
1891             _parserLock.Wait(canReleaseFromAnyThread: false);
1892             ThreadHasParserLockForClose = true;   // In case of error, let the connection know that we already own the parser lock
1893             bool releaseConnectionLock = true;
1894
1895             try {
1896                 RuntimeHelpers.PrepareConstrainedRegions();
1897                 try {
1898 #if DEBUG
1899                     TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
1900                     RuntimeHelpers.PrepareConstrainedRegions();
1901                     try {
1902                         tdsReliabilitySection.Start();
1903 #endif //DEBUG
1904                         Task reconnectTask = parent.ValidateAndReconnect(() => {
1905                             ThreadHasParserLockForClose = false;
1906                             _parserLock.Release();
1907                             releaseConnectionLock = false;
1908                         }, timeout);
1909                         if (reconnectTask != null) {
1910                             AsyncHelper.WaitForCompletion(reconnectTask, timeout);
1911                             return true;
1912                         }
1913                         return false;
1914 #if DEBUG
1915                     }
1916                     finally {
1917                         tdsReliabilitySection.Stop();
1918                     }
1919 #endif //DEBUG
1920                 }
1921                 catch (System.OutOfMemoryException) {
1922                     DoomThisConnection();
1923                     throw;
1924                 }
1925                 catch (System.StackOverflowException) {
1926                     DoomThisConnection();
1927                     throw;
1928                 }
1929                 catch (System.Threading.ThreadAbortException) {
1930                     DoomThisConnection();
1931                     throw;
1932                 }
1933             }
1934             finally {
1935                 if (releaseConnectionLock) {
1936                     ThreadHasParserLockForClose = false;
1937                     _parserLock.Release();
1938                 }
1939             }
1940         }
1941
1942         ////////////////////////////////////////////////////////////////////////////////////////
1943         // PARSER CALLBACKS
1944         ////////////////////////////////////////////////////////////////////////////////////////
1945
1946         internal void BreakConnection() {
1947             var connection = Connection;
1948             Bid.Trace("<sc.SqlInternalConnectionTds.BreakConnection|RES|CPOOL> %d#, Breaking connection.\n", ObjectID);
1949             DoomThisConnection();   // Mark connection as unusable, so it will be destroyed
1950             if (null != connection) {
1951                 connection.Close();                
1952             }
1953         }
1954
1955         internal bool IgnoreEnvChange { // true if we are only draining environment change tokens, used by TdsParser
1956             get {
1957                 return _routingInfo != null; // connection was routed, ignore rest of env change
1958             }
1959         }
1960
1961         internal void OnEnvChange(SqlEnvChange rec) {
1962             Debug.Assert(!IgnoreEnvChange,"This function should not be called if IgnoreEnvChange is set!");
1963             switch (rec.type) {
1964                 case TdsEnums.ENV_DATABASE:
1965                     // If connection is not open and recovery is not in progresss, store the server value as the original.
1966                     if (!_fConnectionOpen && _recoverySessionData == null) {
1967                         _originalDatabase = rec.newValue;
1968                     }
1969
1970                     CurrentDatabase = rec.newValue;
1971                     break;
1972
1973                 case TdsEnums.ENV_LANG:
1974                     // If connection is not open and recovery is not in progresss, store the server value as the original.
1975                     if (!_fConnectionOpen && _recoverySessionData == null) {
1976                         _originalLanguage = rec.newValue;
1977                     }
1978
1979                     _currentLanguage = rec.newValue; // TODO: finish this.
1980                     break;
1981
1982                 case TdsEnums.ENV_PACKETSIZE:
1983                     _currentPacketSize = Int32.Parse(rec.newValue, CultureInfo.InvariantCulture);
1984                     break;
1985
1986                 case TdsEnums.ENV_COLLATION:
1987                     if (_currentSessionData != null) {
1988                         _currentSessionData._collation = rec.newCollation;
1989                     }
1990                     break;
1991
1992                 case TdsEnums.ENV_CHARSET:
1993                 case TdsEnums.ENV_LOCALEID:
1994                 case TdsEnums.ENV_COMPFLAGS:
1995                 case TdsEnums.ENV_BEGINTRAN:
1996                 case TdsEnums.ENV_COMMITTRAN:
1997                 case TdsEnums.ENV_ROLLBACKTRAN:
1998                 case TdsEnums.ENV_ENLISTDTC:
1999                 case TdsEnums.ENV_DEFECTDTC:
2000                     // only used on parser
2001                     break;
2002
2003                 case TdsEnums.ENV_LOGSHIPNODE:
2004                     if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly) {
2005                         throw SQL.ROR_FailoverNotSupportedServer(this);
2006                     }
2007                     _currentFailoverPartner = rec.newValue;
2008                     break;
2009
2010                 case TdsEnums.ENV_PROMOTETRANSACTION:
2011                     PromotedDTCToken = rec.newBinValue;
2012                     break;
2013
2014                 case TdsEnums.ENV_TRANSACTIONENDED:
2015                     break;
2016
2017                 case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS:
2018                     // For now we skip these Yukon only env change notifications
2019                     break;
2020
2021                 case TdsEnums.ENV_SPRESETCONNECTIONACK:
2022                     // connection is being reset 
2023                     if (_currentSessionData != null) {
2024                         _currentSessionData.Reset();
2025                     }                    
2026                     break;
2027
2028                 case TdsEnums.ENV_USERINSTANCE:
2029                     _instanceName = rec.newValue;
2030                     break;
2031
2032                 case TdsEnums.ENV_ROUTING:
2033                     if (Bid.AdvancedOn) {
2034                         Bid.Trace("<sc.SqlInternalConnectionTds.OnEnvChange> %d#, Received routing info\n", ObjectID);
2035                     }
2036                     if (string.IsNullOrEmpty(rec.newRoutingInfo.ServerName) || rec.newRoutingInfo.Protocol != 0 || rec.newRoutingInfo.Port == 0) {
2037                         throw SQL.ROR_InvalidRoutingInfo(this);
2038                     }
2039                     _routingInfo = rec.newRoutingInfo;
2040                     break;
2041
2042                 default:
2043                     Debug.Assert(false, "Missed token in EnvChange!");
2044                     break;
2045             }
2046         }
2047
2048         internal void OnLoginAck(SqlLoginAck rec) {
2049             _loginAck = rec;
2050             // 
2051             if (_recoverySessionData != null) {
2052                 if (_recoverySessionData._tdsVersion != rec.tdsVersion) {
2053                     throw SQL.CR_TDSVersionNotPreserved(this);
2054                 }
2055             }
2056             if (_currentSessionData != null) {
2057                 _currentSessionData._tdsVersion = rec.tdsVersion;
2058             }
2059         }
2060
2061         /// <summary>
2062         /// Generates (if appropriate) and sends a Federated Authentication Access token to the server, using the Federated Authentication Info.
2063         /// </summary>
2064         /// <param name="fedAuthInfo">Federated Authentication Info.</param>
2065         internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) {
2066             Debug.Assert((ConnectionOptions.HasUserIdKeyword && ConnectionOptions.HasPasswordKeyword)
2067                          || _credential != null
2068                          || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired),
2069                          "Credentials aren't provided for calling ADAL");
2070             Debug.Assert(fedAuthInfo != null, "info should not be null.");
2071             Debug.Assert(_dbConnectionPoolAuthenticationContextKey == null, "_dbConnectionPoolAuthenticationContextKey should be null.");
2072
2073             Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, Generating federated authentication token\n", ObjectID);
2074
2075             DbConnectionPoolAuthenticationContext dbConnectionPoolAuthenticationContext = null;
2076
2077             // We want to refresh the token without taking a lock on the context, allowed when the access token is expiring within the next 10 mins.
2078             bool attemptRefreshTokenUnLocked = false;
2079
2080             // We want to refresh the token, if taking the lock on the authentication context is successful.
2081             bool attemptRefreshTokenLocked = false;
2082
2083             // The Federated Authentication returned by TryGetFedAuthTokenLocked or GetFedAuthToken.
2084             SqlFedAuthToken fedAuthToken = null;
2085
2086             if (_dbConnectionPool != null) {
2087                 Debug.Assert(_dbConnectionPool.AuthenticationContexts != null);
2088
2089                 // Construct the dbAuthenticationContextKey with information from FedAuthInfo and store for later use, when inserting in to the token cache.
2090                 _dbConnectionPoolAuthenticationContextKey = new DbConnectionPoolAuthenticationContextKey(fedAuthInfo.stsurl, fedAuthInfo.spn);
2091
2092                 // Try to retrieve the authentication context from the pool, if one does exist for this key.
2093                 if (_dbConnectionPool.AuthenticationContexts.TryGetValue(_dbConnectionPoolAuthenticationContextKey, out dbConnectionPoolAuthenticationContext)) {
2094                     Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null.");
2095
2096                     // The timespan between UTCNow and the token expiry.
2097                     TimeSpan contextValidity = dbConnectionPoolAuthenticationContext.ExpirationTime.Subtract(DateTime.UtcNow);
2098
2099                     // If the authentication context is expiring within next 10 minutes, lets just re-create a token for this connection attempt.
2100                     // And on successful login, try to update the cache with the new token.
2101                     if (contextValidity <= _dbAuthenticationContextUnLockedRefreshTimeSpan) {
2102                         Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The expiration time is less than 10 mins, so trying to get new access token regardless of if an other thread is also trying to update it.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString());
2103
2104                         attemptRefreshTokenUnLocked = true;
2105                     }
2106
2107 #if DEBUG
2108                     // Checking if any failpoints are enabled.
2109                     else if (_forceExpiryUnLocked) {
2110                         attemptRefreshTokenUnLocked = true;
2111                     }
2112                     else if (_forceExpiryLocked) {
2113                         attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out fedAuthToken);
2114                     }
2115 #endif
2116
2117                     // If the token is expiring within the next 45 mins, try to fetch a new token, if there is no thread already doing it.
2118                     // If a thread is already doing the refresh, just use the existing token in the cache and proceed.
2119                     else if (contextValidity <= _dbAuthenticationContextLockedRefreshTimeSpan) {
2120                         if (Bid.AdvancedOn) {
2121                             Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The authentication context needs a refresh.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString());
2122                         }
2123
2124                         // Call the function which tries to acquire a lock over the authentication context before trying to update.
2125                         // If the lock could not be obtained, it will return false, without attempting to fetch a new token.
2126                         attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out fedAuthToken);
2127
2128                         // If TryGetFedAuthTokenLocked returns true, it means lock was obtained and fedAuthToken should not be null.
2129                         // If there was an exception in retrieving the new token, TryGetFedAuthTokenLocked should have thrown, so we won't be here.
2130                         Debug.Assert(!attemptRefreshTokenLocked || fedAuthToken != null, "Either Lock should not have been obtained or fedAuthToken should not be null.");
2131                         Debug.Assert(!attemptRefreshTokenLocked || _newDbConnectionPoolAuthenticationContext != null, "Either Lock should not have been obtained or _newDbConnectionPoolAuthenticationContext should not be null.");
2132
2133                         // Indicate in Bid Trace that we are successful with the update.
2134                         if (attemptRefreshTokenLocked) {
2135                             Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The attempt to get a new access token succeeded under the locked mode.");
2136                         }
2137
2138                     }
2139                     else if (Bid.AdvancedOn) {
2140                         Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, Found an authentication context in the cache that does not need a refresh at this time. Re-using the cached token.\n", ObjectID);
2141                     }
2142                 }
2143             }
2144
2145             // dbConnectionPoolAuthenticationContext will be null if either this is the first connection attempt in the pool or pooling is disabled.
2146             if (dbConnectionPoolAuthenticationContext == null || attemptRefreshTokenUnLocked) {
2147                 // Get the Federated Authentication Token.
2148                 fedAuthToken = GetFedAuthToken(fedAuthInfo);
2149                 Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null.");
2150
2151                 if (_dbConnectionPool != null) {
2152                     // GetFedAuthToken should have updated _newDbConnectionPoolAuthenticationContext.
2153                     Debug.Assert(_newDbConnectionPoolAuthenticationContext != null, "_newDbConnectionPoolAuthenticationContext should not be null.");
2154                 }
2155             }
2156             else if (!attemptRefreshTokenLocked) {
2157                 Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null.");
2158                 Debug.Assert(fedAuthToken == null, "fedAuthToken should be null in this case.");
2159                 Debug.Assert(_newDbConnectionPoolAuthenticationContext == null, "_newDbConnectionPoolAuthenticationContext should be null.");
2160
2161                 fedAuthToken = new SqlFedAuthToken();
2162
2163                 // If the code flow is here, then we are re-using the context from the cache for this connection attempt and not
2164                 // generating a new access token on this thread.
2165                 fedAuthToken.accessToken = dbConnectionPoolAuthenticationContext.AccessToken;
2166             }
2167
2168             Debug.Assert(fedAuthToken != null && fedAuthToken.accessToken != null, "fedAuthToken and fedAuthToken.accessToken cannot be null.");
2169             _parser.SendFedAuthToken(fedAuthToken);
2170         }
2171
2172         /// <summary>
2173         /// Tries to acquire a lock on the authentication context. If successful in acquiring the lock, gets a new token and assigns it in the out parameter. Else returns false.
2174         /// </summary>
2175         /// <param name="fedAuthInfo">Federated Authentication Info</param>
2176         /// <param name="dbConnectionPoolAuthenticationContext">Authentication Context cached in the connection pool.</param>
2177         /// <param name="fedAuthToken">Out parameter, carrying the token if we acquired a lock and got the token.</param>
2178         /// <returns></returns>
2179         internal bool TryGetFedAuthTokenLocked(SqlFedAuthInfo fedAuthInfo, DbConnectionPoolAuthenticationContext dbConnectionPoolAuthenticationContext, out SqlFedAuthToken fedAuthToken) {
2180
2181             Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null.");
2182             Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null.");
2183
2184             fedAuthToken = null;
2185
2186             // Variable which indicates if we did indeed manage to acquire the lock on the authentication context, to try update it.
2187             bool authenticationContextLocked = false;
2188
2189             // Prepare CER to ensure the lock on authentication context is released.
2190             RuntimeHelpers.PrepareConstrainedRegions();
2191             try {
2192                 // Try to obtain a lock on the context. If acquired, this thread got the opportunity to update.
2193                 // Else some other thread is already updating it, so just proceed forward with the existing token in the cache.
2194                 if (dbConnectionPoolAuthenticationContext.LockToUpdate()) {
2195                     Bid.Trace("<sc.SqlInternalConnectionTds.TryGetFedAuthTokenLocked> %d#, Acquired the lock to update the authentication context.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString());
2196
2197                     authenticationContextLocked = true;
2198                 }
2199                 else {
2200                     Bid.Trace("<sc.SqlInternalConnectionTds.TryGetFedAuthTokenLocked> %d#, Refreshing the context is already in progress by another thread.\n", ObjectID);
2201                 }
2202
2203                 if (authenticationContextLocked) {
2204                     // Get the Federated Authentication Token.
2205                     fedAuthToken = GetFedAuthToken(fedAuthInfo);
2206                     Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null.");
2207                 }
2208             }
2209             finally {
2210                 if (authenticationContextLocked) {
2211                     // Release the lock we took on the authentication context, even if we have not yet updated the cache with the new context. Login process can fail at several places after this step and so there is no guarantee that the new context will make it to the cache. So we shouldn't miss resetting the flag. With the reset, at-least another thread may have a chance to update it.
2212                     dbConnectionPoolAuthenticationContext.ReleaseLockToUpdate();
2213                 }
2214             }
2215
2216             return authenticationContextLocked;
2217         }
2218
2219         /// <summary>
2220         /// Get the Federated Authentication Token.
2221         /// </summary>
2222         /// <param name="fedAuthInfo">Information obtained from server as Federated Authentication Info.</param>
2223         /// <returns>SqlFedAuthToken</returns>
2224         internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) {
2225
2226             Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null.");
2227
2228             // No:of milliseconds to sleep for the inital back off.
2229             int sleepInterval = 100;
2230
2231             // No:of attempts, for tracing purposes, if we underwent retries.
2232             int numberOfAttempts = 0;
2233
2234             // Object that will be returned to the caller, containing all required data about the token.
2235             SqlFedAuthToken fedAuthToken = new SqlFedAuthToken();
2236
2237             // Username to use in error messages.
2238             string username = null;
2239
2240             while (true) {
2241                 numberOfAttempts++;
2242                 try {
2243                     if (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) {
2244                         username = TdsEnums.NTAUTHORITYANONYMOUSLOGON;
2245                         fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessTokenForWindowsIntegrated(fedAuthInfo.stsurl,
2246                                                                                                                 fedAuthInfo.spn,
2247                                                                                                                 _clientConnectionId, ActiveDirectoryAuthentication.AdoClientId,
2248                                                                                                                 ref fedAuthToken.expirationFileTime);
2249                     }
2250                     else if (_credential != null) {
2251                         username = _credential.UserId;
2252                         fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessToken(_credential.UserId,
2253                                                                                             _credential.Password,
2254                                                                                             fedAuthInfo.stsurl,
2255                                                                                             fedAuthInfo.spn,
2256                                                                                             _clientConnectionId,
2257                                                                                             ActiveDirectoryAuthentication.AdoClientId,
2258                                                                                             ref fedAuthToken.expirationFileTime);
2259                     }
2260                     else {
2261                         username = ConnectionOptions.UserID;
2262                         fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessToken(ConnectionOptions.UserID,
2263                                                                                             ConnectionOptions.Password,
2264                                                                                             fedAuthInfo.stsurl,
2265                                                                                             fedAuthInfo.spn,
2266                                                                                             _clientConnectionId,
2267                                                                                             ActiveDirectoryAuthentication.AdoClientId,
2268                                                                                             ref fedAuthToken.expirationFileTime);
2269                     }
2270
2271                     Debug.Assert(fedAuthToken.accessToken != null, "AccessToken should not be null.");
2272 #if DEBUG
2273                     if (_forceAdalRetry) {
2274                         // 3399614468 is 0xCAA20004L just for testing.
2275                         throw new AdalException("Force retry in GetFedAuthToken", ActiveDirectoryAuthentication.GetAccessTokenTansisentError, 3399614468, 6);
2276                     }
2277 #endif
2278                     // Break out of the retry loop in successful case.
2279                     break;
2280                 }
2281                 catch (AdalException adalException) {
2282
2283                     uint errorCategory = adalException.GetCategory();
2284
2285                     if (ActiveDirectoryAuthentication.GetAccessTokenTansisentError != errorCategory
2286                         || _timeout.IsExpired
2287                         || _timeout.MillisecondsRemaining <= sleepInterval) {
2288
2289                         string errorStatus = adalException.GetStatus().ToString("X");
2290
2291                         Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken.ADALException category:> %d#  <error:> %s#\n", (int)errorCategory, errorStatus);
2292
2293                         // Error[0]
2294                         SqlErrorCollection sqlErs = new SqlErrorCollection();
2295                         sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, Res.GetString(Res.SQL_ADALFailure, username, ConnectionOptions.Authentication.ToString("G")), ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0));
2296
2297                         // Error[1]
2298                         string errorMessage1 = Res.GetString(Res.SQL_ADALInnerException, errorStatus, adalException.GetState());
2299                         sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, errorMessage1, ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0));
2300
2301                         // Error[2]
2302                         if (!string.IsNullOrEmpty(adalException.Message)) {
2303                             sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, adalException.Message, ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0));
2304                         }
2305                         SqlException exc = SqlException.CreateException(sqlErs, "", this);
2306                         throw exc;
2307                     }
2308
2309                     Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken|ADV> %d#, sleeping %d{Milliseconds}\n", ObjectID, sleepInterval);
2310                     Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken|ADV> %d#, remaining %d{Milliseconds}\n", ObjectID, _timeout.MillisecondsRemaining);
2311
2312                     Thread.Sleep(sleepInterval);
2313                     sleepInterval *= 2;
2314                 }
2315             }
2316
2317             Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null.");
2318             Debug.Assert(fedAuthToken.accessToken != null && fedAuthToken.accessToken.Length > 0, "fedAuthToken.accessToken should not be null or empty.");
2319
2320             // Store the newly generated token in _newDbConnectionPoolAuthenticationContext, only if using pooling.
2321             if (_dbConnectionPool != null) {
2322                 DateTime expirationTime = DateTime.FromFileTimeUtc(fedAuthToken.expirationFileTime);
2323                 _newDbConnectionPoolAuthenticationContext = new DbConnectionPoolAuthenticationContext(fedAuthToken.accessToken, expirationTime);
2324             }
2325
2326             Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken> %d#, Finished generating federated authentication token.\n", ObjectID);
2327
2328             return fedAuthToken;
2329         }
2330
2331         internal void OnFeatureExtAck(int featureId, byte[] data) {
2332             if (_routingInfo != null) {
2333                 return;
2334             }
2335             switch (featureId) {
2336                 case TdsEnums.FEATUREEXT_SRECOVERY: {
2337                         // Session recovery not requested
2338                         if (!_sessionRecoveryRequested) {
2339                             throw SQL.ParsingErrorFeatureId(ParsingErrorState.UnrequestedFeatureAckReceived, featureId);
2340                         }
2341                         _sessionRecoveryAcknowledged = true;
2342
2343 #if DEBUG
2344                         foreach (var s in _currentSessionData._delta) {
2345                             Debug.Assert(s==null, "Delta should be null at this point");
2346                         }
2347 #endif
2348                         Debug.Assert(_currentSessionData._unrecoverableStatesCount == 0, "Unrecoverable states count should be 0");
2349
2350                         int i = 0;
2351                         while (i < data.Length) {
2352                             byte stateId = data[i]; i++;
2353                             int len;
2354                             byte bLen = data[i]; i++;
2355                             if (bLen == 0xFF) {
2356                                 len = BitConverter.ToInt32(data, i); i += 4;
2357                             }
2358                             else {
2359                                 len = bLen;
2360                             }
2361                             byte[] stateData = new byte[len];
2362                             Buffer.BlockCopy(data, i, stateData, 0, len); i += len;
2363                             if (_recoverySessionData == null) {
2364                                 _currentSessionData._initialState[stateId] = stateData;
2365                             }
2366                             else {
2367                                 _currentSessionData._delta[stateId] = new SessionStateRecord { _data = stateData, _dataLength = len, _recoverable = true, _version = 0 };
2368                                 _currentSessionData._deltaDirty = true;
2369                             }
2370                         }
2371                         break;
2372                     }
2373                 case TdsEnums.FEATUREEXT_FEDAUTH: {
2374                         if (Bid.AdvancedOn) {
2375                             Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck> %d#, Received feature extension acknowledgement for federated authentication\n", ObjectID);
2376                         }
2377                         if (!_federatedAuthenticationRequested) {
2378                             Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Did not request federated authentication\n", ObjectID);
2379                             throw SQL.ParsingErrorFeatureId(ParsingErrorState.UnrequestedFeatureAckReceived, featureId);
2380                         }
2381
2382                         Debug.Assert(_fedAuthFeatureExtensionData != null, "_fedAuthFeatureExtensionData must not be null when _federatedAuthenticatonRequested == true");
2383
2384                         switch (_fedAuthFeatureExtensionData.Value.libraryType) {
2385                             case TdsEnums.FedAuthLibrary.ADAL:
2386                             case TdsEnums.FedAuthLibrary.SecurityToken:
2387                                 // The server shouldn't have sent any additional data with the ack (like a nonce)
2388                                 if (data.Length != 0) {
2389                                     Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Federated authentication feature extension ack for ADAL and Security Token includes extra data\n", ObjectID);
2390                                     throw SQL.ParsingError(ParsingErrorState.FedAuthFeatureAckContainsExtraData);
2391                                 }
2392                                 break;
2393
2394                             default:
2395                                 Debug.Assert(false, "Unknown _fedAuthLibrary type");
2396                                 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Attempting to use unknown federated authentication library\n", ObjectID);
2397                                 throw SQL.ParsingErrorLibraryType(ParsingErrorState.FedAuthFeatureAckUnknownLibraryType, (int)_fedAuthFeatureExtensionData.Value.libraryType);
2398                         }
2399                         _federatedAuthenticationAcknowledged = true;
2400
2401                         // If a new authentication context was used as part of this login attempt, try to update the new context in the cache, i.e.dbConnectionPool.AuthenticationContexts.
2402                         // ChooseAuthenticationContextToUpdate will take care that only the context which has more validity will remain in the cache, based on the Update logic.
2403                         if (_newDbConnectionPoolAuthenticationContext != null)
2404                         {
2405                             Debug.Assert(_dbConnectionPool != null, "_dbConnectionPool should not be null when _newDbConnectionPoolAuthenticationContext != null.");
2406
2407                             DbConnectionPoolAuthenticationContext newAuthenticationContextInCacheAfterAddOrUpdate = _dbConnectionPool.AuthenticationContexts.AddOrUpdate(_dbConnectionPoolAuthenticationContextKey, _newDbConnectionPoolAuthenticationContext,
2408                                                                                  (key, oldValue) => DbConnectionPoolAuthenticationContext.ChooseAuthenticationContextToUpdate(oldValue, _newDbConnectionPoolAuthenticationContext));
2409
2410                             Debug.Assert(newAuthenticationContextInCacheAfterAddOrUpdate != null, "newAuthenticationContextInCacheAfterAddOrUpdate should not be null.");
2411 #if DEBUG
2412                             // For debug purposes, assert and trace if we ended up updating the cache with the new one or some other thread's context won the expiration ----.
2413                             if (newAuthenticationContextInCacheAfterAddOrUpdate == _newDbConnectionPoolAuthenticationContext) {
2414                                     Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Updated the new dbAuthenticationContext in the _dbConnectionPool.AuthenticationContexts. \n", ObjectID);
2415                             }
2416                             else {
2417                                     Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, AddOrUpdate attempted on _dbConnectionPool.AuthenticationContexts, but it did not update the new value. \n", ObjectID);
2418                             }
2419 #endif
2420                         }
2421
2422                         break;
2423                     }
2424                 case TdsEnums.FEATUREEXT_TCE: {
2425                         if (Bid.AdvancedOn) {
2426                             Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck> %d#, Received feature extension acknowledgement for TCE\n", ObjectID);
2427                         }
2428
2429                         if (data.Length < 1) {
2430                             Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Unknown version number for TCE\n", ObjectID);
2431                             throw SQL.ParsingError(ParsingErrorState.TceUnknownVersion);
2432                         }
2433
2434                         byte supportedTceVersion = data[0];
2435                         if (0 == supportedTceVersion || supportedTceVersion > TdsEnums.MAX_SUPPORTED_TCE_VERSION) {
2436                             Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Invalid version number for TCE\n", ObjectID);
2437                             throw SQL.ParsingErrorValue(ParsingErrorState.TceInvalidVersion, supportedTceVersion);
2438                         }
2439
2440                         _tceVersionSupported = supportedTceVersion;
2441                         Debug.Assert (_tceVersionSupported == TdsEnums.MAX_SUPPORTED_TCE_VERSION, "Client support TCE version 1");
2442                         _parser.IsColumnEncryptionSupported = true;
2443                         break;
2444                     }
2445
2446                 case TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS: {
2447                         if (Bid.AdvancedOn) {
2448                             Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck> %d#, Received feature extension acknowledgement for GlobalTransactions\n", ObjectID);
2449                         }
2450
2451                         if (data.Length < 1) {
2452                             Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Unknown version number for GlobalTransactions\n", ObjectID);
2453                             throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
2454                         }
2455
2456                         IsGlobalTransaction = true;    
2457                         if (1 == data[0]) {
2458                             IsGlobalTransactionsEnabledForServer = true;
2459                         }
2460                         break;
2461                     }
2462
2463                 default: {
2464                         // Unknown feature ack 
2465                         throw SQL.ParsingErrorFeatureId(ParsingErrorState.UnknownFeatureAck, featureId);
2466                     }
2467             }
2468         }
2469
2470         ////////////////////////////////////////////////////////////////////////////////////////
2471         // Helper methods for Locks
2472         ////////////////////////////////////////////////////////////////////////////////////////
2473         
2474         // Indicates if the current thread claims to hold the parser lock
2475         internal bool ThreadHasParserLockForClose {
2476             get {
2477                 return _threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId;
2478             }
2479             set {
2480                 Debug.Assert(_parserLock.ThreadMayHaveLock(), "Should not modify ThreadHasParserLockForClose without taking the lock first");
2481                 Debug.Assert(_threadIdOwningParserLock == -1 || _threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId, "Another thread already claims to own the parser lock");
2482
2483                 if (value) {
2484                     // If setting to true, then the thread owning the lock is the current thread
2485                     _threadIdOwningParserLock = Thread.CurrentThread.ManagedThreadId;
2486                 }
2487                 else if (_threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId) {
2488                     // If setting to false and currently owns the lock, then no-one owns the lock
2489                     _threadIdOwningParserLock = -1;
2490                 }
2491                 // else This thread didn't own the parser lock and doesn't claim to own it, so do nothing
2492             }
2493         }
2494
2495         internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions) {
2496             return base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
2497         }
2498     }
2499
2500     internal sealed class ServerInfo {
2501         internal string ExtendedServerName   { get; private set; } // the resolved servername with protocol
2502         internal string ResolvedServerName   { get; private set; } // the resolved servername only
2503         internal string ResolvedDatabaseName { get; private set; } // name of target database after resolution
2504         internal string UserProtocol         { get; private set; } // the user specified protocol        
2505
2506         // The original user-supplied server name from the connection string.
2507         // If connection string has no Data Source, the value is set to string.Empty.
2508         // In case of routing, will be changed to routing destination
2509         internal string UserServerName
2510         {
2511             get
2512             {
2513                 return m_userServerName;
2514             }
2515             private set
2516             {
2517                 m_userServerName = value;
2518             }
2519         } private string m_userServerName;
2520
2521         internal readonly string PreRoutingServerName;
2522         
2523         // Initialize server info from connection options, 
2524         internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource) {}
2525
2526         // Initialize server info from connection options, but override DataSource with given server name
2527         internal ServerInfo (SqlConnectionString userOptions, string serverName) {
2528             //-----------------
2529             // Preconditions
2530             Debug.Assert(null != userOptions);
2531
2532             //-----------------
2533             //Method body
2534              
2535             Debug.Assert(serverName != null, "server name should never be null");
2536             UserServerName  = (serverName ?? string.Empty); // ensure user server name is not null
2537
2538             UserProtocol = userOptions.NetworkLibrary;
2539             ResolvedDatabaseName = userOptions.InitialCatalog;
2540             PreRoutingServerName = null;
2541         }
2542
2543
2544         // Initialize server info from connection options, but override DataSource with given server name
2545         internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName) {
2546             //-----------------
2547             // Preconditions
2548             Debug.Assert(null != userOptions && null!=routing);
2549
2550             //-----------------
2551             //Method body
2552             Debug.Assert(routing.ServerName != null, "server name should never be null");
2553             if (routing == null || routing.ServerName == null) {
2554                 UserServerName = string.Empty; // ensure user server name is not null
2555             }
2556             else {
2557                 UserServerName = string.Format(CultureInfo.InvariantCulture, "{0},{1}", routing.ServerName, routing.Port);
2558             }
2559             PreRoutingServerName = preRoutingServerName;
2560             UserProtocol = TdsEnums.TCP;
2561             SetDerivedNames(UserProtocol, UserServerName);
2562             ResolvedDatabaseName = userOptions.InitialCatalog;
2563         }
2564
2565         internal void SetDerivedNames(string protocol, string serverName) {
2566             // The following concatenates the specified netlib network protocol to the host string, if netlib is not null
2567             // and the flag is on.  This allows the user to specify the network protocol for the connection - but only
2568             // when using the Dbnetlib dll.  If the protocol is not specified, the netlib will
2569             // try all protocols in the order listed in the Client Network Utility.  Connect will
2570             // then fail if all protocols fail.
2571             if (!ADP.IsEmpty(protocol)) {
2572                 ExtendedServerName = protocol + ":" + serverName;
2573             }
2574             else {
2575                 ExtendedServerName = serverName;
2576             }
2577             ResolvedServerName = serverName;
2578         }
2579     }
2580 }
2581