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" +