diff --git a/src/NATS.Client/Connection.cs b/src/NATS.Client/Connection.cs index cda1b6c04..8d531ffa0 100644 --- a/src/NATS.Client/Connection.cs +++ b/src/NATS.Client/Connection.cs @@ -1134,7 +1134,15 @@ private void processConnectInit(Srv s) try { conn.ReceiveTimeout = opts.Timeout; - processExpectedInfo(s); + if (!opts.TlsFirst) + { + processExpectedInfo(); + } + checkForSecure(s); + if (opts.TlsFirst) + { + processExpectedInfo(); + } sendConnect(); } catch (IOException ex) @@ -1188,8 +1196,10 @@ internal bool connect(Srv s, out Exception exToThrow) } catch (NATSConnectionException ex) { - if (!ex.IsAuthorizationViolationError() && !ex.IsAuthenticationExpiredError()) + if (!ex.IsAuthenticationOrAuthorizationError()) + { throw; + } ScheduleErrorEvent(s, ex); @@ -1222,7 +1232,7 @@ internal void ScheduleErrorEvent(object sender, NATSException ex, Subscription s opts.AsyncErrorEventHandlerOrDefault(sender, new ErrEventArgs(this, subscription, ex.Message))); } - internal void connect() + internal void connect(bool reconnectOnConnect) { Exception exToThrow = null; @@ -1234,11 +1244,22 @@ internal void connect() { if (status != ConnState.CONNECTED) { - if (exToThrow is NATSException) - throw exToThrow; - if (exToThrow != null) - throw new NATSConnectionException("Failed to connect", exToThrow); - throw new NATSNoServersException("Unable to connect to a server."); + if (reconnectOnConnect) + { + doReconnect(); + } + else + { + if (exToThrow is NATSException) + { + throw exToThrow; + } + if (exToThrow != null) + { + throw new NATSConnectionException("Failed to connect", exToThrow); + } + throw new NATSNoServersException("Unable to connect to a server."); + } } } } @@ -1248,17 +1269,20 @@ internal void connect() // only be called after the INIT protocol has been received. private void checkForSecure(Srv s) { - // Check to see if we need to engage TLS - // Check for mismatch in setups - if (Opts.Secure && !info.TlsRequired) - { - throw new NATSSecureConnWantedException(); - } - else if (info.TlsRequired && !Opts.Secure) + if (!Opts.TlsFirst) { - // If the server asks us to be secure, give it - // a shot. - Opts.Secure = true; + // Check to see if we need to engage TLS + // Check for mismatch in setups + if (Opts.Secure && !info.TlsRequired) + { + throw new NATSSecureConnWantedException(); + } + else if (info.TlsRequired && !Opts.Secure) + { + // If the server asks us to be secure, give it + // a shot. + Opts.Secure = true; + } } // Need to rewrap with bufio if options tell us we need @@ -1272,10 +1296,9 @@ private void checkForSecure(Srv s) // processExpectedInfo will look for the expected first INFO message // sent when a connection is established. The lock should be held entering. // Caller must lock. - private void processExpectedInfo(Srv s) + private void processExpectedInfo() { Control c; - try { conn.SendTimeout = 2; @@ -1298,7 +1321,6 @@ private void processExpectedInfo(Srv s) // do not notify listeners of server changes when we process the first INFO message processInfo(c.args, false); - checkForSecure(s); } internal void SendUnsub(long sid, int max) diff --git a/src/NATS.Client/ConnectionFactory.cs b/src/NATS.Client/ConnectionFactory.cs index c03e1ec9d..615256145 100644 --- a/src/NATS.Client/ConnectionFactory.cs +++ b/src/NATS.Client/ConnectionFactory.cs @@ -36,15 +36,16 @@ public ConnectionFactory() { } /// /// A string containing the URL (or URLs) to the NATS Server. See the Remarks /// section for more information. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - public IConnection CreateConnection(string url) + public IConnection CreateConnection(string url, bool reconnectOnConnect = false) { - return CreateConnection(GetDefaultOptions(url)); + return CreateConnection(GetDefaultOptions(url), reconnectOnConnect); } /// @@ -57,20 +58,21 @@ public IConnection CreateConnection(string url) /// A string containing the URL (or URLs) to the NATS Server. See the Remarks /// section for more information. /// The full path to a chained credentials file. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - public IConnection CreateConnection(string url, string credentialsPath) + public IConnection CreateConnection(string url, string credentialsPath, bool reconnectOnConnect = false) { if (string.IsNullOrWhiteSpace(credentialsPath)) throw new ArgumentException("Invalid credentials path", nameof(credentialsPath)); Options opts = GetDefaultOptions(url); opts.SetUserCredentials(credentialsPath); - return CreateConnection(opts); + return CreateConnection(opts, reconnectOnConnect); } /// @@ -84,13 +86,14 @@ public IConnection CreateConnection(string url, string credentialsPath) /// section for more information. /// The path to a user's public JWT credentials. /// The path to a file for user user's private Nkey seed. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - public IConnection CreateConnection(string url, string jwt, string privateNkey) + public IConnection CreateConnection(string url, string jwt, string privateNkey, bool reconnectOnConnect = false) { if (string.IsNullOrWhiteSpace(jwt)) throw new ArgumentException("Invalid jwt path", nameof(jwt)); @@ -99,7 +102,7 @@ public IConnection CreateConnection(string url, string jwt, string privateNkey) Options opts = GetDefaultOptions(url); opts.SetUserCredentials(jwt, privateNkey); - return CreateConnection(opts); + return CreateConnection(opts, reconnectOnConnect); } /// @@ -127,22 +130,24 @@ public static Options GetDefaultOptions(string server = null) /// /// A string containing the URL (or URLs) to the NATS Server. See the Remarks /// section for more information. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - public IConnection CreateSecureConnection(string url) + public IConnection CreateSecureConnection(string url, bool reconnectOnConnect = false) { Options opts = GetDefaultOptions(url); opts.Secure = true; - return CreateConnection(opts); + return CreateConnection(opts, reconnectOnConnect); } /// /// Create a connection to the NATs server using the default options. /// + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. @@ -150,27 +155,28 @@ public IConnection CreateSecureConnection(string url) /// An exception was encountered while connecting to a NATS Server. See for more /// details. /// - public IConnection CreateConnection() + public IConnection CreateConnection(bool reconnectOnConnect = false) { - return CreateConnection(GetDefaultOptions()); + return CreateConnection(GetDefaultOptions(), reconnectOnConnect); } /// /// Create a connection to a NATS Server defined by the given options. /// /// The NATS client options to use for this connection. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - public IConnection CreateConnection(Options opts) + public IConnection CreateConnection(Options opts, bool reconnectOnConnect = false) { Connection nc = new Connection(opts); try { - nc.connect(); + nc.connect(reconnectOnConnect); } catch (Exception) { @@ -183,6 +189,7 @@ public IConnection CreateConnection(Options opts) /// /// Attempt to connect to the NATS server, with an encoded connection, using the default options. /// + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// /// No connection to a NATS Server could be established. @@ -190,9 +197,9 @@ public IConnection CreateConnection(Options opts) /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - public IEncodedConnection CreateEncodedConnection() + public IEncodedConnection CreateEncodedConnection(bool reconnectOnConnect = false) { - return CreateEncodedConnection(GetDefaultOptions()); + return CreateEncodedConnection(GetDefaultOptions(), reconnectOnConnect); } /// @@ -201,6 +208,7 @@ public IEncodedConnection CreateEncodedConnection() /// /// can contain username/password semantics. /// Comma seperated arrays are also supported, e.g. urlA, urlB. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// /// A string containing the URL (or URLs) to the NATS Server. See the Remarks /// section for more information. @@ -210,9 +218,9 @@ public IEncodedConnection CreateEncodedConnection() /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - public IEncodedConnection CreateEncodedConnection(string url) + public IEncodedConnection CreateEncodedConnection(string url, bool reconnectOnConnect = false) { - return CreateEncodedConnection(GetDefaultOptions(url)); + return CreateEncodedConnection(GetDefaultOptions(url), reconnectOnConnect); } /// @@ -220,17 +228,18 @@ public IEncodedConnection CreateEncodedConnection(string url) /// /// The NATS client options to use for this connection. /// An object connected to the NATS server. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - public IEncodedConnection CreateEncodedConnection(Options opts) + public IEncodedConnection CreateEncodedConnection(Options opts, bool reconnectOnConnect = false) { EncodedConnection nc = new EncodedConnection(opts); try { - nc.connect(); + nc.connect(reconnectOnConnect); } catch (Exception) { diff --git a/src/NATS.Client/Exceptions.cs b/src/NATS.Client/Exceptions.cs index d4b28c651..b7f22f0a3 100644 --- a/src/NATS.Client/Exceptions.cs +++ b/src/NATS.Client/Exceptions.cs @@ -33,15 +33,14 @@ public class NATSConnectionException : NATSException { public NATSConnectionException(string err) : base(err) { } public NATSConnectionException(string err, Exception innerEx) : base(err, innerEx) { } - } - internal static class NATSConnectionExceptionExtensions - { - internal static bool IsAuthorizationViolationError(this NATSConnectionException ex) - => ex?.Message.Equals("'authorization violation'", StringComparison.OrdinalIgnoreCase) == true; - - internal static bool IsAuthenticationExpiredError(this NATSConnectionException ex) - => ex?.Message.Equals("'authentication expired'", StringComparison.OrdinalIgnoreCase) == true; + public bool IsAuthenticationOrAuthorizationError() + { + string lowerMessage = Message.ToLower(); + return lowerMessage.Contains("user authentication") + || lowerMessage.Contains("authorization violation") + || lowerMessage.Contains("authentication expired"); + } } /// diff --git a/src/NATS.Client/IConnectionFactory.cs b/src/NATS.Client/IConnectionFactory.cs index 7d4dadb0a..da1f70181 100644 --- a/src/NATS.Client/IConnectionFactory.cs +++ b/src/NATS.Client/IConnectionFactory.cs @@ -26,13 +26,14 @@ public interface IConnectionFactory /// /// A string containing the URL (or URLs) to the NATS Server. See the Remarks /// section for more information. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - IConnection CreateConnection(string url); + IConnection CreateConnection(string url, bool reconnectOnConnect = false); /// /// Attempt to connect to the NATS server referenced by with NATS 2.0 credentials. @@ -44,13 +45,14 @@ public interface IConnectionFactory /// A string containing the URL (or URLs) to the NATS Server. See the Remarks /// section for more information. /// The full path to a chained credentials file. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - IConnection CreateConnection(string url, string credentialsPath); + IConnection CreateConnection(string url, string credentialsPath, bool reconnectOnConnect = false); /// /// Attempt to connect to the NATS server referenced by with NATS 2.0 credentials. @@ -63,17 +65,19 @@ public interface IConnectionFactory /// section for more information. /// The path to a user's public JWT credentials. /// The path to a file for user user's private Nkey seed. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - IConnection CreateConnection(string url, string jwt, string privateNkey); + IConnection CreateConnection(string url, string jwt, string privateNkey, bool reconnectOnConnect = false); /// /// Create a connection to the NATs server using the default options. /// + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. @@ -81,19 +85,20 @@ public interface IConnectionFactory /// An exception was encountered while connecting to a NATS Server. See for more /// details. /// - IConnection CreateConnection(); + IConnection CreateConnection(bool reconnectOnConnect = false); /// /// Create a connection to a NATS Server defined by the given options. /// /// The NATS client options to use for this connection. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - IConnection CreateConnection(Options opts); + IConnection CreateConnection(Options opts, bool reconnectOnConnect = false); /// /// Attempt to connect to the NATS server using TLS referenced by . @@ -103,6 +108,7 @@ public interface IConnectionFactory /// Comma seperated arrays are also supported, e.g. urlA, urlB. /// /// A string containing the URL (or URLs) to the NATS Server. See the Remarks + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// section for more information. /// An object connected to the NATS server. /// No connection to a NATS Server could be established. @@ -110,11 +116,12 @@ public interface IConnectionFactory /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - IConnection CreateSecureConnection(string url); + IConnection CreateSecureConnection(string url, bool reconnectOnConnect = false); /// /// Attempt to connect to the NATS server, with an encoded connection, using the default options. /// + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// /// No connection to a NATS Server could be established. @@ -122,7 +129,7 @@ public interface IConnectionFactory /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - IEncodedConnection CreateEncodedConnection(); + IEncodedConnection CreateEncodedConnection(bool reconnectOnConnect = false); /// /// Attempt to connect to the NATS server, with an encoded connection, referenced by . @@ -133,24 +140,26 @@ public interface IConnectionFactory /// /// A string containing the URL (or URLs) to the NATS Server. See the Remarks /// section for more information. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - IEncodedConnection CreateEncodedConnection(string url); + IEncodedConnection CreateEncodedConnection(string url, bool reconnectOnConnect = false); /// /// Attempt to connect to the NATS server, with an encoded connection, using the given options. /// /// The NATS client options to use for this connection. + /// if true, the connection will treat the initial connection as any other and attempt reconnects on failure /// An object connected to the NATS server. /// No connection to a NATS Server could be established. /// A timeout occurred connecting to a NATS Server. /// -or- /// An exception was encountered while connecting to a NATS Server. See for more /// details. - IEncodedConnection CreateEncodedConnection(Options opts); + IEncodedConnection CreateEncodedConnection(Options opts, bool reconnectOnConnect = false); } } diff --git a/src/NATS.Client/Internals/JwtUtils.cs b/src/NATS.Client/Internals/JwtUtils.cs index 0969623cc..723a20b45 100644 --- a/src/NATS.Client/Internals/JwtUtils.cs +++ b/src/NATS.Client/Internals/JwtUtils.cs @@ -46,8 +46,8 @@ public static class JwtUtils "------END NATS USER JWT------\n" + "\n" + "************************* IMPORTANT *************************\n" + - " NKEY Seed printed below can be used to sign and prove identity.\n" + - " NKEYs are sensitive and should be treated as secrets.\n" + + "NKEY Seed printed below can be used to sign and prove identity.\n" + + "NKEYs are sensitive and should be treated as secrets.\n" + "\n" + "-----BEGIN USER NKEY SEED-----\n" + "{1}\n" + @@ -173,8 +173,7 @@ public static string IssueUserJWT(NkeyPair signingKey, string publicUserKey, str string claimName = string.IsNullOrWhiteSpace(name) ? publicUserKey : name; - return issueJWT(signingKey, publicUserKey, claimName, expiration, issuedAt, accSigningKeyPub, audience, - nats); + return issueJWT(signingKey, publicUserKey, claimName, expiration, issuedAt, accSigningKeyPub, audience, nats); } /// diff --git a/src/NATS.Client/Options.cs b/src/NATS.Client/Options.cs index f83bfb15c..1dd5306eb 100644 --- a/src/NATS.Client/Options.cs +++ b/src/NATS.Client/Options.cs @@ -36,6 +36,7 @@ public sealed class Options bool allowReconnect = true; bool noEcho = false; bool ignoreDiscoveredServers = false; + bool tlsFirst = false; private bool clientSideLimitChecks = true; IServerProvider serverProvider = null; int maxReconnect = Defaults.MaxReconnect; @@ -286,6 +287,7 @@ internal Options(Options o) noRandomize = o.noRandomize; noEcho = o.noEcho; ignoreDiscoveredServers = o.ignoreDiscoveredServers; + tlsFirst = o.tlsFirst; clientSideLimitChecks = o.clientSideLimitChecks; serverProvider = o.serverProvider; pedantic = o.pedantic; @@ -458,7 +460,7 @@ public bool UseOldRequestStyle /// public bool Secure { - get { return secure; } + get { return tlsFirst || secure; } set { secure = value; } } @@ -723,13 +725,17 @@ public int SubscriptionBatchSize /// Whether or not to ignore discovered servers when considering for connect/reconnect /// public bool IgnoreDiscoveredServers { get => ignoreDiscoveredServers; set => ignoreDiscoveredServers = value; } + + /// + /// Whether or not to to do Tls Handshake First. Valid against servers 2.10.3 and later + /// + public bool TlsFirst { get => tlsFirst; set => tlsFirst = value; } /// /// Whether or not to make client side limit checks, currently only core publish/request max payload /// public bool ClientSideLimitChecks { get => clientSideLimitChecks; set => clientSideLimitChecks = value; } - // TODO After connect adr is complete internal IServerProvider ServerProvider { get => serverProvider; set => serverProvider = value; } private void appendEventHandler(StringBuilder sb, string name, Delegate eh) @@ -856,6 +862,7 @@ public override string ToString() sb.AppendFormat("NoRandomize={0};", NoRandomize); sb.AppendFormat("NoEcho={0};", NoEcho); sb.AppendFormat("IgnoreDiscoveredServers={0};", ignoreDiscoveredServers); + sb.AppendFormat("TlsFirst={0};", tlsFirst); sb.AppendFormat("clientSideLimitChecks={0};", clientSideLimitChecks); sb.AppendFormat("ServerProvider={0};", serverProvider == null ? "Default" : "Provided"); sb.AppendFormat("Pedantic={0};", Pedantic); diff --git a/src/NATS.Client/ServerPool.cs b/src/NATS.Client/ServerPool.cs index 94b364d8c..e01c71a5b 100644 --- a/src/NATS.Client/ServerPool.cs +++ b/src/NATS.Client/ServerPool.cs @@ -17,19 +17,20 @@ namespace NATS.Client { - internal sealed class ServerPool : IServerProvider + internal class ServerPool : IServerProvider { - private readonly object poolLock = new object(); - private readonly LinkedList sList = new LinkedList(); - private Srv currentServer; - private readonly Random rand = new Random(DateTime.Now.Millisecond); - private bool randomize = true; - private bool ignoreDiscoveredServers = true; + protected readonly object poolLock = new object(); + protected readonly LinkedList sList = new LinkedList(); + protected Srv currentServer; + protected readonly Random rand = new Random(DateTime.Now.Millisecond); + protected bool randomize = true; + protected bool ignoreDiscoveredServers = true; + protected SrvEqualityComparer duplicateSrvCheck = new SrvEqualityComparer(); // Used to find duplicates in the server pool. // Loopback is equivalent to localhost, and // a URL match is equivalent. - private class SrvEqualityComparer : IEqualityComparer + internal class SrvEqualityComparer : IEqualityComparer { private bool IsLocal(Uri url) { @@ -65,13 +66,11 @@ public int GetHashCode(Srv obj) } } - private SrvEqualityComparer duplicateSrvCheck = new SrvEqualityComparer(); - // Create the server pool using the options given. // We will place a Url option first, followed by any // Server Options. We will randomize the server pool unless // the NoRandomize flag is set. - public void Setup(Options opts) + public virtual void Setup(Options opts) { randomize = !opts.NoRandomize; ignoreDiscoveredServers = opts.IgnoreDiscoveredServers; @@ -93,7 +92,7 @@ public void Setup(Options opts) } // Used for initially connecting to a server. - public void ConnectToAServer(Predicate connectToServer) + public virtual void ConnectToAServer(Predicate connectToServer) { Srv s; @@ -114,7 +113,7 @@ public void ConnectToAServer(Predicate connectToServer) // Created for "threadsafe" access. It allows the list to grow while // being added to. - private Srv this[int index] + protected virtual Srv this[int index] { get { @@ -129,7 +128,7 @@ private Srv this[int index] } // Sets the currently selected server - public void SetCurrentServer(Srv value) + public virtual void SetCurrentServer(Srv value) { lock (poolLock) { @@ -143,25 +142,23 @@ public void SetCurrentServer(Srv value) // Pop the current server and put onto the end of the list. // Select head of list as long as number of reconnect attempts // under MaxReconnect. - public Srv SelectNextServer(int maxReconnect) + public virtual Srv SelectNextServer(int maxReconnect) { lock (poolLock) { Srv s = currentServer; - if (s == null) - return null; - - int num = sList.Count; - + // remove the current server. - sList.Remove(s); - - if (maxReconnect == Options.ReconnectForever || - (maxReconnect > 0 && s.Reconnects < maxReconnect)) + if (s != null) { - // if we haven't surpassed max reconnects, add it - // to try again. - sList.AddLast(s); + sList.Remove(s); + if (maxReconnect == Options.ReconnectForever || + (maxReconnect > 0 && s.Reconnects < maxReconnect)) + { + // if we haven't surpassed max reconnects, add it + // to try again. + sList.AddLast(s); + } } currentServer = IsEmpty() ? null : sList.First(); @@ -171,7 +168,7 @@ public Srv SelectNextServer(int maxReconnect) } // returns a copy of the list to ensure threadsafety. - public string[] GetServerList(bool implicitOnly) + public virtual string[] GetServerList(bool implicitOnly) { List list; @@ -200,14 +197,14 @@ public string[] GetServerList(bool implicitOnly) // returns true if it modified the pool, false if // the url already exists. - private bool Add(string s, bool isImplicit) + protected virtual bool Add(string s, bool isImplicit) { return Add(new Srv(s, isImplicit)); } // returns true if it modified the pool, false if // the url already exists. - private bool Add(Srv s) + protected virtual bool Add(Srv s) { lock (poolLock) { @@ -241,7 +238,7 @@ private bool Add(Srv s) // // Prune out implicit servers no longer needed. // The Add is idempotent, so just add the entire list. - public bool AcceptDiscoveredServers(string[] discoveredUrls) + public virtual bool AcceptDiscoveredServers(string[] discoveredUrls) { if (ignoreDiscoveredServers || discoveredUrls == null || discoveredUrls.Length == 0) @@ -318,7 +315,7 @@ internal static void Shuffle(IList list) } } - private void Shuffle() + protected virtual void Shuffle() { lock (poolLock) { @@ -333,7 +330,7 @@ private void Shuffle() } } - private bool IsEmpty() + protected virtual bool IsEmpty() { return sList.Count == 0; } @@ -341,7 +338,7 @@ private bool IsEmpty() // It'd be possible to use the sList enumerator here and // implement the IEnumerable interface, but keep it simple // for thread safety. - public Srv First() + public virtual Srv First() { lock (poolLock) { @@ -352,7 +349,7 @@ public Srv First() } } - public bool HasSecureServer() + public virtual bool HasSecureServer() { lock (poolLock) { diff --git a/src/NATS.Client/Srv.cs b/src/NATS.Client/Srv.cs index b182497a2..63255c5f5 100644 --- a/src/NATS.Client/Srv.cs +++ b/src/NATS.Client/Srv.cs @@ -15,7 +15,6 @@ namespace NATS.Client { - // TODO After connect adr is complete internal interface IServerProvider { /// @@ -39,7 +38,7 @@ public class Srv public const int DefaultPort = 4222; public const int NoPortSpecified = -1; - public Uri Url { get; } + public Uri Url { get; private set; } public bool IsImplicit { get; } public bool Secure { get; } public DateTime LastAttempt { get; private set; } @@ -59,14 +58,18 @@ public Srv(string urlString) { Secure = urlString.Contains("tls://"); } - - var uri = new Uri(urlString); - - Url = uri.Port == NoPortSpecified ? new UriBuilder(uri) {Port = DefaultPort}.Uri : uri; + + SetUrl(urlString); LastAttempt = DateTime.Now; } + internal void SetUrl(String urlString) + { + var uri = new Uri(urlString); + Url = uri.Port == NoPortSpecified ? new UriBuilder(uri) {Port = DefaultPort}.Uri : uri; + } + public Srv(string urlString, bool isUrlImplicit) : this(urlString) { IsImplicit = isUrlImplicit; @@ -75,6 +78,11 @@ public Srv(string urlString, bool isUrlImplicit) : this(urlString) public void UpdateLastAttempt() => LastAttempt = DateTime.Now; public TimeSpan TimeSinceLastAttempt => (DateTime.Now - LastAttempt); + + public override string ToString() + { + return $"Url: {Url}, IsImplicit: {IsImplicit}, Secure: {Secure}, DidConnect: {DidConnect}, Reconnects: {Reconnects}"; + } } } diff --git a/src/Tests/IntegrationTests/IntegrationTests.csproj b/src/Tests/IntegrationTests/IntegrationTests.csproj index 751273807..bab278193 100644 --- a/src/Tests/IntegrationTests/IntegrationTests.csproj +++ b/src/Tests/IntegrationTests/IntegrationTests.csproj @@ -82,6 +82,9 @@ Always + + Always + Always @@ -97,6 +100,9 @@ Always + + Always + diff --git a/src/Tests/IntegrationTests/TestAuthorization.cs b/src/Tests/IntegrationTests/TestAuthorization.cs index c966f4f87..5856c7211 100644 --- a/src/Tests/IntegrationTests/TestAuthorization.cs +++ b/src/Tests/IntegrationTests/TestAuthorization.cs @@ -12,13 +12,13 @@ // limitations under the License. using System; -using System.Threading; -using System.Reflection; +using System.IO; using System.Linq; +using System.Reflection; +using System.Threading; using NATS.Client; -using UnitTests; +using NATS.Client.Internals; using Xunit; -using Xunit.Abstractions; namespace IntegrationTests { @@ -27,9 +27,7 @@ namespace IntegrationTests /// public class TestAuthorization : TestSuite { - public TestAuthorization(AuthorizationSuiteContext context) : base(context) - { - } + public TestAuthorization(AuthorizationSuiteContext context) : base(context) {} int hitDisconnect; @@ -65,9 +63,16 @@ private void HandleDisconnect(object sender, ConnEventArgs e) private void ConnectShouldSucceed(string url) { - using (var c = Context.ConnectionFactory.CreateConnection(url)) + try + { + using (var c = Context.ConnectionFactory.CreateConnection(url)) + { + c.Close(); + } + } + catch (Exception e) { - c.Close(); + Assert.False(true, $"Exception not expected {e.Message}"); } } @@ -123,11 +128,6 @@ void connectEncoded(string encoded) connectEncoded("semi%3Bsemi"); connectEncoded("eq%3Deq"); connectEncoded("pct%25pct"); -#if !NET46 - connectEncoded("%2b%3a%c2%a1%c2%a2%c2%a3%c2%a4%c2%a5%c2%a6%c2%a7%c2%a8%c2%a9%c2%aa%c2%ab%c2%ac%20%f0%9f%98%80"); -#endif - // a plus sign in a user or pass is a plus sign, not a space - Assert.Throws(() => connectEncoded("space+space")); } } @@ -240,6 +240,46 @@ var expiredUserJwt Assert.Equal("'Authorization Violation'", ex.Message, StringComparer.OrdinalIgnoreCase); } } + + [Fact] + public void TestRealUserAuthenticationExpired() + { + string accountSeed = "SAAPXJRFMUYDUH3NOZKE7BS2ZDO2P4ND7G6W743MTNA3KCSFPX3HNN6AX4"; + string accountId = "ACPWDUYSZRRF7XAEZKUAGPUH6RPICWEHSTFELYKTOWUVZ4R2XMP4QJJX"; + string userSeed = "SUAJ44FQWKEWGRSIPRFCIGDTVYSMUMRRHB4CPFXXRG5GODO5XY7S2L45ZA"; + + NkeyPair accountPair = Nkeys.FromSeed(accountSeed); + NkeyPair userPair = Nkeys.FromSeed(userSeed); + string publicUserKey = userPair.EncodedPublicKey; + + long expires = 2500; + int wait = 5000; + Duration expiration = Duration.OfMillis(expires); + String jwt = JwtUtils.IssueUserJWT(accountPair, accountId, publicUserKey, "jnatsTestUser", expiration); + + string cred = string.Format(JwtUtils.NatsUserJwtFormat, jwt, userPair.EncodedSeed); + string credsFile = Path.GetTempFileName(); + File.WriteAllText(credsFile, cred); + + CountdownEvent userAuthenticationExpired = new CountdownEvent(1); + + using (NATSServer.CreateWithConfig(Context.Server3.Port, "operatorJnatsTest.conf")) + { + var opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server3.Port); + opts.SetUserCredentials(credsFile); + opts.DisconnectedEventHandler += (sender, e) => + { + if (e.Error.ToString().Contains("user authentication expired")) + { + userAuthenticationExpired.Signal(); + } + }; + + IConnection c = Context.ConnectionFactory.CreateConnection(opts); + userAuthenticationExpired.Wait(wait); + Assert.True(userAuthenticationExpired.IsSet); + } + } #if NET46 [Fact] diff --git a/src/Tests/IntegrationTests/TestJetStream.cs b/src/Tests/IntegrationTests/TestJetStream.cs index feb52dcb5..bd6182b1f 100644 --- a/src/Tests/IntegrationTests/TestJetStream.cs +++ b/src/Tests/IntegrationTests/TestJetStream.cs @@ -167,7 +167,7 @@ public void TestJetStreamSubscribe() { js.PushSubscribeAsync("", Queue(102), (o, a) => { }, false, psoBind); // test 2.9.0 - if (AtLeast290(c)) { + if (AtLeast2_9_0(c)) { ConsumerConfiguration cc = ConsumerConfiguration.Builder().WithName(Name(1)).Build(); pso = PushSubscribeOptions.Builder().WithConfiguration(cc).Build(); IJetStreamPushSyncSubscription sub = js.PushSubscribeSync(SUBJECT, pso); diff --git a/src/Tests/IntegrationTests/TestJetStreamPull.cs b/src/Tests/IntegrationTests/TestJetStreamPull.cs index e2e8cd281..d7d724fe4 100644 --- a/src/Tests/IntegrationTests/TestJetStreamPull.cs +++ b/src/Tests/IntegrationTests/TestJetStreamPull.cs @@ -19,7 +19,6 @@ using NATS.Client.Internals; using NATS.Client.JetStream; using Xunit; -using Xunit.Abstractions; using static UnitTests.TestBase; using static IntegrationTests.JetStreamTestBase; using static NATS.Client.Internals.JetStreamConstants; @@ -29,15 +28,7 @@ namespace IntegrationTests { public class TestJetStreamPull : TestSuite { - private readonly ITestOutputHelper output; - - public TestJetStreamPull(ITestOutputHelper output, AutoServerSuiteContext context) : base(context) - { - this.output = output; - Console.SetOut(new ConsoleWriter(output)); - } - - // public TestJetStreamPull(AutoServerSuiteContext context) : base(context) {} + public TestJetStreamPull(AutoServerSuiteContext context) : base(context) {} [Fact] public void TestFetch() @@ -749,7 +740,7 @@ public void TestExceedsMaxRequestBytes1stMessage() public void TestExceedsMaxRequestBytesNthMessage() { TestEventHandler handler = new TestEventHandler(); - Context.RunInJsServer(AtLeast291, handler.Modifier, c => + Context.RunInJsServer(AtLeast2_9_1, handler.Modifier, c => { string stream = Stream(Nuid.NextGlobal()); string subject = Subject(Nuid.NextGlobal()); @@ -787,7 +778,7 @@ public void TestExceedsMaxRequestBytesNthMessage() public void TestExceedsMaxRequestBytesExactBytes() { TestEventHandler handler = new TestEventHandler(); - Context.RunInJsServer(AtLeast291, handler.Modifier, c => + Context.RunInJsServer(AtLeast2_9_1, handler.Modifier, c => { string stream = Stream(Nuid.NextGlobal()); string subject = "subject-ExMaxRqBytesExactBytes"; diff --git a/src/Tests/IntegrationTests/TestSuite.cs b/src/Tests/IntegrationTests/TestSuite.cs index b57bdc551..955a12220 100644 --- a/src/Tests/IntegrationTests/TestSuite.cs +++ b/src/Tests/IntegrationTests/TestSuite.cs @@ -32,21 +32,25 @@ protected TestSuite(TSuiteContext context) Context = context; } - public bool AtLeast290(IConnection c) { - return AtLeast290(c.ServerInfo); + public bool AtLeast2_9_0(IConnection c) { + return AtLeast2_9_0(c.ServerInfo); } - public bool AtLeast290(ServerInfo si) { + public bool AtLeast2_9_0(ServerInfo si) { return si.IsSameOrNewerThanVersion("2.9.0"); } - public bool AtLeast291(ServerInfo si) { + public bool AtLeast2_9_1(ServerInfo si) { return si.IsSameOrNewerThanVersion("2.9.1"); } - public bool AtLeast210(ServerInfo si) { + public bool AtLeast2_10(ServerInfo si) { return si.IsNewerVersionThan("2.9.99"); } + + public bool AtLeast2_10_3(ServerInfo si) { + return si.IsSameOrNewerThanVersion("2.10.3"); + } } /// @@ -72,6 +76,7 @@ public static class TestSeedPorts public const int AsyncAwaitDeadlocksSuite = 11518; //1pc public const int ConnectionIpV6Suite = 11519; //1pc public const int KvSuite = 11520; //3pc + public const int ConnectionBehaviorSuite = 11523; //2pc public static InterlockedInt AutoPort = new InterlockedInt(11550); } @@ -124,6 +129,7 @@ public void RunInServer(TestServerInfo testServerInfo, Action test) { using (var c = OpenConnection(testServerInfo.Port)) { + InitRunServerInfo(c); test(c); } } @@ -145,12 +151,27 @@ public void RunInJsServer(TestServerInfo testServerInfo, Action options RunInJsServer(testServerInfo, null, optionsModifier, test); } - private static ServerInfo runServerInfo; + public static ServerInfo RunServerInfo { get; private set; } + + public ServerInfo EnsureRunServerInfo() { + if (RunServerInfo == null) { + RunInServer(new TestServerInfo(TestSeedPorts.AutoPort.Increment()), c => {}); + } + return RunServerInfo; + } + + public void InitRunServerInfo(IConnection c) + { + if (RunServerInfo == null) + { + RunServerInfo = c.ServerInfo; + } + } public void RunInJsServer(TestServerInfo testServerInfo, Func versionCheck, Action optionsModifier, Action test) { - if (versionCheck != null && runServerInfo != null) { - if (!versionCheck(runServerInfo)) { + if (versionCheck != null && RunServerInfo != null) { + if (!versionCheck(RunServerInfo)) { return; } versionCheck = null; // since we've already determined it should run, null this out so we don't check below @@ -163,10 +184,10 @@ public void RunInJsServer(TestServerInfo testServerInfo, Func using (var c = OpenConnection(testServerInfo.Port, runOptionsModifier)) { + InitRunServerInfo(c); if (versionCheck != null) { - runServerInfo = c.ServerInfo; - if (!versionCheck(runServerInfo)) { + if (!versionCheck(RunServerInfo)) { return; } } @@ -304,6 +325,15 @@ public class AuthorizationSuiteContext : SuiteContext public readonly TestServerInfo Server3 = new TestServerInfo(SeedPort + 2); } + public class ConnectionBehaviorSuite : SuiteContext + { + private const int SeedPort = TestSeedPorts.AuthorizationSuite; + + public readonly TestServerInfo Server1 = new TestServerInfo(SeedPort); + public readonly TestServerInfo Server2 = new TestServerInfo(SeedPort + 1); + } + + public class ConnectionSuiteContext : SuiteContext { private const int SeedPort = TestSeedPorts.ConnectionSuite; diff --git a/src/Tests/IntegrationTests/TestTLS.cs b/src/Tests/IntegrationTests/TestTLS.cs index 272408a2f..346e718f5 100644 --- a/src/Tests/IntegrationTests/TestTLS.cs +++ b/src/Tests/IntegrationTests/TestTLS.cs @@ -12,14 +12,15 @@ // limitations under the License. using System; +using System.Linq; using System.Net.Security; -using System.Security.Cryptography.X509Certificates; -using NATS.Client; using System.Reflection; -using System.IO; -using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Threading; +using NATS.Client; +using UnitTests; using Xunit; +using Xunit.Abstractions; namespace IntegrationTests { @@ -28,7 +29,14 @@ namespace IntegrationTests /// public class TestTls : TestSuite { - public TestTls(TlsSuiteContext context) : base(context) { } + // public TestTls(TlsSuiteContext context) : base(context) { } + private readonly ITestOutputHelper output; + + public TestTls(ITestOutputHelper output, TlsSuiteContext context) : base(context) + { + this.output = output; + Console.SetOut(new TestBase.ConsoleWriter(output)); + } // A hack to avoid issues with our test self signed cert. // We don't want to require the runner of the test to install the @@ -152,15 +160,16 @@ public void TestTlsFailWithBadAuth() } } - private void TestTLSSecureConnect(bool setSecure) + private void TestTLSSecureConnect(bool setSecure, bool tlsFirst, string conf, TestServerInfo tsi) { - using (NATSServer srv = NATSServer.CreateWithConfig(Context.Server1.Port, "tls.conf")) + using (NATSServer srv = NATSServer.CreateWithConfig(tsi.Port, conf)) { // we can't call create secure connection w/ the certs setup as they are // so we'll override the validation callback - Options opts = Context.GetTestOptions(Context.Server1.Port); + Options opts = Context.GetTestOptions(tsi.Port); opts.Secure = setSecure; opts.TLSRemoteCertificationValidationCallback = verifyServerCert; + opts.TlsFirst = tlsFirst; using (IConnection c = Context.ConnectionFactory.CreateConnection(opts)) { @@ -177,13 +186,31 @@ private void TestTLSSecureConnect(bool setSecure) [Fact] public void TestTlsSuccessSecureConnect() { - TestTLSSecureConnect(true); + TestTLSSecureConnect(true, false, "tls.conf", Context.Server1); } [Fact] public void TestTlsSuccessSecureConnectFromServerInfo() { - TestTLSSecureConnect(false); + TestTLSSecureConnect(false, false, "tls.conf", Context.Server1); + } + + [Fact] + public void TestTlsFirstDontSetSecure() + { + if (AtLeast2_10_3(Context.EnsureRunServerInfo())) + { + TestTLSSecureConnect(false, true, "tls_first.conf", new TestServerInfo(TestSeedPorts.AutoPort.Increment())); + } + } + + [Fact] + public void TestTlsFirstYesSetSecure() + { + if (AtLeast2_10_3(Context.EnsureRunServerInfo())) + { + TestTLSSecureConnect(true, true, "tls_first.conf", new TestServerInfo(TestSeedPorts.AutoPort.Increment())); + } } [Fact] diff --git a/src/Tests/IntegrationTests/config/operatorJnatsTest.conf b/src/Tests/IntegrationTests/config/operatorJnatsTest.conf new file mode 100644 index 000000000..43fb81d75 --- /dev/null +++ b/src/Tests/IntegrationTests/config/operatorJnatsTest.conf @@ -0,0 +1,9 @@ + +# Server that loads an operator JWT + +operator: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJUUzU3UFFYUFRETlVNQ1pCTEFOMzJXNUNPT0xHSUVXWVI1VkJUTTZGTzVOR0dJNVBRNERBIiwiaWF0IjoxNjk2OTY3NDQ4LCJpc3MiOiJPQTZETVFORkVDNExLWENVUDZWQ1hBM0EyVlNGQU1BN0ZIV01YWUk0WjUzRkc0S1g1TzNWWEQ0SyIsIm5hbWUiOiJqbmF0c1Rlc3RPcCIsInN1YiI6Ik9BNkRNUU5GRUM0TEtYQ1VQNlZDWEEzQTJWU0ZBTUE3RkhXTVhZSTRaNTNGRzRLWDVPM1ZYRDRLIiwibmF0cyI6eyJ0eXBlIjoib3BlcmF0b3IiLCJ2ZXJzaW9uIjoyfX0.UY64Gei3lbCwg2as3b-jDb5G1bLhBDJq2xInKI3DrILUsGgI56bpZUdGECl30_3EwqDrU2E044yM74HC-G_pDQ + +resolver: MEMORY +resolver_preload: { + ACPWDUYSZRRF7XAEZKUAGPUH6RPICWEHSTFELYKTOWUVZ4R2XMP4QJJX: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJRUVRURzZPNk9XU0pCWkUyVU9IWFEyRFJIU0ZRSExTMzZRTExJNEJNRVFCRVdRRUZKSlFRIiwiaWF0IjoxNjk2OTY3NDY5LCJpc3MiOiJPQTZETVFORkVDNExLWENVUDZWQ1hBM0EyVlNGQU1BN0ZIV01YWUk0WjUzRkc0S1g1TzNWWEQ0SyIsIm5hbWUiOiJqbmF0c1Rlc3RBY2MiLCJzdWIiOiJBQ1BXRFVZU1pSUkY3WEFFWktVQUdQVUg2UlBJQ1dFSFNURkVMWUtUT1dVVlo0UjJYTVA0UUpKWCIsIm5hdHMiOnsibGltaXRzIjp7InN1YnMiOi0xLCJkYXRhIjotMSwicGF5bG9hZCI6LTEsImltcG9ydHMiOi0xLCJleHBvcnRzIjotMSwid2lsZGNhcmRzIjp0cnVlLCJjb25uIjotMSwibGVhZiI6LTF9LCJkZWZhdWx0X3Blcm1pc3Npb25zIjp7InB1YiI6e30sInN1YiI6e319LCJhdXRob3JpemF0aW9uIjp7ImF1dGhfdXNlcnMiOm51bGx9LCJ0eXBlIjoiYWNjb3VudCIsInZlcnNpb24iOjJ9fQ.PbJixkSWOaTL7-zhLxZvSl02QvU7T4wG8TEE-CtXJzrWLEgrYXgy_I-eSbrAg5VVsgXRlMqBYBsR1HwqLooVCA +} \ No newline at end of file diff --git a/src/Tests/IntegrationTests/config/tls_first.conf b/src/Tests/IntegrationTests/config/tls_first.conf new file mode 100644 index 000000000..93b010a03 --- /dev/null +++ b/src/Tests/IntegrationTests/config/tls_first.conf @@ -0,0 +1,28 @@ + +# Simple TLS config file + +net: localhost + +tls { + # Server cert + cert_file: "./certs/server-cert.pem" + # Server private key + key_file: "./certs/server-key.pem" + handshake_first: 300ms + + cipher_suites: [ + "TLS_RSA_WITH_RC4_128_SHA", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" + ] +} \ No newline at end of file diff --git a/src/Tests/IntegrationTestsInternal/TestConnectionBehavior.cs b/src/Tests/IntegrationTestsInternal/TestConnectionBehavior.cs new file mode 100644 index 000000000..fd4360a37 --- /dev/null +++ b/src/Tests/IntegrationTestsInternal/TestConnectionBehavior.cs @@ -0,0 +1,120 @@ +// Copyright 2022-2023 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Threading; +using IntegrationTests; +using NATS.Client; +using NATS.Client.Internals; +using Xunit; + +namespace IntegrationTestsInternal +{ + public class TestConnectionBehavior : TestSuite + { + public TestConnectionBehavior(ConnectionBehaviorSuite context) : base(context) {} + + [Fact] + public void TestReconnectOnConnect() + { + _testReconnectOnConnect(true); + } + + [Fact] + public void TestNoReconnectOnConnect() + { + _testReconnectOnConnect(false); + } + + private void _testReconnectOnConnect(bool reconnectOnConnect) + { + InterlockedInt disconnects = new InterlockedInt(); + InterlockedInt reconnects = new InterlockedInt(); + + using (NATSServer s1 = NATSServer.CreateWithConfig(Context.Server1.Port, "auth.conf"), + _ = NATSServer.CreateWithConfig(Context.Server2.Port, "auth.conf")) + { + Options opts = Context.GetTestOptions(); + opts.Servers = new[] + { + $"nats://username:passwordx@localhost:{Context.Server1.Port}", + $"nats://username:passwordx@localhost:{Context.Server2.Port}" + }; + TestServerPool pool = new TestServerPool(); + opts.ServerProvider = pool; + opts.NoRandomize = true; + opts.MaxReconnect = 2; + opts.DisconnectedEventHandler += (sender, e) => disconnects.Increment(); + opts.ReconnectedEventHandler += (sender, e) => reconnects.Increment(); + + try + { + using (IConnection c = Context.ConnectionFactory.CreateConnection(opts, reconnectOnConnect)) + { + c.Close(); + } + } + catch (Exception e) + { + if (reconnectOnConnect) + { + Assert.False(true, $"Exception not expected {e.Message}"); + } + } + + Thread.Sleep(100); // ensure events get handled + if (reconnectOnConnect) + { + Assert.Equal(2, disconnects.Read()); + Assert.Equal(1, reconnects.Read()); + Assert.Equal(1, pool.setupCallCount); + Assert.Equal(3, pool.selectNextServerCallCount); + } + else + { + Assert.Equal(0, disconnects.Read()); + Assert.Equal(0, reconnects.Read()); + Assert.Equal(1, pool.setupCallCount); + Assert.Equal(0, pool.selectNextServerCallCount); + } + } + } + } + + internal class TestServerPool : ServerPool + { + internal int setupCallCount = 0; + internal int selectNextServerCallCount = 0; + + public LinkedList SrvList => sList; + + public override void Setup(Options opts) + { + setupCallCount++; + base.Setup(opts); + } + + public override Srv SelectNextServer(int maxReconnect) + { + if (++selectNextServerCallCount == 3) + { + foreach (Srv srv in SrvList) + { + srv.SetUrl(srv.Url.ToString().Replace("passwordx", "password")); + } + } + return base.SelectNextServer(maxReconnect); + } + } +} diff --git a/src/Tests/IntegrationTestsInternal/TestJetStreamConsumer.cs b/src/Tests/IntegrationTestsInternal/TestJetStreamConsumer.cs index 8c61067e0..2e8a81192 100644 --- a/src/Tests/IntegrationTestsInternal/TestJetStreamConsumer.cs +++ b/src/Tests/IntegrationTestsInternal/TestJetStreamConsumer.cs @@ -350,7 +350,7 @@ private static CountdownEvent setupPullFactory(IJetStream js) [Fact] public void TestMultipleSubjectFilters() { - Context.RunInJsServer(AtLeast210, c => { + Context.RunInJsServer(AtLeast2_10, c => { // Setup IJetStream js = c.CreateJetStreamContext(); IJetStreamManagement jsm = c.CreateJetStreamManagementContext(); diff --git a/src/Tests/IntegrationTestsInternal/TestSimplification.cs b/src/Tests/IntegrationTestsInternal/TestSimplification.cs index 75b103c27..1f3144dbb 100644 --- a/src/Tests/IntegrationTestsInternal/TestSimplification.cs +++ b/src/Tests/IntegrationTestsInternal/TestSimplification.cs @@ -32,7 +32,7 @@ public TestSimplification(OneServerSuiteContext context) : base(context) {} [Fact] public void TestStreamContext() { - Context.RunInJsServer(AtLeast291, c => { + Context.RunInJsServer(AtLeast2_9_1, c => { string stream = Stream(Nuid.NextGlobal()); string subject = Subject(Nuid.NextGlobal()); @@ -122,7 +122,7 @@ private static void _TestStreamContext(string expectedStreamName, string subject [Fact] public void TestFetch() { - Context.RunInJsServer(AtLeast291, c => { + Context.RunInJsServer(AtLeast2_9_1, c => { string stream = Stream(Nuid.NextGlobal()); string subject = Subject(Nuid.NextGlobal()); @@ -232,7 +232,7 @@ private static string generateConsumerName(int maxMessages, int maxBytes) { [Fact] public void TestIterableConsumer() { - Context.RunInJsServer(AtLeast291, c => { + Context.RunInJsServer(AtLeast2_9_1, c => { string streamName = Stream(Nuid.NextGlobal()); string subject = Subject(Nuid.NextGlobal()); string durable = Nuid.NextGlobal(); @@ -266,7 +266,7 @@ public void TestIterableConsumer() [Fact] public void TestOrderedIterableConsumerBasic() { - Context.RunInJsServer(AtLeast291, c => { + Context.RunInJsServer(AtLeast2_9_1, c => { string streamName = Stream(Nuid.NextGlobal()); string subject = Subject(Nuid.NextGlobal()); @@ -336,7 +336,7 @@ private static void _testIterable(IJetStream js, int stopCount, IIterableConsume [Fact] public void TestConsumeWithHandler() { - Context.RunInJsServer(AtLeast291, c => { + Context.RunInJsServer(AtLeast2_9_1, c => { string streamName = Stream(Nuid.NextGlobal()); string subject = Subject(Nuid.NextGlobal()); string durable = Nuid.NextGlobal(); @@ -371,7 +371,7 @@ public void TestConsumeWithHandler() [Fact] public void TestNext() { - Context.RunInJsServer(AtLeast291, c => { + Context.RunInJsServer(AtLeast2_9_1, c => { string streamName = Stream(Nuid.NextGlobal()); string subject = Subject(Nuid.NextGlobal()); string durable = Nuid.NextGlobal(); @@ -407,7 +407,7 @@ public void TestCoverage() { string durable5 = Nuid.NextGlobal(); string durable6 = Nuid.NextGlobal(); - Context.RunInJsServer(AtLeast291, c => { + Context.RunInJsServer(AtLeast2_9_1, c => { IJetStreamManagement jsm = c.CreateJetStreamManagementContext(); IJetStream js = c.CreateJetStreamContext(); @@ -595,7 +595,7 @@ protected override bool BeforeChannelAddCheck(Msg msg) [Fact] public void TestOrderedActives() { - Context.RunInJsServer(AtLeast291, c => { + Context.RunInJsServer(AtLeast2_9_1, c => { string streamName = Stream(Nuid.NextGlobal()); string subject = Subject(Nuid.NextGlobal()); string durable = Nuid.NextGlobal(); @@ -673,7 +673,7 @@ private static void testOrderedActiveIterable(IStreamContext sc, OrderedConsumer [Fact] public void TestOrderedConsume() { - Context.RunInJsServer(AtLeast291, c => { + Context.RunInJsServer(AtLeast2_9_1, c => { string streamName = Stream(Nuid.NextGlobal()); string subject = Subject(Nuid.NextGlobal()); @@ -721,7 +721,7 @@ public void TestOrderedConsume() { [Fact] public void TestOrderedMultipleWays() { - Context.RunInJsServer(AtLeast291, c => { + Context.RunInJsServer(AtLeast2_9_1, c => { string streamName = Stream(Nuid.NextGlobal()); string subject = Subject(Nuid.NextGlobal()); diff --git a/src/Tests/UnitTests/TestJwtUtils.cs b/src/Tests/UnitTests/TestJwtUtils.cs index 5094605d2..e1872000e 100644 --- a/src/Tests/UnitTests/TestJwtUtils.cs +++ b/src/Tests/UnitTests/TestJwtUtils.cs @@ -56,8 +56,8 @@ public void IssueUserJWTSuccessMinimal() "------END NATS USER JWT------\n" + "\n" + "************************* IMPORTANT *************************\n" + - " NKEY Seed printed below can be used to sign and prove identity.\n" + - " NKEYs are sensitive and should be treated as secrets.\n" + + "NKEY Seed printed below can be used to sign and prove identity.\n" + + "NKEYs are sensitive and should be treated as secrets.\n" + "\n" + "-----BEGIN USER NKEY SEED-----\n" + "SUAGL3KX4ZBBD53BNNLSHGAAGCMXSEYZ6NTYUBUCPZQGHYNK3ZRQBUDPRY\n" + @@ -105,8 +105,8 @@ public void IssueUserJWTSuccessAllArgs() "------END NATS USER JWT------\n" + "\n" + "************************* IMPORTANT *************************\n" + - " NKEY Seed printed below can be used to sign and prove identity.\n" + - " NKEYs are sensitive and should be treated as secrets.\n" + + "NKEY Seed printed below can be used to sign and prove identity.\n" + + "NKEYs are sensitive and should be treated as secrets.\n" + "\n" + "-----BEGIN USER NKEY SEED-----\n" + "SUAGL3KX4ZBBD53BNNLSHGAAGCMXSEYZ6NTYUBUCPZQGHYNK3ZRQBUDPRY\n" + @@ -168,8 +168,8 @@ public void IssueUserJWTSuccessCustom() "------END NATS USER JWT------\n" + "\n" + "************************* IMPORTANT *************************\n" + - " NKEY Seed printed below can be used to sign and prove identity.\n" + - " NKEYs are sensitive and should be treated as secrets.\n" + + "NKEY Seed printed below can be used to sign and prove identity.\n" + + "NKEYs are sensitive and should be treated as secrets.\n" + "\n" + "-----BEGIN USER NKEY SEED-----\n" + "SUAGL3KX4ZBBD53BNNLSHGAAGCMXSEYZ6NTYUBUCPZQGHYNK3ZRQBUDPRY\n" + @@ -218,8 +218,8 @@ public void IssueUserJWTSuccessCustomLimits() "------END NATS USER JWT------\n" + "\n" + "************************* IMPORTANT *************************\n" + - " NKEY Seed printed below can be used to sign and prove identity.\n" + - " NKEYs are sensitive and should be treated as secrets.\n" + + "NKEY Seed printed below can be used to sign and prove identity.\n" + + "NKEYs are sensitive and should be treated as secrets.\n" + "\n" + "-----BEGIN USER NKEY SEED-----\n" + "SUAGL3KX4ZBBD53BNNLSHGAAGCMXSEYZ6NTYUBUCPZQGHYNK3ZRQBUDPRY\n" +